@elliemae/pui-app-sdk 5.15.0-beta.1 → 5.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,7 +72,7 @@ const endSession = async ({
72
72
  redirectUri,
73
73
  responseType,
74
74
  scope,
75
- code = "1004",
75
+ code = "",
76
76
  skipRevoke = false
77
77
  }) => {
78
78
  try {
@@ -91,7 +91,7 @@ const endSession = async ({
91
91
  const params = {
92
92
  client_id: clientId,
93
93
  redirect_uri: redirectUri,
94
- error_code: code,
94
+ ...code && { error_code: code },
95
95
  response_type: responseType,
96
96
  scope
97
97
  };
@@ -42,25 +42,25 @@ const logRecords = {
42
42
  code: "appsdk05",
43
43
  message: `Application load failed. unable to locate ${assetName} in the manifest`
44
44
  }),
45
- APP_INIT_FAILED: (appId, errMsg) => ({
45
+ APP_INIT_FAILED: (appId, instanceId, errMsg) => ({
46
46
  code: "appsdk06",
47
- message: `Application load failed. Unable to load one or more application resources for appId: ${appId}. ${errMsg}`
47
+ message: `Application load failed. Unable to load one or more application resources for appId: ${appId} and instanceId: ${instanceId}. ${errMsg}`
48
48
  }),
49
- APP_LOADING: (appId) => ({
49
+ APP_LOADING: (appId, instanceId) => ({
50
50
  code: "appsdk07",
51
- message: `Application ${appId} is loading...`
51
+ message: `Application ${appId} with instanceId ${instanceId} is loading...`
52
52
  }),
53
- APP_LOADING_COMPLETE: (appId) => ({
53
+ APP_LOADING_COMPLETE: (appId, instanceId) => ({
54
54
  code: "appsdk08",
55
- message: `Application ${appId} loaded`
55
+ message: `Application ${appId} loaded with instanceId ${instanceId}`
56
56
  }),
57
- APP_UNLOADING: (appId) => ({
57
+ APP_UNLOADING: (appId, instanceId) => ({
58
58
  code: "appsdk09",
59
- message: `Application ${appId} unloading...`
59
+ message: `Application ${appId} with instanceId ${instanceId} unloading...`
60
60
  }),
61
- APP_UNLOADING_COMPLETE: (appId) => ({
61
+ APP_UNLOADING_COMPLETE: (appId, instanceId) => ({
62
62
  code: "appsdk10",
63
- message: `Application ${appId} unloaded`
63
+ message: `Application ${appId} with instanceId ${instanceId} unloaded`
64
64
  }),
65
65
  SSF_HOST_OBJECT_NOT_FOUND: (name) => ({
66
66
  code: "appsdk11",
@@ -32,6 +32,7 @@ __export(guest_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(guest_exports);
34
34
  var import_lodash = __toESM(require("lodash"));
35
+ var import_uuid = require("uuid");
35
36
  var import_pui_theme = require("@elliemae/pui-theme");
36
37
  var import_window = require("../window.js");
37
38
  var import_app_config = require("../app-config/index.js");
@@ -53,6 +54,10 @@ const isCrossDomain = () => {
53
54
  }
54
55
  };
55
56
  class CMicroAppGuest {
57
+ /**
58
+ * unique microapp id
59
+ */
60
+ #uuid;
56
61
  static instance;
57
62
  logger;
58
63
  appId;
@@ -62,9 +67,12 @@ class CMicroAppGuest {
62
67
  onMount;
63
68
  onUnmount;
64
69
  onGetRef;
70
+ // eslint-disable-next-line max-statements
65
71
  constructor(params) {
66
72
  this.containerId = "app-container";
73
+ window.emui.uuid = window.emui.uuid || (0, import_uuid.v4)();
67
74
  this.appId = window.emui?.appId || (0, import_config.getAppConfigValue)("appId");
75
+ this.#uuid = window.emui.uuid;
68
76
  this.props = {
69
77
  host: null,
70
78
  hostUrl: null,
@@ -82,15 +90,7 @@ class CMicroAppGuest {
82
90
  this.getHost = this.getHost.bind(this);
83
91
  this.getLogger = this.getLogger.bind(this);
84
92
  this.getProps = this.getProps.bind(this);
85
- const browserWindow = (0, import_window.getWindow)();
86
- if (browserWindow) {
87
- browserWindow.emui = browserWindow.emui || {};
88
- browserWindow.emui[this.appId] = browserWindow.emui[this.appId] || {};
89
- browserWindow.emui[this.appId].init = this.init.bind(this);
90
- browserWindow.emui[this.appId].mount = this.mount.bind(this);
91
- browserWindow.emui[this.appId].unmount = this.unmount.bind(this);
92
- browserWindow.emui[this.appId].getRef = this.getRef.bind(this);
93
- }
93
+ this.#addAppToGlobalVariable();
94
94
  }
95
95
  static getInstance(params) {
96
96
  if (!this.instance) {
@@ -113,6 +113,60 @@ class CMicroAppGuest {
113
113
  getProps() {
114
114
  return this.props;
115
115
  }
116
+ /**
117
+ * add the microapp to the global variable in the parent window and current window
118
+ */
119
+ #addAppToGlobalVariable() {
120
+ const browserWindow = (0, import_window.getWindow)();
121
+ if (!browserWindow) return;
122
+ browserWindow.emui = browserWindow.emui || {};
123
+ const app = {
124
+ uuid: this.#uuid,
125
+ init: this.init.bind(this),
126
+ mount: this.mount.bind(this),
127
+ unmount: this.unmount.bind(this),
128
+ getRef: this.getRef.bind(this),
129
+ navigate: this.navigate.bind(this)
130
+ };
131
+ if (browserWindow.emui[this.appId]) {
132
+ if (Array.isArray(browserWindow.emui[this.appId])) {
133
+ const existingApps = browserWindow.emui[this.appId];
134
+ const appIndex = existingApps.findIndex((a) => a.uuid === this.#uuid);
135
+ if (appIndex > -1) existingApps[appIndex] = app;
136
+ else existingApps.push(app);
137
+ } else if (browserWindow.emui[this.appId].uuid === this.#uuid) {
138
+ browserWindow.emui[this.appId] = app;
139
+ } else {
140
+ browserWindow.emui[this.appId] = [
141
+ browserWindow.emui[this.appId],
142
+ app
143
+ ];
144
+ }
145
+ } else {
146
+ browserWindow.emui[this.appId] = app;
147
+ }
148
+ window.emui.app = app;
149
+ window.addEventListener("beforeunload", this.#onWindowUnload.bind(this));
150
+ }
151
+ /**
152
+ * remove the microapp from the global variable from the parent window and current window
153
+ */
154
+ #onWindowUnload() {
155
+ const browserWindow = (0, import_window.getWindow)();
156
+ if (Array.isArray(browserWindow.emui[this.appId])) {
157
+ const existingApps = browserWindow.emui[this.appId];
158
+ const index = existingApps.findIndex((app) => app.uuid === this.#uuid);
159
+ if (index > -1) existingApps.splice(index, 1);
160
+ if (existingApps.length === 0) {
161
+ delete browserWindow.emui[this.appId];
162
+ }
163
+ } else {
164
+ delete browserWindow.emui[this.appId];
165
+ }
166
+ delete window.emui.app;
167
+ this.unmount({ containerId: this.containerId }).catch(() => {
168
+ });
169
+ }
116
170
  getSessionStorageItem(key) {
117
171
  let value = sessionStorage.getItem(key);
118
172
  if (!value && this.props.host?.getItem) {
@@ -134,6 +188,9 @@ class CMicroAppGuest {
134
188
  }
135
189
  return host;
136
190
  }
191
+ get uuid() {
192
+ return this.#uuid;
193
+ }
137
194
  async init(options) {
138
195
  this.props = import_lodash.default.merge(this.props, options);
139
196
  this.props.history = options?.history || this.props.history;
@@ -37,8 +37,23 @@ const cssType = /\.css$/;
37
37
  const isCss = (fileName) => cssType.test(fileName);
38
38
  const activeApps = {};
39
39
  const getPathName = (url) => url ? new URL(url).pathname : "";
40
+ const getAppNotFoundError = (id, uuid) => `Application ${id} with instance id ${uuid} is not found. Most probably the appId property of app.config.json is not set to ${id}`;
41
+ const getMicroAppGuestFromWindow = ({
42
+ id,
43
+ uuid
44
+ }) => {
45
+ let microAppGuest = null;
46
+ const guests = window.emui?.[id];
47
+ if (Array.isArray(guests)) {
48
+ microAppGuest = guests.find((guest) => guest.uuid === uuid) ?? null;
49
+ } else {
50
+ microAppGuest = guests;
51
+ }
52
+ return microAppGuest;
53
+ };
40
54
  const initApplication = async ({
41
55
  id,
56
+ uuid,
42
57
  name,
43
58
  hostUrl,
44
59
  history,
@@ -46,18 +61,19 @@ const initApplication = async ({
46
61
  manifestPath,
47
62
  homeRoute
48
63
  }) => {
49
- const app = window.emui?.[id];
50
- if (!app) {
51
- throw new Error(
52
- `Application ${name} with ${id} is not found. Most probably the appId property of app.config.json is not set to ${id}`
53
- );
54
- }
64
+ const app = getMicroAppGuestFromWindow({
65
+ id,
66
+ uuid
67
+ });
68
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
55
69
  const { init } = app;
56
70
  if (!init || typeof init !== "function")
57
71
  throw new Error(
58
72
  `Application ${name} with id ${id} doesn't expose init method.`
59
73
  );
60
- const prevState = await import_web_storage.persistentStorage.get(`state-${id}`);
74
+ const prevState = await import_web_storage.persistentStorage.get(
75
+ `state-${id}-${uuid}`
76
+ );
61
77
  return init({
62
78
  host: import_host.CMicroAppHost.getInstance(),
63
79
  hostUrl,
@@ -70,8 +86,12 @@ const initApplication = async ({
70
86
  hostViewportSize: (0, import_window.getViewportSize)()
71
87
  });
72
88
  };
73
- const mountApp = async ({ id, name }) => {
74
- const app = (window.emui || {})[id] || {};
89
+ const mountApp = async ({ id, uuid, name }) => {
90
+ const app = getMicroAppGuestFromWindow({
91
+ id,
92
+ uuid
93
+ });
94
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
75
95
  const { mount } = app;
76
96
  if (!mount || typeof mount !== "function")
77
97
  throw new Error(
@@ -83,8 +103,11 @@ const mountApp = async ({ id, name }) => {
83
103
  hostViewportSize: (0, import_window.getViewportSize)()
84
104
  });
85
105
  };
86
- const unmountApp = async ({ id, name }) => {
87
- const app = (window.emui || {})[id];
106
+ const unmountApp = async ({ id, uuid, name }) => {
107
+ const app = getMicroAppGuestFromWindow({
108
+ id,
109
+ uuid
110
+ });
88
111
  if (!app) return null;
89
112
  const { unmount } = app;
90
113
  if (!unmount) return null;
@@ -96,64 +119,123 @@ const unmountApp = async ({ id, name }) => {
96
119
  containerId: `${import_const.APP_CONTAINER_ID_PREFIX}${id}`
97
120
  });
98
121
  if (currentState) {
99
- await import_web_storage.persistentStorage.set(`state-${id}`, currentState);
122
+ await import_web_storage.persistentStorage.set(`state-${id}-${uuid}`, currentState);
100
123
  }
101
124
  return Promise.resolve();
102
125
  };
103
- const addAppToActiveAppList = (id, elementIds) => {
104
- const app = (window.emui || {})[id] || {};
126
+ const addAppToActiveAppList = (id, uuid, elementIds) => {
127
+ const app = getMicroAppGuestFromWindow({
128
+ id,
129
+ uuid
130
+ });
131
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
105
132
  const { getRef } = app;
106
- activeApps[id] = { elementIds };
133
+ activeApps[`${id}-${uuid}`] = { elementIds };
107
134
  const host = import_host.CMicroAppHost.getInstance();
108
- if (host) host.activeGuests[id] = getRef && getRef() || {};
135
+ if (host) host.activeGuests[`${id}-${uuid}`] = getRef && getRef() || {};
109
136
  return Promise.resolve();
110
137
  };
111
138
  const waitAndInitApplication = (appConfig, requests) => (
112
139
  // wait for all assets to get downloaded
113
- Promise.all(requests).then(addAppToActiveAppList.bind(null, appConfig.id)).then(initApplication.bind(null, appConfig)).catch((err) => {
114
- const logRecord = import_log_records.logRecords.APP_INIT_FAILED(appConfig.id, err.message);
140
+ Promise.all(requests).then(addAppToActiveAppList.bind(null, appConfig.id, appConfig.uuid)).then(initApplication.bind(null, appConfig)).catch((err) => {
141
+ const logRecord = import_log_records.logRecords.APP_INIT_FAILED(
142
+ appConfig.id,
143
+ appConfig.uuid,
144
+ err.message
145
+ );
115
146
  (0, import_micro_frontend.getLogger)().error({ ...logRecord, exception: err });
116
147
  throw new Error(logRecord.message);
117
148
  })
118
149
  );
119
- const removeAssetsFromDOM = (id, documentEle = document) => {
150
+ const removeAssetsFromDOM = (id, uuid, documentEle = document) => {
120
151
  const host = import_host.CMicroAppHost.getInstance();
121
- if (host) delete host.activeGuests[id];
122
- const { elementIds } = activeApps[id] || {};
152
+ if (host) delete host.activeGuests[`${id}-${uuid}`];
153
+ const { elementIds } = activeApps[`${id}-${uuid}`] || {};
123
154
  if (elementIds) {
124
155
  elementIds.forEach((elementId) => {
125
156
  const ele = documentEle.getElementById(elementId);
126
157
  if (ele) ele.remove();
127
158
  });
128
- delete activeApps[id];
159
+ delete activeApps[`${id}-${uuid}`];
160
+ }
161
+ };
162
+ const addAppToGlobalVariable = ({
163
+ id,
164
+ uuid,
165
+ documentEle
166
+ }) => {
167
+ const newAppInstance = {
168
+ uuid,
169
+ init: null,
170
+ mount: null,
171
+ unmount: null,
172
+ getRef: null,
173
+ navigate: null
174
+ };
175
+ const app = getMicroAppGuestFromWindow({ id, uuid });
176
+ if (app) {
177
+ if (!uuid) {
178
+ throw new Error(
179
+ `Application ${id} is already loaded. uuid is required to load multiple instances of the same app`
180
+ );
181
+ }
182
+ if (Array.isArray(window.emui[id])) {
183
+ window.emui[id].push(newAppInstance);
184
+ } else {
185
+ window.emui[id] = [window.emui[id], newAppInstance];
186
+ }
187
+ } else {
188
+ window.emui[id] = newAppInstance;
189
+ }
190
+ if (documentEle.defaultView) {
191
+ documentEle.defaultView.emui = documentEle.defaultView.emui ?? {};
192
+ documentEle.defaultView.emui.uuid = uuid;
193
+ }
194
+ };
195
+ const removeAppFromGlobalVariable = (id, uuid) => {
196
+ if (Array.isArray(window.emui[id])) {
197
+ const index = window.emui[id].findIndex(
198
+ (app) => app.uuid === uuid
199
+ );
200
+ if (index > -1) window.emui[id].splice(index, 1);
201
+ } else {
202
+ delete window.emui[id];
129
203
  }
130
204
  };
131
205
  const loadApp = async (appConfig) => {
132
- (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_LOADING(appConfig.id));
133
- let assets = appConfig.files;
134
- const manifest = await (0, import_manifest.getAppManifest)(appConfig);
135
- assets = (0, import_manifest.getFullFileNameofAssetsFromManifest)(manifest, appConfig.files);
136
- let counter = 0;
137
- const requests = assets.map((fileName) => {
138
- counter += 1;
139
- return !isCss(fileName) ? (0, import_script.addScriptToDOM)(appConfig, fileName, counter) : (0, import_style.addStylesToDOM)(appConfig, fileName, counter);
140
- });
141
- await waitAndInitApplication(appConfig, requests);
142
- (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_LOADING_COMPLETE(appConfig.id));
206
+ const { id, uuid, documentEle } = appConfig;
207
+ addAppToGlobalVariable({ id, uuid, documentEle });
208
+ try {
209
+ (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_LOADING(id, uuid));
210
+ let assets = appConfig.files;
211
+ const manifest = await (0, import_manifest.getAppManifest)(appConfig);
212
+ assets = (0, import_manifest.getFullFileNameofAssetsFromManifest)(manifest, appConfig.files);
213
+ let counter = 0;
214
+ const requests = assets.map((fileName) => {
215
+ counter += 1;
216
+ return !isCss(fileName) ? (0, import_script.addScriptToDOM)(appConfig, fileName, counter) : (0, import_style.addStylesToDOM)(appConfig, fileName, counter);
217
+ });
218
+ await waitAndInitApplication(appConfig, requests);
219
+ (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_LOADING_COMPLETE(id, uuid));
220
+ } catch (err) {
221
+ removeAppFromGlobalVariable(id, uuid);
222
+ throw err;
223
+ }
143
224
  };
144
225
  const unloadApp = ({
145
226
  id,
227
+ uuid,
146
228
  hostUrl,
147
229
  documentEle
148
230
  }) => {
149
231
  if (!hostUrl) throw new Error("Unable to unload app. hostUrl is required");
150
- (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_UNLOADING(id));
151
- const app = (window.emui || {})[id];
232
+ (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_UNLOADING(id, uuid));
233
+ const app = getMicroAppGuestFromWindow({ id, uuid });
152
234
  if (!app) return;
153
- removeAssetsFromDOM(id, documentEle);
235
+ removeAssetsFromDOM(id, uuid, documentEle);
154
236
  (0, import_script.removeDynamicImportedScripts)(hostUrl, documentEle);
155
237
  (0, import_script.removePrefetchLinks)(hostUrl, documentEle);
156
238
  (0, import_style.removeDynamicImportedStyles)(hostUrl, documentEle);
157
- if (window.emui && window.emui[id]) delete window.emui[id];
158
- (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_UNLOADING_COMPLETE(id));
239
+ removeAppFromGlobalVariable(id, uuid);
240
+ (0, import_micro_frontend.getLogger)().info(import_log_records.logRecords.APP_UNLOADING_COMPLETE(id, uuid));
159
241
  };
@@ -41,6 +41,7 @@ const Div = import_styled_components.default.div`
41
41
  width: 100%;
42
42
  `;
43
43
  const MicroApp = (0, import_react.memo)((props) => {
44
- (0, import_use_app_will_render.useAppWillRender)(props);
44
+ const uuid = (0, import_react.useId)();
45
+ (0, import_use_app_will_render.useAppWillRender)({ uuid, ...props });
45
46
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Div, { id: `${import_const.APP_CONTAINER_ID_PREFIX}${props.id}` });
46
47
  });
@@ -30,6 +30,7 @@ var import_micro_frontend = require("../../utils/micro-frontend/index.js");
30
30
  var import_log_records = require("../../utils/log-records.js");
31
31
  const useAppWillRender = ({
32
32
  id,
33
+ uuid,
33
34
  documentEle,
34
35
  history,
35
36
  theme,
@@ -45,27 +46,27 @@ const useAppWillRender = ({
45
46
  [id]
46
47
  );
47
48
  const unload = (0, import_react.useCallback)(
48
- async (appConfig, loadFailed = false) => {
49
- await (0, import_app_factory.unmountApp)(appConfig);
50
- (0, import_app_factory.unloadApp)(appConfig);
49
+ async (appParams, loadFailed = false) => {
50
+ await (0, import_app_factory.unmountApp)(appParams);
51
+ (0, import_app_factory.unloadApp)(appParams);
51
52
  if (!loadFailed && onUnloadComplete) onUnloadComplete();
52
53
  },
53
54
  // eslint-disable-next-line react-hooks/exhaustive-deps
54
55
  []
55
56
  );
56
57
  const load = (0, import_react.useCallback)(
57
- async (appConfig) => {
58
+ async (appParams) => {
58
59
  if (dispatch) dispatch(import_actions.waitMessage.open());
59
60
  try {
60
61
  if (ref.current) await ref.current;
61
- await (0, import_app_factory.loadApp)(appConfig);
62
- await (0, import_app_factory.mountApp)(appConfig);
62
+ await (0, import_app_factory.loadApp)(appParams);
63
+ await (0, import_app_factory.mountApp)(appParams);
63
64
  } catch (ex) {
64
65
  (0, import_micro_frontend.getLogger)().error({
65
66
  ...import_log_records.logRecords.APP_CONFIG_LOAD_FAILED,
66
67
  exception: ex
67
68
  });
68
- await unload(appConfig, true);
69
+ await unload(appParams, true);
69
70
  throw ex;
70
71
  }
71
72
  if (dispatch) dispatch(import_actions.waitMessage.close());
@@ -81,9 +82,10 @@ const useAppWillRender = ({
81
82
  theme,
82
83
  homeRoute
83
84
  });
84
- load(appConfig);
85
+ const appParams = { ...appConfig, uuid };
86
+ load(appParams);
85
87
  return () => {
86
- ref.current = unload(appConfig);
88
+ ref.current = unload(appParams);
87
89
  };
88
- }, [documentEle, getConfig, history, theme, homeRoute, load, unload]);
90
+ }, [documentEle, getConfig, history, theme, homeRoute, load, unload, uuid]);
89
91
  };
@@ -38,11 +38,12 @@ var import_iframe2 = require("./iframe/index.js");
38
38
  var import_use_frame_loaded = require("./use-frame-loaded.js");
39
39
  var import_use_app_will_render = require("../micro-app/use-app-will-render.js");
40
40
  const App = (0, import_react.memo)(
41
- ({ id, dispose, onUnloadComplete, ...rest }) => {
41
+ ({ id, uuid, dispose, onUnloadComplete, ...rest }) => {
42
42
  const [documentEle, setDocumentEle] = (0, import_react.useState)(null);
43
43
  (0, import_use_frame_loaded.useFrameLoaded)({ id, documentEle, ...rest });
44
44
  (0, import_use_app_will_render.useAppWillRender)({
45
45
  id,
46
+ uuid,
46
47
  documentEle: dispose ? null : documentEle,
47
48
  onUnloadComplete,
48
49
  ...rest
@@ -28,6 +28,7 @@ var import_app = require("./app.js");
28
28
  const MicroIFrameApp = (0, import_react2.memo)(({ entityId = null, ...rest }) => {
29
29
  const [disposePrevApp, setDisposePrevApp] = (0, import_react2.useState)(false);
30
30
  const [appKey, setAppKey] = (0, import_react2.useState)(Date.now());
31
+ const uuid = (0, import_react2.useId)();
31
32
  const prevEntityId = (0, import_use_previous.usePrevious)(entityId);
32
33
  (0, import_react2.useEffect)(() => {
33
34
  if (prevEntityId !== entityId && prevEntityId) {
@@ -38,6 +39,7 @@ const MicroIFrameApp = (0, import_react2.memo)(({ entityId = null, ...rest }) =>
38
39
  import_app.App,
39
40
  {
40
41
  ...rest,
42
+ uuid,
41
43
  key: appKey,
42
44
  dispose: disposePrevApp,
43
45
  onUnloadComplete: () => {
@@ -48,7 +48,7 @@ const endSession = async ({
48
48
  redirectUri,
49
49
  responseType,
50
50
  scope,
51
- code = "1004",
51
+ code = "",
52
52
  skipRevoke = false
53
53
  }) => {
54
54
  try {
@@ -67,7 +67,7 @@ const endSession = async ({
67
67
  const params = {
68
68
  client_id: clientId,
69
69
  redirect_uri: redirectUri,
70
- error_code: code,
70
+ ...code && { error_code: code },
71
71
  response_type: responseType,
72
72
  scope
73
73
  };
@@ -19,25 +19,25 @@ const logRecords = {
19
19
  code: "appsdk05",
20
20
  message: `Application load failed. unable to locate ${assetName} in the manifest`
21
21
  }),
22
- APP_INIT_FAILED: (appId, errMsg) => ({
22
+ APP_INIT_FAILED: (appId, instanceId, errMsg) => ({
23
23
  code: "appsdk06",
24
- message: `Application load failed. Unable to load one or more application resources for appId: ${appId}. ${errMsg}`
24
+ message: `Application load failed. Unable to load one or more application resources for appId: ${appId} and instanceId: ${instanceId}. ${errMsg}`
25
25
  }),
26
- APP_LOADING: (appId) => ({
26
+ APP_LOADING: (appId, instanceId) => ({
27
27
  code: "appsdk07",
28
- message: `Application ${appId} is loading...`
28
+ message: `Application ${appId} with instanceId ${instanceId} is loading...`
29
29
  }),
30
- APP_LOADING_COMPLETE: (appId) => ({
30
+ APP_LOADING_COMPLETE: (appId, instanceId) => ({
31
31
  code: "appsdk08",
32
- message: `Application ${appId} loaded`
32
+ message: `Application ${appId} loaded with instanceId ${instanceId}`
33
33
  }),
34
- APP_UNLOADING: (appId) => ({
34
+ APP_UNLOADING: (appId, instanceId) => ({
35
35
  code: "appsdk09",
36
- message: `Application ${appId} unloading...`
36
+ message: `Application ${appId} with instanceId ${instanceId} unloading...`
37
37
  }),
38
- APP_UNLOADING_COMPLETE: (appId) => ({
38
+ APP_UNLOADING_COMPLETE: (appId, instanceId) => ({
39
39
  code: "appsdk10",
40
- message: `Application ${appId} unloaded`
40
+ message: `Application ${appId} with instanceId ${instanceId} unloaded`
41
41
  }),
42
42
  SSF_HOST_OBJECT_NOT_FOUND: (name) => ({
43
43
  code: "appsdk11",
@@ -1,4 +1,5 @@
1
1
  import _ from "lodash";
2
+ import { v4 as uuidv4 } from "uuid";
2
3
  import { getDefaultTheme } from "@elliemae/pui-theme";
3
4
  import { getWindow } from "../window.js";
4
5
  import { loadAppConfig } from "../app-config/index.js";
@@ -20,6 +21,10 @@ const isCrossDomain = () => {
20
21
  }
21
22
  };
22
23
  class CMicroAppGuest {
24
+ /**
25
+ * unique microapp id
26
+ */
27
+ #uuid;
23
28
  static instance;
24
29
  logger;
25
30
  appId;
@@ -29,9 +34,12 @@ class CMicroAppGuest {
29
34
  onMount;
30
35
  onUnmount;
31
36
  onGetRef;
37
+ // eslint-disable-next-line max-statements
32
38
  constructor(params) {
33
39
  this.containerId = "app-container";
40
+ window.emui.uuid = window.emui.uuid || uuidv4();
34
41
  this.appId = window.emui?.appId || getAppConfigValue("appId");
42
+ this.#uuid = window.emui.uuid;
35
43
  this.props = {
36
44
  host: null,
37
45
  hostUrl: null,
@@ -49,15 +57,7 @@ class CMicroAppGuest {
49
57
  this.getHost = this.getHost.bind(this);
50
58
  this.getLogger = this.getLogger.bind(this);
51
59
  this.getProps = this.getProps.bind(this);
52
- const browserWindow = getWindow();
53
- if (browserWindow) {
54
- browserWindow.emui = browserWindow.emui || {};
55
- browserWindow.emui[this.appId] = browserWindow.emui[this.appId] || {};
56
- browserWindow.emui[this.appId].init = this.init.bind(this);
57
- browserWindow.emui[this.appId].mount = this.mount.bind(this);
58
- browserWindow.emui[this.appId].unmount = this.unmount.bind(this);
59
- browserWindow.emui[this.appId].getRef = this.getRef.bind(this);
60
- }
60
+ this.#addAppToGlobalVariable();
61
61
  }
62
62
  static getInstance(params) {
63
63
  if (!this.instance) {
@@ -80,6 +80,60 @@ class CMicroAppGuest {
80
80
  getProps() {
81
81
  return this.props;
82
82
  }
83
+ /**
84
+ * add the microapp to the global variable in the parent window and current window
85
+ */
86
+ #addAppToGlobalVariable() {
87
+ const browserWindow = getWindow();
88
+ if (!browserWindow) return;
89
+ browserWindow.emui = browserWindow.emui || {};
90
+ const app = {
91
+ uuid: this.#uuid,
92
+ init: this.init.bind(this),
93
+ mount: this.mount.bind(this),
94
+ unmount: this.unmount.bind(this),
95
+ getRef: this.getRef.bind(this),
96
+ navigate: this.navigate.bind(this)
97
+ };
98
+ if (browserWindow.emui[this.appId]) {
99
+ if (Array.isArray(browserWindow.emui[this.appId])) {
100
+ const existingApps = browserWindow.emui[this.appId];
101
+ const appIndex = existingApps.findIndex((a) => a.uuid === this.#uuid);
102
+ if (appIndex > -1) existingApps[appIndex] = app;
103
+ else existingApps.push(app);
104
+ } else if (browserWindow.emui[this.appId].uuid === this.#uuid) {
105
+ browserWindow.emui[this.appId] = app;
106
+ } else {
107
+ browserWindow.emui[this.appId] = [
108
+ browserWindow.emui[this.appId],
109
+ app
110
+ ];
111
+ }
112
+ } else {
113
+ browserWindow.emui[this.appId] = app;
114
+ }
115
+ window.emui.app = app;
116
+ window.addEventListener("beforeunload", this.#onWindowUnload.bind(this));
117
+ }
118
+ /**
119
+ * remove the microapp from the global variable from the parent window and current window
120
+ */
121
+ #onWindowUnload() {
122
+ const browserWindow = getWindow();
123
+ if (Array.isArray(browserWindow.emui[this.appId])) {
124
+ const existingApps = browserWindow.emui[this.appId];
125
+ const index = existingApps.findIndex((app) => app.uuid === this.#uuid);
126
+ if (index > -1) existingApps.splice(index, 1);
127
+ if (existingApps.length === 0) {
128
+ delete browserWindow.emui[this.appId];
129
+ }
130
+ } else {
131
+ delete browserWindow.emui[this.appId];
132
+ }
133
+ delete window.emui.app;
134
+ this.unmount({ containerId: this.containerId }).catch(() => {
135
+ });
136
+ }
83
137
  getSessionStorageItem(key) {
84
138
  let value = sessionStorage.getItem(key);
85
139
  if (!value && this.props.host?.getItem) {
@@ -101,6 +155,9 @@ class CMicroAppGuest {
101
155
  }
102
156
  return host;
103
157
  }
158
+ get uuid() {
159
+ return this.#uuid;
160
+ }
104
161
  async init(options) {
105
162
  this.props = _.merge(this.props, options);
106
163
  this.props.history = options?.history || this.props.history;