UNPKG

@plattar/plattar-web

Version:

Module for interfacing with the Plattar Embeds and Web Viewers (https://www.plattar.com)

1,670 lines (1,358 loc) 64.5 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.PlattarWeb = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ const Util = require("../../util/util"); const ElementController = require("../controllers/element-controller"); const { messenger } = require("@plattar/context-messenger"); class BaseElement extends HTMLElement { constructor() { super(); } connectedCallback() { this._controller = new ElementController(this); } set onready(callback) { if (this._controller) { this._controller.onload = callback; return; } throw new Error("set BaseElement.onready - cannot use as element not connected"); } get messengerInstance() { return messenger; } get messenger() { return this._controller ? this._controller.messenger : undefined; } get context() { return this.messengerInstance.self; } get parent() { return this.messengerInstance.parent; } get element() { return this._controller; } get ready() { return this._controller ? true : false; } get allowDragDrop() { return this._controller ? this._controller.controller.allowDragDrop : false; } set allowDragDrop(value) { if (this._controller) { this._controller.controller.allowDragDrop = value; return; } throw new Error("set BaseElement.allowDragDrop - cannot use as element not connected"); } get permissions() { return []; } get coreAttributes() { return [{ key: "scene-id", map: "scene_id" }]; } usesCoreAttribute(key) { const attr = this.coreAttributes; const length = attr.length; for (let i = 0; i < length; i++) { if (attr[i].key === key) { return true; } } return false; } usesOptionalAttribute(key) { const attr = this.optionalAttributes; const length = attr.length; for (let i = 0; i < length; i++) { if (attr[i].key === key) { return true; } } return false; } usesAttribute(key) { return this.usesCoreAttribute(key) || this.usesOptionalAttribute(key); } get optionalAttributes() { return []; } get hasAllCoreAttributes() { const attr = this.coreAttributes; const length = attr.length; for (let i = 0; i < length; i++) { if (!this.hasAttribute(attr[i].key)) { return false; } } return true; } get allMappedAttributes() { const map = new Map(); const coreAttr = this.coreAttributes; const optAttr = this.optionalAttributes; coreAttr.forEach((ele) => { if (this.hasAttribute(ele.key)) { map.set(ele.map, this.getAttribute(ele.key)); } }); optAttr.forEach((ele) => { if (this.hasAttribute(ele.key)) { map.set(ele.map, this.getAttribute(ele.key)); } }); return map; } get allMappedAttributesQuery() { const attr = this.allMappedAttributes; let queryStr = ""; let first = true; for (const [key, value] of attr.entries()) { queryStr += (first ? ("?" + key + "=" + value) : ("&" + key + "=" + value)); first = false; } return queryStr; } get elementType() { return "none"; } get elementLocation() { return Util.getElementLocation(this.elementType); } } module.exports = BaseElement; },{"../../util/util":32,"../controllers/element-controller":3,"@plattar/context-messenger":16}],2:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class ConfiguratorElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "configurator"; } get elementLocation() { if (this.hasAttribute("show-ui")) { const state = this.getAttribute("show-ui"); return state === "true" ? "configurator/dist/index.html" : super.elementLocation; } return super.elementLocation; } get optionalAttributes() { return [ { key: "config-state", map: "config_state" }, { key: "show-ar", map: "show_ar" }, { key: "scene-graph-id", map: "scene_graph_id" } ]; } } module.exports = ConfiguratorElement; },{"./base/base-element.js":1}],3:[function(require,module,exports){ const Util = require("../../util/util.js"); const { messenger } = require("@plattar/context-messenger"); const IFrameController = require("./iframe-controller.js"); class ElementController { constructor(element) { this._element = element; // observe the changes in scene-id const callback = (mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'attributes' && element.usesAttribute(mutation.attributeName)) { if (element.hasAllCoreAttributes) { this._load(); } } } }; const observer = new MutationObserver(callback); observer.observe(this._element, { attributes: true }); // load initially if all core attributes are set if (element.hasAllCoreAttributes) { this._load(); } } _load() { if (this._controller) { this._controller._destroy(); this._controller = undefined; } const element = this._element; this._server = element.hasAttribute("server") ? element.getAttribute("server") : "production"; const serverLocation = Util.getServerLocation(this._server); if (serverLocation === undefined) { throw new Error("ElementController - attribute \"server\" must be one of \"production\", \"staging\", \"review\" or \"dev\""); } const embedLocation = element.elementLocation; if (embedLocation === undefined) { throw new Error("ElementController - element named \"" + elementType + "\" is invalid"); } const source = serverLocation + embedLocation + element.allMappedAttributesQuery; // ensure iframe ID is randomly generated as we could have multiple iframes // with same Scene ID - such as viewer and editor running on same page this._messengerID = "element_" + Util.id(); this._controller = new IFrameController(element, source, this._messengerID, (node) => { // for cross-origin messenger setup, we need to setup manually // this might require additional iterations messenger.addChild(node); }); } set onload(callback) { if (!callback) { return; } if (this.messenger) { callback(); } else { messenger.onload(this._messengerID, () => { callback(); }); } } get messenger() { return messenger[this._messengerID]; } get context() { return messenger.self; } get parent() { return messenger.parent; } get controller() { return this._controller; } } module.exports = ElementController; },{"../../util/util.js":32,"./iframe-controller.js":4,"@plattar/context-messenger":16}],4:[function(require,module,exports){ const Util = require("../../util/util.js"); class IFrameController { constructor(element, src, id, onelemload = undefined) { this._iframe = document.createElement("iframe"); this._isDraggable = false; // check the onload functionality if cross-origin is defined if (!element.hasAttribute("sameorigin")) { this._iframe.onload = () => { if (onelemload) { onelemload(this._iframe); } }; } this._iframe.setAttribute("id", id); this._iframe.setAttribute("width", element.hasAttribute("width") ? element.getAttribute("width") : "500px"); this._iframe.setAttribute("height", element.hasAttribute("height") ? element.getAttribute("height") : "500px"); this._iframe.setAttribute("src", src); this._iframe.setAttribute("frameBorder", "0"); const permissions = Util.getPermissionString(element.permissions); if (permissions) { this._iframe.setAttribute("allow", permissions); } const shadow = element.shadowRoot || element.attachShadow({ mode: 'open' }); this.allowDragging = false; shadow.append(this._iframe); if (element.hasAttribute("fullscreen")) { const style = document.createElement('style'); style.textContent = `._PlattarFullScreen { width: 100%; height: 100%; position: absolute; top: 0; left: 0; }`; this._iframe.className = "_PlattarFullScreen"; shadow.append(style); this._fsStyle = style; } } set allowDragDrop(value) { if (value) { this._isDraggable = true; this._iframe.style.pointerEvents = "none"; } else { this._isDraggable = false; this._iframe.style.pointerEvents = "auto"; } } _destroy() { if (this._iframe) { this._iframe.remove(); } if (this._fsStyle) { this._fsStyle.remove(); } this._iframe = undefined; this._fsStyle = undefined; } get allowDragDrop() { return this._isDraggable; } get width() { return this._iframe.getAttribute("width"); } get child() { return this._iframe; } set width(value) { this._iframe.setAttribute("width", value); } get height() { return this._iframe.getAttribute("height"); } set height(value) { this._iframe.setAttribute("height", value); } get id() { return this._iframe.id; } } module.exports = IFrameController; },{"../../util/util.js":32}],5:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class EditorElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "editor"; } } module.exports = EditorElement; },{"./base/base-element.js":1}],6:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class EWallElement extends BaseElement { constructor() { super(); } get permissions() { return ["camera *", "autoplay *", "xr-spatial-tracking *", "gyroscope *", "accelerometer *", "magnetometer *"]; } get elementType() { return "ewall"; } } module.exports = EWallElement; },{"./base/base-element.js":1}],7:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class FaceARElement extends BaseElement { constructor() { super(); } get permissions() { return ["camera", "autoplay"]; } get elementType() { return "facear"; } get optionalAttributes() { return [{ key: "variation-id", map: "variationId" }, { key: "variation-sku", map: "variationSku" }, { key: "product-id", map: "productId" }, { key: "config-state", map: "config_state" }, { key: "show-ar", map: "show_ar" }]; } } module.exports = FaceARElement; },{"./base/base-element.js":1}],8:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class GalleryElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "gallery"; } get optionalAttributes() { return []; } } module.exports = GalleryElement; },{"./base/base-element.js":1}],9:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class LauncherElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "launcher"; } get optionalAttributes() { return [ { key: "config-state", map: "config_state" }, { key: "qr-options", map: "qr_options" }, { key: "embed-type", map: "embed_type" }, { key: "product-id", map: "product_id" }, { key: "scene-product-id", map: "scene_product_id" }, { key: "variation-id", map: "variation_id" }, { key: "variation-sku", map: "variation_sku" }, { key: "ar-mode", map: "ar_mode" }, { key: "show-ar-banner", map: "show_ar_banner" }, { key: "scene-graph-id", map: "scene_graph_id" } ]; } } module.exports = LauncherElement; },{"./base/base-element.js":1}],10:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class ModelElement extends BaseElement { constructor() { super(); } get permissions() { return ["camera", "autoplay"]; } get elementType() { return "model"; } get coreAttributes() { return []; } get optionalAttributes() { return [{ key: "mode", map: "mode" }, { key: "capture-id", map: "capture_id" }, { key: "model-id", map: "model_id" }]; } } module.exports = ModelElement; },{"./base/base-element.js":1}],11:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class ProductElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "product"; } get coreAttributes() { return [{ key: "product-id", map: "product_id" }]; } get optionalAttributes() { return [{ key: "variation-id", map: "variation_id" }, { key: "variation-sku", map: "variationSku" }, { key: "show-ar", map: "show_ar" }]; } } module.exports = ProductElement; },{"./base/base-element.js":1}],12:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class StudioElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "studio"; } get optionalAttributes() { return [{ key: "variation-id", map: "variationId" }, { key: "variation-sku", map: "variationSku" }]; } } module.exports = StudioElement; },{"./base/base-element.js":1}],13:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class ViewerElement extends BaseElement { constructor() { super(); } get permissions() { return ["autoplay"]; } get elementType() { return "viewer"; } get optionalAttributes() { return [{ key: "variation-id", map: "variationId" }, { key: "variation-sku", map: "variationSku" }, { key: "product-id", map: "productId" }, { key: "show-ar", map: "show_ar" }]; } } module.exports = ViewerElement; },{"./base/base-element.js":1}],14:[function(require,module,exports){ const BaseElement = require("./base/base-element.js"); class WebXRElement extends BaseElement { constructor() { super(); } get permissions() { return ["camera", "autoplay", "xr-spatial-tracking"]; } get elementType() { return "webxr"; } } module.exports = WebXRElement; },{"./base/base-element.js":1}],15:[function(require,module,exports){ "use strict"; const WebXRElement = require("./elements/webxr-element.js"); const ViewerElement = require("./elements/viewer-element.js"); const ProductElement = require("./elements/product-element.js"); const EWallElement = require("./elements/ewall-element.js"); const FaceARElement = require("./elements/facear-element.js"); const EditorElement = require("./elements/editor-element.js"); const StudioElement = require("./elements/studio-element.js"); const ModelElement = require("./elements/model-element.js"); const ConfiguratorElement = require("./elements/configurator-element.js"); const LauncherElement = require("./elements/launcher-element.js"); const GalleryElement = require("./elements/gallery-element.js"); const Version = require("./version"); if (customElements.get("plattar-webxr") === undefined) { customElements.define("plattar-webxr", WebXRElement); } if (customElements.get("plattar-viewer") === undefined) { customElements.define("plattar-viewer", ViewerElement); } if (customElements.get("plattar-product") === undefined) { customElements.define("plattar-product", ProductElement); } if (customElements.get("plattar-editor") === undefined) { customElements.define("plattar-editor", EditorElement); } if (customElements.get("plattar-facear") === undefined) { customElements.define("plattar-facear", FaceARElement); } if (customElements.get("plattar-8wall") === undefined) { customElements.define("plattar-8wall", EWallElement); } if (customElements.get("plattar-studio") === undefined) { customElements.define("plattar-studio", StudioElement); } if (customElements.get("plattar-model") === undefined) { customElements.define("plattar-model", ModelElement); } if (customElements.get("plattar-configurator") === undefined) { customElements.define("plattar-configurator", ConfiguratorElement); } if (customElements.get("plattar-gallery") === undefined) { customElements.define("plattar-gallery", GalleryElement); } if (customElements.get("plattar-launcher") === undefined) { customElements.define("plattar-launcher", LauncherElement); } console.log("using @plattar/plattar-web v" + Version); module.exports = { version: Version }; },{"./elements/configurator-element.js":2,"./elements/editor-element.js":5,"./elements/ewall-element.js":6,"./elements/facear-element.js":7,"./elements/gallery-element.js":8,"./elements/launcher-element.js":9,"./elements/model-element.js":10,"./elements/product-element.js":11,"./elements/studio-element.js":12,"./elements/viewer-element.js":13,"./elements/webxr-element.js":14,"./version":33}],16:[function(require,module,exports){ "use strict"; const Messenger = require("./messenger/messenger.js"); const Memory = require("./memory/memory.js"); const GlobalEventHandler = require("./messenger/global-event-handler.js"); const Version = require("./version"); // re-create a new messenger instance if it does not exist if (!GlobalEventHandler.instance().messengerInstance) { // create our instances which we only need one each const messengerInstance = new Messenger(); // memory requires the messenger interface to function correctly const memoryInstance = new Memory(messengerInstance); GlobalEventHandler.instance().messengerInstance = messengerInstance; GlobalEventHandler.instance().memoryInstance = memoryInstance; } // re-create a new memory instance if it does not exist if (!GlobalEventHandler.instance().memoryInstance) { // memory requires the messenger interface to function correctly const memoryInstance = new Memory(GlobalEventHandler.instance().messengerInstance); GlobalEventHandler.instance().memoryInstance = memoryInstance; } console.log("using @plattar/context-messenger v" + Version); module.exports = { messenger: GlobalEventHandler.instance().messengerInstance, memory: GlobalEventHandler.instance().memoryInstance, version: Version } },{"./memory/memory.js":17,"./messenger/global-event-handler.js":25,"./messenger/messenger.js":26,"./version":31}],17:[function(require,module,exports){ const PermanentMemory = require("./permanent-memory"); const TemporaryMemory = require("./temporary-memory"); /** * Memory is a singleton that allows setting variables from multiple * iframe contexts */ class Memory { constructor(messengerInstance) { this._messenger = messengerInstance; this._tempMemory = new TemporaryMemory(messengerInstance); this._permMemory = new PermanentMemory(messengerInstance); this._messenger.self.__memory__set_temp_var = (name, data) => { this._tempMemory[name] = data; }; this._messenger.self.__memory__set_perm_var = (name, data) => { this._permMemory[name] = data; }; } get temp() { return this._tempMemory; } get perm() { return this._permMemory; } } module.exports = Memory; },{"./permanent-memory":18,"./temporary-memory":19}],18:[function(require,module,exports){ const WrappedValue = require("./wrapped-value"); class PermanentMemory { constructor(messengerInstance) { return new Proxy(this, { get: (target, prop, receiver) => { // sets the watcher callback if (prop === "watch") { return (variable, callback) => { if (!target[variable]) { target[variable] = new WrappedValue(variable, true, messengerInstance); } target[variable].watch = callback; }; } // clears everything, including specific items if (prop === "clear") { return () => { for (const pitem of Object.getOwnPropertyNames(target)) { delete target[pitem]; localStorage.removeItem(pitem); } }; } // clears everything, including from storage if (prop === "purge") { return () => { localStorage.clear(); for (const pitem of Object.getOwnPropertyNames(target)) { delete target[pitem]; } }; } if (prop === "refresh") { return () => { for (const val of Object.getOwnPropertyNames(target)) { target[val].refresh(); } }; } // on first access, we create a WrappedValue type if (!target[prop]) { target[prop] = new WrappedValue(prop, true, messengerInstance); } return target[prop].value; }, set: (target, prop, value) => { if (!target[prop]) { target[prop] = new WrappedValue(prop, true, messengerInstance); } target[prop].value = value; return true; } }); } } module.exports = PermanentMemory; },{"./wrapped-value":20}],19:[function(require,module,exports){ const WrappedValue = require("./wrapped-value"); class TemporaryMemory { constructor(messengerInstance) { return new Proxy(this, { get: (target, prop, receiver) => { // sets the watcher callback if (prop === "watch") { return (variable, callback) => { if (!target[variable]) { target[variable] = new WrappedValue(variable, false, messengerInstance); } target[variable].watch = callback; }; } // clears everything // purge is the same thing for all temporary variables if (prop === "clear" || prop === "purge") { return () => { for (const val of Object.getOwnPropertyNames(target)) { delete target[val]; } }; } if (prop === "refresh") { return () => { for (const val of Object.getOwnPropertyNames(target)) { target[val].refresh(); } }; } // on first access, we create a WrappedValue type if (!target[prop]) { target[prop] = new WrappedValue(prop, false, messengerInstance); } return target[prop].value; }, set: (target, prop, value) => { if (!target[prop]) { target[prop] = new WrappedValue(prop, false, messengerInstance); } target[prop].value = value; return true; } }); } } module.exports = TemporaryMemory; },{"./wrapped-value":20}],20:[function(require,module,exports){ /** * WrappedValue represents a generic value type with a callback function * for when the value has changed */ class WrappedValue { constructor(varName, isPermanent, messengerInstance) { this._value = undefined; this._callback = undefined; this._isPermanent = isPermanent; this._varName = varName; this._messenger = messengerInstance; if (this._isPermanent) { this._value = JSON.parse(localStorage.getItem(this._varName)); } } /** * Refresh the memory value across all memory instances recursively */ refresh() { if (this._isPermanent) { // broadcast variable to all children this._messenger.broadcast.__memory__set_perm_var(this._varName, this._value); // send variable to the parent if (this._messenger.parent) { this._messenger.parent.__memory__set_perm_var(this._varName, this._value); } } else { // broadcast variable to all children this._messenger.broadcast.__memory__set_temp_var(this._varName, this._value); // send variable to the parent if (this._messenger.parent) { this._messenger.parent.__memory__set_temp_var(this._varName, this._value); } } } /** * Refresh this memory for a specific callable interface */ refreshFor(callable) { // invalid interface check if (!this._messenger[callable]) { return; } if (this._isPermanent) { // set the variable for the specific callable this._messenger[callable].__memory__set_perm_var(this._varName, this._value); } else { // set the variable for the specific callable this._messenger[callable].__memory__set_temp_var(this._varName, this._value); } } get value() { if (this._isPermanent && this._value == undefined) { this._value = JSON.parse(localStorage.getItem(this._varName)); } return this._value; } set value(newValue) { if (typeof newValue === "function") { throw new TypeError("WrappedValue.value cannot be set to a function type"); } const oldValue = this._value; this._value = newValue; // for permanent variables, set the variable type if (this._isPermanent) { localStorage.setItem(this._varName, JSON.stringify(this._value)); } // do not fire callback if the old and new values do not match if (this._callback && oldValue !== newValue) { // recursively update this variable across all memory this.refresh(); // perform the callback that the value has just changed this._callback(oldValue, this._value); } } /** * Watches for any change in the current variable */ set watch(newValue) { if (typeof newValue === "function") { if (newValue.length == 2) { this._callback = newValue; } else { throw new RangeError("WrappedValue.watch callback must accept exactly 2 variables. Try using WrappedValue.watch = (oldVal, newVal) => {}"); } } else { throw new TypeError("WrappedValue.watch must be a type of function. Try using WrappedValue.watch = (oldVal, newVal) => {}"); } } } module.exports = WrappedValue; },{}],21:[function(require,module,exports){ /** * Broadcaster is used to call functions in multiple contexts at the * same time. This can be useful without having to handle complex logic * in the application side. * * See Plattar.messenger.broadcast */ class Broadcaster { constructor(messengerInstance) { this._messengerInstance = messengerInstance; this._interfaces = []; return new Proxy(this, { get: (target, prop, receiver) => { switch (prop) { case "_push": case "_interfaces": return target[prop]; default: break; } // execute the desired function on all available stacks return (...args) => { const interfaces = target._interfaces; const promises = []; interfaces.forEach((callable) => { promises.push(target._messengerInstance[callable][prop](...args)); }); return Promise.allSettled(promises); }; } }); } /** * Adds a new callable interface ID to the list of callables */ _push(interfaceID) { // remove existing in case of double-up const index = this._interfaces.indexOf(interfaceID); if (index > -1) { this._interfaces.splice(index, 1); } this._interfaces.push(interfaceID); } } module.exports = Broadcaster; },{}],22:[function(require,module,exports){ const WrappedFunction = require("./wrapped-local-function"); class CurrentFunctionList { constructor() { return new Proxy(this, { get: (target, prop, receiver) => { // sets the watcher callback if (prop === "watch") { return (variable, callback) => { if (!target[variable]) { target[variable] = new WrappedFunction(variable); } target[variable].watch = callback; }; } // clears everything, including specific items if (prop === "clear" || prop === "purge") { return () => { for (const pitem of Object.getOwnPropertyNames(target)) { delete target[pitem]; } }; } // on first access, we create a WrappedValue type if (!target[prop]) { target[prop] = new WrappedFunction(prop); } // return an anonymous function that executes for this variable return (...args) => { return target[prop].exec(...args); }; }, set: (target, prop, value) => { if (!target[prop]) { target[prop] = new WrappedFunction(prop); } target[prop].value = value; return true; } }); } } module.exports = CurrentFunctionList; },{"./wrapped-local-function":23}],23:[function(require,module,exports){ const Util = require("../util/util.js"); /** * WrappedLocalFunction represents a container that holds and maintains a specific function * that was defined in the current web context. It can also be executed by other web contexts * using the Messenger framework. */ class WrappedLocalFunction { constructor(funcName) { this._value = undefined; this._callback = undefined; this._funcName = funcName; } /** * executes the internally stored function with the provided arguments */ _execute(...args) { const rData = this._value(...args); if (this._callback) { this._callback(rData, ...args); } return rData; } /** * Executes the internal function in a Promise chain. Results of the execution * will be evaluated in the promise chain itself */ exec(...args) { return new Promise((accept, reject) => { if (!this._value) { return reject(new Error("WrappedLocalFunction.exec() function with name " + this._funcName + "() is not defined")); } try { // otherwise execute the function const rObject = this._execute(...args); // we need to check if the returned object is a Promise, if so, handle it // differently. This can happen if the function wants to execute asyn if (Util.isPromise(rObject)) { rObject.then((res) => { return accept(res); }).catch((err) => { return reject(err); }); } else { // otherwise, its a non async object so just execute and return the results return accept(rObject); } } catch (e) { return reject(e); } }); } /** * Stores a function for later execution */ set value(newValue) { if (typeof newValue !== "function") { throw new TypeError("WrappedLocalFunction.value must be a function. To store values use Plattar.memory"); } this._value = newValue; } /** * Watches for when this function is executed by some context */ set watch(newValue) { if (typeof newValue === "function") { this._callback = newValue; } else { throw new TypeError("WrappedLocalFunction.watch must be a type of function. Try using WrappedLocalFunction.watch = (rData, ...args) => {}"); } } } module.exports = WrappedLocalFunction; },{"../util/util.js":30}],24:[function(require,module,exports){ const Util = require("./util/util"); class FunctionObserver { constructor() { this._observers = new Map(); } /** * Adds a new callback/listener using the provided function name * and a callback function */ subscribe(functionName, callback) { if (!functionName || !Util.isFunction(callback)) { return () => { }; } // grab an instance of the observer to add the function into const observers = this._observers; let list = observers.get(functionName); if (!list) { list = []; observers.set(functionName, list); } list.push(callback); return () => { return this.unsubscribe(functionName, callback); }; } /** * Removes an existing callback from the provided function name if it exists */ unsubscribe(functionName, callback) { if (!functionName || !Util.isFunction(callback)) { return false; } const observers = this._observers; const list = observers.get(functionName); // search and remove function from the list if it exists if (list) { const index = list.indexOf(callback); if (index > -1) { list.splice(index, 1); return true; } } return false; } /** * Called by an external unit in order to execute all callbacks * belonging to the provided function */ call(functionName, data) { if (!functionName || !data) { return; } const observers = this._observers; const list = observers.get(functionName); // search and remove function from the list if it exists if (list && list.length > 0) { list.forEach((observer) => { try { if (observer) { observer(data); } } catch (e) {/*silent*/ } }); } } } module.exports = FunctionObserver; },{"./util/util":30}],25:[function(require,module,exports){ const RemoteInterface = require("./remote-interface.js"); /** * This is a singleton class that handles events on a global basis. Allows * registering local event listeners etc.. */ class GlobalEventHandler { constructor() { this._eventListeners = {}; // global handler that forwards events to their respectful places // throughout the framework window.addEventListener("message", (evt) => { const data = evt.data; let jsonData = undefined; try { jsonData = JSON.parse(data); } catch (e) { // catch does nothing // this event might not be what we are looking for jsonData = undefined; } // make sure the event is properly formatted if (jsonData && jsonData.event && jsonData.data) { // see if there are any listeners for this if (this._eventListeners[jsonData.event]) { const remoteInterface = new RemoteInterface(evt.source, evt.origin); // loop through and call all the event handlers this._eventListeners[jsonData.event].forEach((callback) => { try { callback(remoteInterface, jsonData.data); } catch (e) { console.error("GlobalEventHandler.message() error occured during callback "); console.error(e); } }); } } }); } set messengerInstance(value) { this._messenger = value; } set memoryInstance(value) { this._memory = value; } get messengerInstance() { return this._messenger; } get memoryInstance() { return this._memory; } listen(event, callback) { if (typeof callback !== "function") { throw new TypeError("GlobalEventHandler.listen(event, callback) callback must be a type of function."); } if (!this._eventListeners[event]) { this._eventListeners[event] = []; } this._eventListeners[event].push(callback); } } GlobalEventHandler.instance = () => { if (!GlobalEventHandler._default) { GlobalEventHandler._default = new GlobalEventHandler(); } return GlobalEventHandler._default; }; module.exports = GlobalEventHandler; },{"./remote-interface.js":27}],26:[function(require,module,exports){ const CurrentFunctionList = require("./current/current-function-list"); const RemoteInterface = require("./remote-interface"); const RemoteFunctionList = require("./remote/remote-function-list"); const Util = require("./util/util.js"); const GlobalEventHandler = require("./global-event-handler.js"); const Broadcaster = require("./broadcaster.js"); const FunctionObserver = require("./function-observer.js"); /** * Messenger is a singleton that allows calling functions in multiple * contexts */ class Messenger { constructor() { // generate a unique id for this instance of the messenger this._id = Util.id(); // ensure the parent stack does not target itself this._parentStack = RemoteInterface.default(); // allows listening for function calls and returns this._functionObserver = new FunctionObserver(); // allow adding local functions immedietly this._currentFunctionList = new CurrentFunctionList(); // allows calling functions on everything this._broadcaster = new Broadcaster(this); // we still need to confirm if a parent exists and has the messenger // framework added.. see _setup() function this._parentFunctionList = undefined; // these are the callback functions to be executed when specific frames // are loaded const callbacks = new Map(); // set object reference this._callbacks = callbacks; this._setup(); return new Proxy(this, { get: (target, prop, receiver) => { // sets the watcher callback if (prop === "onload") { return (variable, callback) => { if (variable === "self" || variable === "id") { return callback(); } if (target[variable]) { return callback(); } if (callbacks.has(variable)) { const array = callbacks.get(variable); array.push(callback); } else { callbacks.set(variable, [callback]); } }; } switch (prop) { case "id": return target._id; case "self": return target._currentFunctionList; case "broadcast": return target._broadcaster; case "addChild": case "observer": case "_setup": case "_registerListeners": case "_id": case "_broadcaster": case "_functionObserver": case "_callbacks": case "_parentStack": return target[prop]; default: break; } const targetVar = target[prop]; // return undefined if target variable doesn't exist // or it has not been verified yet if (!targetVar || !targetVar.isValid()) { return undefined; } return target[prop]; } }); } /** * Returns the FunctionObserver allowing adding observers to function calls */ get observer() { return this._functionObserver; } /** * This will forcefully add a provided node as a child * The node must be stup via messenger-framework propagation */ addChild(childNode) { const remoteInterface = new RemoteInterface(childNode.contentWindow, "*"); // propagate to the child to setup the proper parent-child relationship remoteInterface.send("__messenger__parent_init_inv", { id: childNode.id }); } /** * Internal function call to initialise the messenger framework */ _setup() { this._registerListeners(); // if a parent exists, send a message calling for an initialisation if (this._parentStack) { this._parentStack.send("__messenger__child_init"); } if (window.location.protocol !== "https:") { console.warn("Messenger[" + this._id + "] requires https but protocol is \"" + window.location.protocol + "\", messenger will not work correctly."); } } /** * Register all critical listener interfaces so the framework can function correctly */ _registerListeners() { GlobalEventHandler.instance().listen("__messenger__child_init", (src, data) => { const iframeID = src.id; // check reserved key list switch (iframeID) { case undefined: throw new Error("Messenger[" + this._id + "].setup() Component ID cannot be undefined"); case "self": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"self\" cannot be used as the keyword is reserved"); case "parent": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"parent\" cannot be used as the keyword is reserved"); case "id": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"id\" cannot be used as the keyword is reserved"); case "onload": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"onload\" cannot be used as the keyword is reserved"); default: break; } // initialise the child iframe as a messenger pipe this[iframeID] = new RemoteFunctionList(iframeID, this._functionObserver); this[iframeID].setup(new RemoteInterface(src.source, src.origin)); // add the interface to the broadcaster this._broadcaster._push(iframeID); const callbacks = this._callbacks; // we have registered callbacks, begin execution if (callbacks.has(iframeID)) { const array = callbacks.get(iframeID); if (array) { array.forEach((item, _) => { try { if (item) { item(); } } catch (err) { } }); } } callbacks.delete(iframeID); src.send("__messenger__parent_init"); }); GlobalEventHandler.instance().listen("__messenger__child_init_inv", (src, data) => { const iframeID = data.id; // check reserved key list switch (iframeID) { case undefined: throw new Error("Messenger[" + this._id + "].setup() Component ID cannot be undefined"); case "self": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"self\" cannot be used as the keyword is reserved"); case "parent": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"parent\" cannot be used as the keyword is reserved"); case "id": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"id\" cannot be used as the keyword is reserved"); case "onload": throw new Error("Messenger[" + this._id + "].setup() Component ID of \"onload\" cannot be used as the keyword is reserved"); default: break; } // initialise the child iframe as a messenger pipe this[iframeID] = new RemoteFunctionList(iframeID, this._functionObserver); this[iframeID].setup(new RemoteInterface(src.source, src.origin)); // add the interface to the broadcaster this._broadcaster._push(iframeID); const callbacks = this._callbacks; // we have registered callbacks, begin execution if (callbacks.has(iframeID)) { const array = callbacks.get(iframeID); if (array) { array.forEach((item, _) => { try { if (item) { item();