@akinon/app-client 1.0.0 → 1.1.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.
Files changed (37) hide show
  1. package/dist/cjs/app-client-provider.d.ts +18 -4
  2. package/dist/cjs/app-client-provider.d.ts.map +1 -1
  3. package/dist/cjs/app-client-provider.js +130 -145
  4. package/dist/cjs/components/error-boundary.d.ts +19 -0
  5. package/dist/cjs/components/error-boundary.d.ts.map +1 -0
  6. package/dist/cjs/components/error-boundary.js +20 -0
  7. package/dist/cjs/index.d.ts +2 -0
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +2 -0
  10. package/dist/cjs/multi-app-provider.d.ts +10 -0
  11. package/dist/cjs/multi-app-provider.d.ts.map +1 -0
  12. package/dist/cjs/multi-app-provider.js +79 -0
  13. package/dist/cjs/types.d.ts +11 -0
  14. package/dist/cjs/types.d.ts.map +1 -0
  15. package/dist/cjs/types.js +2 -0
  16. package/dist/cjs/utils/navigation.d.ts +8 -0
  17. package/dist/cjs/utils/navigation.d.ts.map +1 -0
  18. package/dist/cjs/utils/navigation.js +18 -0
  19. package/dist/esm/app-client-provider.d.ts +18 -4
  20. package/dist/esm/app-client-provider.d.ts.map +1 -1
  21. package/dist/esm/app-client-provider.js +130 -146
  22. package/dist/esm/components/error-boundary.d.ts +19 -0
  23. package/dist/esm/components/error-boundary.d.ts.map +1 -0
  24. package/dist/esm/components/error-boundary.js +16 -0
  25. package/dist/esm/index.d.ts +2 -0
  26. package/dist/esm/index.d.ts.map +1 -1
  27. package/dist/esm/index.js +2 -0
  28. package/dist/esm/multi-app-provider.d.ts +10 -0
  29. package/dist/esm/multi-app-provider.d.ts.map +1 -0
  30. package/dist/esm/multi-app-provider.js +75 -0
  31. package/dist/esm/types.d.ts +11 -0
  32. package/dist/esm/types.d.ts.map +1 -0
  33. package/dist/esm/types.js +1 -0
  34. package/dist/esm/utils/navigation.d.ts +8 -0
  35. package/dist/esm/utils/navigation.d.ts.map +1 -0
  36. package/dist/esm/utils/navigation.js +15 -0
  37. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
- import type { ApplicationData, ApplicationModalSize, ApplicationNavigation, ApplicationParams, ApplicationToastType, FullpageApplicationConfig, PluginApplicationConfig, RegisteredApp, RegisteredAppType, ShellNavigation, ShellNavigationPayload } from '@akinon/app-shared';
1
+ import type { ApplicationData, ApplicationModalSize, ApplicationNavigation, ApplicationNavigationPayload, ApplicationParams, ApplicationToastType, FullpageApplicationConfig, PluginApplicationConfig, RegisteredApp, RegisteredAppType, ShellNavigation, ShellNavigationPayload } from '@akinon/app-shared';
2
+ import Framebus from 'framebus';
2
3
  import React from 'react';
3
4
  /**
4
5
  * Defines the context state for the AppClient, including application data,
@@ -99,6 +100,10 @@ interface AppClientContextState {
99
100
  * @returns {Function} - Function to remove the callback.
100
101
  */
101
102
  onLocaleChange: (callback: (newLocale: string) => void) => void;
103
+ /**
104
+ * Context data for the modal.
105
+ */
106
+ modalContext?: unknown;
102
107
  }
103
108
  /**
104
109
  * Props for the AppClientProvider component.
@@ -112,20 +117,29 @@ interface AppClientProviderProps {
112
117
  * Configuration for the application, including settings like `isDev` and `forceRedirect`.
113
118
  */
114
119
  config: FullpageApplicationConfig | PluginApplicationConfig;
120
+ /**
121
+ * Optional event bus to use instead of creating a new one.
122
+ */
123
+ eventBus?: Framebus;
115
124
  }
125
+ /**
126
+ * Default context state for the AppClient.
127
+ */
128
+ declare const defaultContextState: AppClientContextState;
116
129
  /**
117
130
  * Custom hook to access the AppClient context.
118
131
  *
119
132
  * @returns {AppClientContextState} The current context state.
120
133
  */
121
134
  declare const useAppClient: () => AppClientContextState;
