@plutovr/multi-app-manager
Version:
Pluto Multi App Manager
375 lines (374 loc) • 14.6 kB
JavaScript
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();