@elliemae/pui-app-bridge 2.9.9 → 2.16.6

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.
Files changed (115) hide show
  1. package/dist/cjs/appBridge.js +380 -93
  2. package/dist/cjs/appRegistry.js +136 -0
  3. package/dist/cjs/config/app.js +15 -2
  4. package/dist/cjs/config/microFE.js +3 -3
  5. package/dist/cjs/eventManager.js +16 -16
  6. package/dist/cjs/frame.html +2 -2
  7. package/dist/cjs/frame.js +39 -14
  8. package/dist/cjs/index.html +1 -1
  9. package/dist/cjs/loaders/script.js +5 -5
  10. package/dist/cjs/loaders/style.js +1 -0
  11. package/dist/cjs/microfeHost.js +51 -31
  12. package/dist/cjs/tests/flights/23.1/app.checksum1.js +25 -24
  13. package/dist/cjs/tests/flights/latest/app.checksum.js +25 -24
  14. package/dist/cjs/tests/hotels/23.1/app.checksum.js +27 -24
  15. package/dist/cjs/tests/hotels/latest/app.checksum.js +27 -24
  16. package/dist/cjs/tests/loan/latest/index.js +49 -57
  17. package/dist/cjs/tests/scriptingObjects/analytics.js +7 -7
  18. package/dist/cjs/tests/scriptingObjects/appraisalServiceModule.js +8 -8
  19. package/dist/cjs/tests/scriptingObjects/global.js +1 -2
  20. package/dist/cjs/tests/task/latest/index.dev.js +29 -28
  21. package/dist/cjs/tests/task/latest/index.js +29 -28
  22. package/dist/cjs/tests/travelhub/23.1/app.checksum.js +24 -26
  23. package/dist/cjs/tests/travelhub/23.1/landing.checksum1.js +5 -7
  24. package/dist/cjs/utils.js +31 -1
  25. package/dist/esm/appBridge.js +390 -95
  26. package/dist/esm/appRegistry.js +116 -0
  27. package/dist/esm/config/app.js +15 -2
  28. package/dist/esm/config/microFE.js +3 -3
  29. package/dist/esm/eventManager.js +16 -16
  30. package/dist/esm/frame.html +2 -2
  31. package/dist/esm/frame.js +29 -14
  32. package/dist/esm/index.html +1 -1
  33. package/dist/esm/loaders/script.js +5 -5
  34. package/dist/esm/loaders/style.js +1 -0
  35. package/dist/esm/microfeHost.js +55 -31
  36. package/dist/esm/tests/flights/23.1/app.checksum1.js +25 -24
  37. package/dist/esm/tests/flights/latest/app.checksum.js +25 -24
  38. package/dist/esm/tests/hotels/23.1/app.checksum.js +27 -24
  39. package/dist/esm/tests/hotels/latest/app.checksum.js +27 -24
  40. package/dist/esm/tests/loan/latest/index.js +49 -57
  41. package/dist/esm/tests/scriptingObjects/analytics.js +7 -7
  42. package/dist/esm/tests/scriptingObjects/appraisalServiceModule.js +8 -8
  43. package/dist/esm/tests/scriptingObjects/global.js +1 -2
  44. package/dist/esm/tests/task/latest/index.dev.js +29 -28
  45. package/dist/esm/tests/task/latest/index.js +29 -28
  46. package/dist/esm/tests/travelhub/23.1/app.checksum.js +24 -26
  47. package/dist/esm/tests/travelhub/23.1/landing.checksum1.js +5 -7
  48. package/dist/esm/utils.js +31 -1
  49. package/dist/public/assets/frame.671d9de68be598da64ca.html +47 -0
  50. package/dist/public/creditService/latest/creditService.checksum.js.gz +0 -0
  51. package/dist/public/frame.html +1 -1
  52. package/dist/public/guest/businessObjects.js.gz +0 -0
  53. package/dist/public/guest/util.js.gz +0 -0
  54. package/dist/public/index.html +1 -1
  55. package/dist/public/init.js.gz +0 -0
  56. package/dist/public/js/emuiAppBridge.40c8880c94dbc97b49fd.js +51 -0
  57. package/dist/public/js/emuiAppBridge.40c8880c94dbc97b49fd.js.br +0 -0
  58. package/dist/public/js/emuiAppBridge.40c8880c94dbc97b49fd.js.gz +0 -0
  59. package/dist/public/js/emuiAppBridge.40c8880c94dbc97b49fd.js.map +1 -0
  60. package/dist/public/loan-object.js +1 -1
  61. package/dist/public/loan-object.js.br +0 -0
  62. package/dist/public/loan-object.js.gz +0 -0
  63. package/dist/public/loan-object.js.map +1 -1
  64. package/dist/public/loanValidation/latest/loanValidation.checksum.js.gz +0 -0
  65. package/dist/public/pricingService/latest/pricingService.checksum.js.gz +0 -0
  66. package/dist/public/utils.js.gz +0 -0
  67. package/dist/types/lib/appBridge.d.ts +38 -28
  68. package/dist/types/lib/appRegistry.d.ts +41 -0
  69. package/dist/types/lib/eventManager.d.ts +4 -4
  70. package/dist/types/lib/frame.d.ts +45 -4
  71. package/dist/types/lib/index.d.ts +3 -3
  72. package/dist/types/lib/loaders/script.d.ts +2 -1
  73. package/dist/types/lib/microfeHost.d.ts +15 -25
  74. package/dist/types/lib/tests/flights/23.1/app.checksum1.d.ts +7 -0
  75. package/dist/types/lib/tests/flights/latest/app.checksum.d.ts +7 -0
  76. package/dist/types/lib/tests/hotels/23.1/app.checksum.d.ts +7 -0
  77. package/dist/types/lib/tests/hotels/latest/app.checksum.d.ts +7 -0
  78. package/dist/types/lib/tests/loan/latest/index.d.ts +11 -0
  79. package/dist/types/lib/tests/scriptingObjects/analytics.d.ts +3 -3
  80. package/dist/types/lib/tests/scriptingObjects/appraisalServiceModule.d.ts +2 -1
  81. package/dist/types/lib/tests/task/latest/index.d.ts +10 -0
  82. package/dist/types/lib/tests/task/latest/index.dev.d.ts +10 -0
  83. package/dist/types/lib/tests/travelhub/23.1/app.checksum.d.ts +7 -0
  84. package/dist/types/lib/tests/travelhub/23.1/landing.checksum1.d.ts +2 -0
  85. package/dist/types/lib/typings/appInfo.d.ts +1 -0
  86. package/dist/types/lib/typings/common.d.ts +0 -66
  87. package/dist/types/lib/typings/guest.d.ts +10 -3
  88. package/dist/types/lib/typings/host.d.ts +32 -32
  89. package/dist/types/lib/typings/window.d.ts +6 -1
  90. package/dist/types/lib/utils.d.ts +7 -0
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/dist/umd/671d9de68be598da64ca.html +47 -0
  93. package/dist/umd/creditService/latest/creditService.checksum.js.gz +0 -0
  94. package/dist/umd/frame.html +1 -1
  95. package/dist/umd/guest/businessObjects.js.gz +0 -0
  96. package/dist/umd/guest/util.js.gz +0 -0
  97. package/dist/umd/index.html +1 -1
  98. package/dist/umd/index.js +35 -9
  99. package/dist/umd/index.js.br +0 -0
  100. package/dist/umd/index.js.gz +0 -0
  101. package/dist/umd/index.js.map +1 -1
  102. package/dist/umd/init.js.gz +0 -0
  103. package/dist/umd/loan-object.js +1 -1
  104. package/dist/umd/loan-object.js.br +0 -0
  105. package/dist/umd/loan-object.js.gz +0 -0
  106. package/dist/umd/loan-object.js.map +1 -1
  107. package/dist/umd/loanValidation/latest/loanValidation.checksum.js.gz +0 -0
  108. package/dist/umd/pricingService/latest/pricingService.checksum.js.gz +0 -0
  109. package/dist/umd/utils.js.gz +0 -0
  110. package/package.json +9 -12
  111. package/dist/public/js/emuiAppBridge.530390c3bb03f32357f7.js +0 -25
  112. package/dist/public/js/emuiAppBridge.530390c3bb03f32357f7.js.br +0 -0
  113. package/dist/public/js/emuiAppBridge.530390c3bb03f32357f7.js.gz +0 -0
  114. package/dist/public/js/emuiAppBridge.530390c3bb03f32357f7.js.map +0 -1
  115. package/dist/types/lib/tests/pubsubAPI.test.d.ts +0 -1
