Source: synopticapi.js

'use strict'

/*
 *
 * SCADAvis.io Synoptic API © 2018-2022 Ricardo L. Olsen / DSC Systems ALL RIGHTS RESERVED.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

/**
 * Must be created with the "new" keyword. (use scadavisInit when promise-based method preferred). E.g. var svgraph = new scadavis("div1", "", "https://svgurl.com/svgurl.svg"); 
 * @class scadavis - SCADAvis.io synoptic API class.
 * @param {string} [container] - ID of the container object. If empty or null the iframe will be appended to the body.
 * @param {string} [iframeparams] - Parameter string for configuring iframe (excluding id and src and sandbox) e.g. 'frameborder="0" height="250" width="250"'.
 * @param {string} [svgurl] - URL for the SVG file.
 * @param {{container: string|Object, iframeparams: string, svgurl: string, colorsTable: Object}} [paramsobj] - Alternatively parameters can be passed in an object.
 * Example usage: var svgraph = new scadavis("div1", "", "https://svgurl.com/svgurl.svg"); 
 */
function scadavis(container, iframeparams, svgurl) {
  const _this = this
  const version = '2.0.7'
  let id
  let iframehtm
  let scrolling = ' scrolling="no" '

  if (typeof container === 'object') {
    _this.container = container.container || ''
    _this.apikey = container.apikey || ''
    _this.iframeparams =
      container.iframeparams || 'frameborder="0" height="250" width="250"'
    _this.svgurl = container.svgurl || ''
    _this.colorsTable = container.colorsTable || null
  } else {
    _this.container = container || ''
    _this.apikey = apikey || ''
    _this.iframeparams =
      iframeparams || 'frameborder="0" height="250" width="250"'
    _this.svgurl = svgurl || ''
    _this.colorsTable = null
  }

  _this.iframe = null
  _this.componentloaded = false
  _this.readyfordata = false
  _this.domain = '*'
  _this.rtdata = { data: { type: 'tags', tags: [] } }
  _this.npts = {}
  _this.vals = {}
  _this.qualifs = {}
  _this.descriptions = {}
  _this.npt = 0
  _this.svgobj = null
  _this.zoomobj = null
  _this.moveobj = null
  _this.enabletoolsobj = null
  _this.enablekeyboardobj = null
  _this.enableflashobj = null
  _this.hidewatermarkobj = null
  _this.setcolorobj = []
  _this.setcolorsobj = null
  _this.resetobj = null
  _this.tagsList = ''
  _this.onready = null
  _this.onerror = null
  _this.onclick = null
  _this.loadingSVG = 0
  _this.updateHandle = 0
  _this.resolveFunction = null
  _this.rejectFunction = null

  /**
   * Generate an unique DOM element ID.
   * @private
   * @method guidGenerator
   * @memberof scadavis
   * @returns {string} DOM ID.
   */
  _this.guidGenerator = function () {
    const S4 = function () {
      return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
    }
    return (
      S4() +
      S4() +
      '-' +
      S4() +
      '-' +
      S4() +
      '-' +
      S4() +
      '-' +
      S4() +
      S4() +
      S4()
    )
  }

  /**
   * Create a DOM element from HTML.
   * @private
   * @method createElementFromHTML
   * @memberof scadavis
   * @returns {string} DOM ID.
   */
  _this.createElementFromHTML = function (htmlString) {
    const div = document.createElement('div')
    div.innerHTML = htmlString.trim()
    return div.firstChild
  }

  id = _this.guidGenerator()

  if (typeof _this.container === 'string') {
    if (_this.container.trim().length === 0) {
      _this.container = document.body
    } else {
      _this.container = document.getElementById(_this.container)
      if (_this.container === null) {
        _this.container = document.body
      }
    }
  }
  
  // get library path
  const scripts = document.getElementsByTagName("script")
  let libPath = ''
  for (let i = 0; i < scripts.length; ++i) {
    const pos = scripts[i].src.indexOf('synopticapi.js')
    if (pos>0) {
      libPath = scripts[i].src.substring(0, pos-1)
      break
    }
  }

  // default is scrolling='no'
  if (_this.iframeparams.indexOf('scrolling') >= 0) scrolling = ''

  iframehtm =
    '<iframe id="' +
    id +
    '" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" ' +
    _this.iframeparams +
    scrolling +
    ` src="${libPath}/synoptic.html"></iframe>`
  if (_this.container.innerHTML !== undefined)
    _this.container.appendChild(_this.createElementFromHTML(iframehtm))
  else _this.container.insertAdjacentHTML('afterend', iframehtm)

  _this.iframe = document.getElementById(id)

  /**
   * Load the SVG synoptic display file from a SVG URL.
   * @method loadURL
   * @memberof scadavis
   * @param {string} svgurl - The SVG URL.
   */
  _this.loadURL = function (svgurl) {
    _this.svgobj = null
    _this.readyfordata = false
    _this.svgurl = svgurl
    if (_this.svgurl !== '' && _this.loadingSVG === 0) {
      _this.loadingSVG = 1
      const xhr = new XMLHttpRequest()
      xhr.open('GET', _this.svgurl) // here you point to the SVG synoptic display file
      xhr.onload = function () {
        if (xhr.status === 200) {
          _this.loadingSVG = 0
          if (_this.componentloaded)
            // SCADAvis component already loaded?
            _this.iframe.contentWindow.postMessage(
              xhr.responseText,
              _this.domain
            )
          // send the SVG file contents to the component
          else _this.svgobj = xhr.responseText // buffers the result for later use (this can save some time)
        }
      }
      xhr.onreadystatechange = function (oEvent) {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
          } else {
            _this.loadingSVG = 0
            if (_this.onerror) _this.onerror(xhr.statusText)
            else
              console.warn(
                'SCADAvis.io API: error loading SVG URL. ' + xhr.statusText
              )
          }
        }
      }
      xhr.send()
    }
  }

  /**
   * Update values for tags to the component. Send all tags available. Work as a promise. Only available for version 2+.
   * @method refreshDisplay
   * @memberof scadavis
   * @param {Object.<string, number>} [values] - values in a object like { "tag1" : 1.0, "tag2": 1.2, "tag3": true }.
   * @returns {Object} Returns a promise. The promise resolves after the display refresh is completed.
   */
  _this.refreshDisplay = function (values) {
    return new Promise(function (resolve, reject) {
      if (!_this.readyfordata) {
        reject(new Error('Not ready for data!'))
        return
      }
      if (_this.resolveFunction) {
        reject(new Error('Ongoing display refresh!'))
        return
      }
      _this.resolveFunction = resolve
      _this.rejectFunction = reject
      _this.updateValues(values)
    })
  }

  /**
   * Update values for tags to the component. Send all tags available. (use refreshDisplay when promise-based method preferred)
   * @method updateValues
   * @memberof scadavis
   * @param {Object.<string, number>} [values] - values in a object like { "tag1" : 1.0, "tag2": 1.2, "tag3": true }.
   * @returns {number} Returns request handle or null if not ready.
   */
  _this.updateValues = function (values) {
    if (!_this.readyfordata) return null

    if (typeof values === 'object' && values !== null)
      Object.keys(values).map(function (tag, index) {
        let n
        if (tag in _this.npts) {
          n = _this.npts[tag]
        } else {
          n = ++_this.npt
          _this.npts[tag] = n
        }
        _this.vals[tag] = values[tag]
        _this.qualifs[tag] = 0x00
      })

    const rtdata = {
      data: { type: 'tags', tags: [], handle: ++_this.updateHandle },
    }
    Object.keys(_this.npts).map(function (tag, index) {
      rtdata.data.tags[index] = {}
      rtdata.data.tags[index].path = tag
      rtdata.data.tags[index].value = _this.vals[tag]
      rtdata.data.tags[index].quality = !(_this.qualifs[tag] & 0x80)
      if (typeof _this.vals[tag] == 'number')
        rtdata.data.tags[index].type = 'float'
      else if (typeof _this.vals[tag] == 'boolean')
        rtdata.data.tags[index].type = 'bool'
      else rtdata.data.tags[index].type = 'string'
      rtdata.data.tags[index].parameters = {
        Value: {
          TagClientItem: _this.npts[tag],
          Alarmed: (_this.qualifs[tag] & 0x100) === 0x100,
          Desc: _this.descriptions[tag],
        },
      }
    })
    _this.iframe.contentWindow.postMessage(rtdata, _this.domain)
    return rtdata.handle
  }

  /**
   * Set a value for a tag. The component will be updated immediately if the component is ready for data.
   * Notice that updating the component at too many times per second can cause performance problems.
   * Preferably update many values using storeValue() then call updateValues() once (repeat after a second or more).
   * @method setValue
   * @memberof scadavis
   * @param {string} tag - Tag name.
   * @param {number} value - Value for the tag.
   * @param {bool} [failed=false] - True if value is bad or old, false or absent if value is good.
   * @param {bool} [alarmed=false] - True if value is alarmed, false or absent if value is normal.
   * @param {string} [description=tag] - Description.
   * @returns {bool} Returns true if the component was updated (true) or the value was buffered (false).
   */
  _this.setValue = function (tag, value, failed, alarmed, description) {
    if (tag === '' || tag === undefined || tag === null)
      return _this.readyfordata
    let n
    failed = failed || false
    alarmed = alarmed || false
    description = description || tag
    if (tag in _this.npts) {
      n = _this.npts[tag]
    } else {
      n = ++_this.npt
      _this.npts[tag] = n
    }
    _this.vals[tag] = value
    _this.qualifs[tag] = (failed ? 0x80 : 0x00) | (alarmed ? 0x100 : 0x00)
    _this.descriptions[tag] = description
    if (_this.readyfordata) {
      const rtdata = {
        data: { type: 'tags', tags: [], handle: ++_this.updateHandle },
      }
      rtdata.data.tags[0] = {}
      rtdata.data.tags[0].path = tag
      rtdata.data.tags[0].value = value
      rtdata.data.tags[0].quality = !(_this.qualifs[tag] & 0x80)
      if (typeof value == 'number') rtdata.data.tags[0].type = 'float'
      else if (typeof value == 'boolean') rtdata.data.tags[0].type = 'bool'
      else rtdata.data.tags[0].type = 'string'
      rtdata.data.tags[0].parameters = {
        Value: {
          TagClientItem: n,
          Alarmed: (_this.qualifs[tag] & 0x100) === 0x100,
          Desc: _this.descriptions[tag],
        },
      }
      _this.iframe.contentWindow.postMessage(rtdata, _this.domain)
    }
    return _this.readyfordata
  }

  /**
   * Store a value for a tag. The component will not be updated until called updateValues().
   * @method storeValue
   * @memberof scadavis
   * @param {string} tag - Tag name.
   * @param {number} value - Value for the tag.
   * @param {bool} [failed=false] - True if value is bad or old, false or absent if value is good.
   * @param {bool} [alarmed=false] - True if value is alarmed, false or absent if value is normal.
   * @param {string} [description=tag] - Description.
   * @returns {bool} - Returns true if the component is ready for data, false if not.
   */
  _this.storeValue = function (tag, value, failed, alarmed, description) {
    if (tag === '' || tag === undefined || tag === null)
      return _this.readyfordata
    let n
    failed = failed || false
    alarmed = alarmed || false
    description = description || tag
    if (tag in _this.npts) {
      n = _this.npts[tag]
    } else {
      n = ++_this.npt
      _this.npts[tag] = n
    }
    _this.vals[tag] = value
    _this.qualifs[tag] = (failed ? 0x80 : 0x00) | (alarmed ? 0x100 : 0x00)
    _this.descriptions[tag] = description
    return _this.readyfordata
  }

  /**
   * Reset all data values and tags.
   * @method resetData
   * @memberof scadavis
   */
  _this.resetData = function () {
    _this.npt = 0
    _this.npts = {}
    _this.vals = {}
    _this.qualifs = {}
    _this.descriptions = {}

    const obj = { data: { type: 'resetData' } }
    if (_this.readyfordata) window.postMessage(obj, _this.domain)
    else _this.resetobj = obj
  }

  /**
   * Get a value for a tag.
   * @method getValue
   * @memberof scadavis
   * @param {Object} tag - Tag name.
   * @returns {number} Returns the value for the tag or null if not found.
   */
  _this.getValue = function (tag) {
    if (tag in _this.vals) {
      return _this.vals[tag]
    }
    return null
  }

  /**
   * Recover the API Key.
   * @method getApiKey
   * @memberof scadavis
   * @returns {string} API Key.
   */
  _this.getApiKey = function () {
    return _this.apikey
  }

  /**
   * Get SCADAvis.io API Version.
   * @method getVersion
   * @memberof scadavis
   * @returns {string} SCADAvis.io API Version.
   */
  _this.getVersion = function () {
    return version
  }

  /**
   * Get the DOM element of the iframe.
   * @method getIframe
   * @memberof scadavis
   * @returns {Object} DOM element reference.
   */
  _this.getIframe = function () {
    return _this.iframe
  }

  /**
   * Get the current state of the component.
   * @method getComponentState
   * @memberof scadavis
   * @returns {number} 0=not loaded, 1=loaded and ready for graphics, 2=SVG graphics processed and ready for data.
   */
  _this.getComponentState = function () {
    if (_this.componentloaded == false) return 0
    else if (_this.readyfordata == false) return 1
    return 2
  }

  /**
   * Get SCADAvis.io Component Version.
   * @method getComponentVersion
   * @memberof scadavis
   * @returns {string} SCADAvis.io Component Version.
   */
  _this.getComponentVersion = function () {
    return version
  }

  /**
   * Get tags list from the loaded SVG graphics.
   * @method getTagsList
   * @memberof scadavis
   * @returns {string} Tags list.
   */
  _this.getTagsList = function () {
    return _this.tagsList
  }

  /**
   * Move the graphic. Multiple calls have cumulative effect.
   * @method moveBy
   * @memberof scadavis
   * @param {number} [dx=0] Horizontal distance.
   * @param {number} [dy=0] Vertical distance.
   * @param {boolean} [animate=false] Animate or not.
   */
  _this.moveBy = function (dx, dy, animate) {
    dx = dx || 0
    dy = dy || 0
    animate = animate || false
    const obj = { data: { type: 'moveBy', dx: dx, dy: dy, animate: animate } }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.moveobj = obj
  }

  /**
   * Apply zoom level. Multiple calls have cumulative effect.
   * @method zoomTo
   * @memberof scadavis
   * @param {number} [zoomLevel=1.1] Zoom level. >1 zoom in, <1 zoom out.
   * @param {string|{x: number, y: number}} [target={x:0,y:0}] Id of object to zoom in/out or x/y coordinates.
   * @param {boolean} [animate=false] Animate or not.
   */
  _this.zoomTo = function (zoomLevel, target, animate) {
    zoomLevel = zoomLevel || 1.1
    animate = animate || false
    const obj = {
      data: {
        type: 'zoomTo',
        zoomLevel: zoomLevel,
        target: target,
        animate: animate,
      },
    }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.zoomobj = obj
  }

  /**
   * Apply default zoom level/position.
   * @method zoomToOriginal
   * @memberof scadavis
   * @param {boolean} [animate=false] Animate or not.
   */
  _this.zoomToOriginal = function (animate) {
    animate = animate || false
    const obj = { data: { type: 'zoomToOriginal', animate: animate } }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
  }

  /**
   * Enable or disable pan and zoom tools.
   * @method enableTools
   * @memberof scadavis
   * @param {boolean} [panEnabled=true] Enable/disable Pan tool.
   * @param {boolean} [zoomEnabled=false] Enable/disable Zoom tool.
   */
  _this.enableTools = function (panEnabled, zoomEnabled) {
    if (typeof panEnabled === 'undefined' || panEnabled) panEnabled = true
    if (typeof zoomEnabled === 'undefined') zoomEnabled = false
    const obj = {
      data: {
        type: 'enableTools',
        panEnabled: panEnabled,
        zoomEnabled: zoomEnabled,
      },
    }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.enabletoolsobj = obj
  }

  /**
   * Enable or disable pan and zoom via mouse.
   * @method enableMouse
   * @memberof scadavis
   * @param {boolean} [panEnabled=true] Enable/disable pan via mouse.
   * @param {boolean} [zoomEnabled=true] Enable/disable zoom via mouse.
   */
  _this.enableMouse = function (panEnabled, zoomEnabled) {
    if (typeof panEnabled === 'undefined' || panEnabled) panEnabled = true
    if (typeof zoomEnabled === 'undefined' || zoomEnabled) zoomEnabled = true
    const obj = {
      data: {
        type: 'enableMouse',
        panEnabled: panEnabled,
        zoomEnabled: zoomEnabled,
      },
    }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.enablemouseobj = obj
  }

  /**
   * Set direction of zoom bound to mouse wheel, and event propagation.
   * @method setMouseWheel
   * @memberof scadavis
   * @param {boolean} [directionBackOut=true] true=back/out, false=back/in.
   * @param {boolean} [blockEventPropagation=true] Enable/disable wheel event propagation.
   */
  _this.setMouseWheel = function (directionBackOut, blockEventPropagation) {
    if (typeof directionBackOut === 'undefined' || directionBackOut)
      directionBackOut = true
    if (typeof blockEventPropagation === 'undefined' || blockEventPropagation)
      blockEventPropagation = true
    const obj = {
      data: {
        type: 'setMouseWheel',
        directionBackOut: directionBackOut,
        blockEventPropagation: blockEventPropagation,
      },
    }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.mousewheelobj = obj
  }

  /**
   * Enable or disable keyboard functions (zoom & pan).
   * @method enableKeyboard
   * @memberof scadavis
   * @param {boolean} [keyEnabled=true] Enable/disable Pan tool.
   */
  _this.enableKeyboard = function (keyEnabled) {
    if (typeof keyEnabled === 'undefined' || keyEnabled) keyEnabled = true
    const obj = { data: { type: 'enableKeyboard', keyEnabled: keyEnabled } }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.enablekeyboardobj = obj
  }

  /**
   * Enable or disable alarm flash (objects blinking when alarmed).
   * @method enableAlarmFlash
   * @memberof scadavis
   * @param {boolean} [alarmFlashEnabled=true] Enable/disable global alarm flash.
   */
  _this.enableAlarmFlash = function (alarmFlashEnabled) {
    if (typeof alarmFlashEnabled === 'undefined' || alarmFlashEnabled)
      alarmFlashEnabled = true
    const obj = {
      data: { type: 'enableAlarmFlash', alarmFlashEnabled: alarmFlashEnabled },
    }
    if (_this.componentloaded)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.enableflashobj = obj
  }

  /**
   * Hides the watermark.
   * @method hideWatermark
   * @memberof scadavis
   */
  _this.hideWatermark = function () {
    const obj = { data: { type: 'hideWatermark' } }
    if (_this.readyfordata)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.hidewatermarkobj = obj
  }

  /**
   * Set color code for color shortcuts.
   * @method setColor
   * @memberof scadavis
   * @param {number} [colorNumber] Color shortcut number.
   * @param {string} [colorCode] Color code.
   */
  _this.setColor = function (colorNumber, colorCode) {
    const obj = {
      data: {
        type: 'setColor',
        colorNumber: colorNumber,
        colorCode: colorCode,
      },
    }
    if (_this.componentloaded)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.setcolorobj.push(obj)
  }

  /**
   * Set color codes for color shortcuts.
   * @method setColors
   * @memberof scadavis
   * @param {Object.<number,string>} [colorsTable] Table of color numbers/Color codes.
   */
  _this.setColors = function (colorsTable) {
    const obj = {
      data: {
        type: 'setColors',
        colorsTable: colorsTable,
      },
    }
    if (_this.componentloaded)
      _this.iframe.contentWindow.postMessage(obj, _this.domain)
    else _this.setcolorsobj = obj
  }

  /**
   * Set event listeners.
   * @method on
   * @memberof scadavis
   * @param {string} event Event name, one of: "ready", "click" (the first parameter of callback is the element id).
   * @param {function} callback Callback function.
   * @returns True for valid event, false for invalid event name.
   */
  _this.on = function (event, callback) {
    let ret = false
    switch (event) {
      case 'loaded':
        _this.loaded = callback
        ret = true
        break
      case 'ready':
        _this.onready = callback
        ret = true
        break
      case 'error':
        _this.onerror = callback
        ret = true
        break
      case 'click':
        _this.onclick = callback
        ret = true
        break
      default:
        break
    }

    return ret
  }

  _this.componentloaded = false
  _this.readyfordata = false

  if (_this.colorsTable) _this.setColors(_this.colorsTable)
  if (_this.svgurl !== '') _this.loadURL(_this.svgurl)

  window.addEventListener('message', function (event) {
    // receive messages, watch for messages from the SCADAvis.io component.

    // for better security: check the origin of the message ( must be from the SCADAvis.io domain and component iframe )
    if (
      event.source === _this.iframe.contentWindow
      // && event.origin === _this.domain
    ) {
      // when message of type "updated", resolve promise
      if (
        typeof event.data === 'object' &&
        event.data.data.type !== undefined &&
        event.data.data.type === 'updated' &&
        event.data.data.handle === _this.updateHandle
      ) {
        if (event.data.data.error) {
          if (_this.rejectFunction) _this.rejectFunction(new Error(event.data.data.error))
        } else if (_this.resolveFunction) _this.resolveFunction()
        _this.resolveFunction = null
        _this.rejectFunction = null
        return
      }

      // when message of type "loaded", get and send an SVG file to it
      if (
        typeof event.data === 'object' &&
        event.data.data.type !== undefined &&
        event.data.data.type === 'loaded'
      ) {
        _this.componentloaded = true
        if (_this.setcolorobj.length > 0) {
          for (let i = 0; i < _this.setcolorobj.length; i++)
            event.source.postMessage(_this.setcolorobj[i], event.origin)
          _this.setcolorobj = []
        }
        if (_this.setcolorsobj !== null) {
          event.source.postMessage(_this.setcolorsobj, event.origin)
          _this.setcolorsobj = null
        }
        if (_this.enableflashobj !== null) {
          event.source.postMessage(_this.enableflashobj, event.origin)
          _this.enableflashobj = null
        }
        if (_this.svgobj !== null)
          event.source.postMessage(_this.svgobj, event.origin)
        // send the SVG file contents to the component
        else if (_this.svgurl !== '') _this.loadURL(_this.svgurl)
        return
      }

      // when message type "ready", the SVG screen is processed, then we can send real time data to the SCADAvis.io component
      if (
        typeof event.data === 'object' &&
        event.data.data.type !== undefined &&
        event.data.data.type === 'ready'
      ) {
        _this.readyfordata = true
        _this.tagsList = event.data.data.attributes.tagsList
        if (_this.rtdata.data.tags.length) _this.updateValues()
        if (_this.zoomobj) {
          event.source.postMessage(_this.zoomobj, event.origin)
          _this.zoomobj = null
        }
        if (_this.moveobj) {
          event.source.postMessage(_this.moveobj, event.origin)
          _this.moveobj = null
        }
        if (_this.enabletoolsobj) {
          event.source.postMessage(_this.enabletoolsobj, event.origin)
          _this.enabletoolsobj = null
        }
        if (_this.enablemouseobj) {
          event.source.postMessage(_this.enablemouseobj, event.origin)
          _this.enablemouseobj = null
        }
        if (_this.mousewheelobj) {
          event.source.postMessage(_this.mousewheelobj, event.origin)
          _this.mousewheelobj = null
        }
        if (_this.enablekeyboardobj) {
          event.source.postMessage(_this.enablekeyboardobj, event.origin)
          _this.enablekeyboardobj = null
        }
        if (_this.hidewatermarkobj) {
          event.source.postMessage(_this.hidewatermarkobj, event.origin)
          _this.hidewatermarkobj = null
        }
        if (_this.resetobj) {
          event.source.postMessage(_this.resetobj, event.origin)
          _this.resetobj = null
        }
        if (_this.onready) _this.onready()
        return
      }

      // when message of type "click", emit the event callback
      if (
        typeof event.data === 'object' &&
        event.data.data.type !== undefined &&
        event.data.data.type === 'click'
      ) {
        if (_this.onclick)
          _this.onclick(
            event.data.data.attributes.event,
            event.data.data.attributes.tag
          )
        return
      }
    }
  })
}