135
+ declare let contextValue: AppClientContextState;
122
136
  /**
123
137
  * Component providing the context for AppClient. It initializes communication
124
138
  * with the AppShell and provides methods for action invocation and navigation.
125
139
  *
126
140
  * @param {AppClientProviderProps} props - The props for the AppClientProvider component.
127
141
  */
128
- declare const AppClientProvider: ({ children, config }: AppClientProviderProps) => React.JSX.Element;
129
- export { AppClientProvider, useAppClient };
130
- export type { ApplicationNavigation, FullpageApplicationConfig, PluginApplicationConfig, RegisteredApp, RegisteredAppType, ShellNavigation };
142
+ declare const AppClientProvider: ({ children, config, eventBus }: AppClientProviderProps) => React.JSX.Element;
143
+ export { AppClientProvider, contextValue, defaultContextState, useAppClient };
144
+ export type { ApplicationNavigation, ApplicationNavigationPayload, FullpageApplicationConfig, PluginApplicationConfig, RegisteredApp, RegisteredAppType, ShellNavigation };
131
145
  //# sourceMappingURL=app-client-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-client-provider.d.ts","sourceRoot":"","sources":["../../src/app-client-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAON,MAAM,OAAO,CAAC;AAEf;;;;GAIG;AACH,UAAU,qBAAqB;IAC7B;;OAEG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,YAAY,EAAE,CACZ,SAAS,EAAE,MAAM,EACjB,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KACpB,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB;;;OAGG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACpD;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;KACvB,KAAK,OAAO,CAAC;IACd;;;;;;;;;;OAUG;IACH,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE;QACjC,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;KACvB,KAAK,OAAO,CAAC;IACd;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC;IACrE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/D;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,oBAAoB,EAC3B,cAAc,CAAC,EAAE,MAAM,KACpB,OAAO,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;CACjE;AAED;;GAEG;AACH,UAAU,sBAAsB;IAC9B;;OAEG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;OAEG;IACH,MAAM,EAAE,yBAAyB,GAAG,uBAAuB,CAAC;CAC7D;AAyBD;;;;GAIG;AACH,QAAA,MAAM,YAAY,QAAO,qBAAqD,CAAC;AAwL/E;;;;;GAKG;AACH,QAAA,MAAM,iBAAiB,yBAA0B,sBAAsB,sBAiJtE,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,CAAC"}
