@bleedingdev/modern-js-federation-runtime 0.0.0 → 3.9.0-ultramodern.10
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/LICENSE +21 -0
- package/dist/cjs/module-federation/consume-surface.js +199 -0
- package/dist/cjs/module-federation/distributed-ssr.js +135 -0
- package/dist/cjs/module-federation/index.js +316 -0
- package/dist/cjs/module-federation/last-known-good.js +157 -0
- package/dist/cjs/module-federation/manifest-recovery-runtime-plugin.js +130 -0
- package/dist/cjs/module-federation/surface-resolution-types.js +61 -0
- package/dist/esm/module-federation/consume-surface.mjs +158 -0
- package/dist/esm/module-federation/distributed-ssr.mjs +82 -0
- package/dist/esm/module-federation/index.mjs +115 -0
- package/dist/esm/module-federation/last-known-good.mjs +113 -0
- package/dist/esm/module-federation/manifest-recovery-runtime-plugin.mjs +90 -0
- package/dist/esm/module-federation/surface-resolution-types.mjs +8 -0
- package/dist/esm-node/module-federation/consume-surface.mjs +159 -0
- package/dist/esm-node/module-federation/distributed-ssr.mjs +83 -0
- package/dist/esm-node/module-federation/index.mjs +116 -0
- package/dist/esm-node/module-federation/last-known-good.mjs +114 -0
- package/dist/esm-node/module-federation/manifest-recovery-runtime-plugin.mjs +91 -0
- package/dist/esm-node/module-federation/surface-resolution-types.mjs +9 -0
- package/dist/types/module-federation/consume-surface.d.ts +104 -0
- package/dist/types/module-federation/distributed-ssr.d.ts +61 -0
- package/dist/types/module-federation/index.d.ts +71 -0
- package/dist/types/module-federation/last-known-good.d.ts +52 -0
- package/dist/types/module-federation/manifest-recovery-runtime-plugin.d.ts +25 -0
- package/dist/types/module-federation/surface-resolution-types.d.ts +23 -0
- package/package.json +86 -4
- package/src/module-federation/consume-surface.ts +369 -0
- package/src/module-federation/distributed-ssr.tsx +240 -0
- package/src/module-federation/index.ts +274 -0
- package/src/module-federation/last-known-good.ts +292 -0
- package/src/module-federation/manifest-recovery-runtime-plugin.ts +215 -0
- package/src/module-federation/surface-resolution-types.ts +60 -0
- package/README.md +0 -3
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { useRuntimeContext } from '@modern-js/runtime';
|
|
2
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export const DISTRIBUTED_SSR_FRAGMENTS_LOCALS_KEY =
|
|
5
|
+
'__modernDistributedSsrFragments';
|
|
6
|
+
export const DISTRIBUTED_SSR_FRAGMENT_REQUEST_LOCALS_KEY =
|
|
7
|
+
'__modernDistributedSsrFragmentRequest';
|
|
8
|
+
|
|
9
|
+
export type DistributedSsrFragment = {
|
|
10
|
+
boundaryId: string;
|
|
11
|
+
buildMarker?: string;
|
|
12
|
+
digest?: string;
|
|
13
|
+
expose: string;
|
|
14
|
+
html: string;
|
|
15
|
+
remote: string;
|
|
16
|
+
status: 'ready';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type DistributedSsrFragmentFailure = {
|
|
20
|
+
boundaryId: string;
|
|
21
|
+
expose: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
remote: string;
|
|
24
|
+
status: 'degraded';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type DistributedSsrFragmentResult =
|
|
28
|
+
| DistributedSsrFragment
|
|
29
|
+
| DistributedSsrFragmentFailure;
|
|
30
|
+
|
|
31
|
+
export type DistributedSsrFragmentContext = {
|
|
32
|
+
fragments?: Record<
|
|
33
|
+
string,
|
|
34
|
+
DistributedSsrFragment | DistributedSsrFragmentFailure
|
|
35
|
+
>;
|
|
36
|
+
required: true;
|
|
37
|
+
resolve?: (
|
|
38
|
+
remote: string,
|
|
39
|
+
expose: string,
|
|
40
|
+
props: Record<string, unknown>,
|
|
41
|
+
) => DistributedSsrFragmentResult | PromiseLike<DistributedSsrFragmentResult>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type DistributedSsrBoundaryProps<
|
|
45
|
+
Props extends object = Record<string, unknown>,
|
|
46
|
+
> = {
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
expose: string;
|
|
49
|
+
fallback: ReactNode;
|
|
50
|
+
fragmentProps?: Props;
|
|
51
|
+
remote: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type CreateDistributedSsrComponentOptions<Props extends object> = {
|
|
55
|
+
createComponent: () => ComponentType<Props>;
|
|
56
|
+
expose: string;
|
|
57
|
+
fallback: ReactNode;
|
|
58
|
+
remote: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function distributedSsrFragmentKey(remote: string, expose: string) {
|
|
62
|
+
return `${remote}::${expose}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getDistributedSsrFragmentContext(
|
|
66
|
+
response: { locals?: unknown } | undefined,
|
|
67
|
+
): DistributedSsrFragmentContext | undefined {
|
|
68
|
+
const locals = response?.locals;
|
|
69
|
+
if (typeof locals !== 'object' || locals === null) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const context =
|
|
74
|
+
DISTRIBUTED_SSR_FRAGMENTS_LOCALS_KEY in locals
|
|
75
|
+
? locals[DISTRIBUTED_SSR_FRAGMENTS_LOCALS_KEY]
|
|
76
|
+
: undefined;
|
|
77
|
+
|
|
78
|
+
if (typeof context !== 'object' || context === null) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return context as DistributedSsrFragmentContext;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function useDistributedSsrFragmentProps<Props extends object>({
|
|
86
|
+
boundaryId,
|
|
87
|
+
expose,
|
|
88
|
+
}: {
|
|
89
|
+
boundaryId: string;
|
|
90
|
+
expose: string;
|
|
91
|
+
}): Props {
|
|
92
|
+
const runtimeContext = useRuntimeContext();
|
|
93
|
+
const locals = runtimeContext.requestContext?.response?.locals;
|
|
94
|
+
const request =
|
|
95
|
+
typeof locals === 'object' &&
|
|
96
|
+
locals !== null &&
|
|
97
|
+
DISTRIBUTED_SSR_FRAGMENT_REQUEST_LOCALS_KEY in locals
|
|
98
|
+
? locals[DISTRIBUTED_SSR_FRAGMENT_REQUEST_LOCALS_KEY]
|
|
99
|
+
: undefined;
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
typeof request !== 'object' ||
|
|
103
|
+
request === null ||
|
|
104
|
+
!('boundaryId' in request) ||
|
|
105
|
+
request.boundaryId !== boundaryId ||
|
|
106
|
+
!('expose' in request) ||
|
|
107
|
+
request.expose !== expose
|
|
108
|
+
) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Distributed SSR fragment request contract mismatch for ${boundaryId} ${expose}.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const props = 'props' in request ? request.props : undefined;
|
|
115
|
+
if (typeof props !== 'object' || props === null || Array.isArray(props)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Distributed SSR fragment request props must be an object for ${boundaryId} ${expose}.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return props as Props;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isThenable(
|
|
125
|
+
value:
|
|
126
|
+
| DistributedSsrFragmentResult
|
|
127
|
+
| PromiseLike<DistributedSsrFragmentResult>
|
|
128
|
+
| undefined,
|
|
129
|
+
): value is PromiseLike<DistributedSsrFragmentResult> {
|
|
130
|
+
return (
|
|
131
|
+
value !== undefined && 'then' in value && typeof value.then === 'function'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Keeps the browser on the native Module Federation component while allowing
|
|
137
|
+
* servers that cannot execute remote code (notably workerd) to render a
|
|
138
|
+
* trusted fragment produced by the remote Worker itself.
|
|
139
|
+
*
|
|
140
|
+
* Node SSR has no distributed-fragment context and therefore renders the
|
|
141
|
+
* child normally, preserving Module Federation's in-process SSR path.
|
|
142
|
+
*/
|
|
143
|
+
export function DistributedSsrBoundary<
|
|
144
|
+
Props extends object = Record<string, unknown>,
|
|
145
|
+
>({
|
|
146
|
+
children,
|
|
147
|
+
expose,
|
|
148
|
+
fallback,
|
|
149
|
+
fragmentProps,
|
|
150
|
+
remote,
|
|
151
|
+
}: DistributedSsrBoundaryProps<Props>) {
|
|
152
|
+
const runtimeContext = useRuntimeContext();
|
|
153
|
+
const key = distributedSsrFragmentKey(remote, expose);
|
|
154
|
+
const attributes = {
|
|
155
|
+
'data-modern-distributed-ssr-boundary': key,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (runtimeContext.isBrowser) {
|
|
159
|
+
return <div {...attributes}>{children}</div>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const fragmentContext = getDistributedSsrFragmentContext(
|
|
163
|
+
runtimeContext.requestContext?.response,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Node can execute the Module Federation server runtime. Only workerd's
|
|
167
|
+
// request adapter supplies a required distributed-fragment context.
|
|
168
|
+
if (fragmentContext === undefined) {
|
|
169
|
+
return <div {...attributes}>{children}</div>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const resolverFragmentProps =
|
|
173
|
+
fragmentProps === undefined
|
|
174
|
+
? {}
|
|
175
|
+
: Object.fromEntries(Object.entries(fragmentProps));
|
|
176
|
+
const fragmentOrPromise =
|
|
177
|
+
fragmentContext.resolve?.(remote, expose, resolverFragmentProps) ??
|
|
178
|
+
fragmentContext.fragments?.[key];
|
|
179
|
+
if (isThenable(fragmentOrPromise)) {
|
|
180
|
+
throw fragmentOrPromise;
|
|
181
|
+
}
|
|
182
|
+
const fragment = fragmentOrPromise;
|
|
183
|
+
if (fragment?.status === 'ready') {
|
|
184
|
+
return (
|
|
185
|
+
<div
|
|
186
|
+
{...attributes}
|
|
187
|
+
data-modern-distributed-ssr-build={fragment.buildMarker}
|
|
188
|
+
data-modern-distributed-ssr-digest={fragment.digest}
|
|
189
|
+
data-modern-distributed-ssr-status="ready"
|
|
190
|
+
// The fragment is accepted only after the Worker adapter verifies its
|
|
191
|
+
// declared boundary id and expose marker.
|
|
192
|
+
dangerouslySetInnerHTML={{ __html: fragment.html }}
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div
|
|
199
|
+
{...attributes}
|
|
200
|
+
data-modern-distributed-ssr-reason={fragment?.reason ?? 'missing'}
|
|
201
|
+
data-modern-distributed-ssr-status="degraded"
|
|
202
|
+
>
|
|
203
|
+
{fallback}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Defers creation of the native Module Federation component until React
|
|
210
|
+
* actually renders that child. A workerd request with a verified distributed
|
|
211
|
+
* fragment never constructs the native remote, so its module cannot perform
|
|
212
|
+
* forbidden global-scope network I/O. Browser and Node rendering keep using
|
|
213
|
+
* the native Module Federation component unchanged.
|
|
214
|
+
*/
|
|
215
|
+
export function createDistributedSsrComponent<Props extends object>({
|
|
216
|
+
createComponent,
|
|
217
|
+
expose,
|
|
218
|
+
fallback,
|
|
219
|
+
remote,
|
|
220
|
+
}: CreateDistributedSsrComponentOptions<Props>) {
|
|
221
|
+
let RemoteComponent: ComponentType<Props> | undefined;
|
|
222
|
+
|
|
223
|
+
function DeferredRemoteComponent(props: Props) {
|
|
224
|
+
RemoteComponent ??= createComponent();
|
|
225
|
+
return <RemoteComponent {...props} />;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return function DistributedSsrComponent(props: Props) {
|
|
229
|
+
return (
|
|
230
|
+
<DistributedSsrBoundary
|
|
231
|
+
expose={expose}
|
|
232
|
+
fallback={fallback}
|
|
233
|
+
fragmentProps={props}
|
|
234
|
+
remote={remote}
|
|
235
|
+
>
|
|
236
|
+
<DeferredRemoteComponent {...props} />
|
|
237
|
+
</DistributedSsrBoundary>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off extendsNativeError:off globalFetch:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
export * from './consume-surface';
|
|
4
|
+
export * from './distributed-ssr';
|
|
5
|
+
export {
|
|
6
|
+
DISTRIBUTED_SSR_FRAGMENT_REQUEST_LOCALS_KEY,
|
|
7
|
+
useDistributedSsrFragmentProps,
|
|
8
|
+
} from './distributed-ssr';
|
|
9
|
+
export * from './last-known-good';
|
|
10
|
+
export * from './manifest-recovery-runtime-plugin';
|
|
11
|
+
export * from './surface-resolution-types';
|
|
12
|
+
|
|
13
|
+
export const MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT =
|
|
14
|
+
'modernjs:mf-runtime-fallback';
|
|
15
|
+
|
|
16
|
+
export const MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT =
|
|
17
|
+
'modernjs:mf-runtime-recovery';
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT =
|
|
20
|
+
'/_modern/contract-gates/runtime-fallback';
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER =
|
|
23
|
+
'x-modernjs-runtime-signal-token';
|
|
24
|
+
|
|
25
|
+
export type ModuleFederationFallbackClassification =
|
|
26
|
+
| 'timeout'
|
|
27
|
+
| 'network'
|
|
28
|
+
| 'contract'
|
|
29
|
+
| 'version-skew'
|
|
30
|
+
| 'remote-unavailable';
|
|
31
|
+
|
|
32
|
+
export type ModuleFederationFallbackStatus =
|
|
33
|
+
| 'degraded'
|
|
34
|
+
| 'failed'
|
|
35
|
+
| 'recovered';
|
|
36
|
+
|
|
37
|
+
export type ModuleFederationFallbackTelemetryInput = {
|
|
38
|
+
appName: string;
|
|
39
|
+
classification: ModuleFederationFallbackClassification;
|
|
40
|
+
entry?: string;
|
|
41
|
+
error?: unknown;
|
|
42
|
+
eventName?: string;
|
|
43
|
+
exportName?: string;
|
|
44
|
+
metadata?: Record<string, unknown>;
|
|
45
|
+
phase: 'discovery' | 'load' | 'mount' | 'hydrate' | 'recover';
|
|
46
|
+
remote: string;
|
|
47
|
+
runtimeDigest?: string;
|
|
48
|
+
status?: ModuleFederationFallbackStatus;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ModuleFederationFallbackTelemetryPayload = {
|
|
52
|
+
appName: string;
|
|
53
|
+
entry?: string;
|
|
54
|
+
eventName: string;
|
|
55
|
+
phase: ModuleFederationFallbackTelemetryInput['phase'];
|
|
56
|
+
reason: ModuleFederationFallbackClassification;
|
|
57
|
+
runtimeDigest?: string;
|
|
58
|
+
schemaVersion: 1;
|
|
59
|
+
metadata: {
|
|
60
|
+
classification: ModuleFederationFallbackClassification;
|
|
61
|
+
errorMessage?: string;
|
|
62
|
+
errorName?: string;
|
|
63
|
+
exportName?: string;
|
|
64
|
+
remote: string;
|
|
65
|
+
runtimeDigest?: string;
|
|
66
|
+
status: ModuleFederationFallbackStatus;
|
|
67
|
+
} & Record<string, unknown>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type ModuleFederationFallbackTelemetryEmitOptions = {
|
|
71
|
+
authHeaderName?: string;
|
|
72
|
+
authToken?: string;
|
|
73
|
+
endpoint?: string;
|
|
74
|
+
fetchImpl?: typeof fetch;
|
|
75
|
+
postSignal?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type ModuleFederationFallbackTelemetryEmitResult = {
|
|
79
|
+
dispatched: boolean;
|
|
80
|
+
posted: boolean;
|
|
81
|
+
postStatus?: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export class ModuleFederationRemoteLoadTimeoutError extends Error {
|
|
85
|
+
constructor(remote: string, timeoutMs: number) {
|
|
86
|
+
super(`Loading remote "${remote}" timed out after ${timeoutMs}ms`);
|
|
87
|
+
this.name = 'ModuleFederationRemoteLoadTimeoutError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class ModuleFederationRemoteLoadError extends Error {
|
|
92
|
+
readonly remote: string;
|
|
93
|
+
readonly attempts: number;
|
|
94
|
+
readonly causeError: Error;
|
|
95
|
+
|
|
96
|
+
constructor(remote: string, attempts: number, causeError: Error) {
|
|
97
|
+
super(
|
|
98
|
+
`Unable to load remote "${remote}" after ${attempts} attempt${
|
|
99
|
+
attempts > 1 ? 's' : ''
|
|
100
|
+
}: ${causeError.message}`,
|
|
101
|
+
);
|
|
102
|
+
this.name = 'ModuleFederationRemoteLoadError';
|
|
103
|
+
this.remote = remote;
|
|
104
|
+
this.attempts = attempts;
|
|
105
|
+
this.causeError = causeError;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class ModuleFederationRemoteComponentContractError extends Error {
|
|
110
|
+
constructor(remote: string, exportName: string) {
|
|
111
|
+
super(
|
|
112
|
+
`Remote "${remote}" export "${exportName}" is not a valid React component`,
|
|
113
|
+
);
|
|
114
|
+
this.name = 'ModuleFederationRemoteComponentContractError';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function toError(error: unknown) {
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
return error;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return new Error(
|
|
124
|
+
typeof error === 'string' ? error : 'Unknown remote load error',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function classifyModuleFederationFallback(
|
|
129
|
+
error: unknown,
|
|
130
|
+
): ModuleFederationFallbackClassification {
|
|
131
|
+
const normalizedError = toError(error);
|
|
132
|
+
if (normalizedError instanceof ModuleFederationRemoteLoadError) {
|
|
133
|
+
return classifyModuleFederationFallback(normalizedError.causeError);
|
|
134
|
+
}
|
|
135
|
+
if (normalizedError instanceof ModuleFederationRemoteLoadTimeoutError) {
|
|
136
|
+
return 'timeout';
|
|
137
|
+
}
|
|
138
|
+
if (normalizedError instanceof ModuleFederationRemoteComponentContractError) {
|
|
139
|
+
return 'contract';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const message = normalizedError.message;
|
|
143
|
+
if (
|
|
144
|
+
/version|requiredVersion|singleton|share scope|shared module/i.test(message)
|
|
145
|
+
) {
|
|
146
|
+
return 'version-skew';
|
|
147
|
+
}
|
|
148
|
+
if (/network|fetch|script|timeout|chunk|loading/i.test(message)) {
|
|
149
|
+
return 'network';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return 'remote-unavailable';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function createModuleFederationFallbackTelemetry(
|
|
156
|
+
input: ModuleFederationFallbackTelemetryInput,
|
|
157
|
+
): ModuleFederationFallbackTelemetryPayload {
|
|
158
|
+
const error = input.error !== undefined ? toError(input.error) : undefined;
|
|
159
|
+
const status = input.status ?? 'degraded';
|
|
160
|
+
const eventName =
|
|
161
|
+
input.eventName ??
|
|
162
|
+
(status === 'recovered'
|
|
163
|
+
? MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT
|
|
164
|
+
: MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT);
|
|
165
|
+
const metadata: ModuleFederationFallbackTelemetryPayload['metadata'] = {
|
|
166
|
+
...(input.metadata ?? {}),
|
|
167
|
+
classification: input.classification,
|
|
168
|
+
remote: input.remote,
|
|
169
|
+
status,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (input.exportName !== undefined) {
|
|
173
|
+
metadata.exportName = input.exportName;
|
|
174
|
+
}
|
|
175
|
+
if (input.runtimeDigest !== undefined) {
|
|
176
|
+
metadata.runtimeDigest = input.runtimeDigest;
|
|
177
|
+
}
|
|
178
|
+
if (error !== undefined) {
|
|
179
|
+
metadata.errorName = error.name;
|
|
180
|
+
metadata.errorMessage = error.message;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const payload: ModuleFederationFallbackTelemetryPayload = {
|
|
184
|
+
appName: input.appName,
|
|
185
|
+
eventName,
|
|
186
|
+
metadata,
|
|
187
|
+
phase: input.phase,
|
|
188
|
+
reason: input.classification,
|
|
189
|
+
schemaVersion: 1,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (input.entry !== undefined) {
|
|
193
|
+
payload.entry = input.entry;
|
|
194
|
+
}
|
|
195
|
+
if (input.runtimeDigest !== undefined) {
|
|
196
|
+
payload.runtimeDigest = input.runtimeDigest;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return payload;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function toModuleFederationFallbackAttributes(
|
|
203
|
+
payload: ModuleFederationFallbackTelemetryPayload,
|
|
204
|
+
): Record<string, string> {
|
|
205
|
+
return {
|
|
206
|
+
'data-mf-fallback-app': payload.appName,
|
|
207
|
+
'data-mf-fallback-classification': payload.reason,
|
|
208
|
+
'data-mf-fallback-phase': payload.phase,
|
|
209
|
+
'data-mf-fallback-remote': String(payload.metadata.remote),
|
|
210
|
+
'data-mf-fallback-status': String(payload.metadata.status),
|
|
211
|
+
'data-mf-telemetry-event': payload.eventName,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function emitModuleFederationFallbackTelemetry(
|
|
216
|
+
input: ModuleFederationFallbackTelemetryInput,
|
|
217
|
+
options: ModuleFederationFallbackTelemetryEmitOptions = {},
|
|
218
|
+
): Promise<ModuleFederationFallbackTelemetryEmitResult> {
|
|
219
|
+
const payload = createModuleFederationFallbackTelemetry(input);
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
typeof window !== 'undefined' &&
|
|
223
|
+
typeof window.dispatchEvent === 'function' &&
|
|
224
|
+
typeof CustomEvent !== 'undefined'
|
|
225
|
+
) {
|
|
226
|
+
window.dispatchEvent(
|
|
227
|
+
new CustomEvent(payload.eventName, {
|
|
228
|
+
detail: payload,
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const shouldPost = options.postSignal === true || Boolean(options.endpoint);
|
|
234
|
+
if (!shouldPost) {
|
|
235
|
+
return {
|
|
236
|
+
dispatched: true,
|
|
237
|
+
posted: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
242
|
+
if (typeof fetchImpl !== 'function') {
|
|
243
|
+
return {
|
|
244
|
+
dispatched: true,
|
|
245
|
+
posted: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const headers = new Headers({
|
|
250
|
+
'content-type': 'application/json',
|
|
251
|
+
});
|
|
252
|
+
if (options.authToken !== undefined && options.authToken.length > 0) {
|
|
253
|
+
headers.set(
|
|
254
|
+
options.authHeaderName ?? DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER,
|
|
255
|
+
options.authToken,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const response = await fetchImpl(
|
|
260
|
+
options.endpoint ?? DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT,
|
|
261
|
+
{
|
|
262
|
+
body: JSON.stringify(payload),
|
|
263
|
+
headers,
|
|
264
|
+
method: 'POST',
|
|
265
|
+
keepalive: true,
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
dispatched: true,
|
|
271
|
+
posted: true,
|
|
272
|
+
postStatus: response.status,
|
|
273
|
+
};
|
|
274
|
+
}
|