@@ -1,45 +1,120 @@
1
- import { publish } from "pubsub-js";
2
- import { ScriptingObjectManager } from "@elliemae/microfe-common";
1
+ import { throttle } from "lodash";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import {
4
+ ScriptingObjectNames
5
+ } from "@elliemae/pui-scripting-object";
6
+ import {
7
+ ProxyEvent,
8
+ Event,
9
+ ScriptingObjectManager,
10
+ ScriptingObject,
11
+ isScriptingObjectProxy
12
+ } from "@elliemae/microfe-common";
3
13
  import { EventManager } from "./eventManager.js";
4
- import { Frame } from "./frame.js";
14
+ import { Frame, FRAME_APP_CONTAINER_ID_PREFIX } from "./frame.js";
5
15
  import { getCurrentBreakpoint, getViewportSize } from "./typings/window.js";
6
16
  import { CAppConfig } from "./config/app.js";
7
17
  import { CMicroFEConfig } from "./config/microFE.js";
8
18
  import { StyleLoader, ScriptLoader, ManifestLoader } from "./loaders/index.js";
9
19
  import { CMicroFEHost } from "./microfeHost.js";
10
- const APP_CONTAINER_ID_PREFIX = "pui-app-container-";
20
+ import { CAppRegistry } from "./appRegistry.js";
21
+ const KEEP_ALIVE_INTERVAL = 12e4;
22
+ const userInteractionEvents = ["click", "scroll", "keypress", "touchstart"];
11
23
  const cssType = /\.css$/;