1
+ {"version":3,"file":"app-client-provider.d.ts","sourceRoot":"","sources":["../../src/app-client-provider.tsx"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.useAppClient = exports.AppClientProvider = void 0;
12
+ exports.useAppClient = exports.defaultContextState = exports.contextValue = exports.AppClientProvider = void 0;
13
13
  const app_shared_1 = require("@akinon/app-shared");
14
14
  const framebus_1 = require("framebus");
15
15
  const react_1 = require("react");
@@ -29,6 +29,7 @@ const defaultContextState = {
29
29
  return;
30
30
  }
31
31
  };
32
+ exports.defaultContextState = defaultContextState;
32
33
  /**
33
34
  * Context for the AppClient, providing access to application state and methods.
34
35
  */
@@ -40,138 +41,48 @@ const AppClientContext = (0, react_1.createContext)(defaultContextState);
40
41
  */
41
42
  const useAppClient = () => (0, react_1.useContext)(AppClientContext);
42
43
  exports.useAppClient = useAppClient;
43
- /**
44
- * Navigates to the specified path within the application.
45
- * This method communicates with the AppShell to perform the navigation.
46
- */
47
- const navigate = ({ id, path, external }) => {
48
- const bus = new framebus_1.default();
49
- bus.emit(app_shared_1.EVENTS.NAVIGATE, { id, path, external });
50
- };
51
- /**
52
- * Shows a modal dialog with the specified title and content.
53
- * This method communicates with the AppShell to display the modal dialog.
54
- *
55
- * @returns {boolean} - Indicates if the dialog was successfully shown.
56
- */
57
- const showModalDialog = ({ title, content, onConfirm, onCancel }) => {
58
- const bus = new framebus_1.default();
59
- function handleActionConfirmed() {
60
- bus.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
61
- bus.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
62
- onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
63
- }
64
- function handleActionCanceled() {
65
- bus.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
66
- bus.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
67
- onCancel === null || onCancel === void 0 ? void 0 : onCancel();
68
- }
69
- if (onConfirm) {
70
- bus.on(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
71
- }
72
- if (onCancel) {
73
- bus.on(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
74
- }
75
- return bus.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
76
- actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showModalDialog,
77
- args: [title, content]
78
- });
79
- };
80
- /**
81
- * Shows a confirmation dialog with the specified title and content and can handle the user's input.
82
- * This method communicates with the AppShell to display the confirmation dialog.
83
- *
84
- * @returns {boolean} - Indicates if the dialog was successfully shown.
85
- */
86
- const showConfirmationDialog = ({ title, content, onConfirm, onCancel }) => {
87
- const bus = new framebus_1.default();
88
- function handleActionConfirmed(data) {
89
- bus.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
90
- bus.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
91
- onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(data);
92
- }
93
- function handleActionCanceled() {
94
- bus.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
95
- bus.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
96
- onCancel === null || onCancel === void 0 ? void 0 : onCancel();
97
- }
98
- if (onConfirm) {
99
- bus.on(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
100
- }
101
- if (onCancel) {
102
- bus.on(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
103
- }
104
- return bus.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
105
- actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showConfirmationDialog,
106
- args: [title, content]
107
- });
108
- };
109
- /**
110
- * Displays a toast message with the specified content and type.
111
- * This method communicates with the AppShell to display the toast message.
112
- *
113
- * @param {string} content - The content of the toast message.
114
- * @param {ApplicationToastType} type - The type of the toast message.
115
- * @returns {boolean} - Indicates if the event was emitted successfully.
116
- */
117
- const showToast = (content, type) => {
118
- const bus = new framebus_1.default();
119
- return bus.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
120
- actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showToast,
121
- args: [content, type]
122
- });
123
- };
124
- /**
125
- * Displays an error message dialog with the specified title and content.
126
- * This method communicates with the AppShell to display the error message.
127
- *
128
- * @param {string} title - The title of the error message dialog.
129
- * @param {string} content - The content of the error message dialog.
130
- * @returns {boolean} - Indicates if the event was emitted successfully.
131
- */
132
- const showErrorMessage = (title, content) => {
133
- const bus = new framebus_1.default();
134
- return bus.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
135
- actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showErrorMessage,
136
- args: [title, content]
137
- });
138
- };
139
- /**
140
- * Displays a rich-contentful modal with the specified path and context.
141
- * This method communicateswith the AppShell to display the complicated modals.
142
- *
143
- * @param {string} path - The path of displayed iframe.
144
- * @param {unknown} context - Optional context. Its type must follow the rules: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
145
- * @param {Object} size - Optional size of the modal.
146
- * @param {number | string} size.maxWidth - The maximum width of the modal. If falsish, the modal will be max 540px wide.
147
- * @param {number | string} size.maxHeight - The maximum height of the modal. If falsish, the modal will be max 360px high.
148
- * @param {string} closeIconColor - Optional color of the close icon.
149
- */
150
- const showRichModal = (path, context, size, closeIconColor) => {
151
- const bus = new framebus_1.default();
152
- return bus.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
153
- actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showRichModal,
154
- args: [path, context, size, closeIconColor]
155
- });
156
- };
44
+ let contextValue;
157
45
  /**
158
46
  * Component providing the context for AppClient. It initializes communication
159
47
  * with the AppShell and provides methods for action invocation and navigation.
160
48
  *
161
49
  * @param {AppClientProviderProps} props - The props for the AppClientProvider component.
162
50
  */
