UNPKG

@plutovr/multi-app-manager

Version:
375 lines (374 loc) 14.6 kB
import { v4 as uuidv4 } from "uuid"; /** * Events that Pluto Multi App sends to all running XRPKs * Subscribe to these events with MultiAppManager.addEventListener() * PMAL -> All XRPKs */ export var PMALEventType; (function (PMALEventType) { PMALEventType["appAdded"] = "plutomae-event-app-added"; PMALEventType["appRemoved"] = "plutomae-event-app-removed"; PMALEventType["conversationUserUpdate"] = "plutomae-event-conversation-user-update"; PMALEventType["promiseResponse"] = "plutomae-event-promise-response"; })(PMALEventType || (PMALEventType = {})); /** * Events that are sent to specific XRPKs requesting various actions * PMAL -> Specific XRPK */ export var PMALAppMessageEventType; (function (PMALAppMessageEventType) { PMALAppMessageEventType["shouldPinApp"] = "plutomam-should-pin-app"; PMALAppMessageEventType["shouldUnpinApp"] = "plutomam-should-unpin-app"; PMALAppMessageEventType["getMATransform"] = "plutomam-get-ma-transform"; })(PMALAppMessageEventType || (PMALAppMessageEventType = {})); /** * All events from an XRPK are sent using this type * @disclaimer Not intended to be used directly. Use the provided public functions to send events to Pluto Multi App * */ export const XRPKEventType = "plutomae-xrpk-event"; /** * Event descriptions that can be sent to Pluto Multi App Launcher from an XRPK * @disclaimer These are not event strings, but descriptions of events. Use the provided public functions to send events to Pluto Multi App Launcher * XRPK -> PMAL */ export var XRPKEventTypeDescription; (function (XRPKEventTypeDescription) { XRPKEventTypeDescription["launchApp"] = "launch-xrpk"; XRPKEventTypeDescription["launchAppByNameAndId"] = "launch-xrpk-by-name-id"; XRPKEventTypeDescription["launchAssetByNameAndId"] = "launch-asset-by-name-id"; XRPKEventTypeDescription["duplicateApp"] = "duplicate-xrpk"; XRPKEventTypeDescription["appIsReady"] = "xrpk-loaded"; XRPKEventTypeDescription["removeApp"] = "remove-xrpk"; XRPKEventTypeDescription["getAppList"] = "retrieve-app-list"; XRPKEventTypeDescription["didPinApp"] = "did-pin-app"; XRPKEventTypeDescription["didUnpinApp"] = "did-unpin-app"; XRPKEventTypeDescription["setPinState"] = "set-pin-state"; XRPKEventTypeDescription["currentTransform"] = "current-transform"; XRPKEventTypeDescription["canProvideTransform"] = "can-provide-transform"; XRPKEventTypeDescription["isOwner"] = "is-owner"; XRPKEventTypeDescription["getSavedApps"] = "get-saved-xrpks"; XRPKEventTypeDescription["getSavedAssets"] = "get-saved-assets"; })(XRPKEventTypeDescription || (XRPKEventTypeDescription = {})); export var AssetType; (function (AssetType) { AssetType["image"] = "image"; AssetType["model"] = "model"; })(AssetType || (AssetType = {})); /** * Pluto Multi App Event Handler * Use this class to send and receive events between a mutli app and the Pluto Multi App Launcher */ class MultiAppManager { constructor() { this._appId = this.getAppState().appId; this._eventHandler = new EventHandler(); Object.values(PMALEventType).forEach(type => { this._eventHandler.registerEvent(type); window.parent.addEventListener(type, (e) => this._eventHandler.dispatchEvent(type, e.detail)); }); Object.values(PMALAppMessageEventType).forEach(type => { this._eventHandler.registerEvent(type); window.parent.addEventListener(type, (e) => { if (e.detail.appId === this._appId) { this.appMessageHandler(type, e.detail); } }); }); this._openPromises = {}; window.parent.addEventListener(PMALEventType.promiseResponse, (e) => this.handleResponse(e.detail.responseId, e.detail.data, e.detail.error)); } /** Implement this method so that the transform can be provided when requested by an external source * @param appId The App ID of the multi app whose transform is being requested (should be your multi app) * @returns A Multi App Transform */ set onProvideTransform(func) { this.canProvideTransform(); this._onProvideTransform = func; } /** * Add an event listenter for Pluto Multi App Manager * @param type The `PMALEventType` to subscribe to * @param callback The callback to run when the event is receieved */ addEventListener(type, callback) { this._eventHandler.addEventListener(type, callback); } /** * Request to launch an XRPK in Pluto Multi App Launcher * @param linkUrl The url of the XRPK app. Must link directly to the file (ie. .wbn file) * @param transform A transform for the Pluto Multi App Launcher to pass along to the app being launched (if supported by the app being launched) * @returns A promise once a response is receieved from Pluto Multi App */ launchXRPK(linkUrl, transform) { if (!this.isXRPK()) { return Promise.reject(new Error("App is not running as an xrpk; cannot send an event to Pluto Multi App")); } let responseId = this.generateResponseId(); return new Promise((resolve, reject) => { this._openPromises[responseId] = { resolve, reject }; window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.launchApp, appId: this._appId, url: linkUrl, transform: transform, responseId: responseId, }, })); }); } duplicateXRPK(id, transform, shouldWaitForAppReady) { if (!this.isXRPK()) { return Promise.reject(new Error("App is not running as an xrpk; cannot send an event to Pluto Multi App")); } let responseId = this.generateResponseId(); return new Promise((resolve, reject) => { this._openPromises[responseId] = { resolve, reject }; window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.duplicateApp, appId: this._appId, shouldWaitForAppReady, transform: transform, appType: this.getAppState().type, assetLoader: this.getAppState().assetLoader, sourceAppId: this.getAppState().sourceAppId || this._appId, url: this.getAppState().xrpkpath, responseId, }, })); }); } XRPKLoaded() { window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.appIsReady, appId: this._appId, }, })); } /** * Request to remove an XRPK in Pluto Multi App Launcher * @param appIdToRemove The app ID of the app to be removed * @returns A promise once a response is receieved from Pluto Multi App */ removeXRPK(appIdToRemove) { if (!this.isXRPK()) { return Promise.reject(new Error("App is not running as an xrpk; cannot send an event to Pluto Multi App")); } let responseId = this.generateResponseId(); return new Promise((resolve, reject) => { this._openPromises[responseId] = { resolve, reject }; window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.removeApp, appId: this._appId, appIdToRemove, responseId, }, })); }); } /** * Request to determine whether this client is the owner of this app * @returns A promise once a response is receieved from Pluto Multi App */ getOwnerData() { if (!this.isXRPK()) { return Promise.reject(new Error("App is not running as an xrpk; cannot send an event to Pluto Multi App")); } let responseId = this.generateResponseId(); return new Promise((resolve, reject) => { this._openPromises[responseId] = { resolve, reject }; window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.isOwner, appId: this._appId, responseId, }, })); }); } /** * Get the current app list from Pluto Multi App Launcher * @returns A promise that when resolved includes the app list */ retrieveAppList() { if (!this.isXRPK()) { return Promise.reject(new Error("App is not running as an xrpk; cannot send an event to Pluto Multi App")); } let responseId = this.generateResponseId(); return new Promise((resolve, reject) => { this._openPromises[responseId] = { resolve, reject }; window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.getAppList, appId: this._appId, responseId: responseId, }, })); }); } /** * Let Pluto Multi App Launcher know that the app has been pinned * @param transform An optional transform of where the app was pinned * @returns Returns true if a message was sent to Pluto Multi App Launcher, else false */ didPinApp(transform) { if (!this.isXRPK()) { return false; } window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.didPinApp, appId: this._appId, transform: transform, }, })); return true; } /** * Let Pluto Multi App Launcher know that the app has been unpinned * @returns Returns true if a message was sent to Pluto Multi App Launcher, else false */ didUnpinApp() { if (!this.isXRPK()) { return false; } window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.didUnpinApp, appId: this._appId, }, })); return true; } /** * Let Pluto Multi App Launcher know pinned state of an XRPK * @param pinned True if the app is pinned, otherwise false if the app is unpinned * @returns Returns true if a message was sent to Pluto Multi App Launcher, else false */ setPinState(pinned) { if (!this.isXRPK()) { return false; } window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.setPinState, appId: this._appId, pinned: pinned, }, })); return true; } /** * App State is provided to every app by Pluto Multi App Launcher * @returns The App State object, or an empty object if the App State does not exist */ getAppState() { return window.AppState ? window.AppState : {}; } /** * Send the current transform of a multi app to the Pluto Multi App Launcher * @param transform * @returns Returns true if a message was sent to Pluto Multi App Launcher, else false */ currentMATransform(transform) { if (!this.isXRPK()) { return false; } window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.currentTransform, appId: this._appId, transform: transform, }, })); return true; } /** * Let the Pluto Multi App Launcher know that your app is capable of sending a transform * @param transform * @returns Returns true if a message was sent to Pluto Multi App Launcher, else false */ canProvideTransform() { if (!this.isXRPK()) { return false; } window.parent.dispatchEvent(new CustomEvent(XRPKEventType, { detail: { type: XRPKEventTypeDescription.canProvideTransform, appId: this._appId, }, })); return true; } // Private functions appMessageHandler(type, args) { switch (type) { case PMALAppMessageEventType.getMATransform: if (!("callback" in args)) { console.error("multi-app-manager: A callback function is needed to get an app's transform"); break; } args.callback(this._onProvideTransform ? this._onProvideTransform(this._appId) : undefined); break; default: this._eventHandler.dispatchEvent(type, args); break; } } handleResponse(responseId, data, error) { if (responseId in this._openPromises) { let { resolve, reject } = this._openPromises[responseId]; if (error) reject(error); else if (data) resolve(data); else resolve(); delete this._openPromises[responseId]; } } generateResponseId() { return `${this._appId ? this._appId : "MAM"}-response-${uuidv4()}`; } isXRPK() { if (this._appId === undefined) return false; try { return window.self !== window.top; } catch (e) { return true; } } } // Private helper classes class Event { constructor(type) { this.type = type; this.callbacks = []; } registerCallback(callback) { this.callbacks.push(callback); } } class EventHandler { constructor() { this.events = {}; } registerEvent(type) { var event = new Event(type); this.events[type] = event; } dispatchEvent(type, args) { this.events[type].callbacks.forEach((callback) => { callback(args); }); } addEventListener(type, callback) { this.events[type].registerCallback(callback); } } export default new MultiAppManager();