12
24
  const isCss = (fileName) => cssType.test(fileName);
13
25
  class CAppBridge {
26
+ /**
27
+ * instance of the pui diagnostics logger
28
+ */
14
29
  #logger;
30
+ /**
31
+ * release version of the product
32
+ */
15
33
  #version;
34
+ /**
35
+ * instance of the script loader
36
+ */
16
37
  #scriptLoader;
38
+ /**
39
+ * instance of the style loader
40
+ */
17
41
  #styleLoader;
42
+ /**
43
+ * instance of the app config
44
+ */
18
45
  #appConfig;
46
+ /**
47
+ * instance of the micro frontend config
48
+ */
19
49
  #microFEConfig = new CMicroFEConfig();
50
+ /**
51
+ * instance of the scripting object manager
52
+ */
20
53
  #soManager;
54
+ /**
55
+ * instance of the event manager
56
+ */
21
57
  #eventManager;
58
+ /**
59
+ * list of active apps
60
+ */
22
61
  #activeApps = /* @__PURE__ */ new Map();
62
+ /**
63
+ * instance of the app registry
64
+ */
65
+ #appRegistry = new CAppRegistry();
66
+ /**
67
+ * flag to extend parent session when user interacts with the guest application
68
+ */
69
+ #extendSession = true;
23
70
  /**
24
71
  * Create a new instance of the AppBridge
25
- * @param {AppBridgeParams} params - parameter for the constructor
72
+ * @param {AppBridgeParams} options - App Bridge constructor parameters
26
73
  */
27
- constructor(params) {
28
- const { logger } = params;
74
+ constructor(options) {
75
+ const { logger } = options;
29
76
  if (!logger) throw new Error("logger is required");
30
- this.#logger = params.logger;
31
- this.#version = params.version;
77
+ this.#logger = options.logger;
78
+ this.#version = options.version;
79
+ this.#extendSession = options.extendSession ?? true;
32
80
  this.#appConfig = new CAppConfig({
33
- version: params.version,
34
- baseUrl: params.appConfigBaseUrl
81
+ version: options.version,
82
+ baseUrl: options.appConfigBaseUrl
35
83
  });
36
84
  this.#scriptLoader = new ScriptLoader(logger);
37
85
  this.#styleLoader = new StyleLoader(logger);
38
86
  this.#soManager = new ScriptingObjectManager();
39
87
  this.#eventManager = new EventManager();
40
88
  }
41
- #removeAssetsFromDOM = (id, documentEle = document) => {
42
- const { elementIds } = this.#activeApps.get(id) || {};
89
+ #isFunction(value) {
90
+ return typeof value === "function";
91
+ }
92
+ /**
93
+ * check if the value is a proxy event
94
+ * @param value
95
+ * @returns
96
+ */
97
+ #isProxyEvent = (value) => (
98
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
99
+ value instanceof ProxyEvent || // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
100
+ typeof value?.subscribe === "function"
101
+ );
102
+ // support v1 scripting objects
103
+ /**
104
+ * format error message for app not found
105
+ * @param id app id
106
+ * @param instanceId unique instance id
107
+ * @returns error message
108
+ */
109
+ #getAppNotFoundError = (id, instanceId) => `Application ${id} with instance id ${instanceId} is not found. Most probably the appId property of app.config.json is not set to ${id}`;
110
+ /**
111
+ * remove assets from DOM
112
+ * @param instanceId unique instance id of the app
113
+ * @param documentEle document element of the app
114
+ * @returns void
115
+ */
116
+ #removeAssetsFromDOM = (instanceId, documentEle = document) => {
117
+ const { elementIds } = this.#activeApps.get(instanceId) || {};
43
118
  if (elementIds) {
44
119
  elementIds.forEach((elementId) => {
45
120
  const ele = documentEle.getElementById(elementId);
@@ -47,9 +122,26 @@ class CAppBridge {
47
122
  });
48
123
  }
49
124
  };
