@edynamix/mf-isolation 0.0.17 → 0.0.19
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/README.md +16 -0
- package/dist/host-contract.d.ts +26 -0
- package/dist/host-contract.d.ts.map +1 -0
- package/dist/host-contract.js +12 -0
- package/dist/host-contract.js.map +1 -0
- package/dist/navigation.d.ts +0 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -3
- package/dist/navigation.js.map +1 -1
- package/dist/vite-development.d.ts.map +1 -1
- package/dist/vite-development.js +14 -2
- package/dist/vite-development.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -60,6 +60,21 @@ The Angular-aware plugin uses `:host-context([data-edyn-mf="<scope-id>"])` for E
|
|
|
60
60
|
|
|
61
61
|
Run this plugin through Vite/PostCSS before Angular AOT compilation. Do not combine it with `scopeRemoteCss` in the same Vite configuration.
|
|
62
62
|
|
|
63
|
+
## Host contract
|
|
64
|
+
|
|
65
|
+
The host passes an `IMfShellContext` to a module's `mount` function and expects an `IMfModuleLifecycle` export. Check the host version with `isSupportedMfHostContract`: it accepts `MF_MINIMUM_HOST_CONTRACT_VERSION` and every later version, so additive contract changes do not need a lockstep release of the host and all modules.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { type IMfShellContext, isSupportedMfHostContract, MF_MINIMUM_HOST_CONTRACT_VERSION } from '@edynamix/mf-isolation/host-contract';
|
|
69
|
+
|
|
70
|
+
export const mount = (element: HTMLElement, shellContext: IMfShellContext): void => {
|
|
71
|
+
if (!isSupportedMfHostContract(shellContext)) {
|
|
72
|
+
throw new Error(`This module requires host contract version ${MF_MINIMUM_HOST_CONTRACT_VERSION} or later.`);
|
|
73
|
+
}
|
|
74
|
+
// ...
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
63
78
|
## Navigation manifest
|
|
64
79
|
|
|
65
80
|
Declare module-owned navigation as validated data and emit `navigation.json` from the same Vite build:
|
|
@@ -176,4 +191,5 @@ export const mount = async (host: HTMLElement, context: IShellContext): Promise<
|
|
|
176
191
|
- `@edynamix/mf-isolation`
|
|
177
192
|
- `@edynamix/mf-isolation/angular`
|
|
178
193
|
- `@edynamix/mf-isolation/angular-runtime`
|
|
194
|
+
- `@edynamix/mf-isolation/host-contract`
|
|
179
195
|
- `@edynamix/mf-isolation/postcss`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const MF_HOST_CONTRACT_VERSION: 1;
|
|
2
|
+
export declare const MF_MINIMUM_HOST_CONTRACT_VERSION = 1;
|
|
3
|
+
export type TMfThemeTokens = Record<string, string>;
|
|
4
|
+
export interface IMfModuleNavigationContext {
|
|
5
|
+
readonly groupId: number;
|
|
6
|
+
readonly dealerIds: readonly number[];
|
|
7
|
+
readonly dealerId?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IMfShellContext {
|
|
10
|
+
contractVersion: number;
|
|
11
|
+
dealer: string;
|
|
12
|
+
theme: TMfThemeTokens;
|
|
13
|
+
overlayRoot: HTMLElement;
|
|
14
|
+
styleRoot: ShadowRoot;
|
|
15
|
+
locale: string;
|
|
16
|
+
navigationContext?: IMfModuleNavigationContext;
|
|
17
|
+
refreshSession?: () => Promise<boolean>;
|
|
18
|
+
navigate: (path: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface IMfModuleLifecycle<TShellContext extends IMfShellContext = IMfShellContext> {
|
|
21
|
+
preload: () => void | Promise<void>;
|
|
22
|
+
mount: (element: HTMLElement, context: TShellContext) => void | Promise<void>;
|
|
23
|
+
unmount: (element: HTMLElement) => void | Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare const isSupportedMfHostContract: (context: unknown) => context is IMfShellContext;
|
|
26
|
+
//# sourceMappingURL=host-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-contract.d.ts","sourceRoot":"","sources":["../src/host-contract.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,EAAG,CAAU,CAAC;AACnD,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEpD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,0BAA0B,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB,CAAC,aAAa,SAAS,eAAe,GAAG,eAAe;IACzF,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,eAAO,MAAM,yBAAyB,GAAI,SAAS,OAAO,KAAG,OAAO,IAAI,eAOvE,CAAC","sourcesContent":["// The contract between the eDynamix host and a hosted module. Remotes accept any host at or above the minimum\n// version, so additive changes ship without a lockstep release of the host and every module.\nexport const MF_HOST_CONTRACT_VERSION = 1 as const;\nexport const MF_MINIMUM_HOST_CONTRACT_VERSION = 1;\n\nexport type TMfThemeTokens = Record<string, string>;\n\nexport interface IMfModuleNavigationContext {\n readonly groupId: number;\n readonly dealerIds: readonly number[];\n readonly dealerId?: number;\n}\n\nexport interface IMfShellContext {\n contractVersion: number;\n dealer: string;\n theme: TMfThemeTokens;\n overlayRoot: HTMLElement;\n styleRoot: ShadowRoot;\n locale: string;\n navigationContext?: IMfModuleNavigationContext;\n refreshSession?: () => Promise<boolean>;\n navigate: (path: string) => void;\n}\n\nexport interface IMfModuleLifecycle<TShellContext extends IMfShellContext = IMfShellContext> {\n preload: () => void | Promise<void>;\n mount: (element: HTMLElement, context: TShellContext) => void | Promise<void>;\n unmount: (element: HTMLElement) => void | Promise<void>;\n}\n\nexport const isSupportedMfHostContract = (context: unknown): context is IMfShellContext => {\n if (typeof context !== 'object' || context === null || !('contractVersion' in context)) {\n return false;\n }\n\n const { contractVersion } = context;\n return typeof contractVersion === 'number' && Number.isSafeInteger(contractVersion) && contractVersion >= MF_MINIMUM_HOST_CONTRACT_VERSION;\n};\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The contract between the eDynamix host and a hosted module. Remotes accept any host at or above the minimum
|
|
2
|
+
// version, so additive changes ship without a lockstep release of the host and every module.
|
|
3
|
+
export const MF_HOST_CONTRACT_VERSION = 1;
|
|
4
|
+
export const MF_MINIMUM_HOST_CONTRACT_VERSION = 1;
|
|
5
|
+
export const isSupportedMfHostContract = (context) => {
|
|
6
|
+
if (typeof context !== 'object' || context === null || !('contractVersion' in context)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const { contractVersion } = context;
|
|
10
|
+
return typeof contractVersion === 'number' && Number.isSafeInteger(contractVersion) && contractVersion >= MF_MINIMUM_HOST_CONTRACT_VERSION;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=host-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-contract.js","sourceRoot":"","sources":["../src/host-contract.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,6FAA6F;AAC7F,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAU,CAAC;AACnD,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC;AA4BlD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAgB,EAA8B,EAAE;IACxF,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,iBAAiB,IAAI,OAAO,CAAC,EAAE,CAAC;QACvF,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IACpC,OAAO,OAAO,eAAe,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,gCAAgC,CAAC;AAC7I,CAAC,CAAC","sourcesContent":["// The contract between the eDynamix host and a hosted module. Remotes accept any host at or above the minimum\n// version, so additive changes ship without a lockstep release of the host and every module.\nexport const MF_HOST_CONTRACT_VERSION = 1 as const;\nexport const MF_MINIMUM_HOST_CONTRACT_VERSION = 1;\n\nexport type TMfThemeTokens = Record<string, string>;\n\nexport interface IMfModuleNavigationContext {\n readonly groupId: number;\n readonly dealerIds: readonly number[];\n readonly dealerId?: number;\n}\n\nexport interface IMfShellContext {\n contractVersion: number;\n dealer: string;\n theme: TMfThemeTokens;\n overlayRoot: HTMLElement;\n styleRoot: ShadowRoot;\n locale: string;\n navigationContext?: IMfModuleNavigationContext;\n refreshSession?: () => Promise<boolean>;\n navigate: (path: string) => void;\n}\n\nexport interface IMfModuleLifecycle<TShellContext extends IMfShellContext = IMfShellContext> {\n preload: () => void | Promise<void>;\n mount: (element: HTMLElement, context: TShellContext) => void | Promise<void>;\n unmount: (element: HTMLElement) => void | Promise<void>;\n}\n\nexport const isSupportedMfHostContract = (context: unknown): context is IMfShellContext => {\n if (typeof context !== 'object' || context === null || !('contractVersion' in context)) {\n return false;\n }\n\n const { contractVersion } = context;\n return typeof contractVersion === 'number' && Number.isSafeInteger(contractVersion) && contractVersion >= MF_MINIMUM_HOST_CONTRACT_VERSION;\n};\n"]}
|
package/dist/navigation.d.ts
CHANGED
package/dist/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AAMzD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,wBAAwB,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AAMzD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,wBAAwB,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,OAAO,8BAA8B,CAAC;IACvD,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACpE,QAAQ,EAAE,MAAM,CAAC;CAClB;AA4GD,eAAO,MAAM,kBAAkB,GAAI,OAAO,uBAAuB,KAAG,QAAQ,CAAC,uBAAuB,CAoCnG,CAAC","sourcesContent":["export const MF_NAVIGATION_CONTRACT_VERSION = 1 as const;\n\nconst IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst RELATIVE_ROUTE_PATTERN = /^(?!\\/)(?!.*(?:^|\\/)\\.\\.?(?:\\/|$))[a-zA-Z0-9][a-zA-Z0-9/_-]*$/;\n\nexport type TMfNavigationCounterTone = 'danger' | 'neutral' | 'success' | 'warning';\n\nexport interface IMfNavigationCounterReference {\n id: string;\n tone?: TMfNavigationCounterTone;\n}\n\nexport interface IMfNavigationItem {\n children?: readonly IMfNavigationItem[];\n counters?: readonly IMfNavigationCounterReference[];\n icon?: string;\n id: string;\n label: string;\n order: number;\n path?: string;\n}\n\nexport interface IMfNavigationDefinition {\n applicationId: number;\n contractVersion: typeof MF_NAVIGATION_CONTRACT_VERSION;\n items: readonly IMfNavigationItem[];\n label: string;\n landingPath: string;\n moduleId: string;\n}\n\nexport interface IMfNavigationManifest extends IMfNavigationDefinition {\n revision: string;\n}\n\nconst assertPlainObject = (value: unknown, location: string): Record<string, unknown> => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n return value as Record<string, unknown>;\n};\n\nconst assertExactKeys = (value: Record<string, unknown>, allowedKeys: readonly string[], location: string): void => {\n const allowed = new Set(allowedKeys);\n const unknownKey = Object.keys(value).find((key) => !allowed.has(key));\n if (unknownKey) {\n throw new Error(`${location} contains unsupported property \"${unknownKey}\".`);\n }\n};\n\nconst assertNonEmptyString = (value: unknown, location: string): string => {\n if (typeof value !== 'string' || value.trim() !== value || value.length === 0) {\n throw new Error(`${location} must be a non-empty trimmed string.`);\n }\n\n return value;\n};\n\nconst assertIdentifier = (value: unknown, location: string): string => {\n const identifier = assertNonEmptyString(value, location);\n if (!IDENTIFIER_PATTERN.test(identifier)) {\n throw new Error(`${location} must be a stable lowercase identifier.`);\n }\n\n return identifier;\n};\n\nconst normalizeCounter = (value: unknown, location: string): IMfNavigationCounterReference => {\n const counter = assertPlainObject(value, location);\n assertExactKeys(counter, ['id', 'tone'], location);\n\n const id = assertIdentifier(counter.id, `${location}.id`);\n const tone = counter.tone;\n if (tone !== undefined && tone !== 'danger' && tone !== 'neutral' && tone !== 'success' && tone !== 'warning') {\n throw new Error(`${location}.tone is not supported.`);\n }\n\n return Object.freeze(tone === undefined ? { id } : { id, tone });\n};\n\nconst normalizeItems = (values: unknown, location: string, seenIds: Set<string>): readonly IMfNavigationItem[] => {\n if (!Array.isArray(values)) {\n throw new Error(`${location} must be an array.`);\n }\n\n const items = values.map((value, index) => {\n const itemLocation = `${location}[${index}]`;\n const item = assertPlainObject(value, itemLocation);\n assertExactKeys(item, ['children', 'counters', 'icon', 'id', 'label', 'order', 'path'], itemLocation);\n\n const id = assertIdentifier(item.id, `${itemLocation}.id`);\n if (seenIds.has(id)) {\n throw new Error(`${itemLocation}.id must be unique within the navigation tree.`);\n }\n seenIds.add(id);\n\n const label = assertNonEmptyString(item.label, `${itemLocation}.label`);\n if (!Number.isSafeInteger(item.order) || (item.order as number) < 0) {\n throw new Error(`${itemLocation}.order must be a non-negative safe integer.`);\n }\n\n const path = item.path;\n if (path !== undefined && (typeof path !== 'string' || !RELATIVE_ROUTE_PATTERN.test(path))) {\n throw new Error(`${itemLocation}.path must be an origin-safe module-relative route.`);\n }\n\n const icon = item.icon === undefined ? undefined : assertIdentifier(item.icon, `${itemLocation}.icon`);\n\n if (item.counters !== undefined && !Array.isArray(item.counters)) {\n throw new Error(`${itemLocation}.counters must be an array.`);\n }\n const counters = item.counters?.map((counter, counterIndex) => normalizeCounter(counter, `${itemLocation}.counters[${counterIndex}]`));\n if (counters && new Set(counters.map((counter) => counter.id)).size !== counters.length) {\n throw new Error(`${itemLocation}.counters contains duplicate counter ids.`);\n }\n\n const children = item.children === undefined ? undefined : normalizeItems(item.children, `${itemLocation}.children`, seenIds);\n if (path === undefined && (!children || children.length === 0)) {\n throw new Error(`${itemLocation} must define a path or at least one child.`);\n }\n\n return Object.freeze({\n ...(children === undefined ? {} : { children }),\n ...(counters === undefined ? {} : { counters }),\n ...(icon === undefined ? {} : { icon }),\n id,\n label,\n order: item.order as number,\n ...(path === undefined ? {} : { path }),\n });\n });\n\n return Object.freeze(items.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id)));\n};\n\nexport const defineMfNavigation = (value: IMfNavigationDefinition): Readonly<IMfNavigationDefinition> => {\n const definition = assertPlainObject(value, 'navigation');\n assertExactKeys(definition, ['applicationId', 'contractVersion', 'items', 'label', 'landingPath', 'moduleId'], 'navigation');\n\n if (definition.contractVersion !== MF_NAVIGATION_CONTRACT_VERSION) {\n throw new Error(`navigation.contractVersion must be ${MF_NAVIGATION_CONTRACT_VERSION}.`);\n }\n\n if (!Number.isSafeInteger(definition.applicationId) || (definition.applicationId as number) <= 0) {\n throw new Error('navigation.applicationId must be a positive safe integer.');\n }\n\n const moduleId = assertNonEmptyString(definition.moduleId, 'navigation.moduleId');\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('navigation.moduleId must be a lowercase module identifier.');\n }\n\n const label = assertNonEmptyString(definition.label, 'navigation.label');\n const landingPath = definition.landingPath;\n if (typeof landingPath !== 'string' || !RELATIVE_ROUTE_PATTERN.test(landingPath)) {\n throw new Error('navigation.landingPath must be an origin-safe module-relative route.');\n }\n\n const items = normalizeItems(definition.items, 'navigation.items', new Set());\n if (items.length === 0) {\n throw new Error('navigation.items must contain at least one item.');\n }\n\n return Object.freeze({\n applicationId: definition.applicationId as number,\n contractVersion: MF_NAVIGATION_CONTRACT_VERSION,\n items,\n label,\n landingPath,\n moduleId,\n });\n};\n"]}
|
package/dist/navigation.js
CHANGED
|
@@ -49,7 +49,7 @@ const normalizeItems = (values, location, seenIds) => {
|
|
|
49
49
|
const items = values.map((value, index) => {
|
|
50
50
|
const itemLocation = `${location}[${index}]`;
|
|
51
51
|
const item = assertPlainObject(value, itemLocation);
|
|
52
|
-
assertExactKeys(item, ['
|
|
52
|
+
assertExactKeys(item, ['children', 'counters', 'icon', 'id', 'label', 'order', 'path'], itemLocation);
|
|
53
53
|
const id = assertIdentifier(item.id, `${itemLocation}.id`);
|
|
54
54
|
if (seenIds.has(id)) {
|
|
55
55
|
throw new Error(`${itemLocation}.id must be unique within the navigation tree.`);
|
|
@@ -64,7 +64,6 @@ const normalizeItems = (values, location, seenIds) => {
|
|
|
64
64
|
throw new Error(`${itemLocation}.path must be an origin-safe module-relative route.`);
|
|
65
65
|
}
|
|
66
66
|
const icon = item.icon === undefined ? undefined : assertIdentifier(item.icon, `${itemLocation}.icon`);
|
|
67
|
-
const accessKey = item.accessKey === undefined ? undefined : assertIdentifier(item.accessKey, `${itemLocation}.accessKey`);
|
|
68
67
|
if (item.counters !== undefined && !Array.isArray(item.counters)) {
|
|
69
68
|
throw new Error(`${itemLocation}.counters must be an array.`);
|
|
70
69
|
}
|
|
@@ -77,7 +76,6 @@ const normalizeItems = (values, location, seenIds) => {
|
|
|
77
76
|
throw new Error(`${itemLocation} must define a path or at least one child.`);
|
|
78
77
|
}
|
|
79
78
|
return Object.freeze({
|
|
80
|
-
...(accessKey === undefined ? {} : { accessKey }),
|
|
81
79
|
...(children === undefined ? {} : { children }),
|
|
82
80
|
...(counters === undefined ? {} : { counters }),
|
|
83
81
|
...(icon === undefined ? {} : { icon }),
|
package/dist/navigation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"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","sourcesContent":["export const MF_NAVIGATION_CONTRACT_VERSION = 1 as const;\n\nconst IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst RELATIVE_ROUTE_PATTERN = /^(?!\\/)(?!.*(?:^|\\/)\\.\\.?(?:\\/|$))[a-zA-Z0-9][a-zA-Z0-9/_-]*$/;\n\nexport type TMfNavigationCounterTone = 'danger' | 'neutral' | 'success' | 'warning';\n\nexport interface IMfNavigationCounterReference {\n id: string;\n tone?: TMfNavigationCounterTone;\n}\n\nexport interface IMfNavigationItem {\n accessKey?: string;\n children?: readonly IMfNavigationItem[];\n counters?: readonly IMfNavigationCounterReference[];\n icon?: string;\n id: string;\n label: string;\n order: number;\n path?: string;\n}\n\nexport interface IMfNavigationDefinition {\n applicationId: number;\n contractVersion: typeof MF_NAVIGATION_CONTRACT_VERSION;\n items: readonly IMfNavigationItem[];\n label: string;\n landingPath: string;\n moduleId: string;\n}\n\nexport interface IMfNavigationManifest extends IMfNavigationDefinition {\n revision: string;\n}\n\nconst assertPlainObject = (value: unknown, location: string): Record<string, unknown> => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n return value as Record<string, unknown>;\n};\n\nconst assertExactKeys = (value: Record<string, unknown>, allowedKeys: readonly string[], location: string): void => {\n const allowed = new Set(allowedKeys);\n const unknownKey = Object.keys(value).find((key) => !allowed.has(key));\n if (unknownKey) {\n throw new Error(`${location} contains unsupported property \"${unknownKey}\".`);\n }\n};\n\nconst assertNonEmptyString = (value: unknown, location: string): string => {\n if (typeof value !== 'string' || value.trim() !== value || value.length === 0) {\n throw new Error(`${location} must be a non-empty trimmed string.`);\n }\n\n return value;\n};\n\nconst assertIdentifier = (value: unknown, location: string): string => {\n const identifier = assertNonEmptyString(value, location);\n if (!IDENTIFIER_PATTERN.test(identifier)) {\n throw new Error(`${location} must be a stable lowercase identifier.`);\n }\n\n return identifier;\n};\n\nconst normalizeCounter = (value: unknown, location: string): IMfNavigationCounterReference => {\n const counter = assertPlainObject(value, location);\n assertExactKeys(counter, ['id', 'tone'], location);\n\n const id = assertIdentifier(counter.id, `${location}.id`);\n const tone = counter.tone;\n if (tone !== undefined && tone !== 'danger' && tone !== 'neutral' && tone !== 'success' && tone !== 'warning') {\n throw new Error(`${location}.tone is not supported.`);\n }\n\n return Object.freeze(tone === undefined ? { id } : { id, tone });\n};\n\nconst normalizeItems = (values: unknown, location: string, seenIds: Set<string>): readonly IMfNavigationItem[] => {\n if (!Array.isArray(values)) {\n throw new Error(`${location} must be an array.`);\n }\n\n const items = values.map((value, index) => {\n const itemLocation = `${location}[${index}]`;\n const item = assertPlainObject(value, itemLocation);\n assertExactKeys(item, ['accessKey', 'children', 'counters', 'icon', 'id', 'label', 'order', 'path'], itemLocation);\n\n const id = assertIdentifier(item.id, `${itemLocation}.id`);\n if (seenIds.has(id)) {\n throw new Error(`${itemLocation}.id must be unique within the navigation tree.`);\n }\n seenIds.add(id);\n\n const label = assertNonEmptyString(item.label, `${itemLocation}.label`);\n if (!Number.isSafeInteger(item.order) || (item.order as number) < 0) {\n throw new Error(`${itemLocation}.order must be a non-negative safe integer.`);\n }\n\n const path = item.path;\n if (path !== undefined && (typeof path !== 'string' || !RELATIVE_ROUTE_PATTERN.test(path))) {\n throw new Error(`${itemLocation}.path must be an origin-safe module-relative route.`);\n }\n\n const icon = item.icon === undefined ? undefined : assertIdentifier(item.icon, `${itemLocation}.icon`);\n const accessKey = item.accessKey === undefined ? undefined : assertIdentifier(item.accessKey, `${itemLocation}.accessKey`);\n\n if (item.counters !== undefined && !Array.isArray(item.counters)) {\n throw new Error(`${itemLocation}.counters must be an array.`);\n }\n const counters = item.counters?.map((counter, counterIndex) => normalizeCounter(counter, `${itemLocation}.counters[${counterIndex}]`));\n if (counters && new Set(counters.map((counter) => counter.id)).size !== counters.length) {\n throw new Error(`${itemLocation}.counters contains duplicate counter ids.`);\n }\n\n const children = item.children === undefined ? undefined : normalizeItems(item.children, `${itemLocation}.children`, seenIds);\n if (path === undefined && (!children || children.length === 0)) {\n throw new Error(`${itemLocation} must define a path or at least one child.`);\n }\n\n return Object.freeze({\n ...(accessKey === undefined ? {} : { accessKey }),\n ...(children === undefined ? {} : { children }),\n ...(counters === undefined ? {} : { counters }),\n ...(icon === undefined ? {} : { icon }),\n id,\n label,\n order: item.order as number,\n ...(path === undefined ? {} : { path }),\n });\n });\n\n return Object.freeze(items.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id)));\n};\n\nexport const defineMfNavigation = (value: IMfNavigationDefinition): Readonly<IMfNavigationDefinition> => {\n const definition = assertPlainObject(value, 'navigation');\n assertExactKeys(definition, ['applicationId', 'contractVersion', 'items', 'label', 'landingPath', 'moduleId'], 'navigation');\n\n if (definition.contractVersion !== MF_NAVIGATION_CONTRACT_VERSION) {\n throw new Error(`navigation.contractVersion must be ${MF_NAVIGATION_CONTRACT_VERSION}.`);\n }\n\n if (!Number.isSafeInteger(definition.applicationId) || (definition.applicationId as number) <= 0) {\n throw new Error('navigation.applicationId must be a positive safe integer.');\n }\n\n const moduleId = assertNonEmptyString(definition.moduleId, 'navigation.moduleId');\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('navigation.moduleId must be a lowercase module identifier.');\n }\n\n const label = assertNonEmptyString(definition.label, 'navigation.label');\n const landingPath = definition.landingPath;\n if (typeof landingPath !== 'string' || !RELATIVE_ROUTE_PATTERN.test(landingPath)) {\n throw new Error('navigation.landingPath must be an origin-safe module-relative route.');\n }\n\n const items = normalizeItems(definition.items, 'navigation.items', new Set());\n if (items.length === 0) {\n throw new Error('navigation.items must contain at least one item.');\n }\n\n return Object.freeze({\n applicationId: definition.applicationId as number,\n contractVersion: MF_NAVIGATION_CONTRACT_VERSION,\n items,\n label,\n landingPath,\n moduleId,\n });\n};\n"]}
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"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","sourcesContent":["export const MF_NAVIGATION_CONTRACT_VERSION = 1 as const;\n\nconst IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst RELATIVE_ROUTE_PATTERN = /^(?!\\/)(?!.*(?:^|\\/)\\.\\.?(?:\\/|$))[a-zA-Z0-9][a-zA-Z0-9/_-]*$/;\n\nexport type TMfNavigationCounterTone = 'danger' | 'neutral' | 'success' | 'warning';\n\nexport interface IMfNavigationCounterReference {\n id: string;\n tone?: TMfNavigationCounterTone;\n}\n\nexport interface IMfNavigationItem {\n children?: readonly IMfNavigationItem[];\n counters?: readonly IMfNavigationCounterReference[];\n icon?: string;\n id: string;\n label: string;\n order: number;\n path?: string;\n}\n\nexport interface IMfNavigationDefinition {\n applicationId: number;\n contractVersion: typeof MF_NAVIGATION_CONTRACT_VERSION;\n items: readonly IMfNavigationItem[];\n label: string;\n landingPath: string;\n moduleId: string;\n}\n\nexport interface IMfNavigationManifest extends IMfNavigationDefinition {\n revision: string;\n}\n\nconst assertPlainObject = (value: unknown, location: string): Record<string, unknown> => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n throw new Error(`${location} must be a plain object.`);\n }\n\n return value as Record<string, unknown>;\n};\n\nconst assertExactKeys = (value: Record<string, unknown>, allowedKeys: readonly string[], location: string): void => {\n const allowed = new Set(allowedKeys);\n const unknownKey = Object.keys(value).find((key) => !allowed.has(key));\n if (unknownKey) {\n throw new Error(`${location} contains unsupported property \"${unknownKey}\".`);\n }\n};\n\nconst assertNonEmptyString = (value: unknown, location: string): string => {\n if (typeof value !== 'string' || value.trim() !== value || value.length === 0) {\n throw new Error(`${location} must be a non-empty trimmed string.`);\n }\n\n return value;\n};\n\nconst assertIdentifier = (value: unknown, location: string): string => {\n const identifier = assertNonEmptyString(value, location);\n if (!IDENTIFIER_PATTERN.test(identifier)) {\n throw new Error(`${location} must be a stable lowercase identifier.`);\n }\n\n return identifier;\n};\n\nconst normalizeCounter = (value: unknown, location: string): IMfNavigationCounterReference => {\n const counter = assertPlainObject(value, location);\n assertExactKeys(counter, ['id', 'tone'], location);\n\n const id = assertIdentifier(counter.id, `${location}.id`);\n const tone = counter.tone;\n if (tone !== undefined && tone !== 'danger' && tone !== 'neutral' && tone !== 'success' && tone !== 'warning') {\n throw new Error(`${location}.tone is not supported.`);\n }\n\n return Object.freeze(tone === undefined ? { id } : { id, tone });\n};\n\nconst normalizeItems = (values: unknown, location: string, seenIds: Set<string>): readonly IMfNavigationItem[] => {\n if (!Array.isArray(values)) {\n throw new Error(`${location} must be an array.`);\n }\n\n const items = values.map((value, index) => {\n const itemLocation = `${location}[${index}]`;\n const item = assertPlainObject(value, itemLocation);\n assertExactKeys(item, ['children', 'counters', 'icon', 'id', 'label', 'order', 'path'], itemLocation);\n\n const id = assertIdentifier(item.id, `${itemLocation}.id`);\n if (seenIds.has(id)) {\n throw new Error(`${itemLocation}.id must be unique within the navigation tree.`);\n }\n seenIds.add(id);\n\n const label = assertNonEmptyString(item.label, `${itemLocation}.label`);\n if (!Number.isSafeInteger(item.order) || (item.order as number) < 0) {\n throw new Error(`${itemLocation}.order must be a non-negative safe integer.`);\n }\n\n const path = item.path;\n if (path !== undefined && (typeof path !== 'string' || !RELATIVE_ROUTE_PATTERN.test(path))) {\n throw new Error(`${itemLocation}.path must be an origin-safe module-relative route.`);\n }\n\n const icon = item.icon === undefined ? undefined : assertIdentifier(item.icon, `${itemLocation}.icon`);\n\n if (item.counters !== undefined && !Array.isArray(item.counters)) {\n throw new Error(`${itemLocation}.counters must be an array.`);\n }\n const counters = item.counters?.map((counter, counterIndex) => normalizeCounter(counter, `${itemLocation}.counters[${counterIndex}]`));\n if (counters && new Set(counters.map((counter) => counter.id)).size !== counters.length) {\n throw new Error(`${itemLocation}.counters contains duplicate counter ids.`);\n }\n\n const children = item.children === undefined ? undefined : normalizeItems(item.children, `${itemLocation}.children`, seenIds);\n if (path === undefined && (!children || children.length === 0)) {\n throw new Error(`${itemLocation} must define a path or at least one child.`);\n }\n\n return Object.freeze({\n ...(children === undefined ? {} : { children }),\n ...(counters === undefined ? {} : { counters }),\n ...(icon === undefined ? {} : { icon }),\n id,\n label,\n order: item.order as number,\n ...(path === undefined ? {} : { path }),\n });\n });\n\n return Object.freeze(items.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id)));\n};\n\nexport const defineMfNavigation = (value: IMfNavigationDefinition): Readonly<IMfNavigationDefinition> => {\n const definition = assertPlainObject(value, 'navigation');\n assertExactKeys(definition, ['applicationId', 'contractVersion', 'items', 'label', 'landingPath', 'moduleId'], 'navigation');\n\n if (definition.contractVersion !== MF_NAVIGATION_CONTRACT_VERSION) {\n throw new Error(`navigation.contractVersion must be ${MF_NAVIGATION_CONTRACT_VERSION}.`);\n }\n\n if (!Number.isSafeInteger(definition.applicationId) || (definition.applicationId as number) <= 0) {\n throw new Error('navigation.applicationId must be a positive safe integer.');\n }\n\n const moduleId = assertNonEmptyString(definition.moduleId, 'navigation.moduleId');\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('navigation.moduleId must be a lowercase module identifier.');\n }\n\n const label = assertNonEmptyString(definition.label, 'navigation.label');\n const landingPath = definition.landingPath;\n if (typeof landingPath !== 'string' || !RELATIVE_ROUTE_PATTERN.test(landingPath)) {\n throw new Error('navigation.landingPath must be an origin-safe module-relative route.');\n }\n\n const items = normalizeItems(definition.items, 'navigation.items', new Set());\n if (items.length === 0) {\n throw new Error('navigation.items must contain at least one item.');\n }\n\n return Object.freeze({\n applicationId: definition.applicationId as number,\n contractVersion: MF_NAVIGATION_CONTRACT_VERSION,\n items,\n label,\n landingPath,\n moduleId,\n });\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-development.d.ts","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAQnC,eAAO,MAAM,mCAAmC,sCAAsC,CAAC;AAuEvF,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAwsBD,eAAO,MAAM,sBAAsB,GAAI,SAAS,8BAA8B,KAAG,MAWhF,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;AAExI,eAAO,MAAM,gCAAgC,GAAI,SAAS,8BAA8B,KAAG,2BAK1F,CAAC","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set(['/exsto-icon.svg', '/exsto-text.svg']);\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*\\/api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must use the canonical /module/api/ boundary.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"vite-development.d.ts","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAQnC,eAAO,MAAM,mCAAmC,sCAAsC,CAAC;AAyEvF,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AA0sBD,eAAO,MAAM,sBAAsB,GAAI,SAAS,8BAA8B,KAAG,MAmBhF,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;AAExI,eAAO,MAAM,gCAAgC,GAAI,SAAS,8BAA8B,KAAG,2BAK1F,CAAC","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_ICON_PATH = '/exsto-icon.svg';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set([HOST_ICON_PATH, '/exsto-text.svg']);\nconst ICON_LINK_PATTERN = /<link\\b[^>]*\\brel=[\"'](?:shortcut\\s+)?icon[\"'][^>]*>\\s*/gi;\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*\\/api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must use the canonical /module/api/ boundary.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n // This server refreshes the session's tokens itself, so the browser can only sign in again. A refreshable code\n // would send the host client to a refresh endpoint that only the host has.\n writeJson(response, 401, { code: 'sign_in_required', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n // Locally the module runs inside the host shell, so its page shows the host's icon. The module's own favicon is\n // only copied into the build output, so this server has none to answer the browser's favicon request with.\n transformIndexHtml(html) {\n return {\n html: html.replace(ICON_LINK_PATTERN, ''),\n tags: [{ tag: 'link', attrs: { rel: 'icon', type: 'image/svg+xml', href: HOST_ICON_PATH }, injectTo: 'head' }],\n };\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
|
package/dist/vite-development.js
CHANGED
|
@@ -8,7 +8,9 @@ const CALLBACK_PATH = '/callback';
|
|
|
8
8
|
const LOGGED_OUT_PATH = '/logged-out';
|
|
9
9
|
const LOGIN_PATH = '/api/dev-auth/login';
|
|
10
10
|
export const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';
|
|
11
|
-
const
|
|
11
|
+
const HOST_ICON_PATH = '/exsto-icon.svg';
|
|
12
|
+
const HOST_NAVIGATION_ASSET_PATHS = new Set([HOST_ICON_PATH, '/exsto-text.svg']);
|
|
13
|
+
const ICON_LINK_PATTERN = /<link\b[^>]*\brel=["'](?:shortcut\s+)?icon["'][^>]*>\s*/gi;
|
|
12
14
|
const MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;
|
|
13
15
|
const MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;
|
|
14
16
|
const SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;
|
|
@@ -616,7 +618,9 @@ class MfDevelopmentBff {
|
|
|
616
618
|
async #proxy(request, response, localUrl) {
|
|
617
619
|
const session = await this.#authenticatedSession(request);
|
|
618
620
|
if (!session) {
|
|
619
|
-
|
|
621
|
+
// This server refreshes the session's tokens itself, so the browser can only sign in again. A refreshable code
|
|
622
|
+
// would send the host client to a refresh endpoint that only the host has.
|
|
623
|
+
writeJson(response, 401, { code: 'sign_in_required', error: 'Sign in is required.' });
|
|
620
624
|
return;
|
|
621
625
|
}
|
|
622
626
|
const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);
|
|
@@ -692,6 +696,14 @@ export const mfDevelopmentBffPlugin = (options) => {
|
|
|
692
696
|
middleware(request, response, next);
|
|
693
697
|
});
|
|
694
698
|
},
|
|
699
|
+
// Locally the module runs inside the host shell, so its page shows the host's icon. The module's own favicon is
|
|
700
|
+
// only copied into the build output, so this server has none to answer the browser's favicon request with.
|
|
701
|
+
transformIndexHtml(html) {
|
|
702
|
+
return {
|
|
703
|
+
html: html.replace(ICON_LINK_PATTERN, ''),
|
|
704
|
+
tags: [{ tag: 'link', attrs: { rel: 'icon', type: 'image/svg+xml', href: HOST_ICON_PATH }, injectTo: 'head' }],
|
|
705
|
+
};
|
|
706
|
+
},
|
|
695
707
|
};
|
|
696
708
|
};
|
|
697
709
|
export const createMfDevelopmentBffMiddleware = (options) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-development.js","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set(['/exsto-icon.svg', '/exsto-text.svg']);\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*\\/api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must use the canonical /module/api/ boundary.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"vite-development.js","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAGrD,MAAM,cAAc,GAAG,yBAAyB,CAAC;AACjD,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAC5C,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAChD,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,eAAe,GAAG,aAAa,CAAC;AACtC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AACzC,MAAM,CAAC,MAAM,mCAAmC,GAAG,mCAAmC,CAAC;AACvF,MAAM,cAAc,GAAG,iBAAiB,CAAC;AACzC,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC,CAAC;AACjF,MAAM,iBAAiB,GAAG,2DAA2D,CAAC;AACtF,MAAM,yBAAyB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAClD,MAAM,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC;AACnD,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;AAC5C,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,CAAC;AACrC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,uBAAuB,GAAG,4BAA4B,CAAC;AAC7D,MAAM,wBAAwB,GAAG,6BAA6B,CAAC;AAC/D,MAAM,mBAAmB,GAAG,gCAAgC,CAAC;AAC7D,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAC9C,MAAM,sBAAsB,GAAG;IAC7B,cAAc;IACd,iBAAiB;IACjB,8BAA8B;IAC9B,gCAAgC;IAChC,gCAAgC;CACxB,CAAC;AA6DX,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAE5I,MAAM,cAAc,GAAG,CAAC,KAA8B,EAAE,GAAW,EAAU,EAAE;IAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAA8B,EAAE,GAAW,EAAiB,EAAE;IACpF,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,GAAG,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,SAAiB,EAAE,KAAa,EAAE,cAAc,GAAG,KAAK,EAAO,EAAE;IACzF,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,WAAW,GAAG,cAAc,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC;IACjI,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kBAAkB,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8CAA8C,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,SAAiB,EAAU,EAAE;IACpD,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,IAAI,GAAG,EAAE,EAAU,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACjF,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAEhH,MAAM,SAAS,GAAG,CAAC,OAAwB,EAA+B,EAAE;IAC1E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,MAAc,EAAU,EAAE,CAAC,GAAG,IAAI,IAAI,KAAK,qDAAqD,MAAM,EAAE,CAAC;AAEtJ,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAElE,MAAM,SAAS,GAAG,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa,EAAQ,EAAE;IAClF,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;IACtE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,QAAwB,EAAE,QAAgB,EAAE,UAAoB,EAAE,EAAQ,EAAE;IAC5F,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IACD,QAAQ,CAAC,GAAG,EAAE,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,SAAwB,EAAU,EAAE;IACxD,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QACvD,OAAO,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAE,cAAuB,EAAkB,EAAE;IACrF,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC;IACnC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,cAAc,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO;QACL,WAAW,EAAE,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC;QAClD,SAAS;QACT,OAAO;QACP,YAAY,EAAE,cAAc,CAAC,KAAK,EAAE,eAAe,CAAC;QACpD,SAAS;KACV,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAW,EAAE;IAC9C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC;AAC5G,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CACnB,OAAwB,EACxB,WAAmB,EACnB,aAAmC,EACnC,iBAAmD,EAC1C,EAAE;IACX,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5D,IACE,CAAC,KAAK;YACN,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YACpB;gBACE,eAAe;gBACf,YAAY;gBACZ,gBAAgB;gBAChB,QAAQ;gBACR,MAAM;gBACN,QAAQ;gBACR,mBAAmB;gBACnB,uBAAuB,CAAC,WAAW,EAAE;gBACrC,wBAAwB,CAAC,WAAW,EAAE;aACvC,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAC9B,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,WAAW,EAAE,CAAC,CAAC;IACtD,IAAI,aAAa,EAAE,IAAI,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,KAAc,EAAmC,EAAE;IAC9E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC;IAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAK,OAAkB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAK,QAAmB,GAAG,CAAC,EAAE,CAAC;QAC9H,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAkB,EAAE,OAAO,EAAE,OAAiB,EAAE,CAAC;AACtE,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,OAA4B,EAAE,OAAgB,EAAQ,EAAE;IACnF,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YAC5B,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5G,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,WAAW,KAAK,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACjH,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;YAC7C,SAAS;QACX,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,KAAK,EAAE,QAAkB,EAAE,QAAwB,EAAiB,EAAE;IAC9F,QAAQ,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC7C,IAAI,CAAC,CAAC,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC1H,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,CAAC;IACzF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,QAAQ,CAAC,GAAG,EAAE,CAAC;QACf,OAAO;IACT,CAAC;IACD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,EAAE,QAAkB,EAAE,YAAoB,EAAE,KAAa,EAAmB,EAAE;IAC1G,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,YAAY,EAAE,CAAC;QACrE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,sCAAsC,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM;YACR,CAAC;YACD,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;YAC/B,IAAI,UAAU,GAAG,YAAY,EAAE,CAAC;gBAC9B,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,sCAAsC,CAAC,CAAC;YAClE,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF,MAAM,gBAAgB;IACX,iBAAiB,CAAM;IACvB,kBAAkB,CAAM;IACxB,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IACtC,WAAW,CAAM;IACjB,kBAAkB,CAAM;IACxB,KAAK,GAAG,IAAI,GAAG,EAAiD,CAAC;IACjE,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,gBAAgB,CAAM;IACtB,cAAc,CAAS;IACvB,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;IACtD,qBAAqB,GAA6C,IAAI,CAAC;IACvE,iBAAiB,GAAmC,IAAI,CAAC;IAEzD,YAAY,OAAuC;QACjD,MAAM,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC,iBAAiB,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;QAC9F,IAAI,CAAC,kBAAkB,GAAG,cAAc,CAAC;QACzC,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,iBAAiB,GAAG,IAAI,GAAG,CAAC,8BAA8B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACnF,IAAI,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,OAAO,CAAC,iBAAiB,IAAI,wBAAwB,EAAE,mBAAmB,CAAC,CAAC;QACvH,IAAI,IAAI,CAAC,kBAAkB,CAAC,QAAQ,KAAK,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,IAAI,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;YAC/G,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,eAAe,EAAE,iBAAiB,EAAE,IAAI,CAAC,CAAC;QAC3F,IAAI,IAAI,CAAC,gBAAgB,CAAC,QAAQ,KAAK,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;YACzG,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,eAAe,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAwB,EAAE,QAAwB,EAAE,IAA+B;QAC9F,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;QAEd,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,mCAAmC,EAAE,CAAC;gBACrF,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;gBAChF,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9E,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;gBAC/F,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAC5D,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBACrF,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;gBAC/D,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;gBAC7C,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;gBACjE,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7D,QAAQ,CAAC,QAAQ,EAAE,GAAG,UAAU,aAAa,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;gBAC3G,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,sBAAsB,EAAE,CAAC;gBACxE,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBACzD,IAAI,SAAS,EAAE,CAAC;oBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACnC,CAAC;gBACD,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;gBAC9D,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;gBACrE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;gBAChF,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YACD,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oCAAoC,CAAC;YAC9F,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,mCAAmC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,QAAwB,EAAE,WAAgB,EAAE,IAAwB;QAChG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE;YACxC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,eAAe,EAAE;YACpG,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,YAAY,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC/D,MAAM,gBAAgB,GACpB,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,2CAA2C,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,oCAAoC,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,+BAA+B,CAAC;QACrG,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,mBAAmB,IAAI,EAAE,CAAC,CAAC;QACvF,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAChD,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAChD,QAAQ,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;QACxD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,qBAAqB,KAAK,KAAK,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;aACzH,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YACvB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,KAAK,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC9G,OAAO,EAAE,QAAQ,EAAE,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;QACxG,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACL,OAAO,IAAI,CAAC,qBAAqB,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,iBAAiB,KAAK,IAAI,CAAC,cAAc,EAAE;aAC7C,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE;YAC5B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,aAAa,CAAC,MAAM,mCAAmC,EAAE;gBACvF,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;gBACvC,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;YACjE,CAAC;YACD,MAAM,KAAK,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAClE,CAAC;YACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,uBAAuB,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACxH,IAAI,MAAM,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;YAClF,CAAC;YACD,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAU,EAAE;gBACvC,MAAM,GAAG,GAAG,gBAAgB,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;gBAC9E,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;oBAC1C,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,yCAAyC,CAAC,CAAC;gBACxE,CAAC;gBACD,OAAO,GAAG,CAAC,IAAI,CAAC;YAClB,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;YAChE,OAAO;gBACL,qBAAqB,EAAE,QAAQ,CAAC,wBAAwB,CAAC;gBACzD,kBAAkB,EAAE,QAAQ,CAAC,sBAAsB,CAAC;gBACpD,MAAM;gBACN,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC;gBAC7B,kBAAkB,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,IAAI;gBACvE,aAAa,EAAE,QAAQ,CAAC,gBAAgB,CAAC;aAC1C,CAAC;QACJ,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAC9B,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACL,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAwB,EAAE,QAAwB,EAAE,QAAgB;QAC/E,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACvD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;gBACjG,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;gBACvD,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAC/D,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC9G,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBAC7C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACnB,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC7D,CAAC;gBACD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;QACH,CAAC;QAED,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACjG,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,IAAI,GAAc,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC;QAClH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAE9B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QAC1D,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;QACnD,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC/D,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC;QAChG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;QACxD,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;QAC3E,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;QAC3D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAAwB,EAAE,QAAwB,EAAE,GAAQ;QAC1E,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1D,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1D,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC,CAAC;YAClH,OAAO;QACT,CAAC;QACD,MAAM,aAAa,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,aAAa,EAAE,CAAC;YAClB,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,uBAAuB,EAAE,CAAC,CAAC;YAC/H,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC,CAAC;YACpI,OAAO;QACT,CAAC;QAED,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACjG,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,SAAS,EAAE,aAAa,CAAC,QAAQ;YACjC,IAAI;YACJ,aAAa,EAAE,IAAI,CAAC,YAAY;YAChC,UAAU,EAAE,oBAAoB;YAChC,YAAY,EAAE,IAAI,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI;SACnE,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,EAAE;YACzD,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,mCAAmC,EAAE;YAC5F,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,+BAA+B,aAAa,CAAC,MAAM,IAAI,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,aAAa,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACjG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;YAC9C,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,MAAM,EAAE,SAAS,CAAC,MAAM;YACxB,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC;SACjC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE;YAC5B,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI;YAC/C,OAAO;YACP,mBAAmB,EAAE,OAAO,CAAC,OAAO,EAAE;YACtC,aAAa,EAAE,IAAI,GAAG,EAAE;YACxB,uBAAuB,EAAE,IAAI;YAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;SAClC,CAAC,CAAC;QACH,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,EAAE,uBAAuB,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC5H,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAwB,EAAE,QAAwB;QAC9D,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;QAC9F,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACvH,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACjG,MAAM,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,CAAC;QACxD,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,EAAE,KAAa,EAAE,IAAY,EAAiB,EAAE;YAClE,MAAM,KAAK,CAAC,kBAAkB,EAAE;gBAC9B,IAAI,EAAE,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;gBAC9F,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;gBAChE,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC,CAAC;QACF,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC;YAC3C,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE;SACzF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,OAAwB;QAClD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,qBAAqB,EAAE,CAAC;YAC3D,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC1B,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACnC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YAC7D,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,cAAc,CAAC;YAC7B,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACnC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAsB;QACnC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QAC1C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACjG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,EAAE;YACpD,IAAI,EAAE,IAAI,eAAe,CAAC;gBACxB,SAAS,EAAE,aAAa,CAAC,QAAQ;gBACjC,UAAU,EAAE,eAAe;gBAC3B,aAAa,EAAE,YAAY;aAC5B,CAAC;YACF,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,mCAAmC,EAAE;YAC5F,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;QAChE,OAAO,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACzC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;QACzD,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;QACnE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAwB,EAAE,QAAwB,EAAE,QAAa;QAC5E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,+GAA+G;YAC/G,2EAA2E;YAC3E,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QACD,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACtE,IAAI,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAC7C,IAAI,OAAO,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;YAC7B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAC5C,OAAO,GAAG,OAAO,CAAC;YACpB,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,mBAAmB,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;YAClF,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YAC7E,CAAC;oBAAS,CAAC;gBACT,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACpF,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,OAAwB,EACxB,QAAwB,EACxB,QAAa,EACb,OAAsB,EACtB,WAAoB;QAEpB,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QAC9E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC;QACtF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;QACvC,MAAM,IAAI,GAAsC;YAC9C,OAAO,EAAE,YAAY,CACnB,OAAO,EACP,OAAO,CAAC,WAAW,EACnB,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,EAC/C,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAC1D;YACD,MAAM;YACN,QAAQ,EAAE,QAAQ;SACnB,CAAC;QACF,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1C,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAwB,CAAC;YAC3D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,WAAW,EAAE,CAAC;YAChB,mBAAmB,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/D,CAAC;aAAM,IAAI,QAAQ,CAAC,QAAQ,KAAK,iBAAiB,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClE,OAAO,CAAC,uBAAuB,GAAG,mBAAmB,CACnD,MAAM,QAAQ;iBACX,KAAK,EAAE;iBACP,IAAI,EAAE;iBACN,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CACrB,CAAC;QACJ,CAAC;QACD,MAAM,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,SAAS,GAAG,oBAAoB,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC;gBACvD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,SAAS,GAAG,oBAAoB,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC;gBACvD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,GAAG,qBAAqB,GAAG,GAAG,EAAE,CAAC;gBAC7E,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAAuC,EAAU,EAAE;IACxF,MAAM,UAAU,GAAG,gCAAgC,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO;QACL,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,6BAA6B;QACnC,eAAe,CAAC,MAAM;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACjD,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,gHAAgH;QAChH,2GAA2G;QAC3G,kBAAkB,CAAC,IAAI;YACrB,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;gBACzC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;aAC/G,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAIF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,OAAuC,EAA+B,EAAE;IACvH,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QACjC,KAAK,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_ICON_PATH = '/exsto-icon.svg';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set([HOST_ICON_PATH, '/exsto-text.svg']);\nconst ICON_LINK_PATTERN = /<link\\b[^>]*\\brel=[\"'](?:shortcut\\s+)?icon[\"'][^>]*>\\s*/gi;\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*\\/api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must use the canonical /module/api/ boundary.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n // This server refreshes the session's tokens itself, so the browser can only sign in again. A refreshable code\n // would send the host client to a refresh endpoint that only the host has.\n writeJson(response, 401, { code: 'sign_in_required', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n // Locally the module runs inside the host shell, so its page shows the host's icon. The module's own favicon is\n // only copied into the build output, so this server has none to answer the browser's favicon request with.\n transformIndexHtml(html) {\n return {\n html: html.replace(ICON_LINK_PATTERN, ''),\n tags: [{ tag: 'link', attrs: { rel: 'icon', type: 'image/svg+xml', href: HOST_ICON_PATH }, injectTo: 'head' }],\n };\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edynamix/mf-isolation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "CSS and overlay isolation helpers for eDynamix microfrontends.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./dist/postcss.d.ts",
|
|
34
34
|
"import": "./dist/postcss.js"
|
|
35
35
|
},
|
|
36
|
+
"./host-contract": {
|
|
37
|
+
"types": "./dist/host-contract.d.ts",
|
|
38
|
+
"import": "./dist/host-contract.js"
|
|
39
|
+
},
|
|
36
40
|
"./navigation": {
|
|
37
41
|
"types": "./dist/navigation.d.ts",
|
|
38
42
|
"import": "./dist/navigation.js"
|