163
- const AppClientProvider = ({ children, config }) => {
51
+ const AppClientProvider = ({ children, config, eventBus }) => {
164
52
  const [, setAppId] = (0, react_1.useState)(undefined);
165
53
  const [data, setData] = (0, react_1.useState)(undefined);
54
+ const [modalContext, setModalContext] = (0, react_1.useState)(undefined);
166
55
  const [params, setParams] = (0, react_1.useState)(undefined);
167
56
  const [isLoading, setIsLoading] = (0, react_1.useState)(true);
168
57
  const [locale, setLocale] = (0, react_1.useState)('en');
169
58
  const localeChangeCallbacks = (0, react_1.useRef)([]);
170
- // Function to invoke an action in the AppShell
171
- const invokeAction = (actionKey, ...args) => {
59
+ // Create a ref to store the bus instance
60
+ const busRef = (0, react_1.useRef)();
61
+ // Initialize bus on mount
62
+ (0, react_1.useEffect)(() => {
63
+ busRef.current =
64
+ eventBus ||
65
+ (() => {
66
+ const url = new URL(window.location.href);
67
+ const channel = url.searchParams.get(app_shared_1.CHANNEL_URL_PARAM_NAME);
68
+ return new framebus_1.default({
69
+ channel: (0, app_shared_1.generateBusChannelValue)({ channel })
70
+ });
71
+ })();
72
+ // Send config data to AppShell
73
+ // Add a small delay to ensure the shell is ready
74
+ setTimeout(() => {
75
+ var _a;
76
+ (_a = busRef === null || busRef === void 0 ? void 0 : busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.SET_CONFIG, { config });
77
+ }, 100);
78
+ }, [eventBus, config]);
79
+ // Update standalone functions to use shared bus
80
+ const invokeAction = (0, react_1.useCallback)((actionKey, ...args) => {
81
+ if (!busRef.current)
82
+ return Promise.reject('Bus not initialized');
172
83
  return new Promise((resolve, reject) => {
173
- const bus = new framebus_1.default();
174
- bus.emit(app_shared_1.EVENTS.INVOKE_ACTION, { actionKey, args }, (response) => {
84
+ var _a;
85
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.INVOKE_ACTION, { actionKey, args }, (response) => {
175
86
  if (typeof response === 'object' && response !== null) {
176
87
  const typedResponse = response;
177
88
  if (typedResponse.success) {
@@ -186,33 +97,27 @@ const AppClientProvider = ({ children, config }) => {
186
97
  }
187
98
  });
188
99
  });
189
- };
100
+ }, []);
190
101
  const isFullPageApplication = () => {
191
102
  const castedConfig = config;
192
103
  return Array.isArray(castedConfig.menu);
193
104
  };
194
105
  (0, react_1.useEffect)(() => {
195
- // In production, enforce running in an iframe if forceRedirect is true
106
+ var _a, _b, _c, _d, _e, _f, _g;
196
107
  if (config.forceRedirect && !config.isDev && window.self === window.top) {
197
108
  console.error('This app must be run inside an iframe when forceRedirect is true.');
198
109
  return;
199
110
  }
200
- // Get c from the url of the iframe containing the application.
201
- const url = new URL(window.location.href);
202
- const channel = url.searchParams.get('c');
203
- const bus = channel ? new framebus_1.default({ channel }) : new framebus_1.default();
204
- // Send data from the AppShell upon initialization.
205
- // Pass apps config data.
206
- bus.emit(app_shared_1.EVENTS.SET_CONFIG, { config });
207
- bus.on(app_shared_1.EVENTS.SET_DATA, (receivedData) => {
111
+ // Set up event listeners
112
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(app_shared_1.EVENTS.SET_DATA, (receivedData) => {
208
113
  setData(prevState => (Object.assign(Object.assign({}, prevState), receivedData)));
209
114
  setIsLoading(false);
210
115
  });
211
- bus.on(app_shared_1.EVENTS.SET_PARAMS, message => {
116
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(app_shared_1.EVENTS.SET_PARAMS, message => {
212
117
  const passedParams = message;
213
118
  setParams(passedParams);
214
119
  });
215
- bus.on(app_shared_1.EVENTS.SET_APP_ID, data => {
120
+ (_c = busRef.current) === null || _c === void 0 ? void 0 : _c.on(app_shared_1.EVENTS.SET_APP_ID, data => {
216
121
  const { appId } = data;
217
122
  setAppId(appId);
218
123
  });
@@ -220,14 +125,14 @@ const AppClientProvider = ({ children, config }) => {
220
125
  // Get computed height of iframe element.
221
126
  const height = document.getElementsByTagName('html')[0].offsetHeight;
222
127
  if (height > 0 && window.name) {
223
- bus.emit(app_shared_1.EVENTS.SET_HEIGHT, { height, id: window.name });
128
+ (_d = busRef.current) === null || _d === void 0 ? void 0 : _d.emit(app_shared_1.EVENTS.SET_HEIGHT, { height, id: window.name });
224
129
  }
225
130
  }
226
131
  // Only listen to navigation events if application type is
227
132
  // fullpage. plugin type apps should not have the ability to navigate.
228
133
  if (isFullPageApplication()) {
229
134
  // Listen for navigation events.
230
- bus.on(app_shared_1.EVENTS.NAVIGATE_CHILD, message => {
135
+ (_e = busRef.current) === null || _e === void 0 ? void 0 : _e.on(app_shared_1.EVENTS.NAVIGATE_CHILD, message => {
231
136
  const { path } = message;
232
137
  const { navigation } = config;
233
138
  if (navigation) {
@@ -235,36 +140,116 @@ const AppClientProvider = ({ children, config }) => {
235
140
  navigate({ path });
236
141
  }
237
142
  });
143
+ // Listen for modal context changes
144
+ (_f = busRef.current) === null || _f === void 0 ? void 0 : _f.on(app_shared_1.EVENTS.SET_MODAL_CONTEXT, modalContext => {
145
+ setModalContext(modalContext);
146
+ });
238
147
  }
239
148
  // Listen for locale changes
240
- bus.on(app_shared_1.EVENTS.LOCALE_CHANGED, message => {
149
+ (_g = busRef.current) === null || _g === void 0 ? void 0 : _g.on(app_shared_1.EVENTS.LOCALE_CHANGED, message => {
241
150
  const { locale: newLocale } = message;
242
151
  setLocale(newLocale);
243
152
  localeChangeCallbacks.current.forEach(callback => callback(newLocale));
244
153
  });
245
154
  return () => {
246
- bus.teardown();
155
+ var _a;
156
+ if (!eventBus) {
157
+ // Only teardown if we created the bus
158
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.teardown();
159
+ }
247
160
  };
248
- }, [config]);
161
+ }, [config, eventBus]);
249
162
  const onLocaleChange = (0, react_1.useCallback)((callback) => {
250
163
  localeChangeCallbacks.current.push(callback);
251
164
  return () => {
252
165
  localeChangeCallbacks.current = localeChangeCallbacks.current.filter(cb => cb !== callback);
253
166
  };
254
167
  }, []);
255
- const contextValue = {
168
+ exports.contextValue = contextValue = {
256
169
  data,
257
170
  params,
258
171
  isLoading,
259
172
  invokeAction,
260
- navigate,
261
- showModalDialog,
262
- showConfirmationDialog,
263
- showToast,
264
- showErrorMessage,
265
- showRichModal,
173
+ navigate: (0, react_1.useCallback)((payload) => {
174
+ var _a;
175
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.NAVIGATE, Object.assign({}, payload));
176
+ }, []),
177
+ showModalDialog: (0, react_1.useCallback)((options) => {
178
+ var _a, _b, _c;
179
+ const { title, content, onConfirm, onCancel } = options;
180
+ function handleActionConfirmed() {
181
+ var _a, _b;
182
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
183
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
184
+ onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
185
+ }
186
+ function handleActionCanceled() {
187
+ var _a, _b;
188
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
189
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
190
+ onCancel === null || onCancel === void 0 ? void 0 : onCancel();
191
+ }
192
+ if (onConfirm) {
193
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
194
+ }
195
+ if (onCancel) {
196
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
197
+ }
198
+ return !!((_c = busRef.current) === null || _c === void 0 ? void 0 : _c.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
199
+ actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showModalDialog,
200
+ args: [title, content]
201
+ }));
202
+ }, []),
203
+ showConfirmationDialog: (0, react_1.useCallback)((options) => {
204
+ var _a, _b, _c;
205
+ const { title, content, onConfirm, onCancel } = options;
206
+ function handleActionConfirmed(data) {
207
+ var _a, _b;
208
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
209
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
210
+ onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(data);
211
+ }
212
+ function handleActionCanceled() {
213
+ var _a, _b;
214
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
215
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
216
+ onCancel === null || onCancel === void 0 ? void 0 : onCancel();
217
+ }
218
+ if (onConfirm) {
219
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(app_shared_1.EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
220
+ }
221
+ if (onCancel) {
222
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(app_shared_1.EVENTS.ACTION_CANCELED, handleActionCanceled);
223
+ }
224
+ return !!((_c = busRef.current) === null || _c === void 0 ? void 0 : _c.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
225
+ actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showConfirmationDialog,
226
+ args: [title, content]
227
+ }));
228
+ }, []),
229
+ showToast: (0, react_1.useCallback)((content, type) => {
230
+ var _a;
231
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
232
+ actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showToast,
233
+ args: [content, type]
234
+ }));
235
+ }, []),
236
+ showErrorMessage: (0, react_1.useCallback)((title, content) => {
237
+ var _a;
238
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
239
+ actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showErrorMessage,
240
+ args: [title, content]
241
+ }));
242
+ }, []),
243
+ showRichModal: (0, react_1.useCallback)((path, context, size, closeIconColor) => {
244
+ var _a;
245
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(app_shared_1.EVENTS.INVOKE_DEFAULT_ACTION, {
246
+ actionKey: app_shared_1.DEFAULT_ACTION_KEYS.showRichModal,
247
+ args: [path, context, size, closeIconColor]
248
+ }));
249
+ }, []),
266
250
  locale,
267
- onLocaleChange
251
+ onLocaleChange,
252
+ modalContext
268
253
  };
269
254
  return (react_1.default.createElement(AppClientContext.Provider, { value: contextValue }, children));
270
255
  };
@@ -0,0 +1,19 @@
1
+ import { Component, ReactNode } from 'react';
2
+ interface Props {
3
+ children: ReactNode;
4
+ fallback: ReactNode;
5
+ }
6
+ interface State {
7
+ hasError: boolean;
8
+ }
9
+ export declare class ErrorBoundary extends Component<Props, State> {
10
+ state: {
11
+ hasError: boolean;
12
+ };
13
+ static getDerivedStateFromError(): {
14
+ hasError: boolean;
15
+ };
16
+ render(): ReactNode;
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=error-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../src/components/error-boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7C,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,aAAc,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IACxD,KAAK;;MAAuB;IAE5B,MAAM,CAAC,wBAAwB;;;IAI/B,MAAM;CAOP"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorBoundary = void 0;
4
+ const react_1 = require("react");
5
+ class ErrorBoundary extends react_1.Component {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.state = { hasError: false };
9
+ }
10
+ static getDerivedStateFromError() {
11
+ return { hasError: true };
12
+ }
13
+ render() {
14
+ if (this.state.hasError) {
15
+ return this.props.fallback;
16
+ }
17
+ return this.props.children;
18
+ }
19
+ }
20
+ exports.ErrorBoundary = ErrorBoundary;
@@ -1,2 +1,4 @@
1
1
  export * from './app-client-provider';
2
+ export * from './multi-app-provider';
3
+ export * from './types';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC"}
package/dist/cjs/index.js CHANGED
@@ -15,3 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./app-client-provider"), exports);
18
+ __exportStar(require("./multi-app-provider"), exports);
19
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { MultiAppConfig } from './types';
3
+ interface MultiAppProviderProps {
4
+ children: React.ReactNode;
5
+ multiConfig: MultiAppConfig;
6
+ }
7
+ export declare function MultiAppProvider({ children, multiConfig }: MultiAppProviderProps): React.JSX.Element;
8
+ export declare const APP_NOT_FOUND_CONTENT = "App not found";
9
+ export {};
10
+ //# sourceMappingURL=multi-app-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-app-provider.d.ts","sourceRoot":"","sources":["../../src/multi-app-provider.tsx"],"names":[],"mappings":"AAOA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAIlD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAe9C,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,WAAW,EAAE,cAAc,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,WAAW,EACZ,EAAE,qBAAqB,qBAQvB;AA0BD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APP_NOT_FOUND_CONTENT = void 0;
4
+ exports.MultiAppProvider = MultiAppProvider;
5
+ const app_shared_1 = require("@akinon/app-shared");
6
+ const framebus_1 = require("framebus");
7
+ const react_1 = require("react");
8
+ const app_client_provider_1 = require("./app-client-provider");
9
+ const error_boundary_1 = require("./components/error-boundary");
10
+ function resolveCurrentApp(pathname, apps) {
11
+ // Find the app with the longest matching basePath
12
+ const matchingApp = Object.entries(apps)
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ .filter(([_, app]) => pathname.startsWith(app.basePath))
15
+ .sort((a, b) => b[1].basePath.length - a[1].basePath.length)[0];
16
+ return matchingApp === null || matchingApp === void 0 ? void 0 : matchingApp[1];
17
+ }
18
+ function MultiAppProvider({ children, multiConfig }) {
19
+ return (react_1.default.createElement(error_boundary_1.ErrorBoundary, { fallback: react_1.default.createElement("div", null, "Error loading application") },
20
+ react_1.default.createElement(MultiAppProviderInner, { multiConfig: multiConfig }, children)));
21
+ }
22
+ function validateConfig(config) {
23
+ if (!config.apps || Object.keys(config.apps).length === 0) {
24
+ throw new Error('MultiAppConfig must contain at least one app');
25
+ }
26
+ Object.entries(config.apps).forEach(([key, app]) => {
27
+ if (!app.basePath) {
28
+ throw new Error(`App "${key}" must have a basePath`);
29
+ }
30
+ if (app.type === 'plugin' &&
31
+ !app.config.placeholderId) {
32
+ throw new Error(`Plugin app "${key}" must have a placeholderId`);
33
+ }
34
+ });
35
+ }
36
+ const getChannelValue = () => {
37
+ const url = new URL(window.location.href);
38
+ return url.searchParams.get(app_shared_1.CHANNEL_URL_PARAM_NAME);
39
+ };
40
+ exports.APP_NOT_FOUND_CONTENT = 'App not found';
41
+ function MultiAppProviderInner({ children, multiConfig }) {
42
+ const currentApp = (0, react_1.useMemo)(() => {
43
+ const pathname = window.location.pathname;
44
+ const resolvedApp = resolveCurrentApp(pathname, multiConfig.apps);
45
+ if (!resolvedApp && multiConfig.defaultApp) {
46
+ return multiConfig.apps[multiConfig.defaultApp];
47
+ }
48
+ return resolvedApp;
49
+ }, [multiConfig]);
50
+ const eventBus = (0, react_1.useMemo)(() => {
51
+ if (!currentApp)
52
+ return null;
53
+ return new framebus_1.default({
54
+ channel: (0, app_shared_1.generateBusChannelValue)({
55
+ basePath: currentApp.basePath,
56
+ channel: getChannelValue()
57
+ })
58
+ });
59
+ }, [currentApp]);
60
+ (0, react_1.useEffect)(() => {
61
+ validateConfig(multiConfig);
62
+ Object.values(multiConfig.apps).forEach(app => {
63
+ new framebus_1.default({
64
+ channel: (0, app_shared_1.generateBusChannelValue)({
65
+ basePath: app.basePath,
66
+ channel: getChannelValue()
67
+ })
68
+ }).emit(app_shared_1.EVENTS.SET_CONFIG, {
69
+ config: app.config
70
+ });
71
+ });
72
+ }, [multiConfig]);
73
+ if (!currentApp) {
74
+ // Handle 404 scenario
75
+ return react_1.default.createElement("div", null, exports.APP_NOT_FOUND_CONTENT);
76
+ }
77
+ // Wrap with original AppClientProvider using resolved config
78
+ return (react_1.default.createElement(app_client_provider_1.AppClientProvider, { config: currentApp.config, eventBus: eventBus || undefined }, children));
79
+ }
@@ -0,0 +1,11 @@
1
+ import type { FullpageApplicationConfig, PluginApplicationConfig, RegisteredAppType } from '@akinon/app-shared';
2
+ export interface AppConfig {
3
+ basePath: string;
4
+ config: FullpageApplicationConfig | PluginApplicationConfig;
5
+ type: RegisteredAppType;
6
+ }
7
+ export interface MultiAppConfig {
8
+ apps: Record<string, AppConfig>;
9
+ defaultApp?: string;
10
+ }
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,yBAAyB,GAAG,uBAAuB,CAAC;IAC5D,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ import type { ShellNavigationPayload } from '@akinon/app-shared';
2
+ import type { MultiAppConfig } from '../types';
3
+ export interface ResolveNavigationOptions {
4
+ multiConfig: MultiAppConfig;
5
+ currentBasePath: string;
6
+ }
7
+ export declare function resolveNavigationPath(payload: ShellNavigationPayload, options: ResolveNavigationOptions): ShellNavigationPayload;
8
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/utils/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,cAAc,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,wBAAwB,0BAuBlC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveNavigationPath = resolveNavigationPath;
4
+ function resolveNavigationPath(payload, options) {
5
+ const { path, external } = payload;
6
+ const { multiConfig, currentBasePath } = options;
7
+ // If it's an external path or absolute URL, return as is
8
+ if (external || path.startsWith('http')) {
9
+ return payload;
10
+ }
11
+ // If path starts with any registered app's basePath, use it as is
12
+ const matchingApp = Object.values(multiConfig.apps).find(app => path.startsWith(app.basePath));
13
+ if (matchingApp) {
14
+ return payload;
15
+ }
16
+ // Otherwise, prepend current app's basePath to relative paths
17
+ return Object.assign(Object.assign({}, payload), { path: `${currentBasePath}${path.startsWith('/') ? path : `/${path}`}` });
18
+ }