50
- #addAppToActiveAppList = (id, documentEle, elementIds) => {
51
- const app = window.emui?.[id] || {};
52
- this.#activeApps.set(id, {
125
+ /**
126
+ * add app to active app list
127
+ * @param param0
128
+ * @param param0.id
129
+ * @param param0.instanceId
130
+ * @param elementIds
131
+ * @param param0.documentEle
132
+ */
133
+ #addAppToActiveAppList = ({
134
+ id,
135
+ instanceId,
136
+ documentEle
137
+ }, elementIds) => {
138
+ const app = this.#appRegistry.get({ id, instanceId });
139
+ if (!app) {
140
+ throw new Error(this.#getAppNotFoundError(id, instanceId));
141
+ }
142
+ this.#activeApps.set(instanceId, {
143
+ id,
144
+ instanceId,
53
145
  elementIds,
54
146
  guest: {
55
147
  guestWindow: documentEle?.defaultView,
@@ -60,29 +152,37 @@ class CAppBridge {
60
152
  #waitAndInitApplication = (options, requests) => {
61
153
  const {
62
154
  id,
155
+ instanceId,
156
+ containerId,
63
157
  name,
64
158
  hostUrl,
65
159
  manifestPath,
66
160
  homeRoute,
161
+ initialRoute,
67
162
  history,
68
163
  theme,
69
164
  documentEle
70
165
  } = options;
71
- return Promise.all(requests).then(this.#addAppToActiveAppList.bind(null, id, documentEle)).then(
166
+ return Promise.all(requests).then(
167
+ this.#addAppToActiveAppList.bind(null, { id, instanceId, documentEle })
168
+ ).then(
72
169
  this.#initApplication.bind(null, {
73
170
  id,
171
+ instanceId,
172
+ containerId,
74
173
  name,
75
174
  hostUrl,
76
175
  manifestPath,
77
176
  homeRoute,
177
+ initialRoute,
78
178
  history,
79
179
  theme
80
180
  })
81
181
  ).catch((err) => {
82
- const message = `Application load failed. Unable to load one or more resources for appId: ${options.id}. ${err.message}`;
182
+ const message = `Application load failed. Unable to load one or more resources for ${options.id} with instance id ${instanceId}. ${err.message}`;
83
183
  this.#logger.error({
84
184
  message,
85
- appId: options.id,
185
+ appId: id,
86
186
  exception: err
87
187
  });
88
188
  throw new Error(message);
@@ -90,28 +190,31 @@ class CAppBridge {
90
190
  };
91
191
  #initApplication = async ({
92
192
  id,
93
- name,
193
+ instanceId,
194
+ containerId,
94
195
  hostUrl,
95
196
  manifestPath,
96
197
  homeRoute,
198
+ initialRoute,
97
199
  history,
98
200
  theme
99
201
  }) => {
100
- const app = window.emui?.[id];
202
+ const app = this.#appRegistry.get({ id, instanceId });
101
203
  if (!app) {
102
204
  throw new Error(
103
- `Application ${name} with ${id} is not found. Most probably the appId property of app.config.json is not set to ${id}`
205
+ `Application ${id} with instance id ${instanceId} is not found. Most probably the appId property of app.config.json is not set to ${id}`
104
206
  );
105
207
  }
106
208
  if (!app.init || typeof app.init !== "function")
107
209
  throw new Error(
108
- `Application ${name} with id ${id} doesn't expose init method`
210
+ `Application ${id} with instance id ${instanceId} doesn't expose init method`
109
211
  );
110
212
  const host = new CMicroFEHost({
111
213
  guest: {
112
214
  id
113
215
  },
114
216
  version: this.#version,
217
+ containerId,
115
218
  logger: this.#logger,
116
219
  soManager: this.#soManager,
117
220
  eventManager: this.#eventManager
@@ -121,6 +224,7 @@ class CAppBridge {
121
224
  hostUrl,
122
225
  manifestPath,
123
226
  homeRoute,
227
+ initialRoute,
124
228
  prevState: null,
125
229
  history,
126
230
  theme,
@@ -130,8 +234,10 @@ class CAppBridge {
130
234
  });
131
235
  };
132
236
  #loadApp = async (options) => {
133
- const { id, files, name, hostUrl, documentEle } = options;
134
- this.#logger.debug(`Application ${id} is loading...`);
237
+ const { id, instanceId, files, name, hostUrl, documentEle, isJsModule } = options;
238
+ this.#logger.debug(
239
+ `Application ${id} with instance id ${instanceId} is loading...`
240
+ );
135
241
  let assets = files;
136
242
  const manifest = await ManifestLoader.get(options);
137
243
  assets = ManifestLoader.getFullFileNameofAssets(manifest, files);
@@ -143,55 +249,191 @@ class CAppBridge {
143
249
  hostUrl,
144
250
  documentEle,
145
251
  fileName,
146
- index: counter
252
+ index: counter,
253
+ isJsModule
147
254
  };
148
255
  return !isCss(fileName) ? this.#scriptLoader.add(resourceOptions) : this.#styleLoader.add(resourceOptions);
149
256
  });
