@akinon/app-client 0.14.0 → 1.0.1

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 +126 -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 +126 -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 +5 -5
@@ -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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"}
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { DEFAULT_ACTION_KEYS, EVENTS } from '@akinon/app-shared';
10
+ import { CHANNEL_URL_PARAM_NAME, DEFAULT_ACTION_KEYS, EVENTS, generateBusChannelValue } from '@akinon/app-shared';
11
11
  import Framebus from 'framebus';
12
12
  import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
13
13
  /**
@@ -36,138 +36,44 @@ const AppClientContext = createContext(defaultContextState);
36
36
  * @returns {AppClientContextState} The current context state.
37
37
  */
38
38
  const useAppClient = () => useContext(AppClientContext);
39
- /**
40
- * Navigates to the specified path within the application.
41
- * This method communicates with the AppShell to perform the navigation.
42
- */
43
- const navigate = ({ id, path, external }) => {
44
- const bus = new Framebus();
45
- bus.emit(EVENTS.NAVIGATE, { id, path, external });
46
- };
47
- /**
48
- * Shows a modal dialog with the specified title and content.
49
- * This method communicates with the AppShell to display the modal dialog.
50
- *
51
- * @returns {boolean} - Indicates if the dialog was successfully shown.
52
- */
53
- const showModalDialog = ({ title, content, onConfirm, onCancel }) => {
54
- const bus = new Framebus();
55
- function handleActionConfirmed() {
56
- bus.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
57
- bus.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
58
- onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
59
- }
60
- function handleActionCanceled() {
61
- bus.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
62
- bus.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
63
- onCancel === null || onCancel === void 0 ? void 0 : onCancel();
64
- }
65
- if (onConfirm) {
66
- bus.on(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
67
- }
68
- if (onCancel) {
69
- bus.on(EVENTS.ACTION_CANCELED, handleActionCanceled);
70
- }
71
- return bus.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
72
- actionKey: DEFAULT_ACTION_KEYS.showModalDialog,
73
- args: [title, content]
74
- });
75
- };
76
- /**
77
- * Shows a confirmation dialog with the specified title and content and can handle the user's input.
78
- * This method communicates with the AppShell to display the confirmation dialog.
79
- *
80
- * @returns {boolean} - Indicates if the dialog was successfully shown.
81
- */
82
- const showConfirmationDialog = ({ title, content, onConfirm, onCancel }) => {
83
- const bus = new Framebus();
84
- function handleActionConfirmed(data) {
85
- bus.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
86
- bus.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
87
- onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(data);
88
- }
89
- function handleActionCanceled() {
90
- bus.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
91
- bus.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
92
- onCancel === null || onCancel === void 0 ? void 0 : onCancel();
93
- }
94
- if (onConfirm) {
95
- bus.on(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
96
- }
97
- if (onCancel) {
98
- bus.on(EVENTS.ACTION_CANCELED, handleActionCanceled);
99
- }
100
- return bus.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
101
- actionKey: DEFAULT_ACTION_KEYS.showConfirmationDialog,
102
- args: [title, content]
103
- });
104
- };
105
- /**
106
- * Displays a toast message with the specified content and type.
107
- * This method communicates with the AppShell to display the toast message.
108
- *
109
- * @param {string} content - The content of the toast message.
110
- * @param {ApplicationToastType} type - The type of the toast message.
111
- * @returns {boolean} - Indicates if the event was emitted successfully.
112
- */
113
- const showToast = (content, type) => {
114
- const bus = new Framebus();
115
- return bus.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
116
- actionKey: DEFAULT_ACTION_KEYS.showToast,
117
- args: [content, type]
118
- });
119
- };
120
- /**
121
- * Displays an error message dialog with the specified title and content.
122
- * This method communicates with the AppShell to display the error message.
123
- *
124
- * @param {string} title - The title of the error message dialog.
125
- * @param {string} content - The content of the error message dialog.
126
- * @returns {boolean} - Indicates if the event was emitted successfully.
127
- */
128
- const showErrorMessage = (title, content) => {
129
- const bus = new Framebus();
130
- return bus.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
131
- actionKey: DEFAULT_ACTION_KEYS.showErrorMessage,
132
- args: [title, content]
133
- });
134
- };
135
- /**
136
- * Displays a rich-contentful modal with the specified path and context.
137
- * This method communicateswith the AppShell to display the complicated modals.
138
- *
139
- * @param {string} path - The path of displayed iframe.
140
- * @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
141
- * @param {Object} size - Optional size of the modal.
142
- * @param {number | string} size.maxWidth - The maximum width of the modal. If falsish, the modal will be max 540px wide.
143
- * @param {number | string} size.maxHeight - The maximum height of the modal. If falsish, the modal will be max 360px high.
144
- * @param {string} closeIconColor - Optional color of the close icon.
145
- */
146
- const showRichModal = (path, context, size, closeIconColor) => {
147
- const bus = new Framebus();
148
- return bus.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
149
- actionKey: DEFAULT_ACTION_KEYS.showRichModal,
150
- args: [path, context, size, closeIconColor]
151
- });
152
- };
39
+ let contextValue;
153
40
  /**
154
41
  * Component providing the context for AppClient. It initializes communication
155
42
  * with the AppShell and provides methods for action invocation and navigation.
156
43
  *
157
44
  * @param {AppClientProviderProps} props - The props for the AppClientProvider component.
158
45
  */
