@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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/dist/cjs/buildIdentity.js +73 -0
- package/dist/cjs/contextExtensions.js +74 -0
- package/dist/cjs/headRuntime.js +91 -0
- package/dist/cjs/helmetContext.js +54 -0
- package/dist/cjs/index.js +18 -4
- package/dist/cjs/localisedLoaderIdentity.js +51 -0
- package/dist/cjs/localisedUrls/config.js +49 -0
- package/dist/cjs/localisedUrls/index.js +95 -0
- package/dist/cjs/localisedUrls/normalise.js +87 -0
- package/dist/cjs/localisedUrls/pathname.js +68 -0
- package/dist/cjs/localisedUrls/patterns.js +214 -0
- package/dist/cjs/localisedUrls/redirect.js +100 -0
- package/dist/cjs/localisedUrls/resolve.js +78 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +92 -0
- package/dist/cjs/localisedUrls/routes.js +217 -0
- package/dist/cjs/localisedUrls/types.js +18 -0
- package/dist/cjs/prefetchPolicy.js +52 -0
- package/dist/cjs/rendererHead.js +18 -5
- package/dist/cjs/request-policy/browser.js +139 -0
- package/dist/cjs/request-policy/client.js +234 -0
- package/dist/cjs/request-policy/node.js +149 -0
- package/dist/cjs/request-policy/policyCore.js +266 -0
- package/dist/cjs/request-policy/requestContext.js +137 -0
- package/dist/cjs/request-policy/traceparent.js +56 -0
- package/dist/cjs/request-policy/transport.js +234 -0
- package/dist/cjs/request-policy/types.js +57 -0
- package/dist/cjs/routeHydration.js +127 -0
- package/dist/cjs/routerPrefetchPolicy.js +153 -0
- package/dist/cjs/routerProvider.js +105 -0
- package/dist/cjs/routerState.js +175 -0
- package/dist/cjs/routerStatePlugin.js +113 -0
- package/dist/cjs/routerStateTypes.js +18 -0
- package/dist/cjs/rsc-disabled/rscDisabledClientBrowserRuntime.js +69 -0
- package/dist/cjs/rsc-disabled/rscDisabledClientEdgeRuntime.js +61 -0
- package/dist/cjs/rsc-disabled/rscDisabledClientNodeRuntime.js +65 -0
- package/dist/cjs/rsc-disabled/rscDisabledRuntimeError.js +42 -0
- package/dist/cjs/rsc-disabled/rscDisabledServerEdgeRuntime.js +93 -0
- package/dist/cjs/rsc-disabled/rscDisabledServerNodeRuntime.js +101 -0
- package/dist/cjs/runtimeContextProjection.js +112 -0
- package/dist/cjs/server-config/bffRuntime.js +18 -0
- package/dist/cjs/server-config/index.js +18 -0
- package/dist/cjs/server-config/serverTelemetry.js +18 -0
- package/dist/cjs/shellStream.js +34 -2
- package/dist/cjs/workspaceEvents.js +159 -0
- package/dist/esm/buildIdentity.mjs +35 -0
- package/dist/esm/contextExtensions.mjs +33 -0
- package/dist/esm/headRuntime.mjs +43 -0
- package/dist/esm/helmetContext.mjs +13 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/localisedLoaderIdentity.mjs +13 -0
- package/dist/esm/localisedUrls/config.mjs +11 -0
- package/dist/esm/localisedUrls/index.mjs +8 -0
- package/dist/esm/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/localisedUrls/patterns.mjs +152 -0
- package/dist/esm/localisedUrls/redirect.mjs +53 -0
- package/dist/esm/localisedUrls/resolve.mjs +37 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +54 -0
- package/dist/esm/localisedUrls/routes.mjs +176 -0
- package/dist/esm/localisedUrls/types.mjs +0 -0
- package/dist/esm/prefetchPolicy.mjs +11 -0
- package/dist/esm/rendererHead.mjs +14 -5
- package/dist/esm/request-policy/browser.mjs +11 -0
- package/dist/esm/request-policy/client.mjs +195 -0
- package/dist/esm/request-policy/policyCore.mjs +171 -0
- package/dist/esm/request-policy/requestContext.mjs +75 -0
- package/dist/esm/request-policy/traceparent.mjs +18 -0
- package/dist/esm/request-policy/transport.mjs +196 -0
- package/dist/esm/request-policy/types.mjs +10 -0
- package/dist/esm/routeHydration.mjs +77 -0
- package/dist/esm/routerPrefetchPolicy.mjs +115 -0
- package/dist/esm/routerProvider.mjs +58 -0
- package/dist/esm/routerState.mjs +113 -0
- package/dist/esm/routerStatePlugin.mjs +75 -0
- package/dist/esm/routerStateTypes.mjs +0 -0
- package/dist/esm/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +10 -0
- package/dist/esm/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +8 -0
- package/dist/esm/rsc-disabled/rscDisabledClientNodeRuntime.mjs +9 -0
- package/dist/esm/rsc-disabled/rscDisabledRuntimeError.mjs +4 -0
- package/dist/esm/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +16 -0
- package/dist/esm/rsc-disabled/rscDisabledServerNodeRuntime.mjs +18 -0
- package/dist/esm/runtimeContextProjection.mjs +74 -0
- package/dist/esm/server-config/bffRuntime.mjs +0 -0
- package/dist/esm/server-config/index.mjs +0 -0
- package/dist/esm/server-config/serverTelemetry.mjs +0 -0
- package/dist/esm/shellStream.mjs +34 -2
- package/dist/esm/workspaceEvents.mjs +66 -0
- package/dist/esm-node/buildIdentity.mjs +36 -0
- package/dist/esm-node/contextExtensions.mjs +34 -0
- package/dist/esm-node/headRuntime.mjs +44 -0
- package/dist/esm-node/helmetContext.mjs +14 -0
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/localisedLoaderIdentity.mjs +14 -0
- package/dist/esm-node/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/localisedUrls/index.mjs +9 -0
- package/dist/esm-node/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/localisedUrls/patterns.mjs +153 -0
- package/dist/esm-node/localisedUrls/redirect.mjs +54 -0
- package/dist/esm-node/localisedUrls/resolve.mjs +38 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +55 -0
- package/dist/esm-node/localisedUrls/routes.mjs +177 -0
- package/dist/esm-node/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/prefetchPolicy.mjs +12 -0
- package/dist/esm-node/rendererHead.mjs +14 -5
- package/dist/esm-node/request-policy/browser.mjs +12 -0
- package/dist/esm-node/request-policy/client.mjs +196 -0
- package/dist/esm-node/request-policy/node.mjs +22 -0
- package/dist/esm-node/request-policy/policyCore.mjs +172 -0
- package/dist/esm-node/request-policy/requestContext.mjs +76 -0
- package/dist/esm-node/request-policy/traceparent.mjs +19 -0
- package/dist/esm-node/request-policy/transport.mjs +197 -0
- package/dist/esm-node/request-policy/types.mjs +11 -0
- package/dist/esm-node/routeHydration.mjs +78 -0
- package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
- package/dist/esm-node/routerProvider.mjs +59 -0
- package/dist/esm-node/routerState.mjs +114 -0
- package/dist/esm-node/routerStatePlugin.mjs +76 -0
- package/dist/esm-node/routerStateTypes.mjs +1 -0
- package/dist/esm-node/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +11 -0
- package/dist/esm-node/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +9 -0
- package/dist/esm-node/rsc-disabled/rscDisabledClientNodeRuntime.mjs +10 -0
- package/dist/esm-node/rsc-disabled/rscDisabledRuntimeError.mjs +5 -0
- package/dist/esm-node/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +17 -0
- package/dist/esm-node/rsc-disabled/rscDisabledServerNodeRuntime.mjs +19 -0
- package/dist/esm-node/runtimeContextProjection.mjs +75 -0
- package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
- package/dist/esm-node/server-config/index.mjs +1 -0
- package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
- package/dist/esm-node/shellStream.mjs +34 -2
- package/dist/esm-node/workspaceEvents.mjs +67 -0
- package/dist/types/buildIdentity.d.ts +37 -0
- package/dist/types/contextExtensions.d.ts +34 -0
- package/dist/types/headRuntime.d.ts +12 -0
- package/dist/types/helmetCompat.d.ts +1 -1
- package/dist/types/helmetContext.d.ts +10 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/localisedLoaderIdentity.d.ts +10 -0
- package/dist/types/localisedUrls/config.d.ts +9 -0
- package/dist/types/localisedUrls/index.d.ts +10 -0
- package/dist/types/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/localisedUrls/patterns.d.ts +11 -0
- package/dist/types/localisedUrls/redirect.d.ts +19 -0
- package/dist/types/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +22 -0
- package/dist/types/localisedUrls/routes.d.ts +3 -0
- package/dist/types/localisedUrls/types.d.ts +14 -0
- package/dist/types/prefetchPolicy.d.ts +4 -0
- package/dist/types/rendererHead.d.ts +5 -3
- package/dist/types/request-policy/browser.d.ts +10 -0
- package/dist/types/request-policy/client.d.ts +14 -0
- package/dist/types/request-policy/node.d.ts +10 -0
- package/dist/types/request-policy/policyCore.d.ts +107 -0
- package/dist/types/request-policy/requestContext.d.ts +22 -0
- package/dist/types/request-policy/traceparent.d.ts +10 -0
- package/dist/types/request-policy/transport.d.ts +12 -0
- package/dist/types/request-policy/types.d.ts +144 -0
- package/dist/types/routeHydration.d.ts +24 -0
- package/dist/types/routerPrefetchPolicy.d.ts +28 -0
- package/dist/types/routerProvider.d.ts +55 -0
- package/dist/types/routerState.d.ts +17 -0
- package/dist/types/routerStatePlugin.d.ts +19 -0
- package/dist/types/routerStateTypes.d.ts +70 -0
- package/dist/types/rsc-disabled/rscDisabledClientBrowserRuntime.d.ts +9 -0
- package/dist/types/rsc-disabled/rscDisabledClientEdgeRuntime.d.ts +7 -0
- package/dist/types/rsc-disabled/rscDisabledClientNodeRuntime.d.ts +8 -0
- package/dist/types/rsc-disabled/rscDisabledRuntimeError.d.ts +1 -0
- package/dist/types/rsc-disabled/rscDisabledServerEdgeRuntime.d.ts +15 -0
- package/dist/types/rsc-disabled/rscDisabledServerNodeRuntime.d.ts +17 -0
- package/dist/types/runtimeContextProjection.d.ts +30 -0
- package/dist/types/server-config/bffRuntime.d.ts +122 -0
- package/dist/types/server-config/index.d.ts +2 -0
- package/dist/types/server-config/serverTelemetry.d.ts +322 -0
- package/dist/types/workspaceEvents.d.ts +62 -0
- package/package.json +240 -15
- package/rslib.config.mts +17 -2
- package/rstest.config.mts +33 -2
- package/src/buildIdentity.ts +61 -0
- package/src/contextExtensions.ts +81 -0
- package/src/headRuntime.tsx +77 -0
- package/src/helmetCompat.ts +1 -1
- package/src/helmetContext.ts +31 -0
- package/src/index.ts +2 -0
- package/src/localisedLoaderIdentity.ts +30 -0
- package/src/localisedUrls/config.ts +18 -0
- package/src/localisedUrls/index.ts +25 -0
- package/src/localisedUrls/normalise.ts +62 -0
- package/src/localisedUrls/pathname.ts +60 -0
- package/src/localisedUrls/patterns.ts +283 -0
- package/src/localisedUrls/redirect.ts +107 -0
- package/src/localisedUrls/resolve.ts +106 -0
- package/src/localisedUrls/routeIdentity.ts +83 -0
- package/src/localisedUrls/routes.ts +451 -0
- package/src/localisedUrls/types.ts +18 -0
- package/src/prefetchPolicy.ts +33 -0
- package/src/rendererHead.ts +25 -8
- package/src/request-policy/browser.ts +22 -0
- package/src/request-policy/client.ts +404 -0
- package/src/request-policy/node.ts +41 -0
- package/src/request-policy/policyCore.ts +424 -0
- package/src/request-policy/requestContext.ts +149 -0
- package/src/request-policy/traceparent.ts +43 -0
- package/src/request-policy/transport.ts +349 -0
- package/src/request-policy/types.ts +229 -0
- package/src/routeHydration.ts +208 -0
- package/src/routerPrefetchPolicy.ts +195 -0
- package/src/routerProvider.ts +160 -0
- package/src/routerState.ts +220 -0
- package/src/routerStatePlugin.ts +125 -0
- package/src/routerStateTypes.ts +76 -0
- package/src/rsc-disabled/rscDisabledClientBrowserRuntime.ts +10 -0
- package/src/rsc-disabled/rscDisabledClientEdgeRuntime.ts +8 -0
- package/src/rsc-disabled/rscDisabledClientNodeRuntime.ts +9 -0
- package/src/rsc-disabled/rscDisabledRuntimeError.ts +3 -0
- package/src/rsc-disabled/rscDisabledServerEdgeRuntime.ts +16 -0
- package/src/rsc-disabled/rscDisabledServerNodeRuntime.ts +18 -0
- package/src/runtimeContextProjection.ts +105 -0
- package/src/server-config/bffRuntime.ts +136 -0
- package/src/server-config/index.ts +2 -0
- package/src/server-config/serverTelemetry.ts +337 -0
- package/src/shellStream.ts +55 -25
- package/src/workspaceEvents.ts +324 -0
- package/tests/buildIdentity.test.ts +51 -0
- package/tests/contextExtensions.test.ts +27 -0
- package/tests/headRuntime.test.tsx +229 -0
- package/tests/localisedUrls.test.ts +560 -0
- package/tests/rendererHead.test.ts +52 -121
- package/tests/request-policy/browser-policy.test.ts +108 -0
- package/tests/request-policy/client-isolation.test.ts +109 -0
- package/tests/request-policy/node-policy.test.ts +447 -0
- package/tests/request-policy/policyCore.test.ts +129 -0
- package/tests/request-policy/requestContext.test.ts +66 -0
- package/tests/request-policy/requestFactory.outbound-contract.test.ts +290 -0
- package/tests/request-policy/traceparent.test.ts +52 -0
- package/tests/request-policy/transport-retry.test.ts +367 -0
- package/tests/routeHydration.test.ts +180 -0
- package/tests/routerPrefetchPolicy.test.ts +102 -0
- package/tests/routerProvider.test.ts +29 -0
- package/tests/routerState.test.ts +77 -0
- package/tests/routerStatePlugin.test.ts +63 -0
- package/tests/runtimeContextProjection.test.ts +83 -0
- package/tests/safeFailure.test.ts +10 -28
- package/tests/shellStream.test.ts +39 -2
- package/tests/workspaceEvents.test.ts +221 -0
- package/dist/cjs/rscHtmlStream.js +0 -128
- package/dist/esm/rscHtmlStream.mjs +0 -86
- package/dist/esm-node/rscHtmlStream.mjs +0 -87
- package/dist/types/rscHtmlStream.d.ts +0 -4
- package/src/rscHtmlStream.ts +0 -142
- package/tests/rscHtmlStream.test.ts +0 -153
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared (browser/node) core for the fork-added BFF producer-client policy:
|
|
3
|
+
* envelope construction, operation-context derivation, identity/operation
|
|
4
|
+
* contract violations and header utilities.
|
|
5
|
+
*
|
|
6
|
+
* This module is environment-neutral by design: it must not import node
|
|
7
|
+
* builtins (it is bundled into the browser entry) nor touch `window`
|
|
8
|
+
* directly (it is bundled into the node entry). Environment-specific
|
|
9
|
+
* behaviour (origin resolution, incoming-header forwarding, fetch wiring)
|
|
10
|
+
* stays in `browser.ts` / `node.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Honest scope note: everything produced here (envelope, operation context,
|
|
13
|
+
* schema hash, operation version) is asserted by the CLIENT. It protects
|
|
14
|
+
* against version skew and misconfiguration, not against malicious callers —
|
|
15
|
+
* the server-side policy evaluator in `@modern-js/bff-core` is the actual
|
|
16
|
+
* gate, and only when it is bound to a verified identity.
|
|
17
|
+
*/
|
|
18
|
+
import { parseTraceparent as parseTraceparentHeader } from './traceparent';
|
|
19
|
+
import type {
|
|
20
|
+
IdentityBindingViolation,
|
|
21
|
+
OperationContext,
|
|
22
|
+
OperationContractOptions,
|
|
23
|
+
OperationContractViolation,
|
|
24
|
+
TransportTarget,
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
declare const process:
|
|
28
|
+
| { readonly env?: Readonly<Record<string, string | undefined>> }
|
|
29
|
+
| undefined;
|
|
30
|
+
|
|
31
|
+
export const TRACEPARENT_HEADER = 'traceparent';
|
|
32
|
+
|
|
33
|
+
const readProcessEnv = (key: string) => {
|
|
34
|
+
if (
|
|
35
|
+
typeof process === 'undefined' ||
|
|
36
|
+
typeof process.env === 'undefined' ||
|
|
37
|
+
typeof process.env[key] !== 'string'
|
|
38
|
+
) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return process.env[key];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const isStrictDefaultRequestIdEnabled = () =>
|
|
46
|
+
readProcessEnv('MODERN_BFF_STRICT_DEFAULT_REQUEST_ID') === 'true';
|
|
47
|
+
|
|
48
|
+
export const isSecuredRequestId = (requestId: string) =>
|
|
49
|
+
requestId !== 'default' || isStrictDefaultRequestIdEnabled();
|
|
50
|
+
|
|
51
|
+
export const isEmptyDomain = (domain?: string) =>
|
|
52
|
+
typeof domain !== 'string' || domain.trim() === '';
|
|
53
|
+
|
|
54
|
+
export const firstHeaderValue = (value: unknown) =>
|
|
55
|
+
Array.isArray(value) ? value[0] : value;
|
|
56
|
+
|
|
57
|
+
export const findHeaderKey = (headers: Record<string, any>, header: string) => {
|
|
58
|
+
const normalized = header.toLowerCase();
|
|
59
|
+
return Object.keys(headers).find(key => key.toLowerCase() === normalized);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const readHeader = (headers: Record<string, any>, header: string) => {
|
|
63
|
+
const key = findHeaderKey(headers, header);
|
|
64
|
+
return typeof key === 'string' ? headers[key] : undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const writeHeader = (
|
|
68
|
+
headers: Record<string, any>,
|
|
69
|
+
header: string,
|
|
70
|
+
value: unknown,
|
|
71
|
+
) => {
|
|
72
|
+
if (typeof value === 'undefined') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const key = findHeaderKey(headers, header);
|
|
76
|
+
if (typeof key === 'string' && key !== header) {
|
|
77
|
+
delete headers[key];
|
|
78
|
+
}
|
|
79
|
+
headers[header] = value;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const deleteHeader = (headers: Record<string, any>, header: string) => {
|
|
83
|
+
const key = findHeaderKey(headers, header);
|
|
84
|
+
if (typeof key === 'string') {
|
|
85
|
+
delete headers[key];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const toOrigin = (value?: string) => {
|
|
90
|
+
if (!value) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return new URL(value).origin;
|
|
95
|
+
} catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const parseTraceparentValue = (value: unknown) =>
|
|
101
|
+
parseTraceparentHeader(firstHeaderValue(value) as string | undefined);
|
|
102
|
+
|
|
103
|
+
export const buildOperationContext = ({
|
|
104
|
+
requestId,
|
|
105
|
+
method,
|
|
106
|
+
path,
|
|
107
|
+
operationContext,
|
|
108
|
+
traceparent,
|
|
109
|
+
}: {
|
|
110
|
+
requestId: string;
|
|
111
|
+
method: string;
|
|
112
|
+
path: string;
|
|
113
|
+
operationContext?: OperationContext | undefined;
|
|
114
|
+
traceparent?: unknown;
|
|
115
|
+
}) => {
|
|
116
|
+
const routePath = operationContext?.routePath || path;
|
|
117
|
+
const operationMethod = (
|
|
118
|
+
operationContext?.method ||
|
|
119
|
+
method ||
|
|
120
|
+
'GET'
|
|
121
|
+
).toUpperCase();
|
|
122
|
+
const rawOperationId =
|
|
123
|
+
operationContext?.operationId || `${operationMethod}:${routePath}`;
|
|
124
|
+
const operationId = rawOperationId.startsWith(`${requestId}:`)
|
|
125
|
+
? rawOperationId
|
|
126
|
+
: `${requestId}:${rawOperationId}`;
|
|
127
|
+
const traceparentValue =
|
|
128
|
+
operationContext?.traceparent ||
|
|
129
|
+
(typeof firstHeaderValue(traceparent) === 'string'
|
|
130
|
+
? String(firstHeaderValue(traceparent))
|
|
131
|
+
: undefined);
|
|
132
|
+
const parsedTraceContext =
|
|
133
|
+
operationContext?.traceId && operationContext?.spanId
|
|
134
|
+
? {
|
|
135
|
+
traceId: operationContext.traceId,
|
|
136
|
+
spanId: operationContext.spanId,
|
|
137
|
+
}
|
|
138
|
+
: parseTraceparentValue(traceparentValue);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
requestId,
|
|
142
|
+
operationId,
|
|
143
|
+
routePath,
|
|
144
|
+
method: operationMethod,
|
|
145
|
+
...(operationContext?.schemaHash
|
|
146
|
+
? { schemaHash: operationContext.schemaHash }
|
|
147
|
+
: {}),
|
|
148
|
+
...(typeof operationContext?.operationVersion === 'number'
|
|
149
|
+
? { operationVersion: operationContext.operationVersion }
|
|
150
|
+
: {}),
|
|
151
|
+
...(traceparentValue ? { traceparent: traceparentValue } : {}),
|
|
152
|
+
...(parsedTraceContext
|
|
153
|
+
? {
|
|
154
|
+
traceId: parsedTraceContext.traceId,
|
|
155
|
+
spanId: parsedTraceContext.spanId,
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type OperationContextPayload = ReturnType<typeof buildOperationContext>;
|
|
162
|
+
|
|
163
|
+
export class ProducerClientNotInitializedError extends Error {
|
|
164
|
+
readonly code = 'BFF_PRODUCER_CLIENT_NOT_INITIALIZED';
|
|
165
|
+
|
|
166
|
+
constructor(requestId: string) {
|
|
167
|
+
super(
|
|
168
|
+
`Producer client "${requestId}" is not initialized. Call initProducerClient() (or configure()) before using generated APIs for this requestId.`,
|
|
169
|
+
);
|
|
170
|
+
this.name = 'ProducerClientNotInitializedError';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class ProducerDomainNotConfiguredError extends Error {
|
|
175
|
+
readonly code = 'BFF_PRODUCER_DOMAIN_NOT_CONFIGURED';
|
|
176
|
+
|
|
177
|
+
constructor(requestId: string) {
|
|
178
|
+
super(
|
|
179
|
+
`Producer client "${requestId}" must provide setDomain() during configure().`,
|
|
180
|
+
);
|
|
181
|
+
this.name = 'ProducerDomainNotConfiguredError';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export class CrossOriginEnvelopePolicyError extends Error {
|
|
186
|
+
readonly code = 'BFF_CROSS_ORIGIN_ENVELOPE_NOT_ALLOWED';
|
|
187
|
+
|
|
188
|
+
constructor(requestId: string, sourceOrigin?: string, targetOrigin?: string) {
|
|
189
|
+
super(
|
|
190
|
+
`Cross-origin envelope is not allowed for producer "${requestId}" (${sourceOrigin || 'unknown-origin'} -> ${targetOrigin || 'unknown-origin'}). Configure allowCrossOriginEnvelope to explicitly allow this flow.`,
|
|
191
|
+
);
|
|
192
|
+
this.name = 'CrossOriginEnvelopePolicyError';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class IdentityBindingViolationError extends Error {
|
|
197
|
+
readonly code = 'BFF_IDENTITY_BINDING_VIOLATION';
|
|
198
|
+
|
|
199
|
+
readonly violation: IdentityBindingViolation;
|
|
200
|
+
|
|
201
|
+
constructor(violation: IdentityBindingViolation) {
|
|
202
|
+
super(
|
|
203
|
+
`Identity header "${violation.header}" for producer "${violation.requestId}" was rejected by server-derived identity binding.`,
|
|
204
|
+
);
|
|
205
|
+
this.name = 'IdentityBindingViolationError';
|
|
206
|
+
this.violation = violation;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class OperationContractViolationError extends Error {
|
|
211
|
+
readonly code = 'BFF_OPERATION_CONTRACT_VIOLATION';
|
|
212
|
+
|
|
213
|
+
readonly violation: OperationContractViolation;
|
|
214
|
+
|
|
215
|
+
constructor(violation: OperationContractViolation) {
|
|
216
|
+
super(
|
|
217
|
+
`Operation contract violation "${violation.reason}" for producer "${violation.requestId}" operation "${violation.operationId}".`,
|
|
218
|
+
);
|
|
219
|
+
this.name = 'OperationContractViolationError';
|
|
220
|
+
this.violation = violation;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const validateOperationContract = ({
|
|
225
|
+
requestId,
|
|
226
|
+
target,
|
|
227
|
+
contextPayload,
|
|
228
|
+
operationContract,
|
|
229
|
+
}: {
|
|
230
|
+
requestId: string;
|
|
231
|
+
target: TransportTarget;
|
|
232
|
+
contextPayload: OperationContextPayload;
|
|
233
|
+
operationContract: OperationContractOptions | undefined;
|
|
234
|
+
}) => {
|
|
235
|
+
const operationContractEnabled =
|
|
236
|
+
operationContract?.enabled ?? isSecuredRequestId(requestId);
|
|
237
|
+
|
|
238
|
+
if (!operationContractEnabled) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const strict = operationContract?.strict ?? true;
|
|
243
|
+
const requireSchemaHash = operationContract?.requireSchemaHash ?? true;
|
|
244
|
+
const requireOperationVersion =
|
|
245
|
+
operationContract?.requireOperationVersion ?? true;
|
|
246
|
+
|
|
247
|
+
const maybeReportViolation = (
|
|
248
|
+
reason: OperationContractViolation['reason'],
|
|
249
|
+
) => {
|
|
250
|
+
const violation: OperationContractViolation = {
|
|
251
|
+
requestId,
|
|
252
|
+
target,
|
|
253
|
+
operationId: contextPayload.operationId,
|
|
254
|
+
routePath: contextPayload.routePath,
|
|
255
|
+
method: contextPayload.method,
|
|
256
|
+
schemaHash:
|
|
257
|
+
typeof contextPayload.schemaHash === 'string'
|
|
258
|
+
? contextPayload.schemaHash
|
|
259
|
+
: undefined,
|
|
260
|
+
operationVersion:
|
|
261
|
+
typeof contextPayload.operationVersion === 'number'
|
|
262
|
+
? contextPayload.operationVersion
|
|
263
|
+
: undefined,
|
|
264
|
+
reason,
|
|
265
|
+
};
|
|
266
|
+
operationContract?.onViolation?.(violation);
|
|
267
|
+
if (strict) {
|
|
268
|
+
throw new OperationContractViolationError(violation);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (requireSchemaHash && typeof contextPayload.schemaHash !== 'string') {
|
|
273
|
+
maybeReportViolation('missing_schema_hash');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (
|
|
277
|
+
requireOperationVersion &&
|
|
278
|
+
typeof contextPayload.operationVersion !== 'number'
|
|
279
|
+
) {
|
|
280
|
+
maybeReportViolation('missing_operation_version');
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const resolveConfiguredRequest = <F>(
|
|
285
|
+
configuredRequests: Map<string, F>,
|
|
286
|
+
requestId: string,
|
|
287
|
+
fallback: F,
|
|
288
|
+
): F => {
|
|
289
|
+
const configuredRequest = configuredRequests.get(requestId);
|
|
290
|
+
if (configuredRequest) {
|
|
291
|
+
return configuredRequest;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (requestId !== 'default') {
|
|
295
|
+
throw new ProducerClientNotInitializedError(requestId);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return fallback;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Builds the JSON value for the `x-modernjs-bff-envelope` header and throws
|
|
303
|
+
* when the cross-origin envelope policy denies the flow. Returns the
|
|
304
|
+
* serialized envelope.
|
|
305
|
+
*/
|
|
306
|
+
export const buildEnvelopeHeaderValue = ({
|
|
307
|
+
requestId,
|
|
308
|
+
target,
|
|
309
|
+
sourceOrigin,
|
|
310
|
+
targetOrigin,
|
|
311
|
+
traceContext,
|
|
312
|
+
allowCrossOriginEnvelope,
|
|
313
|
+
}: {
|
|
314
|
+
requestId: string;
|
|
315
|
+
target: TransportTarget;
|
|
316
|
+
sourceOrigin: string | undefined;
|
|
317
|
+
targetOrigin: string | undefined;
|
|
318
|
+
traceContext: { traceId: string; spanId: string } | null | undefined;
|
|
319
|
+
allowCrossOriginEnvelope:
|
|
320
|
+
| boolean
|
|
321
|
+
| ((options: {
|
|
322
|
+
requestId: string;
|
|
323
|
+
sourceOrigin?: string;
|
|
324
|
+
targetOrigin?: string;
|
|
325
|
+
target: TransportTarget;
|
|
326
|
+
}) => boolean)
|
|
327
|
+
| undefined;
|
|
328
|
+
}) => {
|
|
329
|
+
const isCrossOrigin =
|
|
330
|
+
Boolean(sourceOrigin) &&
|
|
331
|
+
Boolean(targetOrigin) &&
|
|
332
|
+
sourceOrigin !== targetOrigin;
|
|
333
|
+
if (isCrossOrigin) {
|
|
334
|
+
const isAllowed =
|
|
335
|
+
typeof allowCrossOriginEnvelope === 'function'
|
|
336
|
+
? allowCrossOriginEnvelope({
|
|
337
|
+
requestId,
|
|
338
|
+
sourceOrigin,
|
|
339
|
+
targetOrigin,
|
|
340
|
+
target,
|
|
341
|
+
}) === true
|
|
342
|
+
: allowCrossOriginEnvelope === true;
|
|
343
|
+
if (!isAllowed) {
|
|
344
|
+
throw new CrossOriginEnvelopePolicyError(
|
|
345
|
+
requestId,
|
|
346
|
+
sourceOrigin,
|
|
347
|
+
targetOrigin,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return JSON.stringify({
|
|
353
|
+
requestId,
|
|
354
|
+
target,
|
|
355
|
+
timestamp: Date.now(),
|
|
356
|
+
sourceOrigin,
|
|
357
|
+
targetOrigin,
|
|
358
|
+
...(traceContext
|
|
359
|
+
? {
|
|
360
|
+
traceId: traceContext.traceId,
|
|
361
|
+
spanId: traceContext.spanId,
|
|
362
|
+
}
|
|
363
|
+
: {}),
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Derives the operation context for a secured requestId, validates it against
|
|
369
|
+
* the configured operation contract and writes the `x-operation-id` and
|
|
370
|
+
* `x-modernjs-bff-operation-context` headers (without clobbering caller
|
|
371
|
+
* provided values for the id header).
|
|
372
|
+
*/
|
|
373
|
+
export const attachOperationContextHeaders = ({
|
|
374
|
+
headers,
|
|
375
|
+
requestId,
|
|
376
|
+
target,
|
|
377
|
+
method,
|
|
378
|
+
path,
|
|
379
|
+
operationContext,
|
|
380
|
+
operationContract,
|
|
381
|
+
operationContextHeader,
|
|
382
|
+
operationContextDetailHeader,
|
|
383
|
+
}: {
|
|
384
|
+
headers: Record<string, any>;
|
|
385
|
+
requestId: string;
|
|
386
|
+
target: TransportTarget;
|
|
387
|
+
method: string;
|
|
388
|
+
path: string;
|
|
389
|
+
operationContext: OperationContext | undefined;
|
|
390
|
+
operationContract: OperationContractOptions | undefined;
|
|
391
|
+
operationContextHeader: string;
|
|
392
|
+
operationContextDetailHeader: string;
|
|
393
|
+
}) => {
|
|
394
|
+
if (
|
|
395
|
+
typeof readHeader(headers, TRACEPARENT_HEADER) === 'undefined' &&
|
|
396
|
+
operationContext?.traceparent
|
|
397
|
+
) {
|
|
398
|
+
writeHeader(headers, TRACEPARENT_HEADER, operationContext.traceparent);
|
|
399
|
+
}
|
|
400
|
+
const contextPayload = buildOperationContext({
|
|
401
|
+
requestId,
|
|
402
|
+
method,
|
|
403
|
+
path,
|
|
404
|
+
operationContext,
|
|
405
|
+
traceparent: readHeader(headers, TRACEPARENT_HEADER),
|
|
406
|
+
});
|
|
407
|
+
validateOperationContract({
|
|
408
|
+
requestId,
|
|
409
|
+
target,
|
|
410
|
+
contextPayload,
|
|
411
|
+
operationContract,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
if (typeof readHeader(headers, operationContextHeader) === 'undefined') {
|
|
415
|
+
writeHeader(headers, operationContextHeader, contextPayload.operationId);
|
|
416
|
+
}
|
|
417
|
+
writeHeader(
|
|
418
|
+
headers,
|
|
419
|
+
operationContextDetailHeader,
|
|
420
|
+
JSON.stringify(contextPayload),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return contextPayload;
|
|
424
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { parseTraceparent } from './traceparent';
|
|
2
|
+
import type { OperationContext } from './types';
|
|
3
|
+
|
|
4
|
+
export const BFF_LOCALE_HEADER = 'accept-language';
|
|
5
|
+
export const BFF_TRACEPARENT_HEADER = 'traceparent';
|
|
6
|
+
|
|
7
|
+
export type RequestContextInput = {
|
|
8
|
+
headers?: Record<string, unknown>;
|
|
9
|
+
locale?: string;
|
|
10
|
+
traceparent?: string;
|
|
11
|
+
operationContext?: OperationContext;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type RequestContextSnapshot = {
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
locale?: string;
|
|
17
|
+
traceparent?: string;
|
|
18
|
+
traceId?: string;
|
|
19
|
+
spanId?: string;
|
|
20
|
+
operationContext?: OperationContext;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const readHeader = (
|
|
24
|
+
headers: Record<string, unknown> | undefined,
|
|
25
|
+
header: string,
|
|
26
|
+
) => {
|
|
27
|
+
if (!headers) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalized = header.toLowerCase();
|
|
32
|
+
const key = Object.keys(headers).find(
|
|
33
|
+
current => current.toLowerCase() === normalized,
|
|
34
|
+
);
|
|
35
|
+
if (!key) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const value = headers[key];
|
|
40
|
+
return Array.isArray(value) ? value[0] : value;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const readString = (value: unknown) =>
|
|
44
|
+
typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
45
|
+
|
|
46
|
+
function createOperationContextSnapshot(
|
|
47
|
+
operationContext: OperationContext | undefined,
|
|
48
|
+
safeContext: Pick<
|
|
49
|
+
OperationContext,
|
|
50
|
+
'locale' | 'traceparent' | 'traceId' | 'spanId'
|
|
51
|
+
>,
|
|
52
|
+
): OperationContext | undefined {
|
|
53
|
+
if (!operationContext) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const snapshot: OperationContext = {
|
|
58
|
+
...operationContext,
|
|
59
|
+
...(safeContext.locale || operationContext?.locale
|
|
60
|
+
? { locale: safeContext.locale || operationContext?.locale }
|
|
61
|
+
: {}),
|
|
62
|
+
...(safeContext.traceparent || operationContext?.traceparent
|
|
63
|
+
? {
|
|
64
|
+
traceparent: safeContext.traceparent || operationContext?.traceparent,
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
...(safeContext.traceId || operationContext?.traceId
|
|
68
|
+
? { traceId: safeContext.traceId || operationContext?.traceId }
|
|
69
|
+
: {}),
|
|
70
|
+
...(safeContext.spanId || operationContext?.spanId
|
|
71
|
+
? { spanId: safeContext.spanId || operationContext?.spanId }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return Object.keys(snapshot).some(
|
|
76
|
+
key => typeof snapshot[key as keyof OperationContext] !== 'undefined',
|
|
77
|
+
)
|
|
78
|
+
? snapshot
|
|
79
|
+
: undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createRequestContextSnapshot(
|
|
83
|
+
input: RequestContextInput = {},
|
|
84
|
+
): RequestContextSnapshot {
|
|
85
|
+
const locale =
|
|
86
|
+
readString(input.locale) ||
|
|
87
|
+
readString(readHeader(input.headers, BFF_LOCALE_HEADER));
|
|
88
|
+
const traceparent =
|
|
89
|
+
readString(input.traceparent) ||
|
|
90
|
+
readString(input.operationContext?.traceparent) ||
|
|
91
|
+
readString(readHeader(input.headers, BFF_TRACEPARENT_HEADER));
|
|
92
|
+
const parsedTraceparent =
|
|
93
|
+
input.operationContext?.traceId && input.operationContext?.spanId
|
|
94
|
+
? {
|
|
95
|
+
traceId: input.operationContext.traceId,
|
|
96
|
+
spanId: input.operationContext.spanId,
|
|
97
|
+
}
|
|
98
|
+
: parseTraceparent(traceparent);
|
|
99
|
+
|
|
100
|
+
const headers: Record<string, string> = {};
|
|
101
|
+
if (locale) {
|
|
102
|
+
headers[BFF_LOCALE_HEADER] = locale;
|
|
103
|
+
}
|
|
104
|
+
if (traceparent) {
|
|
105
|
+
headers[BFF_TRACEPARENT_HEADER] = traceparent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const operationContext = createOperationContextSnapshot(
|
|
109
|
+
input.operationContext,
|
|
110
|
+
{
|
|
111
|
+
...(locale ? { locale } : {}),
|
|
112
|
+
...(traceparent ? { traceparent } : {}),
|
|
113
|
+
...(parsedTraceparent
|
|
114
|
+
? {
|
|
115
|
+
traceId: parsedTraceparent.traceId,
|
|
116
|
+
spanId: parsedTraceparent.spanId,
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
headers,
|
|
124
|
+
...(locale ? { locale } : {}),
|
|
125
|
+
...(traceparent ? { traceparent } : {}),
|
|
126
|
+
...(parsedTraceparent
|
|
127
|
+
? {
|
|
128
|
+
traceId: parsedTraceparent.traceId,
|
|
129
|
+
spanId: parsedTraceparent.spanId,
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
...(operationContext ? { operationContext } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createRequestContextHeaders(
|
|
137
|
+
input: RequestContextInput = {},
|
|
138
|
+
): Record<string, string> {
|
|
139
|
+
return createRequestContextSnapshot(input).headers;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { parseTraceparent, type TraceparentContext } from './traceparent';
|
|
143
|
+
export type * from './types';
|
|
144
|
+
export {
|
|
145
|
+
BFF_DEFAULT_PROTECTED_IDENTITY_HEADERS,
|
|
146
|
+
BFF_ENVELOPE_HEADER,
|
|
147
|
+
BFF_OPERATION_CONTEXT_DETAIL_HEADER,
|
|
148
|
+
BFF_OPERATION_CONTEXT_HEADER,
|
|
149
|
+
} from './types';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
|
|
2
|
+
|
|
3
|
+
export type TraceparentContext = {
|
|
4
|
+
traceId: string;
|
|
5
|
+
spanId: string;
|
|
6
|
+
sampled: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const isAllZeroHex = (value: string) => /^0+$/.test(value);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse a W3C trace context `traceparent` header (version 00).
|
|
13
|
+
* All-zero trace ids and span ids are rejected per the W3C spec.
|
|
14
|
+
*/
|
|
15
|
+
export function parseTraceparent(
|
|
16
|
+
traceparent: string | undefined | null,
|
|
17
|
+
): TraceparentContext | undefined {
|
|
18
|
+
if (!traceparent) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const match = traceparent.trim().match(TRACEPARENT_REGEX);
|
|
23
|
+
if (!match) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [, rawTraceId, rawSpanId, rawFlags] = match;
|
|
28
|
+
if (!rawTraceId || !rawSpanId || !rawFlags) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const traceId = rawTraceId.toLowerCase();
|
|
33
|
+
const spanId = rawSpanId.toLowerCase();
|
|
34
|
+
if (isAllZeroHex(traceId) || isAllZeroHex(spanId)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
traceId,
|
|
40
|
+
spanId,
|
|
41
|
+
sampled: (Number.parseInt(rawFlags, 16) & 0x1) === 1,
|
|
42
|
+
};
|
|
43
|
+
}
|