150
257
  await this.#waitAndInitApplication(options, requests);
151
- this.#logger.audit({ message: "Application loaded", appId: id });
258
+ this.#logger.audit(
259
+ `Application ${id} with instance id ${instanceId} loaded`
260
+ );
152
261
  };
153
- #unloadApp = ({ id, hostUrl, documentEle }) => {
154
- this.#logger.debug(`Application ${id} unloading...`);
155
- const app = window.emui?.[id];
262
+ #unloadApp = ({ id, instanceId, hostUrl, documentEle }) => {
263
+ this.#logger.debug(
264
+ `Application ${id} with instance id ${instanceId} unloading...`
265
+ );
266
+ const app = this.#appRegistry.get({
267
+ id,
268
+ instanceId
269
+ });
156
270
  if (!app) return;
157
- this.#removeAssetsFromDOM(id, documentEle);
271
+ this.#removeAssetsFromDOM(instanceId, documentEle);
158
272
  this.#scriptLoader.removeDynamicImportedScripts(hostUrl, documentEle);
159
273
  this.#scriptLoader.removePrefetchLinks(hostUrl, documentEle);
160
274
  this.#styleLoader.removeDynamicImportedStyles(hostUrl, documentEle);
161
- if (window.emui?.[id]) delete window.emui[id];
162
- this.#activeApps.delete(id);
163
- this.#logger.info({ message: "Application unloaded", appId: id });
275
+ this.#appRegistry.delete({ id, instanceId });
276
+ this.#logger.audit(
277
+ `Application ${id} with instance id ${instanceId} unloaded`
278
+ );
164
279
  };
165
- #mountApp = async ({ id, name }) => {
166
- const app = window.emui?.[id] || {};
280
+ #mountApp = async ({ id, instanceId }) => {
281
+ const app = this.#appRegistry.get({ id, instanceId });
167
282
  if (!app?.mount || typeof app?.mount !== "function")
168
283
  throw new Error(
169
- `Application ${name} with id ${id} doesn't expose mount method`
284
+ `Application ${id} with instance id ${instanceId} doesn't expose mount method`
170
285
  );
171
286
  return app.mount({
172
- containerId: `${APP_CONTAINER_ID_PREFIX}${id}`,
287
+ containerId: `${FRAME_APP_CONTAINER_ID_PREFIX}${id}`,
173
288
  hostBreakpoint: getCurrentBreakpoint(),
174
289
  hostViewportSize: getViewportSize()
175
290
  });
176
291
  };
177
- #unmountApp = ({ id, name }) => {
178
- const app = window.emui?.[id] || {};
292
+ #unmountApp = ({ id, instanceId }) => {
293
+ const app = this.#appRegistry.get({ id, instanceId });
179
294
  if (!app?.unmount) return null;
180
295
  if (typeof app.unmount !== "function")
181
296
  throw new Error(
182
- `Application ${name} with id ${id} doesn't expose unmount method`
297
+ `Application ${id} with instance id ${instanceId} doesn't expose unmount method`
183
298
  );
184
299
  return app.unmount({
185
- containerId: `${APP_CONTAINER_ID_PREFIX}${id}`
300
+ containerId: `${FRAME_APP_CONTAINER_ID_PREFIX}${id}`
186
301
  });
187
302
  };
