@elliemae/pui-app-sdk 5.14.2 → 5.15.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.
@@ -24,8 +24,23 @@ const cssType = /\.css$/;
24
24
  const isCss = (fileName) => cssType.test(fileName);
25
25
  const activeApps = {};
26
26
  const getPathName = (url) => url ? new URL(url).pathname : "";
27
+ 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}`;
28
+ const getMicroAppGuestFromWindow = ({
29
+ id,
30
+ uuid
31
+ }) => {
32
+ let microAppGuest = null;
33
+ const guests = window.emui?.[id];
34
+ if (Array.isArray(guests)) {
35
+ microAppGuest = guests.find((guest) => guest.uuid === uuid) ?? null;
36
+ } else {
37
+ microAppGuest = guests;
38
+ }
39
+ return microAppGuest;
40
+ };
27
41
  const initApplication = async ({
28
42
  id,
43
+ uuid,
29
44
  name,
30
45
  hostUrl,
31
46
  history,
@@ -33,18 +48,19 @@ const initApplication = async ({
33
48
  manifestPath,
34
49
  homeRoute
35
50
  }) => {
36
- const app = window.emui?.[id];
37
- if (!app) {
38
- throw new Error(
39
- `Application ${name} with ${id} is not found. Most probably the appId property of app.config.json is not set to ${id}`
40
- );
41
- }
51
+ const app = getMicroAppGuestFromWindow({
52
+ id,
53
+ uuid
54
+ });
55
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
42
56
  const { init } = app;
43
57
  if (!init || typeof init !== "function")
44
58
  throw new Error(
45
59
  `Application ${name} with id ${id} doesn't expose init method.`
46
60
  );
