@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
JavaScript
(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();