159
- const AppClientProvider = ({ children, config }) => {
46
+ const AppClientProvider = ({ children, config, eventBus }) => {
160
47
  const [, setAppId] = useState(undefined);
161
48
  const [data, setData] = useState(undefined);
49
+ const [modalContext, setModalContext] = useState(undefined);
162
50
  const [params, setParams] = useState(undefined);
163
51
  const [isLoading, setIsLoading] = useState(true);
164
52
  const [locale, setLocale] = useState('en');
165
53
  const localeChangeCallbacks = useRef([]);
166
- // Function to invoke an action in the AppShell
167
- const invokeAction = (actionKey, ...args) => {
54
+ // Create a ref to store the bus instance
55
+ const busRef = useRef();
56
+ // Initialize bus on mount
57
+ useEffect(() => {
58
+ busRef.current =
59
+ eventBus ||
60
+ (() => {
61
+ const url = new URL(window.location.href);
62
+ const channel = url.searchParams.get(CHANNEL_URL_PARAM_NAME);
63
+ return new Framebus({
64
+ channel: generateBusChannelValue({ channel })
65
+ });
66
+ })();
67
+ // Send config data to AppShell
68
+ busRef.current.emit(EVENTS.SET_CONFIG, { config });
69
+ }, [eventBus, config]);
70
+ // Update standalone functions to use shared bus
71
+ const invokeAction = useCallback((actionKey, ...args) => {
72
+ if (!busRef.current)
73
+ return Promise.reject('Bus not initialized');
168
74
  return new Promise((resolve, reject) => {
169
- const bus = new Framebus();
170
- bus.emit(EVENTS.INVOKE_ACTION, { actionKey, args }, (response) => {
75
+ var _a;
76
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(EVENTS.INVOKE_ACTION, { actionKey, args }, (response) => {
171
77
  if (typeof response === 'object' && response !== null) {
172
78
  const typedResponse = response;
173
79
  if (typedResponse.success) {
@@ -182,33 +88,27 @@ const AppClientProvider = ({ children, config }) => {
182
88
  }
183
89
  });
184
90
  });
185
- };
91
+ }, []);
186
92
  const isFullPageApplication = () => {
187
93
  const castedConfig = config;
188
94
  return Array.isArray(castedConfig.menu);
189
95
  };
190
96
  useEffect(() => {
191
- // In production, enforce running in an iframe if forceRedirect is true
97
+ var _a, _b, _c, _d, _e, _f, _g;
192
98
  if (config.forceRedirect && !config.isDev && window.self === window.top) {
193
99
  console.error('This app must be run inside an iframe when forceRedirect is true.');
194
100
  return;
195
101
  }
196
- // Get c from the url of the iframe containing the application.
197
- const url = new URL(window.location.href);
198
- const channel = url.searchParams.get('c');
199
- const bus = channel ? new Framebus({ channel }) : new Framebus();
200
- // Send data from the AppShell upon initialization.
201
- // Pass apps config data.
202
- bus.emit(EVENTS.SET_CONFIG, { config });
203
- bus.on(EVENTS.SET_DATA, (receivedData) => {
102
+ // Set up event listeners
103
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(EVENTS.SET_DATA, (receivedData) => {
204
104
  setData(prevState => (Object.assign(Object.assign({}, prevState), receivedData)));
205
105
  setIsLoading(false);
206
106
  });
207
- bus.on(EVENTS.SET_PARAMS, message => {
107
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(EVENTS.SET_PARAMS, message => {
208
108
  const passedParams = message;
209
109
  setParams(passedParams);
210
110
  });
211
- bus.on(EVENTS.SET_APP_ID, data => {
111
+ (_c = busRef.current) === null || _c === void 0 ? void 0 : _c.on(EVENTS.SET_APP_ID, data => {
212
112
  const { appId } = data;
213
113
  setAppId(appId);
214
114
  });
@@ -216,14 +116,14 @@ const AppClientProvider = ({ children, config }) => {
216
116
  // Get computed height of iframe element.
217
117
  const height = document.getElementsByTagName('html')[0].offsetHeight;
218
118
  if (height > 0 && window.name) {
219
- bus.emit(EVENTS.SET_HEIGHT, { height, id: window.name });
119
+ (_d = busRef.current) === null || _d === void 0 ? void 0 : _d.emit(EVENTS.SET_HEIGHT, { height, id: window.name });
220
120
  }
221
121
  }
222
122
  // Only listen to navigation events if application type is
223
123
  // fullpage. plugin type apps should not have the ability to navigate.
224
124
  if (isFullPageApplication()) {
225
125
  // Listen for navigation events.
226
- bus.on(EVENTS.NAVIGATE_CHILD, message => {
126
+ (_e = busRef.current) === null || _e === void 0 ? void 0 : _e.on(EVENTS.NAVIGATE_CHILD, message => {
227
127
  const { path } = message;
228
128
  const { navigation } = config;
229
129
  if (navigation) {
@@ -231,37 +131,117 @@ const AppClientProvider = ({ children, config }) => {
231
131
  navigate({ path });
232
132
  }
233
133
  });
134
+ // Listen for modal context changes
135
+ (_f = busRef.current) === null || _f === void 0 ? void 0 : _f.on(EVENTS.SET_MODAL_CONTEXT, modalContext => {
136
+ setModalContext(modalContext);
137
+ });
234
138
  }
235
139
  // Listen for locale changes
236
- bus.on(EVENTS.LOCALE_CHANGED, message => {
140
+ (_g = busRef.current) === null || _g === void 0 ? void 0 : _g.on(EVENTS.LOCALE_CHANGED, message => {
237
141
  const { locale: newLocale } = message;
238
142
  setLocale(newLocale);
239
143
  localeChangeCallbacks.current.forEach(callback => callback(newLocale));
240
144
  });
241
145
  return () => {
242
- bus.teardown();
146
+ var _a;
147
+ if (!eventBus) {
148
+ // Only teardown if we created the bus
149
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.teardown();
150
+ }
243
151
  };
244
- }, [config]);
152
+ }, [config, eventBus]);
245
153
  const onLocaleChange = useCallback((callback) => {
246
154
  localeChangeCallbacks.current.push(callback);
247
155
  return () => {
248
156
  localeChangeCallbacks.current = localeChangeCallbacks.current.filter(cb => cb !== callback);
249
157
  };
250
158
  }, []);
251
- const contextValue = {
159
+ contextValue = {
252
160
  data,
253
161
  params,
254
162
  isLoading,
255
163
  invokeAction,
256
- navigate,
257
- showModalDialog,
258
- showConfirmationDialog,
259
- showToast,
260
- showErrorMessage,
261
- showRichModal,
164
+ navigate: useCallback((payload) => {
165
+ var _a;
166
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(EVENTS.NAVIGATE, Object.assign({}, payload));
167
+ }, []),
168
+ showModalDialog: useCallback((options) => {
169
+ var _a, _b, _c;
170
+ const { title, content, onConfirm, onCancel } = options;
171
+ function handleActionConfirmed() {
172
+ var _a, _b;
173
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
174
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
175
+ onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
176
+ }
177
+ function handleActionCanceled() {
178
+ var _a, _b;
179
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
180
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
181
+ onCancel === null || onCancel === void 0 ? void 0 : onCancel();
182
+ }
183
+ if (onConfirm) {
184
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
185
+ }
186
+ if (onCancel) {
187
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(EVENTS.ACTION_CANCELED, handleActionCanceled);
188
+ }
189
+ return !!((_c = busRef.current) === null || _c === void 0 ? void 0 : _c.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
190
+ actionKey: DEFAULT_ACTION_KEYS.showModalDialog,
191
+ args: [title, content]
192
+ }));
193
+ }, []),
194
+ showConfirmationDialog: useCallback((options) => {
195
+ var _a, _b, _c;
196
+ const { title, content, onConfirm, onCancel } = options;
197
+ function handleActionConfirmed(data) {
198
+ var _a, _b;
199
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
200
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
201
+ onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(data);
202
+ }
203
+ function handleActionCanceled() {
204
+ var _a, _b;
205
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.off(EVENTS.ACTION_CANCELED, handleActionCanceled);
206
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.off(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
207
+ onCancel === null || onCancel === void 0 ? void 0 : onCancel();
208
+ }
209
+ if (onConfirm) {
210
+ (_a = busRef.current) === null || _a === void 0 ? void 0 : _a.on(EVENTS.ACTION_CONFIRMED, handleActionConfirmed);
211
+ }
212
+ if (onCancel) {
213
+ (_b = busRef.current) === null || _b === void 0 ? void 0 : _b.on(EVENTS.ACTION_CANCELED, handleActionCanceled);
214
+ }
215
+ return !!((_c = busRef.current) === null || _c === void 0 ? void 0 : _c.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
216
+ actionKey: DEFAULT_ACTION_KEYS.showConfirmationDialog,
217
+ args: [title, content]
218
+ }));
219
+ }, []),
220
+ showToast: useCallback((content, type) => {
221
+ var _a;
222
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
223
+ actionKey: DEFAULT_ACTION_KEYS.showToast,
224
+ args: [content, type]
225
+ }));
226
+ }, []),
227
+ showErrorMessage: useCallback((title, content) => {
228
+ var _a;
229
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
230
+ actionKey: DEFAULT_ACTION_KEYS.showErrorMessage,
231
+ args: [title, content]
232
+ }));
233
+ }, []),
234
+ showRichModal: useCallback((path, context, size, closeIconColor) => {
235
+ var _a;
236
+ return !!((_a = busRef.current) === null || _a === void 0 ? void 0 : _a.emit(EVENTS.INVOKE_DEFAULT_ACTION, {
237
+ actionKey: DEFAULT_ACTION_KEYS.showRichModal,
238
+ args: [path, context, size, closeIconColor]
239
+ }));
240
+ }, []),
262
241
  locale,
263
- onLocaleChange
242
+ onLocaleChange,
243
+ modalContext
264
244
  };
265
245
  return (React.createElement(AppClientContext.Provider, { value: contextValue }, children));
266
246
  };
267
- export { AppClientProvider, useAppClient };
247
+ export { AppClientProvider, contextValue, defaultContextState, useAppClient };
@@ -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,16 @@
1
+ import { Component } from 'react';
2
+ export class ErrorBoundary extends Component {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.state = { hasError: false };
6
+ }
7
+ static getDerivedStateFromError() {
8
+ return { hasError: true };
9
+ }
10
+ render() {
11
+ if (this.state.hasError) {
12
+ return this.props.fallback;
13
+ }
14
+ return this.props.children;
15
+ }
16
+ }
@@ -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/esm/index.js CHANGED
@@ -1 +1,3 @@
1
1
  export * from './app-client-provider';
2
+ export * from './multi-app-provider';
3
+ export * from './types';
@@ -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,75 @@
1
+ import { CHANNEL_URL_PARAM_NAME, EVENTS, generateBusChannelValue } from '@akinon/app-shared';
2
+ import Framebus from 'framebus';
3
+ import React, { useEffect, useMemo } from 'react';
4
+ import { AppClientProvider } from './app-client-provider';
5
+ import { ErrorBoundary } from './components/error-boundary';
6
+ function resolveCurrentApp(pathname, apps) {
7
+ // Find the app with the longest matching basePath
8
+ const matchingApp = Object.entries(apps)
9
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
+ .filter(([_, app]) => pathname.startsWith(app.basePath))
11
+ .sort((a, b) => b[1].basePath.length - a[1].basePath.length)[0];
12
+ return matchingApp === null || matchingApp === void 0 ? void 0 : matchingApp[1];
13
+ }
14
+ export function MultiAppProvider({ children, multiConfig }) {
15
+ return (React.createElement(ErrorBoundary, { fallback: React.createElement("div", null, "Error loading application") },
16
+ React.createElement(MultiAppProviderInner, { multiConfig: multiConfig }, children)));
17
+ }
18
+ function validateConfig(config) {
19
+ if (!config.apps || Object.keys(config.apps).length === 0) {
20
+ throw new Error('MultiAppConfig must contain at least one app');
21
+ }
22
+ Object.entries(config.apps).forEach(([key, app]) => {
23
+ if (!app.basePath) {
24
+ throw new Error(`App "${key}" must have a basePath`);
25
+ }
26
+ if (app.type === 'plugin' &&
27
+ !app.config.placeholderId) {
28
+ throw new Error(`Plugin app "${key}" must have a placeholderId`);
29
+ }
30
+ });
31
+ }
32
+ const getChannelValue = () => {
33
+ const url = new URL(window.location.href);
34
+ return url.searchParams.get(CHANNEL_URL_PARAM_NAME);
35
+ };
36
+ export const APP_NOT_FOUND_CONTENT = 'App not found';
37
+ function MultiAppProviderInner({ children, multiConfig }) {
38
+ const currentApp = useMemo(() => {
39
+ const pathname = window.location.pathname;
40
+ const resolvedApp = resolveCurrentApp(pathname, multiConfig.apps);
41
+ if (!resolvedApp && multiConfig.defaultApp) {
42
+ return multiConfig.apps[multiConfig.defaultApp];
43
+ }
44
+ return resolvedApp;
45
+ }, [multiConfig]);
46
+ const eventBus = useMemo(() => {
47
+ if (!currentApp)
48
+ return null;
49
+ return new Framebus({
50
+ channel: generateBusChannelValue({
51
+ basePath: currentApp.basePath,
52
+ channel: getChannelValue()
53
+ })
54
+ });
55
+ }, [currentApp]);
56
+ useEffect(() => {
57
+ validateConfig(multiConfig);
58
+ Object.values(multiConfig.apps).forEach(app => {
59
+ new Framebus({
60
+ channel: generateBusChannelValue({
61
+ basePath: app.basePath,
62
+ channel: getChannelValue()
63
+ })
64
+ }).emit(EVENTS.SET_CONFIG, {
65
+ config: app.config
66
+ });
67
+ });
68
+ }, [multiConfig]);
69
+ if (!currentApp) {
70
+ // Handle 404 scenario
71
+ return React.createElement("div", null, APP_NOT_FOUND_CONTENT);
72
+ }
73
+ // Wrap with original AppClientProvider using resolved config
74
+ return (React.createElement(AppClientProvider, { config: currentApp.config, eventBus: eventBus || undefined }, children));
75
+ }
@@ -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 @@
1
+ export {};
@@ -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,15 @@
1
+ export function resolveNavigationPath(payload, options) {
2
+ const { path, external } = payload;
3
+ const { multiConfig, currentBasePath } = options;
4
+ // If it's an external path or absolute URL, return as is
5
+ if (external || path.startsWith('http')) {
6
+ return payload;
7
+ }
8
+ // If path starts with any registered app's basePath, use it as is
9
+ const matchingApp = Object.values(multiConfig.apps).find(app => path.startsWith(app.basePath));
10
+ if (matchingApp) {
11
+ return payload;
12
+ }
13
+ // Otherwise, prepend current app's basePath to relative paths
14
+ return Object.assign(Object.assign({}, payload), { path: `${currentBasePath}${path.startsWith('/') ? path : `/${path}`}` });
15
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/app-client",
3
3
  "description": "Akinon AppClient library. This library is used to create a new plugin or an application which will reside in Akinon's applications.",
4
- "version": "0.14.0",
4
+ "version": "1.0.1",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "dist/esm/index.js",
@@ -10,15 +10,15 @@
10
10
  "dist"
11
11
  ],
12
12
  "dependencies": {
13
- "framebus": "^6.0.1",
14
- "@akinon/app-shared": "0.15.0"
13
+ "framebus": "^6.0.2",
14
+ "@akinon/app-shared": "1.0.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "clean-package": "2.2.0",
18
18
  "copyfiles": "^2.4.1",
19
19
  "rimraf": "^5.0.5",
20
- "typescript": "^5.2.2",
21
- "@akinon/typescript-config": "0.4.0"
20
+ "typescript": "*",
21
+ "@akinon/typescript-config": "1.0.1"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": ">=18",