47
- const prevState = await persistentStorage.get(`state-${id}`);
61
+ const prevState = await persistentStorage.get(
62
+ `state-${id}-${uuid}`
63
+ );
48
64
  return init({
49
65
  host: CMicroAppHost.getInstance(),
50
66
  hostUrl,
@@ -57,8 +73,12 @@ const initApplication = async ({
57
73
  hostViewportSize: getViewportSize()
58
74
  });
59
75
  };
60
- const mountApp = async ({ id, name }) => {
61
- const app = (window.emui || {})[id] || {};
76
+ const mountApp = async ({ id, uuid, name }) => {
77
+ const app = getMicroAppGuestFromWindow({
78
+ id,
79
+ uuid
80
+ });
81
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
62
82
  const { mount } = app;
63
83
  if (!mount || typeof mount !== "function")
64
84
  throw new Error(
@@ -70,8 +90,11 @@ const mountApp = async ({ id, name }) => {
70
90
  hostViewportSize: getViewportSize()
71
91
  });
72
92
  };
73
- const unmountApp = async ({ id, name }) => {
74
- const app = (window.emui || {})[id];
93
+ const unmountApp = async ({ id, uuid, name }) => {
94
+ const app = getMicroAppGuestFromWindow({
95
+ id,
96
+ uuid
97
+ });
75
98
  if (!app) return null;
76
99
  const { unmount } = app;
77
100
  if (!unmount) return null;
@@ -83,66 +106,125 @@ const unmountApp = async ({ id, name }) => {
83
106
  containerId: `${APP_CONTAINER_ID_PREFIX}${id}`
84
107
  });
85
108
  if (currentState) {
86
- await persistentStorage.set(`state-${id}`, currentState);
109
+ await persistentStorage.set(`state-${id}-${uuid}`, currentState);
87
110
  }
88
111
  return Promise.resolve();
89
112
  };
90
- const addAppToActiveAppList = (id, elementIds) => {
91
- const app = (window.emui || {})[id] || {};
113
+ const addAppToActiveAppList = (id, uuid, elementIds) => {
114
+ const app = getMicroAppGuestFromWindow({
115
+ id,
116
+ uuid
117
+ });
118
+ if (!app) throw new Error(getAppNotFoundError(id, uuid));
92
119
  const { getRef } = app;
93
- activeApps[id] = { elementIds };
120
+ activeApps[`${id}-${uuid}`] = { elementIds };
94
121
  const host = CMicroAppHost.getInstance();
95
- if (host) host.activeGuests[id] = getRef && getRef() || {};
122
+ if (host) host.activeGuests[`${id}-${uuid}`] = getRef && getRef() || {};
96
123
  return Promise.resolve();
97
124
  };
98
125
  const waitAndInitApplication = (appConfig, requests) => (
99
126
  // wait for all assets to get downloaded
100
- Promise.all(requests).then(addAppToActiveAppList.bind(null, appConfig.id)).then(initApplication.bind(null, appConfig)).catch((err) => {
101
- const logRecord = logRecords.APP_INIT_FAILED(appConfig.id, err.message);
127
+ Promise.all(requests).then(addAppToActiveAppList.bind(null, appConfig.id, appConfig.uuid)).then(initApplication.bind(null, appConfig)).catch((err) => {
128
+ const logRecord = logRecords.APP_INIT_FAILED(
129
+ appConfig.id,
130
+ appConfig.uuid,
131
+ err.message
132
+ );
102
133
  getLogger().error({ ...logRecord, exception: err });
103
134
  throw new Error(logRecord.message);
104
135
  })
105
136
  );
106
- const removeAssetsFromDOM = (id, documentEle = document) => {
137
+ const removeAssetsFromDOM = (id, uuid, documentEle = document) => {
107
138
  const host = CMicroAppHost.getInstance();
108
- if (host) delete host.activeGuests[id];
109
- const { elementIds } = activeApps[id] || {};
139
+ if (host) delete host.activeGuests[`${id}-${uuid}`];
140
+ const { elementIds } = activeApps[`${id}-${uuid}`] || {};
110
141
  if (elementIds) {
111
142
  elementIds.forEach((elementId) => {
112
143
  const ele = documentEle.getElementById(elementId);
113
144
  if (ele) ele.remove();
114
145
  });
115
- delete activeApps[id];
146
+ delete activeApps[`${id}-${uuid}`];
147
+ }
148
+ };
149
+ const addAppToGlobalVariable = ({
150
+ id,
151
+ uuid,
152
+ documentEle
153
+ }) => {
154
+ const newAppInstance = {
155
+ uuid,
156
+ init: null,
157
+ mount: null,
158
+ unmount: null,
159
+ getRef: null,
160
+ navigate: null
161
+ };
162
+ const app = getMicroAppGuestFromWindow({ id, uuid });
163
+ if (app) {
164
+ if (!uuid) {
165
+ throw new Error(
166
+ `Application ${id} is already loaded. uuid is required to load multiple instances of the same app`
167
+ );
168
+ }
169
+ if (Array.isArray(window.emui[id])) {
170
+ window.emui[id].push(newAppInstance);
171
+ } else {
172
+ window.emui[id] = [window.emui[id], newAppInstance];
173
+ }
174
+ } else {
175
+ window.emui[id] = newAppInstance;
176
+ }
177
+ if (documentEle.defaultView) {
178
+ documentEle.defaultView.emui = documentEle.defaultView.emui ?? {};
179
+ documentEle.defaultView.emui.uuid = uuid;
180
+ }
181
+ };
182
+ const removeAppFromGlobalVariable = (id, uuid) => {
183
+ if (Array.isArray(window.emui[id])) {
184
+ const index = window.emui[id].findIndex(
185
+ (app) => app.uuid === uuid
186
+ );
187
+ if (index > -1) window.emui[id].splice(index, 1);
188
+ } else {
189
+ delete window.emui[id];
116
190
  }
117
191
  };
118
192
  const loadApp = async (appConfig) => {
119
- getLogger().info(logRecords.APP_LOADING(appConfig.id));
120
- let assets = appConfig.files;
121
- const manifest = await getAppManifest(appConfig);
122
- assets = getFullFileNameofAssetsFromManifest(manifest, appConfig.files);
123
- let counter = 0;
124
- const requests = assets.map((fileName) => {
125
- counter += 1;
126
- return !isCss(fileName) ? addScriptToDOM(appConfig, fileName, counter) : addStylesToDOM(appConfig, fileName, counter);
127
- });
128
- await waitAndInitApplication(appConfig, requests);
129
- getLogger().info(logRecords.APP_LOADING_COMPLETE(appConfig.id));
193
+ const { id, uuid, documentEle } = appConfig;
194
+ addAppToGlobalVariable({ id, uuid, documentEle });
195
+ try {
196
+ getLogger().info(logRecords.APP_LOADING(id, uuid));
197
+ let assets = appConfig.files;
198
+ const manifest = await getAppManifest(appConfig);
199
+ assets = getFullFileNameofAssetsFromManifest(manifest, appConfig.files);
200
+ let counter = 0;
201
+ const requests = assets.map((fileName) => {
202
+ counter += 1;
203
+ return !isCss(fileName) ? addScriptToDOM(appConfig, fileName, counter) : addStylesToDOM(appConfig, fileName, counter);
204
+ });
205
+ await waitAndInitApplication(appConfig, requests);
206
+ getLogger().info(logRecords.APP_LOADING_COMPLETE(id, uuid));
207
+ } catch (err) {
208
+ removeAppFromGlobalVariable(id, uuid);
209
+ throw err;
210
+ }
130
211
  };
131
212
  const unloadApp = ({
132
213
  id,
214
+ uuid,
133
215
  hostUrl,
134
216
  documentEle
135
217
  }) => {
136
218
  if (!hostUrl) throw new Error("Unable to unload app. hostUrl is required");
137
- getLogger().info(logRecords.APP_UNLOADING(id));
138
- const app = (window.emui || {})[id];
219
+ getLogger().info(logRecords.APP_UNLOADING(id, uuid));
220
+ const app = getMicroAppGuestFromWindow({ id, uuid });
139
221
  if (!app) return;
140
- removeAssetsFromDOM(id, documentEle);
222
+ removeAssetsFromDOM(id, uuid, documentEle);
141
223
  removeDynamicImportedScripts(hostUrl, documentEle);
142
224
  removePrefetchLinks(hostUrl, documentEle);
143
225
  removeDynamicImportedStyles(hostUrl, documentEle);
144
- if (window.emui && window.emui[id]) delete window.emui[id];
145
- getLogger().info(logRecords.APP_UNLOADING_COMPLETE(id));
226
+ removeAppFromGlobalVariable(id, uuid);
227
+ getLogger().info(logRecords.APP_UNLOADING_COMPLETE(id, uuid));
146
228
  };
147
229
  export {
148
230
  loadApp,
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { memo } from "react";
2
+ import { memo, useId } from "react";
3
3
  import styled from "styled-components";
4
4
  import { APP_CONTAINER_ID_PREFIX } from "./const.js";
5
5
  import { useAppWillRender } from "./use-app-will-render.js";
@@ -8,7 +8,8 @@ const Div = styled.div`
8
8
  width: 100%;
9
9
  `;
10
10
  const MicroApp = memo((props) => {
11
- useAppWillRender(props);
11
+ const uuid = useId();
12
+ useAppWillRender({ uuid, ...props });
12
13
  return /* @__PURE__ */ jsx(Div, { id: `${APP_CONTAINER_ID_PREFIX}${props.id}` });
13
14
  });
14
15
  export {
@@ -15,6 +15,7 @@ import {
15
15
  import { logRecords } from "../../utils/log-records.js";
16
16
  const useAppWillRender = ({
17
17
  id,
18
+ uuid,
18
19
  documentEle,
19
20
  history,
20
21
  theme,
@@ -30,27 +31,27 @@ const useAppWillRender = ({
30
31
  [id]
31
32
  );
32
33
  const unload = useCallback(
33
- async (appConfig, loadFailed = false) => {
34
- await unmountApp(appConfig);
35
- unloadApp(appConfig);
34
+ async (appParams, loadFailed = false) => {
35
+ await unmountApp(appParams);
36
+ unloadApp(appParams);
36
37
  if (!loadFailed && onUnloadComplete) onUnloadComplete();
37
38
  },
38
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
39
40
  []
40
41
  );
41
42
  const load = useCallback(
42
- async (appConfig) => {
43
+ async (appParams) => {
43
44
  if (dispatch) dispatch(waitMessage.open());
44
45
  try {
45
46
  if (ref.current) await ref.current;
46
- await loadApp(appConfig);
47
- await mountApp(appConfig);
47
+ await loadApp(appParams);
48
+ await mountApp(appParams);
48
49
  } catch (ex) {
49
50
  getLogger().error({
50
51
  ...logRecords.APP_CONFIG_LOAD_FAILED,
51
52
  exception: ex
52
53
  });
53
- await unload(appConfig, true);
54
+ await unload(appParams, true);
54
55
  throw ex;
55
56
  }
56
57
  if (dispatch) dispatch(waitMessage.close());
@@ -66,11 +67,12 @@ const useAppWillRender = ({
66
67
  theme,
67
68
  homeRoute
68
69
  });
69
- load(appConfig);
70
+ const appParams = { ...appConfig, uuid };
71
+ load(appParams);
70
72
  return () => {
71
- ref.current = unload(appConfig);
73
+ ref.current = unload(appParams);
72
74
  };
73
- }, [documentEle, getConfig, history, theme, homeRoute, load, unload]);
75
+ }, [documentEle, getConfig, history, theme, homeRoute, load, unload, uuid]);
74
76
  };
75
77
  export {
76
78
  useAppWillRender
@@ -7,11 +7,12 @@ import {
7
7
  useAppWillRender
8
8
  } from "../micro-app/use-app-will-render.js";
9
9
  const App = memo(
10
- ({ id, dispose, onUnloadComplete, ...rest }) => {
10
+ ({ id, uuid, dispose, onUnloadComplete, ...rest }) => {
11
11
  const [documentEle, setDocumentEle] = useState(null);
12
12
  useFrameLoaded({ id, documentEle, ...rest });
13
13
  useAppWillRender({
14
14
  id,
15
+ uuid,
15
16
  documentEle: dispose ? null : documentEle,
16
17
  onUnloadComplete,
17
18
  ...rest
@@ -1,10 +1,11 @@
1
1
  import { createElement } from "react";
2
- import { useEffect, useState, memo } from "react";
2
+ import { useEffect, useState, memo, useId } from "react";
3
3
  import { usePrevious } from "../use-previous.js";
4
4
  import { App } from "./app.js";
5
5
  const MicroIFrameApp = memo(({ entityId = null, ...rest }) => {
6
6
  const [disposePrevApp, setDisposePrevApp] = useState(false);
7
7
  const [appKey, setAppKey] = useState(Date.now());
8
+ const uuid = useId();
8
9
  const prevEntityId = usePrevious(entityId);
9
10
  useEffect(() => {
10
11
  if (prevEntityId !== entityId && prevEntityId) {
@@ -15,6 +16,7 @@ const MicroIFrameApp = memo(({ entityId = null, ...rest }) => {
15
16
  App,
16
17
  {
17
18
  ...rest,
19
+ uuid,
18
20
  key: appKey,
19
21
  dispose: disposePrevApp,
20
22
  onUnloadComplete: () => {
@@ -19,23 +19,23 @@ export declare const logRecords: {
19
19
  code: string;
20
20
  message: string;
21
21
  };
22
- APP_INIT_FAILED: (appId: string, errMsg: string) => {
22
+ APP_INIT_FAILED: (appId: string, instanceId: string, errMsg: string) => {
23
23
  code: string;
24
24
  message: string;
25
25
  };
26
- APP_LOADING: (appId: string) => {
26
+ APP_LOADING: (appId: string, instanceId: string) => {
27
27
  code: string;
28
28
  message: string;
29
29
  };
30
- APP_LOADING_COMPLETE: (appId: string) => {
30
+ APP_LOADING_COMPLETE: (appId: string, instanceId: string) => {
31
31
  code: string;
32
32
  message: string;
33
33
  };
34
- APP_UNLOADING: (appId: string) => {
34
+ APP_UNLOADING: (appId: string, instanceId: string) => {
35
35
  code: string;
36
36
  message: string;
37
37
  };
38
- APP_UNLOADING_COMPLETE: (appId: string) => {
38
+ APP_UNLOADING_COMPLETE: (appId: string, instanceId: string) => {
39
39
  code: string;
40
40
  message: string;
41
41
  };
@@ -20,6 +20,7 @@ interface ConstructorParams {
20
20
  history?: History;
21
21
  }
22
22
  export declare class CMicroAppGuest implements IMicroAppGuest {
23
+ #private;
23
24
  private static instance;
24
25
  private readonly logger;
25
26
  private readonly appId;
@@ -38,6 +39,7 @@ export declare class CMicroAppGuest implements IMicroAppGuest {
38
39
  getProps(): GuestProps;
39
40
  private getSessionStorageItem;
40
41
  private getSSFAdapter;
42
+ get uuid(): string;
41
43
  init(this: CMicroAppGuest, options: InitOptions): Promise<void>;
42
44
  mount(this: CMicroAppGuest, options?: MountOptions): Promise<void>;
43
45
  unmount(this: CMicroAppGuest, options: MountOptions): Promise<JSONValue>;
@@ -2,7 +2,7 @@ import { IMicroAppGuest, IMicroAppHost, BAEvent } from '@elliemae/pui-micro-fron
2
2
  import { BreakPoint } from '@elliemae/pui-theme';
3
3
  import { Logger } from '@elliemae/pui-diagnostics';
4
4
  export type EMUI = {
5
- [key: string]: IMicroAppGuest;
5
+ [key: string]: IMicroAppGuest | IMicroAppGuest[];
6
6
  } & {
7
7
  _BASE_PATH: string;
8
8
  _ASSET_PATH: string;
@@ -10,6 +10,8 @@ export type EMUI = {
10
10
  MicroAppHost?: IMicroAppHost;
11
11
  logger?: Logger;
12
12
  appId: string;
13
+ uuid: string;
14
+ app?: IMicroAppGuest;
13
15
  };
14
16
  declare global {
15
17
  interface Window {
@@ -1,9 +1,13 @@
1
1
  import { MicroAppConfig } from '../../../utils/micro-frontend/types.js';
2
- export declare const mountApp: ({ id, name }: MicroAppConfig) => Promise<void>;
3
- export declare const unmountApp: ({ id, name }: MicroAppConfig) => Promise<void | null>;
4
- export declare const loadApp: (appConfig: MicroAppConfig) => Promise<void>;
5
- export declare const unloadApp: ({ id, hostUrl, documentEle, }: {
2
+ export type AppParams = MicroAppConfig & {
3
+ uuid: string;
4
+ };
5
+ export declare const mountApp: ({ id, uuid, name }: AppParams) => Promise<void>;
6
+ export declare const unmountApp: ({ id, uuid, name }: AppParams) => Promise<void | null>;
7
+ export declare const loadApp: (appConfig: AppParams) => Promise<void>;
8
+ export declare const unloadApp: ({ id, uuid, hostUrl, documentEle, }: {
6
9
  id: string;
10
+ uuid: string;
7
11
  hostUrl?: string;
8
12
  documentEle: Document;
9
13
  }) => void;
@@ -2,7 +2,7 @@ import { Theme } from '@elliemae/pui-theme';
2
2
  import { History } from 'history';
3
3
  export type MicroAppProps = {
4
4
  id: string;
5
- documentEle?: HTMLDocument | null;
5
+ documentEle?: Document | null;
6
6
  history?: History;
7
7
  theme?: Theme;
8
8
  homeRoute?: string;
@@ -1,7 +1,8 @@
1
1
  import { MicroAppProps } from './types.js';
2
2
  export type OnUnloadCompleteFn = () => void;
3
3
  type UseAppWillRenderArgs = MicroAppProps & {
4
+ uuid: string;
4
5
  onUnloadComplete?: OnUnloadCompleteFn;
5
6
  };
6
- export declare const useAppWillRender: ({ id, documentEle, history, theme, homeRoute, onUnloadComplete, }: UseAppWillRenderArgs) => void;
7
+ export declare const useAppWillRender: ({ id, uuid, documentEle, history, theme, homeRoute, onUnloadComplete, }: UseAppWillRenderArgs) => void;
7
8
  export {};
@@ -1,6 +1,7 @@
1
1
  import { OnUnloadCompleteFn } from '../micro-app/use-app-will-render.js';
2
2
  import { MicroIFrameAppProps } from './types.js';
3
3
  type AppProps = MicroIFrameAppProps & {
4
+ uuid: string;
4
5
  dispose: boolean;
5
6
  onUnloadComplete: OnUnloadCompleteFn;
6
7
  };