303
+ /**
304
+ * manage session for the guest application by calling the keepSessionAlive method exposed by parent
305
+ * @param root0
306
+ * @param root0.id
307
+ * @param root0.instanceId
308
+ */
309
+ #manageSession = ({ id, instanceId }) => {
310
+ if (!this.#extendSession) return;
311
+ try {
312
+ const appObj = this.#soManager.getObject(
313
+ ScriptingObjectNames.Application
314
+ );
315
+ if (!appObj) {
316
+ this.#logger.warn({
317
+ message: `Application scripting object not available for ${id} to manage session`
318
+ });
319
+ return;
320
+ }
321
+ const app = this.#activeApps.get(instanceId);
322
+ if (!app) return;
323
+ app.keepAlive = throttle(
324
+ async () => {
325
+ try {
326
+ await appObj.keepSessionAlive();
327
+ } catch (e) {
328
+ this.#logger.error(
329
+ `Error keeping session alive. ${e.message}`
330
+ );
331
+ }
332
+ },
333
+ KEEP_ALIVE_INTERVAL
334
+ // throttle time
335
+ );
336
+ const frameEle = Frame.get(instanceId);
337
+ userInteractionEvents.forEach((eventType) => {
338
+ frameEle?.contentDocument?.addEventListener(eventType, app.keepAlive);
339
+ });
340
+ } catch (err) {
341
+ this.#logger.warn({
342
+ message: `Application scripting object not available for ${id} to manage session`,
343
+ exception: err
344
+ });
345
+ }
346
+ };
347
+ /**
348
+ * clear session management for the guest application
349
+ * @param instanceId unique instance id of the application
350
+ * @param app
351
+ * @returns
352
+ */
353
+ #clearSession = (app) => {
354
+ if (!this.#extendSession || !app) return;
355
+ const { keepAlive } = app;
356
+ if (keepAlive) {
357
+ userInteractionEvents.forEach((eventType) => {
358
+ document.removeEventListener(eventType, keepAlive);
359
+ });
360
+ app.keepAlive?.cancel();
361
+ }
362
+ };
188
363
  /**
189
364
  * registers scripting object to the host
190
365
  * @param {ValueOf<AppObjects>} so scripting object
191
366
  * @param {AddScriptingObjectParams} params params to add scripting object
192
367
  */
193
368
  addScriptingObject = (so, params) => {
194
- this.#soManager.addScriptingObject(so, params);
369
+ if (isScriptingObjectProxy(so)) {
370
+ const clonedSo = this.cloneScriptingObject(so);
371
+ this.#soManager.addScriptingObject(clonedSo, params);
372
+ } else {
373
+ this.#soManager.addScriptingObject(so, params);
374
+ }
375
+ };
376
+ /**
377
+ * Create new Scripting Object from SSF scripting object proxy
378
+ * @param proxy - reference to the scripting object obtained through getObject method
379
+ * @returns cloned version of the scripting object
380
+ */
381
+ cloneScriptingObject = (proxy) => {
382
+ if (!proxy) throw new Error("proxy is required");
383
+ const so = new ScriptingObject(proxy.id, proxy.objectType);
384
+ let unsubscribers = [];
385
+ Object.keys(proxy).forEach((propName) => {
386
+ const propValue = proxy[propName];
387
+ if (this.#isProxyEvent(propValue)) {
388
+ const event = new Event({
389
+ name: propValue.name || propName,
390
+ objectId: so.id
391
+ });
392
+ Object.defineProperty(so, propName, {
393
+ value: event,
394
+ enumerable: true
395
+ });
396
+ const listener = ({
397
+ eventParams,
398
+ eventOptions
399
+ }) => this.dispatchEvent({
400
+ event,
401
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
402
+ eventParams,
403
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
404
+ eventOptions
405
+ });
406
+ const token = propValue.subscribe(listener);
407
+ unsubscribers.push(() => {
408
+ propValue.unsubscribe(token);
409
+ });
410
+ } else if (this.#isFunction(propValue)) {
411
+ Object.defineProperty(so, propName, {
412
+ value: async (...args) => {
413
+ const retVal = await propValue(...args);
414
+ return isScriptingObjectProxy(retVal) ? this.cloneScriptingObject(retVal) : retVal;
415
+ },
416
+ enumerable: true
417
+ });
418
+ if (propName === "dispose") {
419
+ const defaultImpl = so.dispose;
420
+ Object.defineProperty(so, propName, {
421
+ value: () => {
422
+ so._dispose();
423
+ return defaultImpl.apply(so);
424
+ },
425
+ enumerable: true
426
+ });
427
+ }
428
+ }
429
+ });
430
+ so._dispose = () => {
431
+ unsubscribers.forEach((unsub) => {
432
+ unsub?.();
433
+ });
434
+ unsubscribers = [];
435
+ };
436
+ return so;
195
437
  };
196
438
  /**
197
439
  * Close all active guest micro frontend applications
@@ -201,43 +443,68 @@ class CAppBridge {
201
443
  };
202
444
  /**
203
445
  * Close guest micro frontend application
204
- * @param id unique id of guest application
446
+ * @param instanceId unique instance id of the application
205
447
  */