/**
 * Initialization of the library via promise. Only available for version 2+.
 * @function scadavisInit
 * @global
 * @param {string} [container] - ID of the container object. If empty or null the iframe will be appended to the body.
 * @param {string} [iframeparams] - Parameter string for configuring iframe (excluding id and src and sandbox) e.g. 'frameborder="0" height="250" width="250"'.
 * @param {string} [svgurl] - URL for the SVG file.
 * @param {{container: string|Object, iframeparams: string, svgurl: string, colorsTable: Object}} [paramsobj] - Alternatively parameters can be passed in an object.
 * @returns {Object} Returns a promise with the {@link scadavis} object as a parameter. The promise resolves after the svg file is preprocessed (if an SVG file was specified) or after the component is loaded.
 * Example usage: scadavisInit( {container: "div1", svgurl: "file.svg"} ).then(function (sv) { ... });
 */
function scadavisInit(container, iframeparams, svgurl) {
  return new Promise(function (resolve, reject) {
    try {
      if (typeof container === 'object') {
        svgurl = svgurl || container.svgurl
        delete container.svgurl
      }
      const sv = new scadavis(container, iframeparams)
      if (!svgurl) {
        resolve(sv)
        return
      }
      sv.on('error', function (errMsg) {
        reject(errMsg)
      })
      sv.on('ready', function () {
        resolve(sv)
      })
      sv.loadURL(svgurl)
    } catch (e) {
      reject(e)
    }
  })
}