/** * handles the whole sequence of loading the app via widget integration */ window.WidgetLoader = (function (me, scriptSrc) { const ON_GRAPH_MOUSE_MOVE = "onGraphMouseMove"; const ON_GRAPH_CLICK = "onGraphClick"; const ON_ZOOM = "onZoom"; const ON_SCORE_CLICK = "onScoreClicked"; const ON_SCORE_POSITION_CHANGE = "onScorePositionChange"; const ON_PLAYBACK_STATE_CHANGE = "onPlaybackStateChange"; const ON_SCORE_LOADED = "onScoreLoaded"; const ON_CLOSE_INTENT = "onWindowCloseIntent"; const ON_RECEIVED_CONFIG = "onReceivedConfig"; const ON_RESIZE = "onResize"; const WIDGET_IFRAME_ID = "TuttiTempiWidget"; /** * Simple object check. * @param item * @returns {boolean} */ function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } /** * Deep merge two objects. * @param target * @param ...sources */ function mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return mergeDeep(target, ...sources); } /** * */ function WidgetLoader(params, onLoaded) { console.debug('WidgetLoader()', params); let that = this; this.socket = {}; this.rootElement = params.rootElement; this.graph = params.graph; this.score = params.score; this.filter = params.filter; this.onPlaybackStateChange = params.onPlaybackStateChange; this.onWindowCloseIntent = () => window.close(); const publicPath = scriptSrc.substring(0, scriptSrc.lastIndexOf('/')); const searchStr = window.location.search; const searchParams = decodeHashParams(searchStr.slice(1, searchStr.length)); const hashStr = window.location.hash; const hashParams = decodeHashParams(hashStr.slice(1, hashStr.length)); let allowedSourceTypes = params.allowedSourceTypes; let hideAllowedSourceTypes = false; if (allowedSourceTypes && allowedSourceTypes.display === false) { hideAllowedSourceTypes = true; } if (allowedSourceTypes) delete allowedSourceTypes.display; let width = params.width ? params.width : (params.rootElement.offsetWidth > 0) ? params.rootElement.offsetWidth : window.innerWidth; let height = params.height ? params.height : (params.rootElement.offsetHeight > 0) ? params.rootElement.offsetHeight : window.innerHeight; const iFrameUrl = publicPath + '/iframe.html' + '?' + '&width=' + width + '&height=' + height + ((params.score && typeof params.score.display !== "undefined") ? '&showScore=' + params.score.display : "") + ((params.score && typeof params.score.geometryApiEndpoint !== 'undefined') ? '&geometryApiEndpoint=' + params.score.geometryApiEndpoint : "") + (typeof params.musicWorkId !== 'undefined' ? '&musicWorkId=' + encodeURIComponent(params.musicWorkId) : "") + ((params.filter && typeof params.filter.value !== "undefined" && params.filter.value !== "") ? '&filterValue=' + encodeURIComponent(params.filter.value) : "") + ((params.filter && typeof params.filter.notable !== "undefined") ? '&filterNotable=' + params.filter.notable : "") + ((params.filter && typeof params.filter.popular !== "undefined") ? '&filterPopular=' + params.filter.popular : "") + ((params.filter && typeof params.filter.recordingList !== 'undefined') ? '&filterRecordingList=' + encodeURIComponent(JSON.stringify(params.filter.recordingList)) : "") + (typeof params.allowedSourceTypes !== "undefined" ? '&allowedSourceTypes=' + JSON.stringify(params.allowedSourceTypes) : "") + (hideAllowedSourceTypes ? "&hideAllowedSourceTypes=true" : "") + (params.hideSideMenu === 'true' || params.hideSideMenu === true ? '&hideSideMenu=' + params.hideSideMenu : "") + (params.spotifyClientId ? '&spotifyClientId=' + params.spotifyClientId : "") + (params.spotifyScopeList ? '&spotifyScopeList=' + params.spotifyScopeList : "") + ((hashParams.error || searchParams.error) ? '&error=' + (hashParams.error || searchParams.error) : "") + ((hashParams.access_token || searchParams.access_token) ? '&access_token=' + (hashParams.access_token || searchParams.access_token) : "") + ((hashParams.expires_in || searchParams.expires_in) ? '&expires_in=' + (hashParams.expires_in || searchParams.expires_in) : "") ; function initXDMConnection() { if (typeof window.NSEasyXDM === 'undefined' || typeof window.NSEasyXDM.easyXDM === 'undefined') { console.debug('easyXDM not loaded yet, waiting'); setTimeout(function () { initXDMConnection(); }, 250); return; } console.debug('init socket', that); that.socket = new window.NSEasyXDM.easyXDM.Socket({ remote: iFrameUrl, isHost: true, container: params.rootElement, props: { height: height, width: width, allow: "encrypted-media, autoplay", frameborder: 0, scrolling: 'no', id: WIDGET_IFRAME_ID }, onMessage: function (message, origin) { handleXdmMessage(that, message, origin); }, onReady: function () { if (typeof onLoaded === 'function') { onLoaded(that); } } }); } console.debug('WidgetLoader: start init xdm with provider iframe url ' + iFrameUrl); initXDMConnection(); } /** * a private function handling all events coming from the widget instance (onLoads, onClicks, etc.) * @param widget * @param message * @param origin */ function handleXdmMessage(widget, message, origin) { let m = JSON.parse(message); // console.log('message from app arrived. message: ', message, widget); switch (m.type) { case (ON_GRAPH_CLICK): widget.graph && typeof widget.graph.onClick === "function" && widget.graph.onClick( m.data.selectedRecordingId, m.data.selectedRecordingSection, m.data.recordingTime, m.data.workTime); return; case (ON_GRAPH_MOUSE_MOVE): widget.graph && typeof widget.graph.onMouseMove === "function" && widget.graph.onMouseMove( m.data.recordingId, m.data.sectionId, m.data.workPosition); return; case (ON_ZOOM): widget.graph && typeof widget.graph.onZoom === "function" && widget.graph.onZoom(m.data.from, m.data.to); return; case (ON_SCORE_CLICK): widget.score && typeof widget.score.onClick === "function" && widget.score.onClick( m.data.page, m.data.measure, m.data.workTime); return; case (ON_SCORE_POSITION_CHANGE): widget.score && typeof widget.score.onPositionChange === "function" && widget.score.onPositionChange( m.data.page, m.data.measure, m.data.workTime); return; case (ON_PLAYBACK_STATE_CHANGE): typeof widget.onPlaybackStateChange === "function" && widget.onPlaybackStateChange( m.data.playbackState); return; case (ON_SCORE_LOADED): widget.score && typeof widget.score.onScoreLoaded === "function" && widget.score.onScoreLoaded(m.data.scoreId); return; case (ON_CLOSE_INTENT): typeof widget.onWindowCloseIntent === 'function' && widget.onWindowCloseIntent(); return; case (ON_RECEIVED_CONFIG): console.debug("Received config ", m.data); return; case (ON_RESIZE): console.log("resizing", widget); return; default: console.debug(`ignoring unknown message with type ${m.type}`); return; } } me.init = function (params, onLoaded) { console.debug('WidgetLoader.init()', params); if (!params.hasOwnProperty("rootElement")) { throw new WidgetException("root element not provided, cannot initialize tuttitempi"); } if (typeof easyXDM === 'undefined') loadEasyXDM(); me = new WidgetLoader(params, onLoaded); }; me.resize = (width, height) => { clearTimeout(me.resizeTimeout); me.resizeTimeout = setTimeout(() => { me.socket.postMessage && me.socket.postMessage(JSON.stringify({ type: "onResize", data: { width, height } })); let iframe = document.getElementById(WIDGET_IFRAME_ID); iframe.setAttribute("width", String(width - 15)); iframe.setAttribute("height", String(height - 15)); }, 500); }; me.clickOnMeasure = (page, measureNumber, totalMeasures) => { me.socket.postMessage && me.socket.postMessage(JSON.stringify({ type: 'clickOnMeasure', page: page, measureNumber: measureNumber, totalMeasures: totalMeasures })); }; me.resumePlayback = () => { me.socket.postMessage && me.socket.postMessage(JSON.stringify({ type: 'resumePlayback' })); }; me.pausePlayback = () => { me.socket.postMessage && me.socket.postMessage(JSON.stringify({ type: 'pausePlayback' })); }; me.loadMusicWork = (musicWorkId) => { me.socket.postMessage && me.socket.postMessage(JSON.stringify({ type: 'loadMusicWork', musicWorkId })); }; me.updateConfig = (params) => { me.graph = mergeDeep(me.graph, params.graph); me.score = mergeDeep(me.score, params.score); if (params.onPlaybackStateChange) { me.onPlaybackStateChange = params.onPlaybackStateChange; } } function WidgetException(message) { this.message = message; this.name = "TuttiTempiWidgetException"; } function loadEasyXDM() { var pnsv = document.createElement('script'); pnsv.type = 'text/javascript'; pnsv.async = true; pnsv.src = '//cdnjs.cloudflare.com/ajax/libs/easyXDM/2.4.20/easyXDM.debug.js'; pnsv.onload = () => { console.debug("loaded easyXDM"); window.NSEasyXDM = {easyXDM: easyXDM.noConflict("NSEasyXDM")}; }; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(pnsv); } /** * decodes a parameter string (p1=v1&p2=v2&... syntax) into the object {p1: v1, p2: v2, ...} */ function decodeHashParams(str) { const hashParams = {}; const a = /\+/g; // Regex for replacing addition symbol with a space const r = /([^&;=]+)=?([^&;]*)/g; const d = (s) => decodeURIComponent(s.replace(a, " ")); let e; while (e = r.exec(str)) { hashParams[d(e[1])] = d(e[2]); } return hashParams; } return me; }(window.WidgetLoader || {}, document.currentScript.getAttribute('src')));