206
- closeApp = async (id) => {
207
- if (!id) throw new Error("id is required");
208
- const appConfig = this.#microFEConfig.getConfigById(id);
209
- if (!appConfig) {
210
- throw new Error(`Application with id ${id} is not found`);
448
+ closeApp = async (instanceId) => {
449
+ if (!instanceId) throw new Error("instanceId is required");
450
+ const app = this.#activeApps.get(instanceId);
451
+ if (!app) {
452
+ this.#logger.warn(
453
+ `Application with instance id ${instanceId} is not found`
454
+ );
455
+ return;
211
456
  }
212
- const { name, hostUrl } = appConfig;
457
+ this.#activeApps.delete(instanceId);
458
+ const { id } = app;
459
+ const appConfig = this.#microFEConfig.getConfigById(id);
460
+ const { hostUrl } = appConfig;
213
461
  try {
214
- await this.#unmountApp({ id, name });
462
+ this.#clearSession(app);
463
+ await this.#unmountApp({ id, instanceId });
215
464
  } finally {
216
- const frameEle = Frame.get(id);
217
- if (!frameEle?.contentDocument) {
218
- throw new Error(`Iframe for application with id ${id} is not found`);
465
+ const frameEle = Frame.get(instanceId);
466
+ if (frameEle?.contentDocument) {
467
+ this.#unloadApp({
468
+ id,
469
+ instanceId,
470
+ hostUrl,
471
+ documentEle: frameEle.contentDocument
472
+ });
219
473
  }
220
- this.#unloadApp({ id, hostUrl, documentEle: frameEle.contentDocument });
221
- this.#soManager.removeAllScriptingObjects(id);
222
- Frame.remove(id);
474
+ Frame.remove(instanceId);
223
475
  }
224
476
  };
225
477
  /**
226
478
  * dispatch event to guest microfrontend application
227
- * @param {DispatchEventParams<EventId, Params>} params - event parameters
479
+ * @param {DispatchEventParams<EventId, Params, Options>} params - event parameters
228
480
  */
229
- dispatchEvent = async (params) => this.#eventManager.dispatchEvent(params);
481
+ dispatchEvent = async (params) => {
482
+ const {
483
+ event: { id, name }
484
+ } = params;
485
+ if (!id) throw new Error("Event Id is required");
486
+ const objectId = id.split(".")?.[0];
487
+ const scriptingObject = this.#soManager.getObject(objectId);
488
+ if (!scriptingObject) {
489
+ this.#logger.warn(
490
+ `Attempt to dispatch event ${name} on unknown object ${objectId}`
491
+ );
492
+ return Promise.resolve();
493
+ }
494
+ return this.#eventManager.dispatchEvent(scriptingObject, params);
495
+ };
230
496
  /**
231
- * Get guest by id
497
+ * Get App by instanceId
232
498
  * @param id unique id of guest
499
+ * @param instanceId
233
500
  * @returns guest instance
234
501
  */
235
- getGuest = (id) => this.#activeApps.get(id)?.guest;
502
+ getApp = (instanceId) => this.#activeApps.get(instanceId)?.guest;
236
503
  /**
237
- * Get list of active guests
504
+ * Get list of active apps
238
505
  * @returns list of active guests
239
506
  */
