@squide/firefly 9.3.1 → 9.3.3
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.
- package/CHANGELOG.md +19 -0
- package/dist/AppRouter.d.ts +8 -12
- package/dist/AppRouter.js +68 -8
- package/dist/AppRouter.js.map +1 -0
- package/dist/AppRouterContext.d.ts +5 -9
- package/dist/AppRouterContext.js +30 -1
- package/dist/AppRouterContext.js.map +1 -0
- package/dist/AppRouterReducer.d.ts +23 -26
- package/dist/AppRouterReducer.js +339 -4
- package/dist/AppRouterReducer.js.map +1 -0
- package/dist/FireflyRuntime.d.ts +5 -8
- package/dist/FireflyRuntime.js +66 -5
- package/dist/FireflyRuntime.js.map +1 -0
- package/dist/GlobalDataQueriesError.d.ts +2 -4
- package/dist/GlobalDataQueriesError.js +19 -1
- package/dist/GlobalDataQueriesError.js.map +1 -0
- package/dist/RootRoute.d.ts +1 -5
- package/dist/RootRoute.js +48 -3
- package/dist/RootRoute.js.map +1 -0
- package/dist/boostrap.d.ts +8 -13
- package/dist/boostrap.js +54 -1
- package/dist/boostrap.js.map +1 -0
- package/dist/index.d.ts +23 -28
- package/dist/index.js +51 -25
- package/dist/index.js.map +1 -0
- package/dist/useCanFetchProtectedData.d.ts +1 -3
- package/dist/useCanFetchProtectedData.js +17 -2
- package/dist/useCanFetchProtectedData.js.map +1 -0
- package/dist/useCanFetchPublicData.d.ts +1 -3
- package/dist/useCanFetchPublicData.js +17 -2
- package/dist/useCanFetchPublicData.js.map +1 -0
- package/dist/useCanRegisterDeferredRegistrations.d.ts +1 -3
- package/dist/useCanRegisterDeferredRegistrations.js +14 -2
- package/dist/useCanRegisterDeferredRegistrations.js.map +1 -0
- package/dist/useCanUpdateDeferredRegistrations.d.ts +1 -3
- package/dist/useCanUpdateDeferredRegistrations.js +16 -2
- package/dist/useCanUpdateDeferredRegistrations.js.map +1 -0
- package/dist/useDeferredRegistrations.d.ts +5 -8
- package/dist/useDeferredRegistrations.js +74 -6
- package/dist/useDeferredRegistrations.js.map +1 -0
- package/dist/useExecuteOnce.d.ts +1 -3
- package/dist/useExecuteOnce.js +29 -1
- package/dist/useExecuteOnce.js.map +1 -0
- package/dist/useIsActiveRouteProtected.d.ts +1 -3
- package/dist/useIsActiveRouteProtected.js +23 -1
- package/dist/useIsActiveRouteProtected.js.map +1 -0
- package/dist/useIsBootstrapping.d.ts +3 -7
- package/dist/useIsBootstrapping.js +22 -2
- package/dist/useIsBootstrapping.js.map +1 -0
- package/dist/useNavigationItems.d.ts +3 -7
- package/dist/useNavigationItems.js +26 -2
- package/dist/useNavigationItems.js.map +1 -0
- package/dist/useProtectedDataQueries.d.ts +6 -8
- package/dist/useProtectedDataQueries.js +126 -5
- package/dist/useProtectedDataQueries.js.map +1 -0
- package/dist/usePublicDataQueries.d.ts +5 -7
- package/dist/usePublicDataQueries.js +111 -5
- package/dist/usePublicDataQueries.js.map +1 -0
- package/dist/useRegisterDeferredRegistrations.d.ts +4 -9
- package/dist/useRegisterDeferredRegistrations.js +19 -1
- package/dist/useRegisterDeferredRegistrations.js.map +1 -0
- package/dist/useStrictRegistrationMode.d.ts +1 -3
- package/dist/useStrictRegistrationMode.js +40 -1
- package/dist/useStrictRegistrationMode.js.map +1 -0
- package/dist/useUpdateDeferredRegistrations.d.ts +4 -9
- package/dist/useUpdateDeferredRegistrations.js +30 -2
- package/dist/useUpdateDeferredRegistrations.js.map +1 -0
- package/package.json +29 -24
- package/src/AppRouter.tsx +63 -0
- package/src/AppRouterContext.ts +27 -0
- package/src/AppRouterReducer.ts +363 -0
- package/src/FireflyRuntime.tsx +71 -0
- package/src/GlobalDataQueriesError.ts +17 -0
- package/src/RootRoute.tsx +25 -0
- package/src/boostrap.ts +62 -0
- package/src/index.ts +28 -0
- package/src/useCanFetchProtectedData.ts +26 -0
- package/src/useCanFetchPublicData.ts +23 -0
- package/src/useCanRegisterDeferredRegistrations.ts +24 -0
- package/src/useCanUpdateDeferredRegistrations.ts +23 -0
- package/src/useDeferredRegistrations.ts +59 -0
- package/src/useExecuteOnce.ts +23 -0
- package/src/useIsActiveRouteProtected.ts +12 -0
- package/src/useIsBootstrapping.ts +45 -0
- package/src/useNavigationItems.ts +18 -0
- package/src/useProtectedDataQueries.ts +101 -0
- package/src/usePublicDataQueries.ts +88 -0
- package/src/useRegisterDeferredRegistrations.ts +9 -0
- package/src/useStrictRegistrationMode.ts +28 -0
- package/src/useUpdateDeferredRegistrations.ts +16 -0
- package/dist/chunk-4RUCDAUT.js +0 -18
- package/dist/chunk-BFHHJOJT.js +0 -41
- package/dist/chunk-CTLPFYLM.js +0 -65
- package/dist/chunk-G32YX2KR.js +0 -22
- package/dist/chunk-GXSW4CZS.js +0 -9
- package/dist/chunk-H2ILEMPE.js +0 -28
- package/dist/chunk-HE5HKFL3.js +0 -16
- package/dist/chunk-IOMSOUAL.js +0 -20
- package/dist/chunk-JFMSLZ74.js +0 -253
- package/dist/chunk-L44KFU57.js +0 -34
- package/dist/chunk-LC233OPR.js +0 -54
- package/dist/chunk-MKTGJHQR.js +0 -71
- package/dist/chunk-N2GOIQ5E.js +0 -14
- package/dist/chunk-PMVN3U47.js +0 -21
- package/dist/chunk-PP3MRMJJ.js +0 -11
- package/dist/chunk-ROE2YHN5.js +0 -44
- package/dist/chunk-TURKCH7J.js +0 -20
- package/dist/chunk-WOPD33CM.js +0 -25
- package/dist/chunk-WVRMJZV6.js +0 -18
- package/dist/chunk-XDWYPVAJ.js +0 -21
- package/dist/chunk-YRWFYWK4.js +0 -11
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { addLocalModuleRegistrationStatusChangedListener, getLocalModuleRegistrationStatus, removeLocalModuleRegistrationStatusChangedListener, useEventBus, useLogger } from "@squide/core";
|
|
2
|
+
import { addRemoteModuleRegistrationStatusChangedListener, areModulesReady, areModulesRegistered, getRemoteModuleRegistrationStatus, removeRemoteModuleRegistrationStatusChangedListener } from "@squide/module-federation";
|
|
3
|
+
import { addMswStateChangedListener, isMswReady, removeMswStateChangedListener } from "@squide/msw";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useReducer, type Dispatch } from "react";
|
|
5
|
+
import { useExecuteOnce } from "./useExecuteOnce.ts";
|
|
6
|
+
import { isApplicationBootstrapping } from "./useIsBootstrapping.ts";
|
|
7
|
+
|
|
8
|
+
export type ActiveRouteVisiblity = "unknown" | "public" | "protected";
|
|
9
|
+
|
|
10
|
+
export interface AppRouterState {
|
|
11
|
+
waitForMsw: boolean;
|
|
12
|
+
waitForPublicData: boolean;
|
|
13
|
+
waitForProtectedData: boolean;
|
|
14
|
+
areModulesRegistered: boolean;
|
|
15
|
+
areModulesReady: boolean;
|
|
16
|
+
isMswReady: boolean;
|
|
17
|
+
isPublicDataReady: boolean;
|
|
18
|
+
isProtectedDataReady: boolean;
|
|
19
|
+
publicDataUpdatedAt?: number;
|
|
20
|
+
protectedDataUpdatedAt?: number;
|
|
21
|
+
deferredRegistrationsUpdatedAt?: number;
|
|
22
|
+
activeRouteVisibility: ActiveRouteVisiblity;
|
|
23
|
+
isUnauthorized: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AppRouterActionType =
|
|
27
|
+
| "modules-registered"
|
|
28
|
+
| "modules-ready"
|
|
29
|
+
| "msw-ready"
|
|
30
|
+
| "public-data-ready"
|
|
31
|
+
| "protected-data-ready"
|
|
32
|
+
| "public-data-updated"
|
|
33
|
+
| "protected-data-updated"
|
|
34
|
+
| "deferred-registrations-updated"
|
|
35
|
+
| "active-route-is-public"
|
|
36
|
+
| "active-route-is-protected"
|
|
37
|
+
| "is-unauthorized";
|
|
38
|
+
|
|
39
|
+
// The followings event const are a concatenation of "squide-" with AppRouterActionType.
|
|
40
|
+
// They are dispatched by the useEnhancedReducerDispatch hook.
|
|
41
|
+
export const ModulesRegisteredEvent = "squide-modules-registered";
|
|
42
|
+
export const ModulesReadyEvent = "squide-modules-ready";
|
|
43
|
+
export const MswReadyEvent = "squide-msw-ready";
|
|
44
|
+
export const PublicDataReadyEvent = "squide-public-data-ready";
|
|
45
|
+
export const ProtectedDataReadyEvent = "squide-protected-data-ready";
|
|
46
|
+
export const PublicDataUpdatedEvent = "squide-public-data-updated";
|
|
47
|
+
export const ProtectedDataUpdatedEvent = "squide-protected-data-updated";
|
|
48
|
+
export const DeferredRegistrationsUpdatedEvent = "squide-deferred-registrations-updated";
|
|
49
|
+
export const ApplicationBoostrappedEvent = "squide-app-boostrapped";
|
|
50
|
+
|
|
51
|
+
export interface AppRouterAction {
|
|
52
|
+
type: AppRouterActionType;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type AppRouterDispatch = Dispatch<AppRouterAction>;
|
|
56
|
+
|
|
57
|
+
function reducer(state: AppRouterState, action: AppRouterAction) {
|
|
58
|
+
let newState = state;
|
|
59
|
+
|
|
60
|
+
switch (action.type) {
|
|
61
|
+
case "modules-registered": {
|
|
62
|
+
newState = {
|
|
63
|
+
...newState,
|
|
64
|
+
areModulesRegistered: true
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "modules-ready": {
|
|
70
|
+
newState = {
|
|
71
|
+
...newState,
|
|
72
|
+
areModulesReady: true,
|
|
73
|
+
// Will be set even if the app is not using deferred registrations.
|
|
74
|
+
deferredRegistrationsUpdatedAt: Date.now()
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "msw-ready": {
|
|
80
|
+
newState = {
|
|
81
|
+
...newState,
|
|
82
|
+
isMswReady: true
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "public-data-ready": {
|
|
88
|
+
newState = {
|
|
89
|
+
...newState,
|
|
90
|
+
isPublicDataReady: true,
|
|
91
|
+
publicDataUpdatedAt: Date.now()
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "protected-data-ready": {
|
|
97
|
+
newState = {
|
|
98
|
+
...newState,
|
|
99
|
+
isProtectedDataReady: true,
|
|
100
|
+
protectedDataUpdatedAt: Date.now()
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "public-data-updated": {
|
|
106
|
+
newState = {
|
|
107
|
+
...newState,
|
|
108
|
+
publicDataUpdatedAt: Date.now()
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "protected-data-updated": {
|
|
114
|
+
newState = {
|
|
115
|
+
...newState,
|
|
116
|
+
protectedDataUpdatedAt: Date.now()
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "deferred-registrations-updated": {
|
|
122
|
+
newState = {
|
|
123
|
+
...newState,
|
|
124
|
+
deferredRegistrationsUpdatedAt: Date.now()
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "active-route-is-public": {
|
|
130
|
+
newState = {
|
|
131
|
+
...newState,
|
|
132
|
+
activeRouteVisibility: "public"
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "active-route-is-protected": {
|
|
138
|
+
newState = {
|
|
139
|
+
...newState,
|
|
140
|
+
activeRouteVisibility: "protected"
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "is-unauthorized": {
|
|
146
|
+
newState = {
|
|
147
|
+
...newState,
|
|
148
|
+
isUnauthorized: true
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
default: {
|
|
154
|
+
throw new Error(`[squide] The AppRouter component state reducer doesn't support action type "${action.type}".`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return newState;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function getAreModulesRegistered() {
|
|
162
|
+
const localModuleStatus = getLocalModuleRegistrationStatus();
|
|
163
|
+
const remoteModuleStatus = getRemoteModuleRegistrationStatus();
|
|
164
|
+
|
|
165
|
+
return areModulesRegistered(localModuleStatus, remoteModuleStatus);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getAreModulesReady() {
|
|
169
|
+
const localModuleStatus = getLocalModuleRegistrationStatus();
|
|
170
|
+
const remoteModuleStatus = getRemoteModuleRegistrationStatus();
|
|
171
|
+
|
|
172
|
+
return areModulesReady(localModuleStatus, remoteModuleStatus);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function useModuleRegistrationStatusDispatcher(areModulesRegisteredValue: boolean, areModulesReadyValue: boolean, dispatch: AppRouterDispatch) {
|
|
176
|
+
const logger = useLogger();
|
|
177
|
+
|
|
178
|
+
const dispatchModulesRegistered = useExecuteOnce(useCallback(() => {
|
|
179
|
+
if (getAreModulesRegistered()) {
|
|
180
|
+
dispatch({ type: "modules-registered" });
|
|
181
|
+
|
|
182
|
+
logger.debug("[squide] %cModules are registered%c.", "color: white; background-color: green;", "");
|
|
183
|
+
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return false;
|
|
188
|
+
}, [dispatch, logger]));
|
|
189
|
+
|
|
190
|
+
const dispatchModulesReady = useExecuteOnce(useCallback(() => {
|
|
191
|
+
if (getAreModulesReady()) {
|
|
192
|
+
dispatch({ type: "modules-ready" });
|
|
193
|
+
|
|
194
|
+
logger.debug("[squide] %cModules are ready%c.", "color: white; background-color: green;", "");
|
|
195
|
+
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return false;
|
|
200
|
+
}, [dispatch, logger]));
|
|
201
|
+
|
|
202
|
+
return useEffect(() => {
|
|
203
|
+
if (!areModulesRegisteredValue) {
|
|
204
|
+
addLocalModuleRegistrationStatusChangedListener(dispatchModulesRegistered);
|
|
205
|
+
addRemoteModuleRegistrationStatusChangedListener(dispatchModulesRegistered);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!areModulesReadyValue) {
|
|
209
|
+
addLocalModuleRegistrationStatusChangedListener(dispatchModulesReady);
|
|
210
|
+
addRemoteModuleRegistrationStatusChangedListener(dispatchModulesReady);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return () => {
|
|
214
|
+
removeLocalModuleRegistrationStatusChangedListener(dispatchModulesRegistered);
|
|
215
|
+
removeRemoteModuleRegistrationStatusChangedListener(dispatchModulesRegistered);
|
|
216
|
+
|
|
217
|
+
removeLocalModuleRegistrationStatusChangedListener(dispatchModulesReady);
|
|
218
|
+
removeRemoteModuleRegistrationStatusChangedListener(dispatchModulesReady);
|
|
219
|
+
};
|
|
220
|
+
}, [areModulesRegisteredValue, areModulesReadyValue, dispatchModulesRegistered, dispatchModulesReady]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function useMswStatusDispatcher(isMswReadyValue: boolean, dispatch: AppRouterDispatch) {
|
|
224
|
+
const logger = useLogger();
|
|
225
|
+
|
|
226
|
+
const dispatchMswReady = useExecuteOnce(useCallback(() => {
|
|
227
|
+
if (isMswReady()) {
|
|
228
|
+
dispatch({ type: "msw-ready" });
|
|
229
|
+
|
|
230
|
+
logger.debug("[squide] %cMSW is ready%c.", "color: white; background-color: green;", "");
|
|
231
|
+
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return false;
|
|
236
|
+
}, [dispatch, logger]));
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (!isMswReadyValue) {
|
|
240
|
+
addMswStateChangedListener(dispatchMswReady);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return () => {
|
|
244
|
+
removeMswStateChangedListener(dispatchMswReady);
|
|
245
|
+
};
|
|
246
|
+
}, [isMswReadyValue, dispatchMswReady]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function useBootstrappingCompletedDispatcher(state: AppRouterState) {
|
|
250
|
+
const eventBus = useEventBus();
|
|
251
|
+
|
|
252
|
+
const areModulesRegisteredValue = state.areModulesRegistered;
|
|
253
|
+
const isBoostrapping = isApplicationBootstrapping(state);
|
|
254
|
+
|
|
255
|
+
useExecuteOnce(useCallback(() => {
|
|
256
|
+
if (areModulesRegisteredValue && !isBoostrapping) {
|
|
257
|
+
eventBus.dispatch(ApplicationBoostrappedEvent);
|
|
258
|
+
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return false;
|
|
263
|
+
}, [areModulesRegisteredValue, isBoostrapping, eventBus]), true);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let dispatchProxyFactory: ((reactDispatch: AppRouterDispatch) => AppRouterDispatch) | undefined;
|
|
267
|
+
|
|
268
|
+
// This function should only be used by tests.
|
|
269
|
+
export function __setAppReducerDispatchProxyFactory(factory: (reactDispatch: AppRouterDispatch) => AppRouterDispatch) {
|
|
270
|
+
dispatchProxyFactory = factory;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// This function should only be used by tests.
|
|
274
|
+
export function __clearAppReducerDispatchProxy() {
|
|
275
|
+
dispatchProxyFactory = undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function useReducerDispatchProxy(reactDispatch: AppRouterDispatch) {
|
|
279
|
+
return useMemo(() => {
|
|
280
|
+
return dispatchProxyFactory ? dispatchProxyFactory(reactDispatch) : reactDispatch;
|
|
281
|
+
}, [reactDispatch]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function useEnhancedReducerDispatch(reducerDispatch: AppRouterDispatch) {
|
|
285
|
+
const logger = useLogger();
|
|
286
|
+
const eventBus = useEventBus();
|
|
287
|
+
|
|
288
|
+
return useCallback((action: AppRouterAction) => {
|
|
289
|
+
logger.debug("[squide] The following action has been dispatched to the AppRouter reducer:", action);
|
|
290
|
+
eventBus.dispatch(`squide-${action.type}`);
|
|
291
|
+
|
|
292
|
+
reducerDispatch(action);
|
|
293
|
+
}, [reducerDispatch, logger, eventBus]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function useAppRouterReducer(waitForMsw: boolean, waitForPublicData: boolean, waitForProtectedData: boolean): [AppRouterState, AppRouterDispatch] {
|
|
297
|
+
const areModulesInitiallyRegistered = getAreModulesRegistered();
|
|
298
|
+
const areModulesInitiallyReady = getAreModulesReady();
|
|
299
|
+
const isMswInitiallyReady = isMswReady();
|
|
300
|
+
|
|
301
|
+
const eventBus = useEventBus();
|
|
302
|
+
|
|
303
|
+
// When modules are initially registered, the reducer action will never be dispatched, therefore the event would not be dispatched as well.
|
|
304
|
+
// To ensure the bootstrapping events sequencing, the event is manually dispatched when the modules are initially registered.
|
|
305
|
+
useExecuteOnce(useCallback(() => {
|
|
306
|
+
if (areModulesInitiallyRegistered) {
|
|
307
|
+
eventBus.dispatch(ModulesRegisteredEvent);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return true;
|
|
311
|
+
}, [areModulesInitiallyRegistered, eventBus]), true);
|
|
312
|
+
|
|
313
|
+
// When modules are initially registered, the reducer action will never be dispatched, therefore the event would not be dispatched as well.
|
|
314
|
+
// To ensure the bootstrapping events sequencing, the event is manually dispatched when the modules are initially registered.
|
|
315
|
+
useExecuteOnce(useCallback(() => {
|
|
316
|
+
if (areModulesInitiallyReady) {
|
|
317
|
+
eventBus.dispatch(ModulesReadyEvent);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return true;
|
|
321
|
+
}, [areModulesInitiallyReady, eventBus]), true);
|
|
322
|
+
|
|
323
|
+
// When modules are initially registered, the reducer action will never be dispatched, therefore the event would not be dispatched as well.
|
|
324
|
+
// To ensure the bootstrapping events sequencing, the event is manually dispatched when the modules are initially registered.
|
|
325
|
+
useExecuteOnce(useCallback(() => {
|
|
326
|
+
if (isMswInitiallyReady) {
|
|
327
|
+
eventBus.dispatch(MswReadyEvent);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return true;
|
|
331
|
+
}, [isMswInitiallyReady, eventBus]), true);
|
|
332
|
+
|
|
333
|
+
const [state, reactDispatch] = useReducer(reducer, {
|
|
334
|
+
waitForMsw,
|
|
335
|
+
waitForPublicData,
|
|
336
|
+
waitForProtectedData,
|
|
337
|
+
// When the modules registration functions are awaited, the event listeners are registered after the modules are registered.
|
|
338
|
+
areModulesRegistered: areModulesInitiallyRegistered,
|
|
339
|
+
areModulesReady: areModulesInitiallyReady,
|
|
340
|
+
isMswReady: isMswInitiallyReady,
|
|
341
|
+
isPublicDataReady: false,
|
|
342
|
+
isProtectedDataReady: false,
|
|
343
|
+
activeRouteVisibility: "unknown",
|
|
344
|
+
isUnauthorized: false
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const {
|
|
348
|
+
areModulesRegistered: areModulesRegisteredValue,
|
|
349
|
+
areModulesReady: areModulesReadyValue,
|
|
350
|
+
isMswReady: isMswReadyValue
|
|
351
|
+
} = state;
|
|
352
|
+
|
|
353
|
+
// The dispatch proxy is strictly an utility allowing tests to mock the useReducer dispatch function. It's easier
|
|
354
|
+
// than mocking the import from React.
|
|
355
|
+
const dispatchProxy = useReducerDispatchProxy(reactDispatch);
|
|
356
|
+
const dispatch = useEnhancedReducerDispatch(dispatchProxy);
|
|
357
|
+
|
|
358
|
+
useModuleRegistrationStatusDispatcher(areModulesRegisteredValue, areModulesReadyValue, dispatch);
|
|
359
|
+
useMswStatusDispatcher(isMswReadyValue, dispatch);
|
|
360
|
+
useBootstrappingCompletedDispatcher(state);
|
|
361
|
+
|
|
362
|
+
return [state, dispatch];
|
|
363
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RegisterRouteOptions, RuntimeOptions } from "@squide/core";
|
|
2
|
+
import { MswPlugin, MswPluginName } from "@squide/msw";
|
|
3
|
+
import { ReactRouterRuntime, type Route } from "@squide/react-router";
|
|
4
|
+
import type { RequestHandler } from "msw";
|
|
5
|
+
import { getAreModulesRegistered } from "./AppRouterReducer.ts";
|
|
6
|
+
|
|
7
|
+
export interface FireflyRuntimeOptions extends RuntimeOptions {
|
|
8
|
+
useMsw?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class FireflyRuntime extends ReactRouterRuntime {
|
|
12
|
+
readonly #useMsw: boolean;
|
|
13
|
+
|
|
14
|
+
constructor({ plugins, useMsw, ...options }: FireflyRuntimeOptions = {}) {
|
|
15
|
+
if (useMsw) {
|
|
16
|
+
super({
|
|
17
|
+
plugins: [
|
|
18
|
+
...(plugins ?? []),
|
|
19
|
+
runtime => new MswPlugin(runtime)
|
|
20
|
+
],
|
|
21
|
+
...options
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.#useMsw = true;
|
|
25
|
+
} else {
|
|
26
|
+
super({
|
|
27
|
+
plugins,
|
|
28
|
+
...options
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.#useMsw = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
registerRequestHandlers(handlers: RequestHandler[]) {
|
|
36
|
+
const mswPlugin = this.getPlugin(MswPluginName) as MswPlugin;
|
|
37
|
+
|
|
38
|
+
if (!mswPlugin) {
|
|
39
|
+
throw new Error("[squide] Cannot register the provided MSW request handlers because the runtime hasn't been initialized with MSW. Did you instanciate the FireflyRuntime with the \"useMsw\" option?");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (getAreModulesRegistered()) {
|
|
43
|
+
throw new Error("[squide] Cannot register an MSW request handlers once the modules are registered. Are you trying to register an MSW request handler in a deferred registration function? Only navigation items can be registered in a deferred registration function.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mswPlugin.registerRequestHandlers(handlers);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Must define a return type otherwise we get an "error TS2742: The inferred type of 'requestHandlers' cannot be named" error.
|
|
50
|
+
get requestHandlers(): RequestHandler[] {
|
|
51
|
+
const mswPlugin = this.getPlugin(MswPluginName) as MswPlugin;
|
|
52
|
+
|
|
53
|
+
if (!mswPlugin) {
|
|
54
|
+
throw new Error("[squide] Cannot retrieve MSW request handlers because the runtime hasn't been initialized with MSW. Did you instanciate the FireflyRuntime with the \"useMsw\" option?");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return mswPlugin.requestHandlers;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
registerRoute(route: Route, options: RegisterRouteOptions = {}) {
|
|
61
|
+
if (getAreModulesRegistered()) {
|
|
62
|
+
throw new Error("[squide] Cannot register a route once the modules are registered. Are you trying to register a route in a deferred registration function? Only navigation items can be registered in a deferred registration function.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
super.registerRoute(route, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get isMswEnabled() {
|
|
69
|
+
return this.#useMsw;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class GlobalDataQueriesError extends Error {
|
|
2
|
+
readonly #errors: Error[];
|
|
3
|
+
|
|
4
|
+
constructor(message: string, errors: Error[]) {
|
|
5
|
+
super(message);
|
|
6
|
+
|
|
7
|
+
this.#errors = errors;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get errors() {
|
|
11
|
+
return this.#errors;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isGlobalDataQueriesError(error?: unknown): error is GlobalDataQueriesError {
|
|
16
|
+
return error !== undefined && error !== null && error instanceof GlobalDataQueriesError;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Outlet } from "react-router-dom";
|
|
3
|
+
import { useAppRouterDispatcher, useAppRouterState } from "./AppRouterContext.ts";
|
|
4
|
+
import { useIsActiveRouteProtected } from "./useIsActiveRouteProtected.ts";
|
|
5
|
+
|
|
6
|
+
export function RootRoute() {
|
|
7
|
+
const state = useAppRouterState();
|
|
8
|
+
const isActiveRouteProtected = useIsActiveRouteProtected(state.areModulesReady);
|
|
9
|
+
|
|
10
|
+
const dispatch = useAppRouterDispatcher();
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Dispatching the active route visibility must be done in a route because React Router's useLocation
|
|
14
|
+
// hook throws if it's not called from a child of the router component.
|
|
15
|
+
if (isActiveRouteProtected) {
|
|
16
|
+
dispatch({ type: "active-route-is-protected" });
|
|
17
|
+
} else {
|
|
18
|
+
dispatch({ type: "active-route-is-public" });
|
|
19
|
+
}
|
|
20
|
+
}, [isActiveRouteProtected, dispatch]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Outlet />
|
|
24
|
+
);
|
|
25
|
+
}
|
package/src/boostrap.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isFunction, registerLocalModules, type ModuleRegisterFunction, type ModuleRegistrationError, type RegisterModulesOptions } from "@squide/core";
|
|
2
|
+
import { registerRemoteModules, type RemoteDefinition, type RemoteModuleRegistrationError } from "@squide/module-federation";
|
|
3
|
+
import { setMswAsReady } from "@squide/msw";
|
|
4
|
+
import type { FireflyRuntime } from "./FireflyRuntime.tsx";
|
|
5
|
+
|
|
6
|
+
export const ApplicationBootstrappingStartedEvent = "squide-app-bootstrapping-started";
|
|
7
|
+
|
|
8
|
+
let isBootstrapped = false;
|
|
9
|
+
|
|
10
|
+
export type StartMswFunction<TRuntime = FireflyRuntime> = (runtime: TRuntime) => Promise<void>;
|
|
11
|
+
|
|
12
|
+
export interface BootstrapAppOptions<TRuntime extends FireflyRuntime = FireflyRuntime, TContext = unknown, TData = unknown> extends RegisterModulesOptions<TContext> {
|
|
13
|
+
localModules?: ModuleRegisterFunction<TRuntime, TContext, TData>[];
|
|
14
|
+
remotes?: RemoteDefinition[];
|
|
15
|
+
startMsw?: StartMswFunction<TRuntime>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function bootstrap<TRuntime extends FireflyRuntime = FireflyRuntime, TContext = unknown, TData = unknown>(runtime: TRuntime, options: BootstrapAppOptions<TRuntime, TContext, TData> = {}) {
|
|
19
|
+
const {
|
|
20
|
+
localModules = [],
|
|
21
|
+
remotes = [],
|
|
22
|
+
context,
|
|
23
|
+
startMsw
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
if (isBootstrapped) {
|
|
27
|
+
throw new Error("[squide] A squide application can only be bootstrapped once. Did you call the \"bootstrap\" function twice?");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
runtime.eventBus.dispatch(ApplicationBootstrappingStartedEvent);
|
|
31
|
+
|
|
32
|
+
let localModuleErrors: ModuleRegistrationError[] = [];
|
|
33
|
+
let remoteModuleErrors: RemoteModuleRegistrationError[] = [];
|
|
34
|
+
|
|
35
|
+
localModuleErrors = await registerLocalModules<TRuntime, TContext, TData>(localModules, runtime, { context });
|
|
36
|
+
remoteModuleErrors = await registerRemoteModules(remotes, runtime, { context });
|
|
37
|
+
|
|
38
|
+
if (runtime.isMswEnabled) {
|
|
39
|
+
if (!isFunction(startMsw)) {
|
|
40
|
+
throw new Error("[squide] When MSW is enabled, the \"startMsw\" function must be provided.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await startMsw(runtime);
|
|
45
|
+
|
|
46
|
+
setMswAsReady();
|
|
47
|
+
} catch (error: unknown) {
|
|
48
|
+
runtime.logger.debug("[squide] An error occured while starting MSW.", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isBootstrapped = true;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
localModuleErrors,
|
|
56
|
+
remoteModuleErrors
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function __resetBootstrapGuard() {
|
|
61
|
+
isBootstrapped = false;
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export * from "@squide/core";
|
|
2
|
+
export * from "@squide/module-federation";
|
|
3
|
+
export * from "@squide/msw";
|
|
4
|
+
export * from "@squide/react-router";
|
|
5
|
+
|
|
6
|
+
export * from "./FireflyRuntime.tsx";
|
|
7
|
+
|
|
8
|
+
export * from "./AppRouter.tsx";
|
|
9
|
+
export * from "./AppRouterContext.ts";
|
|
10
|
+
export * from "./AppRouterReducer.ts";
|
|
11
|
+
|
|
12
|
+
export * from "./GlobalDataQueriesError.ts";
|
|
13
|
+
export * from "./useCanFetchProtectedData.ts";
|
|
14
|
+
export * from "./useCanFetchPublicData.ts";
|
|
15
|
+
export * from "./useCanRegisterDeferredRegistrations.ts";
|
|
16
|
+
export * from "./useCanUpdateDeferredRegistrations.ts";
|
|
17
|
+
export * from "./useDeferredRegistrations.ts";
|
|
18
|
+
export * from "./useIsActiveRouteProtected.ts";
|
|
19
|
+
export * from "./useIsBootstrapping.ts";
|
|
20
|
+
export * from "./useNavigationItems.ts";
|
|
21
|
+
export * from "./useProtectedDataQueries.ts";
|
|
22
|
+
export * from "./usePublicDataQueries.ts";
|
|
23
|
+
export * from "./useRegisterDeferredRegistrations.ts";
|
|
24
|
+
export * from "./useStrictRegistrationMode.ts";
|
|
25
|
+
export * from "./useUpdateDeferredRegistrations.ts";
|
|
26
|
+
|
|
27
|
+
export * from "./boostrap.ts";
|
|
28
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useAppRouterState } from "./AppRouterContext.ts";
|
|
2
|
+
|
|
3
|
+
export function useCanFetchProtectedData() {
|
|
4
|
+
const {
|
|
5
|
+
waitForMsw,
|
|
6
|
+
areModulesRegistered,
|
|
7
|
+
areModulesReady,
|
|
8
|
+
isMswReady,
|
|
9
|
+
isProtectedDataReady,
|
|
10
|
+
activeRouteVisibility
|
|
11
|
+
} = useAppRouterState();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
// Always return true when the protected data has already been fetched sucessfully so TanStack Query can update the data in the background.
|
|
15
|
+
isProtectedDataReady
|
|
16
|
+
|| (
|
|
17
|
+
// Wait until the modules has been registered, but do not wait for the deferred registrations to be registered as they will probably
|
|
18
|
+
// depends on the protected data.
|
|
19
|
+
(areModulesRegistered || areModulesReady)
|
|
20
|
+
// Only fetch the protected data for protected routes, aka do not fetch the protected data for public routes.
|
|
21
|
+
&& activeRouteVisibility === "protected"
|
|
22
|
+
// Wait for MSW since the endpoints for the protected data might be an MSW endpoint when in development.
|
|
23
|
+
&& (!waitForMsw || isMswReady)
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useAppRouterState } from "./AppRouterContext.ts";
|
|
2
|
+
|
|
3
|
+
export function useCanFetchPublicData() {
|
|
4
|
+
const {
|
|
5
|
+
waitForMsw,
|
|
6
|
+
areModulesRegistered,
|
|
7
|
+
areModulesReady,
|
|
8
|
+
isMswReady,
|
|
9
|
+
isPublicDataReady
|
|
10
|
+
} = useAppRouterState();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
// Always return true when the public data has already been fetched sucessfully so TanStack Query can update the data in the background.
|
|
14
|
+
isPublicDataReady
|
|
15
|
+
|| (
|
|
16
|
+
// Wait until the modules has been registered, but do not wait for the deferred registrations to be registered has they will probably
|
|
17
|
+
// depends on the protected data.
|
|
18
|
+
(areModulesRegistered || areModulesReady)
|
|
19
|
+
// Wait for MSW since the endpoints for the protected data might be an MSW endpoint when in development.
|
|
20
|
+
&& (!waitForMsw || isMswReady)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useAppRouterState } from "./AppRouterContext.ts";
|
|
2
|
+
|
|
3
|
+
export function useCanRegisterDeferredRegistrations() {
|
|
4
|
+
const {
|
|
5
|
+
waitForPublicData,
|
|
6
|
+
waitForProtectedData,
|
|
7
|
+
areModulesReady,
|
|
8
|
+
areModulesRegistered,
|
|
9
|
+
isPublicDataReady,
|
|
10
|
+
isProtectedDataReady,
|
|
11
|
+
activeRouteVisibility,
|
|
12
|
+
isUnauthorized
|
|
13
|
+
} = useAppRouterState();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
!isUnauthorized
|
|
17
|
+
// Wait for the modules to be registered but make sure the deferred registrations has not been registered yet (updates are handled by another hook).
|
|
18
|
+
&& areModulesRegistered && !areModulesReady
|
|
19
|
+
// && (!waitForMsw || isMswReady)
|
|
20
|
+
// Wait for the initial data to be ready since the deferred registrations will probably need that data.
|
|
21
|
+
&& (!waitForPublicData || isPublicDataReady)
|
|
22
|
+
&& (!waitForProtectedData || activeRouteVisibility === "public" || isProtectedDataReady)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useAppRouterState } from "./AppRouterContext.ts";
|
|
2
|
+
|
|
3
|
+
export function useCanUpdateDeferredRegistrations() {
|
|
4
|
+
const {
|
|
5
|
+
areModulesReady,
|
|
6
|
+
publicDataUpdatedAt,
|
|
7
|
+
protectedDataUpdatedAt,
|
|
8
|
+
deferredRegistrationsUpdatedAt
|
|
9
|
+
} = useAppRouterState();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
// Do not trigger an update if the deferred registrations has not been registered yet (if there are deferred registrations, once they are
|
|
13
|
+
// registered, the modules will be marked as ready).
|
|
14
|
+
areModulesReady
|
|
15
|
+
// Make sure the apps is actually having deferred registrations.
|
|
16
|
+
&& deferredRegistrationsUpdatedAt
|
|
17
|
+
// If either the public data or the protected data has been updated, update the deferred registrations.
|
|
18
|
+
&& (
|
|
19
|
+
(publicDataUpdatedAt && publicDataUpdatedAt > deferredRegistrationsUpdatedAt) ||
|
|
20
|
+
(protectedDataUpdatedAt && protectedDataUpdatedAt > deferredRegistrationsUpdatedAt)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
}
|