240
- getGuests = () => [...this.#activeApps.values()].map((app) => app.guest);
507
+ getApps = () => [...this.#activeApps.values()].map((app) => app.guest);
241
508
  /**
242
509
  * Initialize appBridge
243
510
  */
@@ -250,87 +517,115 @@ class CAppBridge {
250
517
  };
251
518
  /**
252
519
  * Mount guest micro frontend application into DOM
253
- * @param id unique id of guest micro frontend application
254
- * @throws Error if application with given id is not found in configuration
520
+ * @param instanceId unique instance id of guest micro frontend application
521
+ * @throws Error if application with given instance id is not found in configuration
255
522
  */
256
- mountApp = async (id) => {
257
- if (!id) throw new Error("id is required");
523
+ mountApp = async (instanceId) => {
524
+ if (!instanceId) throw new Error("instanceId is required");
525
+ const { id } = this.#activeApps.get(instanceId) || {};
526
+ if (!id) {
527
+ throw new Error(
528
+ `Application with instance id ${instanceId} is not found`
529
+ );
530
+ }
258
531
  const appConfig = this.#microFEConfig.getConfigById(id);
259
532
  if (!appConfig) {
260
533
  throw new Error(`Application with id ${id} is not found`);
261
534
  }
262
- await this.#mountApp(appConfig);
535
+ await this.#mountApp({ ...appConfig, instanceId });
263
536
  };
264
537
  /**
265
538
  * Open guest micro frontend application
266
539
  * @param {OpenAppParams} params - options to open guest application
267
540
  */
268
541
  openApp = async (params) => {
269
- const { id, frameOptions, history, theme } = params;
542
+ const { id, frameOptions, history, theme, homeRoute, initialRoute } = params;
543
+ const instanceId = uuidv4();
270
544
  const appConfig = this.#microFEConfig.getConfigById(id);
271
545
  if (!appConfig) {
272
- throw new Error(`Application with id ${id} is not found`);
546
+ throw new Error(`Application ${id} is not found in app config`);
273
547
  }
274
- if (Frame.get(id))
275
- throw new Error(`Application with id ${id} is already open`);
276
- const frameEle = await Frame.create(id, {
277
- title: appConfig.name,
278
- ...frameOptions
548
+ const frameEle = await Frame.create({
549
+ id,
550
+ instanceId,
551
+ manifestPath: appConfig.manifestPath,
552
+ hostUrl: appConfig.hostUrl,
553
+ options: {
554
+ title: appConfig.name,
555
+ ...frameOptions
556
+ }
279
557
  });
280
558
  if (!frameEle?.contentDocument)
281
559
  throw new Error("unable to create iframe for the microapp");
282
560
  try {
561
+ this.#appRegistry.add({
562
+ id,
563
+ instanceId,
564
+ documentEle: frameEle.contentDocument
565
+ });
283
566
  await this.#loadApp({
284
- ...appConfig,
567
+ instanceId,
285
568
  history,
286
569
  theme,
287
- documentEle: frameEle.contentDocument
570
+ documentEle: frameEle.contentDocument,
571
+ containerId: frameOptions?.containerId,
572
+ ...appConfig,
573
+ homeRoute: homeRoute ?? appConfig.homeRoute,
574
+ initialRoute
288
575
  });
289
- await this.#mountApp(appConfig);
290
- return this.#activeApps.get(id)?.guest;
576
+ await this.#mountApp({ instanceId, ...appConfig });
577
+ this.#manageSession({ id, instanceId });
578
+ return instanceId;
291
579
  } catch (err) {
292
580
  this.#unloadApp({
293
581
  id,
582
+ instanceId,
294
583
  hostUrl: appConfig.hostUrl,
295
584
  documentEle: frameEle.contentDocument
296
585
  });
297
- Frame.remove(id);
586
+ Frame.remove(instanceId);
298
587
  throw err;
299
588
  }
300
589
  };
301
- /**
302
- * emit event to all subscribers (deprecated)
303
- * @deprecated use dispatchEvent instead
304
- * @param eventId unique id of the event. The format is [scripting object name].[event name]
305
- * @param data data to be sent to the subscribers of the event
306
- * @returns true if event is published successfully
307
- */
308
- publish = (eventId, data) => publish(eventId, data);
309
590
  /**
310
591
  * remove all listeners
311
592
  */
312
- removeAllEventListeners = () => {
593
+ removeAllEventSubscriptions = () => {
313
594
  this.#eventManager.unsubscribeAll();
314
595
  };
596
+ /**
597
+ * removes all scripting objects from host
598
+ * @param guestId unique id of the guest application
599
+ */
600
+ removeAllScriptingObjects = (guestId) => {
601
+ this.#soManager.removeAllScriptingObjects(guestId);
602
+ };
315
603
  /**
316
604
  * removes scripting object from the host
317
605
  * @param objectId unique id of the scripting object
606
+ * @param guestId
318
607
  */
319
- removeScriptingObject = (objectId) => {
320
- this.#soManager.removeScriptingObject(objectId);
608
+ removeScriptingObject = (objectId, guestId) => {
609
+ this.#soManager.removeScriptingObject(objectId, guestId);
321
610
  };
322
611
  /**
323
612
  * Unmount guest micro frontend application from DOM
324
- * @param id unique id of guest micro frontend application
613
+ * @param instanceId unique instance id of guest micro frontend application
325
614
  * @throws Error if application with given id is not found in configuration
326
615
  */
327
- unmountApp = async (id) => {
328
- if (!id) throw new Error("id is required");
616
+ unmountApp = async (instanceId) => {
617
+ if (!instanceId) throw new Error("instanceId is required");
618
+ const { id } = this.#activeApps.get(instanceId) || {};
619
+ if (!id) {
620
+ throw new Error(
621
+ `Application with instance id ${instanceId} is not found`
622
+ );
623
+ }
329
624
  const appConfig = this.#microFEConfig.getConfigById(id);
330
625
  if (!appConfig) {
331
626
  throw new Error(`Application with id ${id} is not found`);
332
627
  }
333
- await this.#unmountApp(appConfig);
628
+ await this.#unmountApp({ ...appConfig, instanceId });
334
629
  };
335
630
  }
336
631
  export {