@monetizationos/proxy 1.4.2 → 1.5.0
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 +60 -0
- package/dist/MOSProxy.d.ts +8 -2
- package/dist/MOSProxy.js +17 -5
- package/dist/MOSProxy.js.map +1 -1
- package/dist/MOSProxyBuilder.d.ts +25 -1
- package/dist/MOSProxyBuilder.js +31 -0
- package/dist/MOSProxyBuilder.js.map +1 -1
- package/dist/adapters/ConfigFactory.d.ts +26 -0
- package/dist/adapters/ConfigFactory.js +2 -0
- package/dist/adapters/ConfigFactory.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/apiRequestHeaders.d.ts +1 -1
- package/dist/apiRequestHeaders.js +1 -1
- package/dist/apiRequestHeaders.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +26 -19
- package/dist/config.js.map +1 -1
- package/dist/configResolution.d.ts +38 -0
- package/dist/configResolution.js +143 -0
- package/dist/configResolution.js.map +1 -0
- package/dist/hostPathMatcher.d.ts +33 -0
- package/dist/hostPathMatcher.js +65 -0
- package/dist/hostPathMatcher.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/stages/fetchSurfaceDecisions.d.ts +2 -1
- package/dist/stages/fetchSurfaceDecisions.js +2 -1
- package/dist/stages/fetchSurfaceDecisions.js.map +1 -1
- package/dist/stages/surfaceDecisions.js +2 -0
- package/dist/stages/surfaceDecisions.js.map +1 -1
- package/dist/surfaceDecisionCookies.d.ts +1 -0
- package/dist/surfaceDecisionCookies.js +24 -0
- package/dist/surfaceDecisionCookies.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,66 @@ export default {
|
|
|
52
52
|
|
|
53
53
|
Stages 3–6 run on HTML responses only and auto-skip for everything else. Call `.withoutHtmlTransformation()` to disable them entirely.
|
|
54
54
|
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Optional fields on `MOSConfigInput`:
|
|
58
|
+
|
|
59
|
+
| Field | Description |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `mosEndpointsPrefix` | Path prefix routed to the MonetizationOS endpoint proxy. Default: `/mos-endpoints/`. |
|
|
62
|
+
| `surfaceDecisionsIgnorePaths` | Comma-separated regex patterns. Matching request pathnames skip the surface-decisions call. |
|
|
63
|
+
| `surfaceDecisionsCookies` | Comma-separated regex patterns. Matching cookies from the incoming request and the origin `Set-Cookie` headers are forwarded to the surface-decisions API as `http.cookies` (`Record<string, string>`). Origin values win when the same name appears in both. Omitted when unset or when no cookies match. |
|
|
64
|
+
| `createAnonymousIdentifierFallback` | When `true` (default), JWT surface-decision requests ask MonetizationOS to mint an anonymous identifier if JWT auth fails. |
|
|
65
|
+
| `originRequestHeaders` | Headers added to or replacing client headers on every origin request. |
|
|
66
|
+
| `injectScriptUrl` | Script URL injected into the `<head>` of HTML responses. |
|
|
67
|
+
|
|
68
|
+
Example — forward specific cookies to surface decisions:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
.withConfig({
|
|
72
|
+
// ...
|
|
73
|
+
surfaceDecisionsCookies: "^__session$, ^theme$, ^mos_",
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each entry is a regex tested against the cookie **name**. Plain names like `^__session$` match exactly; prefixes like `^mos_` match any cookie whose name starts with `mos_`.
|
|
78
|
+
|
|
79
|
+
## Per-request config
|
|
80
|
+
|
|
81
|
+
`.withConfig(...)` also accepts a `ConfigFactory` — `(request: Request) => MOSConfigInput | Promise<MOSConfigInput>` — so one deployment can front several brands. The factory returns the complete config for each request; look it up however you like (KV, env JSON, a host table). `hostPathMatcher` is a ready-made factory that picks a rule by host and path prefix and shallow-merges its config over a shared base:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { MOSProxyBuilder, hostPathMatcher, type MOSConfigInput } from "@monetizationos/proxy";
|
|
85
|
+
|
|
86
|
+
const base = {
|
|
87
|
+
mosHost: "https://api.monetizationos.com",
|
|
88
|
+
mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,
|
|
89
|
+
anonymousSessionCookieName: "anon-session-id",
|
|
90
|
+
authenticatedUserJwtCookieName: "__session",
|
|
91
|
+
} satisfies Partial<MOSConfigInput>;
|
|
92
|
+
|
|
93
|
+
const proxy = new MOSProxyBuilder()
|
|
94
|
+
.withConfig(
|
|
95
|
+
hostPathMatcher(
|
|
96
|
+
[
|
|
97
|
+
{
|
|
98
|
+
host: "news.example.com",
|
|
99
|
+
config: { originUrl: "https://origin.news.example.com", surfaceSlug: "news-web" },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
host: "news.example.com",
|
|
103
|
+
pathPrefix: "/sports",
|
|
104
|
+
config: { originUrl: "https://origin.sports.example.com", surfaceSlug: "sports-web" },
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
base,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
.withUnresolvedConfigHandler(() => new Response("Not found", { status: 404 }))
|
|
111
|
+
.withHtmlRewriter(myHtmlRewriterAdapter)
|
|
112
|
+
.build();
|
|
113
|
+
```
|
|
114
|
+
|
|
55
115
|
## Adapters
|
|
56
116
|
|
|
57
117
|
- `Fetcher` — `(request: Request) => Promise<Response>`. Configure separately for origin traffic (`.withOriginFetcher`) and MOS API traffic (`.withApiFetcher`). Both default to `globalThis.fetch`; override on runtimes that need a backend binding (e.g. Fastly Compute) or custom dispatch.
|
package/dist/MOSProxy.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider';
|
|
2
|
+
import type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory';
|
|
2
3
|
import type { Fetcher } from './adapters/Fetcher';
|
|
3
4
|
import type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter';
|
|
4
5
|
import type { IdentityProvider } from './adapters/IdentityProvider';
|
|
@@ -21,13 +22,17 @@ export interface MOSProxyHtmlPipelineErrorContext {
|
|
|
21
22
|
*/
|
|
22
23
|
export type MOSProxyHtmlPipelineErrorHandler = (ctx: MOSProxyHtmlPipelineErrorContext) => Response | Promise<Response>;
|
|
23
24
|
export interface MOSProxyOptions {
|
|
24
|
-
config
|
|
25
|
+
/** A static config, or a per-request {@link ConfigFactory}. */
|
|
26
|
+
config: MOSConfigInput | ConfigFactory;
|
|
25
27
|
originFetcher: Fetcher;
|
|
26
28
|
apiFetcher: Fetcher | null;
|
|
27
29
|
htmlRewriter: HtmlRewriterAdapter | null;
|
|
28
30
|
clientMetadataProvider: ClientMetadataProvider | null;
|
|
29
31
|
identityProvider: IdentityProvider | null;
|
|
30
32
|
resourceProvider: ResourceProvider | null;
|
|
33
|
+
onUnresolvedConfig?: UnresolvedConfigHandler | null;
|
|
34
|
+
/** Max number of distinct resolved configs to keep normalized in memory. Default 256. */
|
|
35
|
+
maxCachedConfigs?: number;
|
|
31
36
|
logger?: MOSProxyLogger;
|
|
32
37
|
onHtmlPipelineError?: MOSProxyHtmlPipelineErrorHandler;
|
|
33
38
|
customEndpointsEnabled: boolean;
|
|
@@ -42,7 +47,8 @@ export interface MOSProxyOptions {
|
|
|
42
47
|
*/
|
|
43
48
|
export declare class MOSProxy {
|
|
44
49
|
private readonly opts;
|
|
45
|
-
private readonly
|
|
50
|
+
private readonly logger;
|
|
51
|
+
private readonly configResolution;
|
|
46
52
|
constructor(opts: MOSProxyOptions);
|
|
47
53
|
handle(request: Request): Promise<Response>;
|
|
48
54
|
}
|
package/dist/MOSProxy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConfigResolution } from './configResolution';
|
|
2
2
|
import { consoleLogger } from './logger';
|
|
3
3
|
import customEndpointRequest from './stages/customEndpoint';
|
|
4
4
|
import isRedirectResponse from './stages/isRedirectResponse';
|
|
@@ -9,21 +9,33 @@ import shouldIgnorePath from './stages/shouldIgnorePath';
|
|
|
9
9
|
import handleSurfaceBehavior from './stages/surfaceBehavior';
|
|
10
10
|
import handleSurfaceComponents from './stages/surfaceComponents';
|
|
11
11
|
import getSurfaceDecisions from './stages/surfaceDecisions';
|
|
12
|
+
const DEFAULT_MAX_CACHED_CONFIGS = 256;
|
|
12
13
|
/**
|
|
13
14
|
* Platform-agnostic MonetizationOS proxy. Build one with `MOSProxyBuilder`, then call
|
|
14
15
|
* `proxy.handle(request)` from your platform's fetch entry point.
|
|
15
16
|
*/
|
|
16
17
|
export class MOSProxy {
|
|
17
18
|
opts;
|
|
18
|
-
|
|
19
|
+
logger;
|
|
20
|
+
configResolution;
|
|
19
21
|
constructor(opts) {
|
|
20
22
|
this.opts = opts;
|
|
21
|
-
this.
|
|
23
|
+
this.logger = opts.logger ?? consoleLogger;
|
|
24
|
+
this.configResolution = new ConfigResolution({
|
|
25
|
+
input: opts.config,
|
|
26
|
+
onUnresolved: opts.onUnresolvedConfig ?? null,
|
|
27
|
+
maxCachedConfigs: opts.maxCachedConfigs ?? DEFAULT_MAX_CACHED_CONFIGS,
|
|
28
|
+
logger: this.logger,
|
|
29
|
+
});
|
|
22
30
|
}
|
|
23
31
|
async handle(request) {
|
|
24
32
|
const { originFetcher, apiFetcher, htmlRewriter, clientMetadataProvider, identityProvider, resourceProvider, onHtmlPipelineError } = this.opts;
|
|
25
|
-
const logger = this.
|
|
26
|
-
const
|
|
33
|
+
const logger = this.logger;
|
|
34
|
+
const resolved = await this.configResolution.resolve(request);
|
|
35
|
+
if (resolved instanceof Response) {
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
const ctx = { config: resolved, logger };
|
|
27
39
|
// Stage 1a: custom endpoint routing
|
|
28
40
|
if (this.opts.customEndpointsEnabled) {
|
|
29
41
|
if (!apiFetcher) {
|
package/dist/MOSProxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MOSProxy.js","sourceRoot":"","sources":["../src/MOSProxy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider'\nimport type { Fetcher } from './adapters/Fetcher'\nimport type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter'\nimport type { IdentityProvider } from './adapters/IdentityProvider'\nimport type { ResourceProvider } from './adapters/ResourceProvider'\nimport { type MOSConfig, normalizeMOSConfig } from './config'\nimport type { PipelineContext } from './context'\nimport { consoleLogger, type MOSProxyLogger } from './logger'\nimport customEndpointRequest from './stages/customEndpoint'\nimport isRedirectResponse from './stages/isRedirectResponse'\nimport rewriteOriginResponse from './stages/linkRewriting'\nimport { handleMosAuthenticatedApiRoutes } from './stages/mosAuthenticatedApi'\nimport performOriginRequest from './stages/originRequest'\nimport shouldIgnorePath from './stages/shouldIgnorePath'\nimport handleSurfaceBehavior from './stages/surfaceBehavior'\nimport handleSurfaceComponents from './stages/surfaceComponents'\nimport getSurfaceDecisions from './stages/surfaceDecisions'\nimport type { MOSConfigInput, MosAuthenticatedApiRoute } from './types'\n\nexport type MOSProxyHtmlPipelineStage =\n | 'origin-response'\n | 'link-rewriting'\n | 'surface-decisions'\n | 'surface-behavior'\n | 'surface-components'\n\nexport interface MOSProxyHtmlPipelineErrorContext {\n error: unknown\n stage: MOSProxyHtmlPipelineStage\n request: Request\n lastSafeResponse: Response\n}\n\n/**\n * Called when the HTML pipeline throws. Return a `Response` to fully control the proxy's reply, or\n * re-throw to surface the error to the platform handler. If omitted, the proxy fails open and\n * returns `lastSafeResponse`. If the callback itself throws or returns a non-Response value, the\n * proxy logs a warning and falls back to `lastSafeResponse` so the site cannot be taken down by a\n * bug in the handler.\n */\nexport type MOSProxyHtmlPipelineErrorHandler = (ctx: MOSProxyHtmlPipelineErrorContext) => Response | Promise<Response>\n\nexport interface MOSProxyOptions {\n config: MOSConfigInput\n originFetcher: Fetcher\n apiFetcher: Fetcher | null\n htmlRewriter: HtmlRewriterAdapter | null\n clientMetadataProvider: ClientMetadataProvider | null\n identityProvider: IdentityProvider | null\n resourceProvider: ResourceProvider | null\n logger?: MOSProxyLogger\n onHtmlPipelineError?: MOSProxyHtmlPipelineErrorHandler\n customEndpointsEnabled: boolean\n linkRewritingEnabled: boolean\n surfaceDecisionsEnabled: boolean\n htmlTransformationEnabled: boolean\n mosAuthenticatedApiRoutes?: MosAuthenticatedApiRoute[]\n}\n\n/**\n * Platform-agnostic MonetizationOS proxy. Build one with `MOSProxyBuilder`, then call\n * `proxy.handle(request)` from your platform's fetch entry point.\n */\nexport class MOSProxy {\n private readonly config: MOSConfig\n\n constructor(private readonly opts: MOSProxyOptions) {\n this.config = normalizeMOSConfig(opts.config, opts.logger ?? consoleLogger)\n }\n\n async handle(request: Request): Promise<Response> {\n const { originFetcher, apiFetcher, htmlRewriter, clientMetadataProvider, identityProvider, resourceProvider, onHtmlPipelineError } =\n this.opts\n const logger = this.opts.logger ?? consoleLogger\n const ctx: PipelineContext = { config: this.config, logger }\n\n // Stage 1a: custom endpoint routing\n if (this.opts.customEndpointsEnabled) {\n if (!apiFetcher) {\n throw new Error('MOSProxy: customEndpoints is enabled but no API fetcher is configured')\n }\n const customEndpointResponse = await customEndpointRequest(ctx, request, apiFetcher)\n if (customEndpointResponse) {\n return customEndpointResponse\n }\n }\n\n // Stage 1b: authenticated API routing\n const authenticatedApiResult = await handleMosAuthenticatedApiRoutes(\n request,\n this.opts.mosAuthenticatedApiRoutes ?? [],\n ctx,\n identityProvider,\n clientMetadataProvider,\n apiFetcher,\n )\n if (authenticatedApiResult) {\n return authenticatedApiResult\n }\n\n // Stage 1c: origin fetch\n const originResponse = await performOriginRequest(ctx, request, originFetcher)\n\n // Auto-skip HTML pipeline for non-HTML content, or when HTML transformation is disabled entirely\n const isHtml = originResponse.headers.get('Content-Type')?.startsWith('text/html') ?? false\n if (!isHtml || !this.opts.htmlTransformationEnabled) {\n return originResponse\n }\n\n let stage: MOSProxyHtmlPipelineStage = 'origin-response'\n let failOpenResponse = cloneResponseForFallback(originResponse, logger, stage)\n\n try {\n // Stage 2: rewrite origin links (optional)\n let rewrittenResponse: Response = originResponse\n if (this.opts.linkRewritingEnabled) {\n stage = 'link-rewriting'\n rewrittenResponse = await rewriteOriginResponse(ctx, request, originResponse)\n failOpenResponse = cloneResponseForFallback(rewrittenResponse, logger, stage)\n }\n\n // Stage 3: surface decisions (optional)\n if (!this.opts.surfaceDecisionsEnabled) {\n return rewrittenResponse\n }\n if (shouldIgnorePath(ctx, request) || isRedirectResponse(rewrittenResponse)) {\n return rewrittenResponse\n }\n\n if (!apiFetcher) {\n throw new Error('MOSProxy: surfaceDecisions is enabled but no API fetcher is configured')\n }\n\n stage = 'surface-decisions'\n const [modifiedResponse, surfaceDecisions] = await getSurfaceDecisions(\n ctx,\n request,\n rewrittenResponse,\n apiFetcher,\n htmlRewriter,\n clientMetadataProvider,\n identityProvider,\n resourceProvider,\n )\n if (!surfaceDecisions) {\n return modifiedResponse\n }\n failOpenResponse = cloneResponseForFallback(modifiedResponse, logger, stage)\n\n // Stage 4: surface behavior (HTTP-level)\n stage = 'surface-behavior'\n const [surfaceDecisionResponse, returnImmediately] = handleSurfaceBehavior(modifiedResponse, surfaceDecisions)\n if (returnImmediately) {\n return surfaceDecisionResponse\n }\n failOpenResponse = cloneResponseForFallback(surfaceDecisionResponse, logger, stage)\n\n // Stage 5: surface components (HTML-level) — requires an HTML rewriter\n if (!htmlRewriter) {\n return surfaceDecisionResponse\n }\n stage = 'surface-components'\n return await handleSurfaceComponents(ctx, surfaceDecisionResponse, surfaceDecisions, htmlRewriter)\n } catch (error) {\n logger.log({\n level: 'error',\n code: 'html-pipeline-failed',\n message: 'HTML pipeline failed.',\n context: { stage, hasErrorHandler: Boolean(onHtmlPipelineError) },\n error,\n })\n\n if (!onHtmlPipelineError) {\n return failOpenResponse\n }\n\n try {\n const handlerResponse = await onHtmlPipelineError({\n error,\n stage,\n request,\n lastSafeResponse: failOpenResponse,\n })\n if (!(handlerResponse instanceof Response)) {\n logger.log({\n level: 'warn',\n code: 'html-pipeline-error-handler-invalid',\n message: 'onHtmlPipelineError did not return a Response; falling back to the last safe response.',\n context: { stage },\n })\n return failOpenResponse\n }\n return handlerResponse\n } catch (handlerError) {\n logger.log({\n level: 'warn',\n code: 'html-pipeline-error-handler-threw',\n message: 'onHtmlPipelineError threw; falling back to the last safe response.',\n context: { stage },\n error: handlerError,\n })\n return failOpenResponse\n }\n }\n }\n}\n\nconst cloneResponseForFallback = (response: Response, logger: MOSProxyLogger, stage: MOSProxyHtmlPipelineStage): Response => {\n try {\n return response.clone()\n } catch (error) {\n logger.log({\n level: 'warn',\n code: 'response-clone-failed',\n message: 'Could not clone response for fail-open fallback.',\n context: { stage },\n error,\n })\n return response\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MOSProxy.js","sourceRoot":"","sources":["../src/MOSProxy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider'\nimport type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory'\nimport type { Fetcher } from './adapters/Fetcher'\nimport type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter'\nimport type { IdentityProvider } from './adapters/IdentityProvider'\nimport type { ResourceProvider } from './adapters/ResourceProvider'\nimport { ConfigResolution } from './configResolution'\nimport type { PipelineContext } from './context'\nimport { consoleLogger, type MOSProxyLogger } from './logger'\nimport customEndpointRequest from './stages/customEndpoint'\nimport isRedirectResponse from './stages/isRedirectResponse'\nimport rewriteOriginResponse from './stages/linkRewriting'\nimport { handleMosAuthenticatedApiRoutes } from './stages/mosAuthenticatedApi'\nimport performOriginRequest from './stages/originRequest'\nimport shouldIgnorePath from './stages/shouldIgnorePath'\nimport handleSurfaceBehavior from './stages/surfaceBehavior'\nimport handleSurfaceComponents from './stages/surfaceComponents'\nimport getSurfaceDecisions from './stages/surfaceDecisions'\nimport type { MOSConfigInput, MosAuthenticatedApiRoute } from './types'\n\nexport type MOSProxyHtmlPipelineStage =\n | 'origin-response'\n | 'link-rewriting'\n | 'surface-decisions'\n | 'surface-behavior'\n | 'surface-components'\n\nexport interface MOSProxyHtmlPipelineErrorContext {\n error: unknown\n stage: MOSProxyHtmlPipelineStage\n request: Request\n lastSafeResponse: Response\n}\n\n/**\n * Called when the HTML pipeline throws. Return a `Response` to fully control the proxy's reply, or\n * re-throw to surface the error to the platform handler. If omitted, the proxy fails open and\n * returns `lastSafeResponse`. If the callback itself throws or returns a non-Response value, the\n * proxy logs a warning and falls back to `lastSafeResponse` so the site cannot be taken down by a\n * bug in the handler.\n */\nexport type MOSProxyHtmlPipelineErrorHandler = (ctx: MOSProxyHtmlPipelineErrorContext) => Response | Promise<Response>\n\nexport interface MOSProxyOptions {\n /** A static config, or a per-request {@link ConfigFactory}. */\n config: MOSConfigInput | ConfigFactory\n originFetcher: Fetcher\n apiFetcher: Fetcher | null\n htmlRewriter: HtmlRewriterAdapter | null\n clientMetadataProvider: ClientMetadataProvider | null\n identityProvider: IdentityProvider | null\n resourceProvider: ResourceProvider | null\n onUnresolvedConfig?: UnresolvedConfigHandler | null\n /** Max number of distinct resolved configs to keep normalized in memory. Default 256. */\n maxCachedConfigs?: number\n logger?: MOSProxyLogger\n onHtmlPipelineError?: MOSProxyHtmlPipelineErrorHandler\n customEndpointsEnabled: boolean\n linkRewritingEnabled: boolean\n surfaceDecisionsEnabled: boolean\n htmlTransformationEnabled: boolean\n mosAuthenticatedApiRoutes?: MosAuthenticatedApiRoute[]\n}\n\nconst DEFAULT_MAX_CACHED_CONFIGS = 256\n\n/**\n * Platform-agnostic MonetizationOS proxy. Build one with `MOSProxyBuilder`, then call\n * `proxy.handle(request)` from your platform's fetch entry point.\n */\nexport class MOSProxy {\n private readonly logger: MOSProxyLogger\n private readonly configResolution: ConfigResolution\n\n constructor(private readonly opts: MOSProxyOptions) {\n this.logger = opts.logger ?? consoleLogger\n this.configResolution = new ConfigResolution({\n input: opts.config,\n onUnresolved: opts.onUnresolvedConfig ?? null,\n maxCachedConfigs: opts.maxCachedConfigs ?? DEFAULT_MAX_CACHED_CONFIGS,\n logger: this.logger,\n })\n }\n\n async handle(request: Request): Promise<Response> {\n const { originFetcher, apiFetcher, htmlRewriter, clientMetadataProvider, identityProvider, resourceProvider, onHtmlPipelineError } =\n this.opts\n const logger = this.logger\n\n const resolved = await this.configResolution.resolve(request)\n if (resolved instanceof Response) {\n return resolved\n }\n const ctx: PipelineContext = { config: resolved, logger }\n\n // Stage 1a: custom endpoint routing\n if (this.opts.customEndpointsEnabled) {\n if (!apiFetcher) {\n throw new Error('MOSProxy: customEndpoints is enabled but no API fetcher is configured')\n }\n const customEndpointResponse = await customEndpointRequest(ctx, request, apiFetcher)\n if (customEndpointResponse) {\n return customEndpointResponse\n }\n }\n\n // Stage 1b: authenticated API routing\n const authenticatedApiResult = await handleMosAuthenticatedApiRoutes(\n request,\n this.opts.mosAuthenticatedApiRoutes ?? [],\n ctx,\n identityProvider,\n clientMetadataProvider,\n apiFetcher,\n )\n if (authenticatedApiResult) {\n return authenticatedApiResult\n }\n\n // Stage 1c: origin fetch\n const originResponse = await performOriginRequest(ctx, request, originFetcher)\n\n // Auto-skip HTML pipeline for non-HTML content, or when HTML transformation is disabled entirely\n const isHtml = originResponse.headers.get('Content-Type')?.startsWith('text/html') ?? false\n if (!isHtml || !this.opts.htmlTransformationEnabled) {\n return originResponse\n }\n\n let stage: MOSProxyHtmlPipelineStage = 'origin-response'\n let failOpenResponse = cloneResponseForFallback(originResponse, logger, stage)\n\n try {\n // Stage 2: rewrite origin links (optional)\n let rewrittenResponse: Response = originResponse\n if (this.opts.linkRewritingEnabled) {\n stage = 'link-rewriting'\n rewrittenResponse = await rewriteOriginResponse(ctx, request, originResponse)\n failOpenResponse = cloneResponseForFallback(rewrittenResponse, logger, stage)\n }\n\n // Stage 3: surface decisions (optional)\n if (!this.opts.surfaceDecisionsEnabled) {\n return rewrittenResponse\n }\n if (shouldIgnorePath(ctx, request) || isRedirectResponse(rewrittenResponse)) {\n return rewrittenResponse\n }\n\n if (!apiFetcher) {\n throw new Error('MOSProxy: surfaceDecisions is enabled but no API fetcher is configured')\n }\n\n stage = 'surface-decisions'\n const [modifiedResponse, surfaceDecisions] = await getSurfaceDecisions(\n ctx,\n request,\n rewrittenResponse,\n apiFetcher,\n htmlRewriter,\n clientMetadataProvider,\n identityProvider,\n resourceProvider,\n )\n if (!surfaceDecisions) {\n return modifiedResponse\n }\n failOpenResponse = cloneResponseForFallback(modifiedResponse, logger, stage)\n\n // Stage 4: surface behavior (HTTP-level)\n stage = 'surface-behavior'\n const [surfaceDecisionResponse, returnImmediately] = handleSurfaceBehavior(modifiedResponse, surfaceDecisions)\n if (returnImmediately) {\n return surfaceDecisionResponse\n }\n failOpenResponse = cloneResponseForFallback(surfaceDecisionResponse, logger, stage)\n\n // Stage 5: surface components (HTML-level) — requires an HTML rewriter\n if (!htmlRewriter) {\n return surfaceDecisionResponse\n }\n stage = 'surface-components'\n return await handleSurfaceComponents(ctx, surfaceDecisionResponse, surfaceDecisions, htmlRewriter)\n } catch (error) {\n logger.log({\n level: 'error',\n code: 'html-pipeline-failed',\n message: 'HTML pipeline failed.',\n context: { stage, hasErrorHandler: Boolean(onHtmlPipelineError) },\n error,\n })\n\n if (!onHtmlPipelineError) {\n return failOpenResponse\n }\n\n try {\n const handlerResponse = await onHtmlPipelineError({\n error,\n stage,\n request,\n lastSafeResponse: failOpenResponse,\n })\n if (!(handlerResponse instanceof Response)) {\n logger.log({\n level: 'warn',\n code: 'html-pipeline-error-handler-invalid',\n message: 'onHtmlPipelineError did not return a Response; falling back to the last safe response.',\n context: { stage },\n })\n return failOpenResponse\n }\n return handlerResponse\n } catch (handlerError) {\n logger.log({\n level: 'warn',\n code: 'html-pipeline-error-handler-threw',\n message: 'onHtmlPipelineError threw; falling back to the last safe response.',\n context: { stage },\n error: handlerError,\n })\n return failOpenResponse\n }\n }\n }\n}\n\nconst cloneResponseForFallback = (response: Response, logger: MOSProxyLogger, stage: MOSProxyHtmlPipelineStage): Response => {\n try {\n return response.clone()\n } catch (error) {\n logger.log({\n level: 'warn',\n code: 'response-clone-failed',\n message: 'Could not clone response for fail-open fallback.',\n context: { stage },\n error,\n })\n return response\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider';
|
|
2
|
+
import type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory';
|
|
2
3
|
import type { Fetcher } from './adapters/Fetcher';
|
|
3
4
|
import type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter';
|
|
4
5
|
import type { IdentityProvider } from './adapters/IdentityProvider';
|
|
@@ -29,6 +30,8 @@ import type { MOSConfigInput, MosAuthenticatedApiRoute } from './types';
|
|
|
29
30
|
* authenticatedUserJwtCookieName: '__session',
|
|
30
31
|
* injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',
|
|
31
32
|
* surfaceDecisionsIgnorePaths: '',
|
|
33
|
+
* // Optional: forward specific cookies to surface decisions
|
|
34
|
+
* surfaceDecisionsCookies: '^__session$, ^theme$',
|
|
32
35
|
* originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },
|
|
33
36
|
* createAnonymousIdentifierFallback: true,
|
|
34
37
|
* })
|
|
@@ -46,6 +49,8 @@ export declare class MOSProxyBuilder {
|
|
|
46
49
|
private _clientMetadataProvider;
|
|
47
50
|
private _identityProvider;
|
|
48
51
|
private _resourceProvider;
|
|
52
|
+
private _onUnresolvedConfig;
|
|
53
|
+
private _configCacheSize;
|
|
49
54
|
private _logger;
|
|
50
55
|
private _onHtmlPipelineError;
|
|
51
56
|
private _customEndpoints;
|
|
@@ -53,7 +58,14 @@ export declare class MOSProxyBuilder {
|
|
|
53
58
|
private _surfaceDecisions;
|
|
54
59
|
private _htmlTransformation;
|
|
55
60
|
private _additionalAuthenticatedApiRoutes;
|
|
56
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Sets the proxy configuration. Pass a {@link MOSConfigInput} for one fixed config, or a
|
|
63
|
+
* {@link ConfigFactory} to compute a full config per request so one deployment can front several
|
|
64
|
+
* brands. A factory returns the complete config (you merge shared fields with per-brand values
|
|
65
|
+
* yourself). See {@link hostPathMatcher} for a ready-made host/path table, and
|
|
66
|
+
* {@link withUnresolvedConfigHandler} for requests it can't resolve.
|
|
67
|
+
*/
|
|
68
|
+
withConfig(config: MOSConfigInput | ConfigFactory): this;
|
|
57
69
|
withOriginFetcher(fetcher: Fetcher): this;
|
|
58
70
|
withApiFetcher(fetcher: Fetcher): this;
|
|
59
71
|
withHtmlRewriter(rewriter: HtmlRewriterAdapter): this;
|
|
@@ -73,6 +85,18 @@ export declare class MOSProxyBuilder {
|
|
|
73
85
|
* override (e.g. a content tier or canonical id) — `id`/`meta` are preserved unless you set them.
|
|
74
86
|
*/
|
|
75
87
|
withResourceProvider(provider: ResourceProvider): this;
|
|
88
|
+
/**
|
|
89
|
+
* Handles requests a {@link ConfigFactory} couldn't resolve (it threw or returned a config that
|
|
90
|
+
* wouldn't normalize). Return a `Response` to fail closed (e.g. a 404 for an unknown host), or
|
|
91
|
+
* return nothing to fall through to that host's last-known-good config. With no handler and no
|
|
92
|
+
* last-known-good config for the host, the error propagates instead of serving another brand's config.
|
|
93
|
+
*/
|
|
94
|
+
withUnresolvedConfigHandler(handler: UnresolvedConfigHandler): this;
|
|
95
|
+
/**
|
|
96
|
+
* Max number of normalized configs to keep in memory. Defaults to 256; raise it above your
|
|
97
|
+
* live brand count if a deployment fronts more than that.
|
|
98
|
+
*/
|
|
99
|
+
withConfigCacheSize(max: number): this;
|
|
76
100
|
withLogger(logger: MOSProxyLogger): this;
|
|
77
101
|
/**
|
|
78
102
|
* Register a callback to handle HTML pipeline errors. The proxy fails open by default and
|
package/dist/MOSProxyBuilder.js
CHANGED
|
@@ -22,6 +22,8 @@ import { MOSProxy } from './MOSProxy';
|
|
|
22
22
|
* authenticatedUserJwtCookieName: '__session',
|
|
23
23
|
* injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',
|
|
24
24
|
* surfaceDecisionsIgnorePaths: '',
|
|
25
|
+
* // Optional: forward specific cookies to surface decisions
|
|
26
|
+
* surfaceDecisionsCookies: '^__session$, ^theme$',
|
|
25
27
|
* originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },
|
|
26
28
|
* createAnonymousIdentifierFallback: true,
|
|
27
29
|
* })
|
|
@@ -39,6 +41,8 @@ export class MOSProxyBuilder {
|
|
|
39
41
|
_clientMetadataProvider = null;
|
|
40
42
|
_identityProvider = null;
|
|
41
43
|
_resourceProvider = null;
|
|
44
|
+
_onUnresolvedConfig = null;
|
|
45
|
+
_configCacheSize = null;
|
|
42
46
|
_logger = null;
|
|
43
47
|
_onHtmlPipelineError = null;
|
|
44
48
|
_customEndpoints = true;
|
|
@@ -46,6 +50,13 @@ export class MOSProxyBuilder {
|
|
|
46
50
|
_surfaceDecisions = true;
|
|
47
51
|
_htmlTransformation = true;
|
|
48
52
|
_additionalAuthenticatedApiRoutes = [];
|
|
53
|
+
/**
|
|
54
|
+
* Sets the proxy configuration. Pass a {@link MOSConfigInput} for one fixed config, or a
|
|
55
|
+
* {@link ConfigFactory} to compute a full config per request so one deployment can front several
|
|
56
|
+
* brands. A factory returns the complete config (you merge shared fields with per-brand values
|
|
57
|
+
* yourself). See {@link hostPathMatcher} for a ready-made host/path table, and
|
|
58
|
+
* {@link withUnresolvedConfigHandler} for requests it can't resolve.
|
|
59
|
+
*/
|
|
49
60
|
withConfig(config) {
|
|
50
61
|
this._config = config;
|
|
51
62
|
return this;
|
|
@@ -87,6 +98,24 @@ export class MOSProxyBuilder {
|
|
|
87
98
|
this._resourceProvider = provider;
|
|
88
99
|
return this;
|
|
89
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Handles requests a {@link ConfigFactory} couldn't resolve (it threw or returned a config that
|
|
103
|
+
* wouldn't normalize). Return a `Response` to fail closed (e.g. a 404 for an unknown host), or
|
|
104
|
+
* return nothing to fall through to that host's last-known-good config. With no handler and no
|
|
105
|
+
* last-known-good config for the host, the error propagates instead of serving another brand's config.
|
|
106
|
+
*/
|
|
107
|
+
withUnresolvedConfigHandler(handler) {
|
|
108
|
+
this._onUnresolvedConfig = handler;
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Max number of normalized configs to keep in memory. Defaults to 256; raise it above your
|
|
113
|
+
* live brand count if a deployment fronts more than that.
|
|
114
|
+
*/
|
|
115
|
+
withConfigCacheSize(max) {
|
|
116
|
+
this._configCacheSize = max;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
90
119
|
withLogger(logger) {
|
|
91
120
|
this._logger = logger;
|
|
92
121
|
return this;
|
|
@@ -149,6 +178,8 @@ export class MOSProxyBuilder {
|
|
|
149
178
|
clientMetadataProvider: this._clientMetadataProvider,
|
|
150
179
|
identityProvider: this._identityProvider,
|
|
151
180
|
resourceProvider: this._resourceProvider,
|
|
181
|
+
onUnresolvedConfig: this._onUnresolvedConfig ?? undefined,
|
|
182
|
+
maxCachedConfigs: this._configCacheSize ?? undefined,
|
|
152
183
|
logger: this._logger ?? undefined,
|
|
153
184
|
onHtmlPipelineError: this._onHtmlPipelineError ?? undefined,
|
|
154
185
|
customEndpointsEnabled: this._customEndpoints,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MOSProxyBuilder.js","sourceRoot":"","sources":["../src/MOSProxyBuilder.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider'\nimport type { Fetcher } from './adapters/Fetcher'\nimport type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter'\nimport type { IdentityProvider } from './adapters/IdentityProvider'\nimport type { ResourceProvider } from './adapters/ResourceProvider'\nimport type { MOSProxyLogger } from './logger'\nimport { MOSProxy, type MOSProxyHtmlPipelineErrorHandler, type MOSProxyOptions } from './MOSProxy'\nimport type { MOSConfigInput, MosAuthenticatedApiRoute } from './types'\n\n/**\n * Fluent builder for `MOSProxy`. Provide configuration and platform adapters, then `build()`.\n *\n * Defaults:\n * - custom endpoint routing: enabled\n * - link rewriting: enabled\n * - surface decisions: enabled\n * - HTML transformation: enabled (auto-skipped per request for non-HTML responses)\n * - origin/API fetcher: `globalThis.fetch` (override on runtimes that need a backend binding, e.g. Fastly)\n *\n * @example\n * ```ts\n * const proxy = new MOSProxyBuilder()\n * .withConfig({\n * originUrl: 'https://news.example.com',\n * surfaceSlug: 'web',\n * mosHost: 'https://api.monetizationos.com',\n * mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,\n * mosEndpointsPrefix: '/mos-endpoints/',\n * anonymousSessionCookieName: 'anon-session-id',\n * authenticatedUserJwtCookieName: '__session',\n * injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',\n * surfaceDecisionsIgnorePaths: '',\n * originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },\n * createAnonymousIdentifierFallback: true,\n * })\n * .withHtmlRewriter(myHtmlRewriterAdapter)\n * .build()\n *\n * export default { fetch: (request: Request) => proxy.handle(request) }\n * ```\n */\nexport class MOSProxyBuilder {\n private _config: MOSConfigInput | null = null\n private _originFetcher: Fetcher = globalThis.fetch\n private _apiFetcher: Fetcher = globalThis.fetch\n private _htmlRewriter: HtmlRewriterAdapter | null = null\n private _clientMetadataProvider: ClientMetadataProvider | null = null\n private _identityProvider: IdentityProvider | null = null\n private _resourceProvider: ResourceProvider | null = null\n private _logger: MOSProxyLogger | null = null\n private _onHtmlPipelineError: MOSProxyHtmlPipelineErrorHandler | null = null\n private _customEndpoints = true\n private _linkRewriting = true\n private _surfaceDecisions = true\n private _htmlTransformation = true\n private _additionalAuthenticatedApiRoutes: MosAuthenticatedApiRoute[] = []\n\n withConfig(config: MOSConfigInput): this {\n this._config = config\n return this\n }\n\n withOriginFetcher(fetcher: Fetcher): this {\n this._originFetcher = fetcher\n return this\n }\n\n withApiFetcher(fetcher: Fetcher): this {\n this._apiFetcher = fetcher\n return this\n }\n\n withHtmlRewriter(rewriter: HtmlRewriterAdapter): this {\n this._htmlRewriter = rewriter\n return this\n }\n\n withClientMetadata(provider: ClientMetadataProvider): this {\n this._clientMetadataProvider = provider\n return this\n }\n\n /**\n * Override identity provision for the surface-decisions API. Provide `resolve` to control the\n * identity payload sent to the API (e.g. resolved from a request header instead of cookies),\n * and/or `persist` to control how identity is recorded back on the response (e.g. suppress the\n * default anonymous-session cookie or write it elsewhere). Either method is optional; omitted\n * methods fall back to the built-in defaults.\n */\n withIdentityProvider(provider: IdentityProvider): this {\n this._identityProvider = provider\n return this\n }\n\n /**\n * Override the resource object sent to the surface-decisions API per request. `build` receives\n * only the `Request`; the proxy derives defaults (`{ id: pathname, meta: pageMetadata }`) and\n * shallow-merges your returned record over them. Return only the keys you want to add or\n * override (e.g. a content tier or canonical id) — `id`/`meta` are preserved unless you set them.\n */\n withResourceProvider(provider: ResourceProvider): this {\n this._resourceProvider = provider\n return this\n }\n\n withLogger(logger: MOSProxyLogger): this {\n this._logger = logger\n return this\n }\n\n /**\n * Register a callback to handle HTML pipeline errors. The proxy fails open by default and\n * returns the last safe (unconsumed) response — provide this if you want to shape that\n * response yourself (e.g. render a custom error page, return a 503, or re-throw so your\n * platform's error middleware handles it). If the callback throws or returns a non-Response\n * value, the proxy logs a warning and falls back to the last safe response.\n */\n withHtmlPipelineErrorHandler(handler: MOSProxyHtmlPipelineErrorHandler): this {\n this._onHtmlPipelineError = handler\n return this\n }\n\n withoutCustomEndpoints(): this {\n this._customEndpoints = false\n return this\n }\n\n withoutLinkRewriting(): this {\n this._linkRewriting = false\n return this\n }\n\n withoutSurfaceDecisions(): this {\n this._surfaceDecisions = false\n return this\n }\n\n /**\n * Disable the HTML transformation pipeline entirely (stages 3–6).\n *\n * Use for API-only proxies: neither an HTML rewriter adapter nor an API fetcher is required\n * if custom endpoints and surface decisions are both disabled alongside this.\n */\n withoutHtmlTransformation(): this {\n this._htmlTransformation = false\n return this\n }\n\n /**\n * Add an additional route which, when matched by an incoming request, will be forwarded to the MonetizationOS API\n * with authentication handled by the configured identity provider.\n * This is in addition to the default `/mos-api/offer-redemptions` route.\n */\n withMosAuthenticatedApiRoutes(...routes: MosAuthenticatedApiRoute[]): this {\n this._additionalAuthenticatedApiRoutes.push(...routes)\n return this\n }\n\n build(): MOSProxy {\n if (!this._config) {\n throw new Error('MOSProxyBuilder: withConfig(...) is required')\n }\n\n const needsHtmlRewriter = this._htmlTransformation && this._surfaceDecisions\n if (needsHtmlRewriter && !this._htmlRewriter) {\n throw new Error(\n 'MOSProxyBuilder: withHtmlRewriter(...) is required when surface decisions is enabled; call withoutSurfaceDecisions() or withoutHtmlTransformation() if you do not need it',\n )\n }\n\n const opts: MOSProxyOptions = {\n config: this._config,\n originFetcher: this._originFetcher,\n apiFetcher: this._apiFetcher,\n htmlRewriter: this._htmlRewriter,\n clientMetadataProvider: this._clientMetadataProvider,\n identityProvider: this._identityProvider,\n resourceProvider: this._resourceProvider,\n logger: this._logger ?? undefined,\n onHtmlPipelineError: this._onHtmlPipelineError ?? undefined,\n customEndpointsEnabled: this._customEndpoints,\n linkRewritingEnabled: this._linkRewriting,\n surfaceDecisionsEnabled: this._surfaceDecisions,\n htmlTransformationEnabled: this._htmlTransformation,\n mosAuthenticatedApiRoutes: [\n ...this._additionalAuthenticatedApiRoutes,\n {\n matchPath: '/mos-api/offer-redemptions',\n method: 'POST',\n mosPath: '/api/v1/offer-redemptions',\n },\n ],\n }\n return new MOSProxy(opts)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MOSProxyBuilder.js","sourceRoot":"","sources":["../src/MOSProxyBuilder.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAA+D,MAAM,YAAY,CAAA;AAGlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,OAAO,eAAe;IAChB,OAAO,GAA0C,IAAI,CAAA;IACrD,cAAc,GAAY,UAAU,CAAC,KAAK,CAAA;IAC1C,WAAW,GAAY,UAAU,CAAC,KAAK,CAAA;IACvC,aAAa,GAA+B,IAAI,CAAA;IAChD,uBAAuB,GAAkC,IAAI,CAAA;IAC7D,iBAAiB,GAA4B,IAAI,CAAA;IACjD,iBAAiB,GAA4B,IAAI,CAAA;IACjD,mBAAmB,GAAmC,IAAI,CAAA;IAC1D,gBAAgB,GAAkB,IAAI,CAAA;IACtC,OAAO,GAA0B,IAAI,CAAA;IACrC,oBAAoB,GAA4C,IAAI,CAAA;IACpE,gBAAgB,GAAG,IAAI,CAAA;IACvB,cAAc,GAAG,IAAI,CAAA;IACrB,iBAAiB,GAAG,IAAI,CAAA;IACxB,mBAAmB,GAAG,IAAI,CAAA;IAC1B,iCAAiC,GAA+B,EAAE,CAAA;IAE1E;;;;;;OAMG;IACH,UAAU,CAAC,MAAsC;QAC7C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,OAAO,IAAI,CAAA;IACf,CAAC;IAED,iBAAiB,CAAC,OAAgB;QAC9B,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;QAC7B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,cAAc,CAAC,OAAgB;QAC3B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAA;QAC1B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,gBAAgB,CAAC,QAA6B;QAC1C,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAA;QAC7B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,kBAAkB,CAAC,QAAgC;QAC/C,IAAI,CAAC,uBAAuB,GAAG,QAAQ,CAAA;QACvC,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,QAA0B;QAC3C,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAA;QACjC,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,QAA0B;QAC3C,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAA;QACjC,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACH,2BAA2B,CAAC,OAAgC;QACxD,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAA;QAClC,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,GAAW;QAC3B,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAA;QAC3B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,UAAU,CAAC,MAAsB;QAC7B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;;;OAMG;IACH,4BAA4B,CAAC,OAAyC;QAClE,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAA;QACnC,OAAO,IAAI,CAAA;IACf,CAAC;IAED,sBAAsB;QAClB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAA;QAC7B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,oBAAoB;QAChB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;QAC3B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,uBAAuB;QACnB,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAA;QAC9B,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACH,yBAAyB;QACrB,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAA;QAChC,OAAO,IAAI,CAAA;IACf,CAAC;IAED;;;;OAIG;IACH,6BAA6B,CAAC,GAAG,MAAkC;QAC/D,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAA;QACtD,OAAO,IAAI,CAAA;IACf,CAAC;IAED,KAAK;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;QACnE,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,iBAAiB,CAAA;QAC5E,IAAI,iBAAiB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACX,2KAA2K,CAC9K,CAAA;QACL,CAAC;QAED,MAAM,IAAI,GAAoB;YAC1B,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,aAAa,EAAE,IAAI,CAAC,cAAc;YAClC,UAAU,EAAE,IAAI,CAAC,WAAW;YAC5B,YAAY,EAAE,IAAI,CAAC,aAAa;YAChC,sBAAsB,EAAE,IAAI,CAAC,uBAAuB;YACpD,gBAAgB,EAAE,IAAI,CAAC,iBAAiB;YACxC,gBAAgB,EAAE,IAAI,CAAC,iBAAiB;YACxC,kBAAkB,EAAE,IAAI,CAAC,mBAAmB,IAAI,SAAS;YACzD,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,SAAS;YACpD,MAAM,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;YACjC,mBAAmB,EAAE,IAAI,CAAC,oBAAoB,IAAI,SAAS;YAC3D,sBAAsB,EAAE,IAAI,CAAC,gBAAgB;YAC7C,oBAAoB,EAAE,IAAI,CAAC,cAAc;YACzC,uBAAuB,EAAE,IAAI,CAAC,iBAAiB;YAC/C,yBAAyB,EAAE,IAAI,CAAC,mBAAmB;YACnD,yBAAyB,EAAE;gBACvB,GAAG,IAAI,CAAC,iCAAiC;gBACzC;oBACI,SAAS,EAAE,4BAA4B;oBACvC,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,2BAA2B;iBACvC;aACJ;SACJ,CAAA;QACD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;CACJ","sourcesContent":["import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider'\nimport type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory'\nimport type { Fetcher } from './adapters/Fetcher'\nimport type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter'\nimport type { IdentityProvider } from './adapters/IdentityProvider'\nimport type { ResourceProvider } from './adapters/ResourceProvider'\nimport type { MOSProxyLogger } from './logger'\nimport { MOSProxy, type MOSProxyHtmlPipelineErrorHandler, type MOSProxyOptions } from './MOSProxy'\nimport type { MOSConfigInput, MosAuthenticatedApiRoute } from './types'\n\n/**\n * Fluent builder for `MOSProxy`. Provide configuration and platform adapters, then `build()`.\n *\n * Defaults:\n * - custom endpoint routing: enabled\n * - link rewriting: enabled\n * - surface decisions: enabled\n * - HTML transformation: enabled (auto-skipped per request for non-HTML responses)\n * - origin/API fetcher: `globalThis.fetch` (override on runtimes that need a backend binding, e.g. Fastly)\n *\n * @example\n * ```ts\n * const proxy = new MOSProxyBuilder()\n * .withConfig({\n * originUrl: 'https://news.example.com',\n * surfaceSlug: 'web',\n * mosHost: 'https://api.monetizationos.com',\n * mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,\n * mosEndpointsPrefix: '/mos-endpoints/',\n * anonymousSessionCookieName: 'anon-session-id',\n * authenticatedUserJwtCookieName: '__session',\n * injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',\n * surfaceDecisionsIgnorePaths: '',\n * // Optional: forward specific cookies to surface decisions\n * surfaceDecisionsCookies: '^__session$, ^theme$',\n * originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },\n * createAnonymousIdentifierFallback: true,\n * })\n * .withHtmlRewriter(myHtmlRewriterAdapter)\n * .build()\n *\n * export default { fetch: (request: Request) => proxy.handle(request) }\n * ```\n */\nexport class MOSProxyBuilder {\n private _config: MOSConfigInput | ConfigFactory | null = null\n private _originFetcher: Fetcher = globalThis.fetch\n private _apiFetcher: Fetcher = globalThis.fetch\n private _htmlRewriter: HtmlRewriterAdapter | null = null\n private _clientMetadataProvider: ClientMetadataProvider | null = null\n private _identityProvider: IdentityProvider | null = null\n private _resourceProvider: ResourceProvider | null = null\n private _onUnresolvedConfig: UnresolvedConfigHandler | null = null\n private _configCacheSize: number | null = null\n private _logger: MOSProxyLogger | null = null\n private _onHtmlPipelineError: MOSProxyHtmlPipelineErrorHandler | null = null\n private _customEndpoints = true\n private _linkRewriting = true\n private _surfaceDecisions = true\n private _htmlTransformation = true\n private _additionalAuthenticatedApiRoutes: MosAuthenticatedApiRoute[] = []\n\n /**\n * Sets the proxy configuration. Pass a {@link MOSConfigInput} for one fixed config, or a\n * {@link ConfigFactory} to compute a full config per request so one deployment can front several\n * brands. A factory returns the complete config (you merge shared fields with per-brand values\n * yourself). See {@link hostPathMatcher} for a ready-made host/path table, and\n * {@link withUnresolvedConfigHandler} for requests it can't resolve.\n */\n withConfig(config: MOSConfigInput | ConfigFactory): this {\n this._config = config\n return this\n }\n\n withOriginFetcher(fetcher: Fetcher): this {\n this._originFetcher = fetcher\n return this\n }\n\n withApiFetcher(fetcher: Fetcher): this {\n this._apiFetcher = fetcher\n return this\n }\n\n withHtmlRewriter(rewriter: HtmlRewriterAdapter): this {\n this._htmlRewriter = rewriter\n return this\n }\n\n withClientMetadata(provider: ClientMetadataProvider): this {\n this._clientMetadataProvider = provider\n return this\n }\n\n /**\n * Override identity provision for the surface-decisions API. Provide `resolve` to control the\n * identity payload sent to the API (e.g. resolved from a request header instead of cookies),\n * and/or `persist` to control how identity is recorded back on the response (e.g. suppress the\n * default anonymous-session cookie or write it elsewhere). Either method is optional; omitted\n * methods fall back to the built-in defaults.\n */\n withIdentityProvider(provider: IdentityProvider): this {\n this._identityProvider = provider\n return this\n }\n\n /**\n * Override the resource object sent to the surface-decisions API per request. `build` receives\n * only the `Request`; the proxy derives defaults (`{ id: pathname, meta: pageMetadata }`) and\n * shallow-merges your returned record over them. Return only the keys you want to add or\n * override (e.g. a content tier or canonical id) — `id`/`meta` are preserved unless you set them.\n */\n withResourceProvider(provider: ResourceProvider): this {\n this._resourceProvider = provider\n return this\n }\n\n /**\n * Handles requests a {@link ConfigFactory} couldn't resolve (it threw or returned a config that\n * wouldn't normalize). Return a `Response` to fail closed (e.g. a 404 for an unknown host), or\n * return nothing to fall through to that host's last-known-good config. With no handler and no\n * last-known-good config for the host, the error propagates instead of serving another brand's config.\n */\n withUnresolvedConfigHandler(handler: UnresolvedConfigHandler): this {\n this._onUnresolvedConfig = handler\n return this\n }\n\n /**\n * Max number of normalized configs to keep in memory. Defaults to 256; raise it above your\n * live brand count if a deployment fronts more than that.\n */\n withConfigCacheSize(max: number): this {\n this._configCacheSize = max\n return this\n }\n\n withLogger(logger: MOSProxyLogger): this {\n this._logger = logger\n return this\n }\n\n /**\n * Register a callback to handle HTML pipeline errors. The proxy fails open by default and\n * returns the last safe (unconsumed) response — provide this if you want to shape that\n * response yourself (e.g. render a custom error page, return a 503, or re-throw so your\n * platform's error middleware handles it). If the callback throws or returns a non-Response\n * value, the proxy logs a warning and falls back to the last safe response.\n */\n withHtmlPipelineErrorHandler(handler: MOSProxyHtmlPipelineErrorHandler): this {\n this._onHtmlPipelineError = handler\n return this\n }\n\n withoutCustomEndpoints(): this {\n this._customEndpoints = false\n return this\n }\n\n withoutLinkRewriting(): this {\n this._linkRewriting = false\n return this\n }\n\n withoutSurfaceDecisions(): this {\n this._surfaceDecisions = false\n return this\n }\n\n /**\n * Disable the HTML transformation pipeline entirely (stages 3–6).\n *\n * Use for API-only proxies: neither an HTML rewriter adapter nor an API fetcher is required\n * if custom endpoints and surface decisions are both disabled alongside this.\n */\n withoutHtmlTransformation(): this {\n this._htmlTransformation = false\n return this\n }\n\n /**\n * Add an additional route which, when matched by an incoming request, will be forwarded to the MonetizationOS API\n * with authentication handled by the configured identity provider.\n * This is in addition to the default `/mos-api/offer-redemptions` route.\n */\n withMosAuthenticatedApiRoutes(...routes: MosAuthenticatedApiRoute[]): this {\n this._additionalAuthenticatedApiRoutes.push(...routes)\n return this\n }\n\n build(): MOSProxy {\n if (!this._config) {\n throw new Error('MOSProxyBuilder: withConfig(...) is required')\n }\n\n const needsHtmlRewriter = this._htmlTransformation && this._surfaceDecisions\n if (needsHtmlRewriter && !this._htmlRewriter) {\n throw new Error(\n 'MOSProxyBuilder: withHtmlRewriter(...) is required when surface decisions is enabled; call withoutSurfaceDecisions() or withoutHtmlTransformation() if you do not need it',\n )\n }\n\n const opts: MOSProxyOptions = {\n config: this._config,\n originFetcher: this._originFetcher,\n apiFetcher: this._apiFetcher,\n htmlRewriter: this._htmlRewriter,\n clientMetadataProvider: this._clientMetadataProvider,\n identityProvider: this._identityProvider,\n resourceProvider: this._resourceProvider,\n onUnresolvedConfig: this._onUnresolvedConfig ?? undefined,\n maxCachedConfigs: this._configCacheSize ?? undefined,\n logger: this._logger ?? undefined,\n onHtmlPipelineError: this._onHtmlPipelineError ?? undefined,\n customEndpointsEnabled: this._customEndpoints,\n linkRewritingEnabled: this._linkRewriting,\n surfaceDecisionsEnabled: this._surfaceDecisions,\n htmlTransformationEnabled: this._htmlTransformation,\n mosAuthenticatedApiRoutes: [\n ...this._additionalAuthenticatedApiRoutes,\n {\n matchPath: '/mos-api/offer-redemptions',\n method: 'POST',\n mosPath: '/api/v1/offer-redemptions',\n },\n ],\n }\n return new MOSProxy(opts)\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MOSConfigInput } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a complete `MOSConfigInput` for a request. Throw when there's no config; the proxy then
|
|
4
|
+
* calls the {@link UnresolvedConfigHandler}, falls back to the host's last-known-good config, or rethrows.
|
|
5
|
+
*
|
|
6
|
+
* Runs on every request. The proxy caches only the normalize step (by config content), not the call,
|
|
7
|
+
* so cache an expensive lookup like KV yourself if the per-request read is an issue.
|
|
8
|
+
*/
|
|
9
|
+
export type ConfigFactory = (request: Request) => MOSConfigInput | Promise<MOSConfigInput>;
|
|
10
|
+
/** Why per-request config resolution did not produce a usable config. */
|
|
11
|
+
export type UnresolvedConfigReason = 'invalid-config';
|
|
12
|
+
/** Context passed to an {@link UnresolvedConfigHandler}. */
|
|
13
|
+
export interface UnresolvedConfigContext {
|
|
14
|
+
request: Request;
|
|
15
|
+
/** `invalid-config`: the factory threw, or its config failed to normalize. */
|
|
16
|
+
reason: UnresolvedConfigReason;
|
|
17
|
+
/** The error that caused resolution to fail. */
|
|
18
|
+
error?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Handles a request whose config couldn't be resolved. Return a `Response` to fail closed (e.g. a 404
|
|
22
|
+
* for an unknown host), or return nothing to fall through to the host's last-known-good config. If
|
|
23
|
+
* neither is available, the error propagates. A handler that throws or returns a non-`Response` is
|
|
24
|
+
* logged and treated as if it returned nothing.
|
|
25
|
+
*/
|
|
26
|
+
export type UnresolvedConfigHandler = (ctx: UnresolvedConfigContext) => Response | undefined | Promise<Response | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigFactory.js","sourceRoot":"","sources":["../../src/adapters/ConfigFactory.ts"],"names":[],"mappings":"","sourcesContent":["import type { MOSConfigInput } from '../types'\n\n/**\n * Returns a complete `MOSConfigInput` for a request. Throw when there's no config; the proxy then\n * calls the {@link UnresolvedConfigHandler}, falls back to the host's last-known-good config, or rethrows.\n *\n * Runs on every request. The proxy caches only the normalize step (by config content), not the call,\n * so cache an expensive lookup like KV yourself if the per-request read is an issue.\n */\nexport type ConfigFactory = (request: Request) => MOSConfigInput | Promise<MOSConfigInput>\n\n/** Why per-request config resolution did not produce a usable config. */\nexport type UnresolvedConfigReason = 'invalid-config'\n\n/** Context passed to an {@link UnresolvedConfigHandler}. */\nexport interface UnresolvedConfigContext {\n request: Request\n /** `invalid-config`: the factory threw, or its config failed to normalize. */\n reason: UnresolvedConfigReason\n /** The error that caused resolution to fail. */\n error?: unknown\n}\n\n/**\n * Handles a request whose config couldn't be resolved. Return a `Response` to fail closed (e.g. a 404\n * for an unknown host), or return nothing to fall through to the host's last-known-good config. If\n * neither is available, the error propagates. A handler that throws or returns a non-`Response` is\n * logged and treated as if it returned nothing.\n */\nexport type UnresolvedConfigHandler = (ctx: UnresolvedConfigContext) => Response | undefined | Promise<Response | undefined>\n"]}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { ClientMetadataProvider } from './ClientMetadataProvider';
|
|
2
|
+
export type { ConfigFactory, UnresolvedConfigContext, UnresolvedConfigHandler, UnresolvedConfigReason, } from './ConfigFactory';
|
|
2
3
|
export type { Fetcher } from './Fetcher';
|
|
3
4
|
export type { ContentOptions, ElementHandlers, HtmlRewriterAdapter, HtmlRewriterCapabilities, HtmlRewriterSession, RewriterElement, RewriterText, } from './HtmlRewriterAdapter';
|
|
4
5
|
export type { Identity, IdentityProvider, PersistIdentityArgs, ResolveIdentityArgs } from './IdentityProvider';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAkBA,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,GACrB,MAAM,oBAAoB,CAAA","sourcesContent":["export type { ClientMetadataProvider } from './ClientMetadataProvider'\nexport type {\n ConfigFactory,\n UnresolvedConfigContext,\n UnresolvedConfigHandler,\n UnresolvedConfigReason,\n} from './ConfigFactory'\nexport type { Fetcher } from './Fetcher'\nexport type {\n ContentOptions,\n ElementHandlers,\n HtmlRewriterAdapter,\n HtmlRewriterCapabilities,\n HtmlRewriterSession,\n RewriterElement,\n RewriterText,\n} from './HtmlRewriterAdapter'\nexport type { Identity, IdentityProvider, PersistIdentityArgs, ResolveIdentityArgs } from './IdentityProvider'\nexport {\n buildIdentity,\n defaultPersistIdentity,\n defaultResolveIdentity,\n getExistingCookies,\n} from './IdentityProvider'\nexport type { Resource, ResourceProvider } from './ResourceProvider'\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const MOS_PROXY_VERSION_HEADER = "X-MOS-Proxy-Version";
|
|
2
2
|
export declare const MOS_PROXY_CLIENT_HEADER = "X-MOS-Proxy-Client";
|
|
3
|
-
export declare const MOS_PROXY_PACKAGE_VERSION = "1.
|
|
3
|
+
export declare const MOS_PROXY_PACKAGE_VERSION = "1.5.0";
|
|
4
4
|
export declare const setMosProxyHeaders: (headers: Headers) => void;
|
|
5
5
|
export declare const withMosProxyHeaders: (headers?: HeadersInit) => Headers;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { formatClientRuntimeHeader } from './clientRuntime';
|
|
2
2
|
export const MOS_PROXY_VERSION_HEADER = 'X-MOS-Proxy-Version';
|
|
3
3
|
export const MOS_PROXY_CLIENT_HEADER = 'X-MOS-Proxy-Client';
|
|
4
|
-
export const MOS_PROXY_PACKAGE_VERSION = '1.
|
|
4
|
+
export const MOS_PROXY_PACKAGE_VERSION = '1.5.0';
|
|
5
5
|
export const setMosProxyHeaders = (headers) => {
|
|
6
6
|
headers.set(MOS_PROXY_VERSION_HEADER, MOS_PROXY_PACKAGE_VERSION);
|
|
7
7
|
headers.set(MOS_PROXY_CLIENT_HEADER, formatClientRuntimeHeader());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiRequestHeaders.js","sourceRoot":"","sources":["../src/apiRequestHeaders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAA;AAC7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAA;AAC3D,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAA;AAEhD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAgB,EAAQ,EAAE;IACzD,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,CAAA;IAChE,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,yBAAyB,EAAE,CAAC,CAAA;AACrE,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAqB,EAAW,EAAE;IAClE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACxB,OAAO,IAAI,CAAA;AACf,CAAC,CAAA","sourcesContent":["import { formatClientRuntimeHeader } from './clientRuntime'\n\nexport const MOS_PROXY_VERSION_HEADER = 'X-MOS-Proxy-Version'\nexport const MOS_PROXY_CLIENT_HEADER = 'X-MOS-Proxy-Client'\nexport const MOS_PROXY_PACKAGE_VERSION = '1.
|
|
1
|
+
{"version":3,"file":"apiRequestHeaders.js","sourceRoot":"","sources":["../src/apiRequestHeaders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAA;AAC7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAA;AAC3D,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAA;AAEhD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAgB,EAAQ,EAAE;IACzD,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,CAAA;IAChE,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,yBAAyB,EAAE,CAAC,CAAA;AACrE,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAqB,EAAW,EAAE;IAClE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACxB,OAAO,IAAI,CAAA;AACf,CAAC,CAAA","sourcesContent":["import { formatClientRuntimeHeader } from './clientRuntime'\n\nexport const MOS_PROXY_VERSION_HEADER = 'X-MOS-Proxy-Version'\nexport const MOS_PROXY_CLIENT_HEADER = 'X-MOS-Proxy-Client'\nexport const MOS_PROXY_PACKAGE_VERSION = '1.5.0'\n\nexport const setMosProxyHeaders = (headers: Headers): void => {\n headers.set(MOS_PROXY_VERSION_HEADER, MOS_PROXY_PACKAGE_VERSION)\n headers.set(MOS_PROXY_CLIENT_HEADER, formatClientRuntimeHeader())\n}\n\nexport const withMosProxyHeaders = (headers?: HeadersInit): Headers => {\n const next = new Headers(headers)\n setMosProxyHeaders(next)\n return next\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface MOSConfig {
|
|
|
11
11
|
authenticatedUserJwtCookieName: string;
|
|
12
12
|
injectScriptUrl: string | undefined;
|
|
13
13
|
surfaceDecisionsIgnorePathPatterns: RegExp[];
|
|
14
|
+
surfaceDecisionsCookiePatterns: RegExp[];
|
|
14
15
|
originRequestHeaders: Record<string, string>;
|
|
15
16
|
createAnonymousIdentifierFallback: boolean;
|
|
16
17
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import { consoleLogger } from './logger';
|
|
2
2
|
const DEFAULT_ENDPOINTS_PREFIX = '/mos-endpoints/';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
if (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
3
|
+
function parseCommaSeparatedRegexPatterns(value, invalidPatternCode, invalidPatternMessage, logger) {
|
|
4
|
+
const patterns = [];
|
|
5
|
+
if (!value) {
|
|
6
|
+
return patterns;
|
|
7
|
+
}
|
|
8
|
+
for (const raw of value.split(',')) {
|
|
9
|
+
const pattern = raw.trim();
|
|
10
|
+
if (!pattern)
|
|
11
|
+
continue;
|
|
12
|
+
try {
|
|
13
|
+
patterns.push(new RegExp(pattern));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
logger.log({
|
|
17
|
+
level: 'warn',
|
|
18
|
+
code: invalidPatternCode,
|
|
19
|
+
message: invalidPatternMessage(pattern),
|
|
20
|
+
context: { pattern },
|
|
21
|
+
error,
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
return patterns;
|
|
26
|
+
}
|
|
27
|
+
export function normalizeMOSConfig(config, logger = consoleLogger) {
|
|
28
|
+
const ignorePathPatterns = parseCommaSeparatedRegexPatterns(config.surfaceDecisionsIgnorePaths, 'invalid-ignore-path-pattern', (pattern) => `Invalid surfaceDecisionsIgnorePaths regex pattern: ${pattern}`, logger);
|
|
29
|
+
const cookiePatterns = parseCommaSeparatedRegexPatterns(config.surfaceDecisionsCookies, 'invalid-surface-decisions-cookie-pattern', (pattern) => `Invalid surfaceDecisionsCookies regex pattern: ${pattern}`, logger);
|
|
24
30
|
const secretKeyParts = config.mosSecretKey.split('_');
|
|
25
31
|
return {
|
|
26
32
|
originUrl: new URL(config.originUrl),
|
|
@@ -33,6 +39,7 @@ export function normalizeMOSConfig(config, logger = consoleLogger) {
|
|
|
33
39
|
authenticatedUserJwtCookieName: config.authenticatedUserJwtCookieName,
|
|
34
40
|
injectScriptUrl: config.injectScriptUrl,
|
|
35
41
|
surfaceDecisionsIgnorePathPatterns: ignorePathPatterns,
|
|
42
|
+
surfaceDecisionsCookiePatterns: cookiePatterns,
|
|
36
43
|
originRequestHeaders: { ...(config.originRequestHeaders ?? {}) },
|
|
37
44
|
createAnonymousIdentifierFallback: config.createAnonymousIdentifierFallback !== false,
|
|
38
45
|
};
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA6C,MAAM,UAAU,CAAA;AAGnF,MAAM,wBAAwB,GAAG,iBAAiB,CAAA;AAElD,SAAS,gCAAgC,CACrC,KAAyB,EACzB,kBAAmC,EACnC,qBAAkD,EAClD,MAAsB;IAEtB,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,QAAQ,CAAA;IACnB,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;QAC1B,IAAI,CAAC,OAAO;YAAE,SAAQ;QACtB,IAAI,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC;gBACP,KAAK,EAAE,MAAM;gBACb,IAAI,EAAE,kBAAkB;gBACxB,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC;gBACvC,OAAO,EAAE,EAAE,OAAO,EAAE;gBACpB,KAAK;aACR,CAAC,CAAA;QACN,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAA;AACnB,CAAC;AAkBD,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,SAAyB,aAAa;IAC7F,MAAM,kBAAkB,GAAG,gCAAgC,CACvD,MAAM,CAAC,2BAA2B,EAClC,6BAA6B,EAC7B,CAAC,OAAO,EAAE,EAAE,CAAC,sDAAsD,OAAO,EAAE,EAC5E,MAAM,CACT,CAAA;IACD,MAAM,cAAc,GAAG,gCAAgC,CACnD,MAAM,CAAC,uBAAuB,EAC9B,0CAA0C,EAC1C,CAAC,OAAO,EAAE,EAAE,CAAC,kDAAkD,OAAO,EAAE,EACxE,MAAM,CACT,CAAA;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAErD,OAAO;QACH,SAAS,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;QACpC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;QAChC,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE;QACvE,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,IAAI,wBAAwB;QACzE,0BAA0B,EAAE,MAAM,CAAC,0BAA0B;QAC7D,8BAA8B,EAAE,MAAM,CAAC,8BAA8B;QACrE,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,kCAAkC,EAAE,kBAAkB;QACtD,8BAA8B,EAAE,cAAc;QAC9C,oBAAoB,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,oBAAoB,IAAI,EAAE,CAAC,EAAE;QAChE,iCAAiC,EAAE,MAAM,CAAC,iCAAiC,KAAK,KAAK;KACxF,CAAA;AACL,CAAC","sourcesContent":["import { consoleLogger, type MOSProxyLogCode, type MOSProxyLogger } from './logger'\nimport type { MOSConfigInput } from './types'\n\nconst DEFAULT_ENDPOINTS_PREFIX = '/mos-endpoints/'\n\nfunction parseCommaSeparatedRegexPatterns(\n value: string | undefined,\n invalidPatternCode: MOSProxyLogCode,\n invalidPatternMessage: (pattern: string) => string,\n logger: MOSProxyLogger,\n): RegExp[] {\n const patterns: RegExp[] = []\n if (!value) {\n return patterns\n }\n\n for (const raw of value.split(',')) {\n const pattern = raw.trim()\n if (!pattern) continue\n try {\n patterns.push(new RegExp(pattern))\n } catch (error) {\n logger.log({\n level: 'warn',\n code: invalidPatternCode,\n message: invalidPatternMessage(pattern),\n context: { pattern },\n error,\n })\n }\n }\n\n return patterns\n}\n\nexport interface MOSConfig {\n originUrl: URL\n surfaceSlug: string\n mosHost: URL\n mosSecretKey: string\n mosEnvironment: string\n mosEndpointsPrefix: string\n anonymousSessionCookieName: string\n authenticatedUserJwtCookieName: string\n injectScriptUrl: string | undefined\n surfaceDecisionsIgnorePathPatterns: RegExp[]\n surfaceDecisionsCookiePatterns: RegExp[]\n originRequestHeaders: Record<string, string>\n createAnonymousIdentifierFallback: boolean\n}\n\nexport function normalizeMOSConfig(config: MOSConfigInput, logger: MOSProxyLogger = consoleLogger): MOSConfig {\n const ignorePathPatterns = parseCommaSeparatedRegexPatterns(\n config.surfaceDecisionsIgnorePaths,\n 'invalid-ignore-path-pattern',\n (pattern) => `Invalid surfaceDecisionsIgnorePaths regex pattern: ${pattern}`,\n logger,\n )\n const cookiePatterns = parseCommaSeparatedRegexPatterns(\n config.surfaceDecisionsCookies,\n 'invalid-surface-decisions-cookie-pattern',\n (pattern) => `Invalid surfaceDecisionsCookies regex pattern: ${pattern}`,\n logger,\n )\n\n const secretKeyParts = config.mosSecretKey.split('_')\n\n return {\n originUrl: new URL(config.originUrl),\n surfaceSlug: config.surfaceSlug,\n mosHost: new URL(config.mosHost),\n mosSecretKey: config.mosSecretKey,\n mosEnvironment: `${secretKeyParts[1] ?? ''}_${secretKeyParts[2] ?? ''}`,\n mosEndpointsPrefix: config.mosEndpointsPrefix || DEFAULT_ENDPOINTS_PREFIX,\n anonymousSessionCookieName: config.anonymousSessionCookieName,\n authenticatedUserJwtCookieName: config.authenticatedUserJwtCookieName,\n injectScriptUrl: config.injectScriptUrl,\n surfaceDecisionsIgnorePathPatterns: ignorePathPatterns,\n surfaceDecisionsCookiePatterns: cookiePatterns,\n originRequestHeaders: { ...(config.originRequestHeaders ?? {}) },\n createAnonymousIdentifierFallback: config.createAnonymousIdentifierFallback !== false,\n }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory';
|
|
2
|
+
import { type MOSConfig } from './config';
|
|
3
|
+
import type { MOSProxyLogger } from './logger';
|
|
4
|
+
import type { MOSConfigInput } from './types';
|
|
5
|
+
export interface ConfigResolutionOptions {
|
|
6
|
+
/** Either a static config (normalized once) or a per-request factory returning a full config. */
|
|
7
|
+
input: MOSConfigInput | ConfigFactory;
|
|
8
|
+
onUnresolved: UnresolvedConfigHandler | null;
|
|
9
|
+
maxCachedConfigs: number;
|
|
10
|
+
logger: MOSProxyLogger;
|
|
11
|
+
}
|
|
12
|
+
/** Thrown when no config could be resolved for a request and the host has no last-known-good fallback. */
|
|
13
|
+
export declare class ConfigUnresolvableError extends Error {
|
|
14
|
+
readonly host: string;
|
|
15
|
+
constructor(host: string, options?: {
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the config for a request. A static config is normalized once up front. A factory runs per
|
|
21
|
+
* request and its result is normalized and cached by content. If the factory throws or its config
|
|
22
|
+
* won't normalize, we fall back to the handler, then the host's last-known-good config, then throw.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ConfigResolution {
|
|
25
|
+
private readonly source;
|
|
26
|
+
private readonly onUnresolved;
|
|
27
|
+
private readonly maxCachedConfigs;
|
|
28
|
+
private readonly logger;
|
|
29
|
+
private readonly cache;
|
|
30
|
+
private readonly lastGoodByHost;
|
|
31
|
+
constructor(opts: ConfigResolutionOptions);
|
|
32
|
+
resolve(request: Request): Promise<MOSConfig | Response>;
|
|
33
|
+
private remember;
|
|
34
|
+
private rememberGood;
|
|
35
|
+
private touch;
|
|
36
|
+
private logFailure;
|
|
37
|
+
private failOver;
|
|
38
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { normalizeMOSConfig } from './config';
|
|
2
|
+
/** Thrown when no config could be resolved for a request and the host has no last-known-good fallback. */
|
|
3
|
+
export class ConfigUnresolvableError extends Error {
|
|
4
|
+
host;
|
|
5
|
+
constructor(host, options) {
|
|
6
|
+
super(`MOSProxy: could not resolve a config for host "${host}" and no last-known-good config is available`, options);
|
|
7
|
+
this.host = host;
|
|
8
|
+
this.name = 'ConfigUnresolvableError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// Negative-cache marker: holds the error so cache hits report the same cause as the first failure.
|
|
12
|
+
class FailedNormalization {
|
|
13
|
+
error;
|
|
14
|
+
constructor(error) {
|
|
15
|
+
this.error = error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the config for a request. A static config is normalized once up front. A factory runs per
|
|
20
|
+
* request and its result is normalized and cached by content. If the factory throws or its config
|
|
21
|
+
* won't normalize, we fall back to the handler, then the host's last-known-good config, then throw.
|
|
22
|
+
*/
|
|
23
|
+
export class ConfigResolution {
|
|
24
|
+
source;
|
|
25
|
+
onUnresolved;
|
|
26
|
+
maxCachedConfigs;
|
|
27
|
+
logger;
|
|
28
|
+
// Normalized configs keyed by content. A FailedNormalization marks a config that failed to normalize.
|
|
29
|
+
cache = new Map();
|
|
30
|
+
// Last config that normalized for each host. Keyed by host so a brand never falls back to another's.
|
|
31
|
+
lastGoodByHost = new Map();
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
if (!Number.isInteger(opts.maxCachedConfigs) || opts.maxCachedConfigs < 1) {
|
|
34
|
+
throw new RangeError(`MOSProxy: maxCachedConfigs must be a positive integer (got ${opts.maxCachedConfigs})`);
|
|
35
|
+
}
|
|
36
|
+
this.onUnresolved = opts.onUnresolved;
|
|
37
|
+
this.maxCachedConfigs = opts.maxCachedConfigs;
|
|
38
|
+
this.logger = opts.logger;
|
|
39
|
+
this.source =
|
|
40
|
+
typeof opts.input === 'function'
|
|
41
|
+
? { kind: 'factory', factory: opts.input }
|
|
42
|
+
: { kind: 'static', config: normalizeMOSConfig(opts.input, opts.logger) };
|
|
43
|
+
}
|
|
44
|
+
async resolve(request) {
|
|
45
|
+
const source = this.source;
|
|
46
|
+
if (source.kind === 'static') {
|
|
47
|
+
return source.config;
|
|
48
|
+
}
|
|
49
|
+
const host = new URL(request.url).hostname;
|
|
50
|
+
let input;
|
|
51
|
+
try {
|
|
52
|
+
input = await source.factory(request);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Log every time: a throwing factory has no config to dedupe against.
|
|
56
|
+
this.logFailure('Config factory threw.', error);
|
|
57
|
+
return this.failOver(request, host, error);
|
|
58
|
+
}
|
|
59
|
+
const key = JSON.stringify(input);
|
|
60
|
+
const cached = this.cache.get(key);
|
|
61
|
+
if (cached !== undefined) {
|
|
62
|
+
this.touch(this.cache, key, cached); // bump to newest on hit so hot configs outlive cold ones
|
|
63
|
+
if (cached instanceof FailedNormalization) {
|
|
64
|
+
return this.failOver(request, host, cached.error);
|
|
65
|
+
}
|
|
66
|
+
this.rememberGood(host, cached);
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
69
|
+
let normalized;
|
|
70
|
+
try {
|
|
71
|
+
normalized = normalizeMOSConfig(input, this.logger);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.remember(key, new FailedNormalization(error));
|
|
75
|
+
this.logFailure('Resolved config failed to normalize.', error);
|
|
76
|
+
return this.failOver(request, host, error);
|
|
77
|
+
}
|
|
78
|
+
this.remember(key, normalized);
|
|
79
|
+
this.rememberGood(host, normalized);
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
remember(key, value) {
|
|
83
|
+
this.touch(this.cache, key, value);
|
|
84
|
+
}
|
|
85
|
+
rememberGood(host, config) {
|
|
86
|
+
this.touch(this.lastGoodByHost, host, config);
|
|
87
|
+
}
|
|
88
|
+
// Add or move key to newest, then drop the oldest entry if we're over the cap (LRU).
|
|
89
|
+
touch(map, key, value) {
|
|
90
|
+
map.delete(key);
|
|
91
|
+
map.set(key, value);
|
|
92
|
+
if (map.size > this.maxCachedConfigs) {
|
|
93
|
+
const oldest = map.keys().next().value;
|
|
94
|
+
if (oldest !== undefined) {
|
|
95
|
+
map.delete(oldest);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
logFailure(message, error) {
|
|
100
|
+
this.logger.log({ level: 'error', code: 'config-resolution-failed', message, error });
|
|
101
|
+
}
|
|
102
|
+
async failOver(request, host, error) {
|
|
103
|
+
const handler = this.onUnresolved;
|
|
104
|
+
if (handler) {
|
|
105
|
+
try {
|
|
106
|
+
const response = await handler({ request, reason: 'invalid-config', error });
|
|
107
|
+
if (response instanceof Response) {
|
|
108
|
+
return response;
|
|
109
|
+
}
|
|
110
|
+
// Nothing means "use last-known-good". Anything that isn't a Response is a handler bug.
|
|
111
|
+
if (response != null) {
|
|
112
|
+
this.logger.log({
|
|
113
|
+
level: 'warn',
|
|
114
|
+
code: 'unresolved-config-handler-invalid',
|
|
115
|
+
message: 'onUnresolvedConfig did not return a Response; falling back to the last-known-good config.',
|
|
116
|
+
context: { host },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (handlerError) {
|
|
121
|
+
this.logger.log({
|
|
122
|
+
level: 'warn',
|
|
123
|
+
code: 'unresolved-config-handler-threw',
|
|
124
|
+
message: 'onUnresolvedConfig threw; falling back to the last-known-good config.',
|
|
125
|
+
context: { host },
|
|
126
|
+
error: handlerError,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const lastGood = this.lastGoodByHost.get(host);
|
|
131
|
+
if (lastGood) {
|
|
132
|
+
this.logger.log({
|
|
133
|
+
level: 'warn',
|
|
134
|
+
code: 'config-resolution-served-last-known-good',
|
|
135
|
+
message: 'Serving the last-known-good config for this host after a resolution failure.',
|
|
136
|
+
context: { host },
|
|
137
|
+
});
|
|
138
|
+
return lastGood;
|
|
139
|
+
}
|
|
140
|
+
throw new ConfigUnresolvableError(host, { cause: error });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=configResolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configResolution.js","sourceRoot":"","sources":["../src/configResolution.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConfigFactory, UnresolvedConfigHandler } from './adapters/ConfigFactory'\nimport { type MOSConfig, normalizeMOSConfig } from './config'\nimport type { MOSProxyLogger } from './logger'\nimport type { MOSConfigInput } from './types'\n\nexport interface ConfigResolutionOptions {\n /** Either a static config (normalized once) or a per-request factory returning a full config. */\n input: MOSConfigInput | ConfigFactory\n onUnresolved: UnresolvedConfigHandler | null\n maxCachedConfigs: number\n logger: MOSProxyLogger\n}\n\n/** Thrown when no config could be resolved for a request and the host has no last-known-good fallback. */\nexport class ConfigUnresolvableError extends Error {\n constructor(\n readonly host: string,\n options?: { cause?: unknown },\n ) {\n super(`MOSProxy: could not resolve a config for host \"${host}\" and no last-known-good config is available`, options)\n this.name = 'ConfigUnresolvableError'\n }\n}\n\n// Discriminated union so resolve() can switch on kind without null checks or casts.\ntype ConfigSource = { readonly kind: 'static'; readonly config: MOSConfig } | { readonly kind: 'factory'; readonly factory: ConfigFactory }\n\n// Negative-cache marker: holds the error so cache hits report the same cause as the first failure.\nclass FailedNormalization {\n constructor(readonly error: unknown) {}\n}\n\n/**\n * Resolves the config for a request. A static config is normalized once up front. A factory runs per\n * request and its result is normalized and cached by content. If the factory throws or its config\n * won't normalize, we fall back to the handler, then the host's last-known-good config, then throw.\n */\nexport class ConfigResolution {\n private readonly source: ConfigSource\n private readonly onUnresolved: UnresolvedConfigHandler | null\n private readonly maxCachedConfigs: number\n private readonly logger: MOSProxyLogger\n // Normalized configs keyed by content. A FailedNormalization marks a config that failed to normalize.\n private readonly cache = new Map<string, MOSConfig | FailedNormalization>()\n // Last config that normalized for each host. Keyed by host so a brand never falls back to another's.\n private readonly lastGoodByHost = new Map<string, MOSConfig>()\n\n constructor(opts: ConfigResolutionOptions) {\n if (!Number.isInteger(opts.maxCachedConfigs) || opts.maxCachedConfigs < 1) {\n throw new RangeError(`MOSProxy: maxCachedConfigs must be a positive integer (got ${opts.maxCachedConfigs})`)\n }\n this.onUnresolved = opts.onUnresolved\n this.maxCachedConfigs = opts.maxCachedConfigs\n this.logger = opts.logger\n this.source =\n typeof opts.input === 'function'\n ? { kind: 'factory', factory: opts.input }\n : { kind: 'static', config: normalizeMOSConfig(opts.input, opts.logger) }\n }\n\n async resolve(request: Request): Promise<MOSConfig | Response> {\n const source = this.source\n if (source.kind === 'static') {\n return source.config\n }\n\n const host = new URL(request.url).hostname\n\n let input: MOSConfigInput\n try {\n input = await source.factory(request)\n } catch (error) {\n // Log every time: a throwing factory has no config to dedupe against.\n this.logFailure('Config factory threw.', error)\n return this.failOver(request, host, error)\n }\n\n const key = JSON.stringify(input)\n\n const cached = this.cache.get(key)\n if (cached !== undefined) {\n this.touch(this.cache, key, cached) // bump to newest on hit so hot configs outlive cold ones\n if (cached instanceof FailedNormalization) {\n return this.failOver(request, host, cached.error)\n }\n this.rememberGood(host, cached)\n return cached\n }\n\n let normalized: MOSConfig\n try {\n normalized = normalizeMOSConfig(input, this.logger)\n } catch (error) {\n this.remember(key, new FailedNormalization(error))\n this.logFailure('Resolved config failed to normalize.', error)\n return this.failOver(request, host, error)\n }\n\n this.remember(key, normalized)\n this.rememberGood(host, normalized)\n return normalized\n }\n\n private remember(key: string, value: MOSConfig | FailedNormalization): void {\n this.touch(this.cache, key, value)\n }\n\n private rememberGood(host: string, config: MOSConfig): void {\n this.touch(this.lastGoodByHost, host, config)\n }\n\n // Add or move key to newest, then drop the oldest entry if we're over the cap (LRU).\n private touch<V>(map: Map<string, V>, key: string, value: V): void {\n map.delete(key)\n map.set(key, value)\n if (map.size > this.maxCachedConfigs) {\n const oldest = map.keys().next().value\n if (oldest !== undefined) {\n map.delete(oldest)\n }\n }\n }\n\n private logFailure(message: string, error: unknown): void {\n this.logger.log({ level: 'error', code: 'config-resolution-failed', message, error })\n }\n\n private async failOver(request: Request, host: string, error?: unknown): Promise<MOSConfig | Response> {\n const handler = this.onUnresolved\n if (handler) {\n try {\n const response = await handler({ request, reason: 'invalid-config', error })\n if (response instanceof Response) {\n return response\n }\n // Nothing means \"use last-known-good\". Anything that isn't a Response is a handler bug.\n if (response != null) {\n this.logger.log({\n level: 'warn',\n code: 'unresolved-config-handler-invalid',\n message: 'onUnresolvedConfig did not return a Response; falling back to the last-known-good config.',\n context: { host },\n })\n }\n } catch (handlerError) {\n this.logger.log({\n level: 'warn',\n code: 'unresolved-config-handler-threw',\n message: 'onUnresolvedConfig threw; falling back to the last-known-good config.',\n context: { host },\n error: handlerError,\n })\n }\n }\n\n const lastGood = this.lastGoodByHost.get(host)\n if (lastGood) {\n this.logger.log({\n level: 'warn',\n code: 'config-resolution-served-last-known-good',\n message: 'Serving the last-known-good config for this host after a resolution failure.',\n context: { host },\n })\n return lastGood\n }\n\n throw new ConfigUnresolvableError(host, { cause: error })\n }\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ConfigFactory } from './adapters/ConfigFactory';
|
|
2
|
+
import type { MOSConfigInput } from './types';
|
|
3
|
+
/** Non-optional keys of `T`. */
|
|
4
|
+
type RequiredKeys<T> = {
|
|
5
|
+
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
|
6
|
+
}[keyof T];
|
|
7
|
+
/** Required config keys not supplied by base `B`. */
|
|
8
|
+
type MissingRequiredConfigKeys<B> = Exclude<RequiredKeys<MOSConfigInput>, keyof B>;
|
|
9
|
+
/** Rule config: the required keys `base` lacks, plus any other field optionally. */
|
|
10
|
+
type RuleConfig<B> = Partial<MOSConfigInput> & Required<Pick<MOSConfigInput, MissingRequiredConfigKeys<B>>>;
|
|
11
|
+
/** A host/path → config rule for {@link hostPathMatcher}. */
|
|
12
|
+
export interface HostPathRule<B = Record<never, never>> {
|
|
13
|
+
/** Exact request hostname (case-insensitive, port-free, no wildcards). Omit to match any host. */
|
|
14
|
+
host?: string;
|
|
15
|
+
/** Path prefix, matched on segment boundaries (`/news` matches `/news/x`, not `/newsletter`). Omit, or pass `/`, for any path. */
|
|
16
|
+
pathPrefix?: string;
|
|
17
|
+
/** This rule's config, shallow-merged over `base`. Must contain every required field `base` lacks. */
|
|
18
|
+
config: RuleConfig<B>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds a {@link ConfigFactory} from host/path rules. Each rule's `config` is shallow-merged over
|
|
22
|
+
* `base`; the result must be a complete {@link MOSConfigInput}.
|
|
23
|
+
*
|
|
24
|
+
* Resolution order — the first match wins:
|
|
25
|
+
* 1. Rules whose `host` is the request host, longest matching `pathPrefix` first (ties: earlier rule).
|
|
26
|
+
* 2. Host-less rules, ranked the same way; one with no `pathPrefix` is the global catch-all (at most one).
|
|
27
|
+
* 3. No match throws.
|
|
28
|
+
*
|
|
29
|
+
* `config` is type-checked to supply the keys `base` lacks, but only when `base` is written
|
|
30
|
+
* `satisfies Partial<MOSConfigInput>` (not annotated). `normalizeMOSConfig` re-checks at runtime.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hostPathMatcher<const B extends Partial<MOSConfigInput> = Record<never, never>>(rules: HostPathRule<B>[], base?: B): ConfigFactory;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function normalizePrefix(pathPrefix) {
|
|
2
|
+
if (pathPrefix === undefined) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = pathPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
6
|
+
return trimmed === '' ? undefined : `/${trimmed}`;
|
|
7
|
+
}
|
|
8
|
+
/** Whether `prefix` matches `pathname`: absent, equal, or on a segment boundary. */
|
|
9
|
+
function prefixMatches(prefix, pathname) {
|
|
10
|
+
return prefix === undefined || pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
11
|
+
}
|
|
12
|
+
/** Best `eligible` rule by longest matching prefix; ties go to the first declared. */
|
|
13
|
+
function selectBestRule(rules, pathname, eligible) {
|
|
14
|
+
let best;
|
|
15
|
+
let bestPrefixLength = -1;
|
|
16
|
+
for (const rule of rules) {
|
|
17
|
+
if (!eligible(rule) || !prefixMatches(rule.prefix, pathname)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const prefixLength = rule.prefix === undefined ? 0 : rule.prefix.length;
|
|
21
|
+
// Strict > keeps the first rule on a tie.
|
|
22
|
+
if (best === undefined || prefixLength > bestPrefixLength) {
|
|
23
|
+
best = rule;
|
|
24
|
+
bestPrefixLength = prefixLength;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a {@link ConfigFactory} from host/path rules. Each rule's `config` is shallow-merged over
|
|
31
|
+
* `base`; the result must be a complete {@link MOSConfigInput}.
|
|
32
|
+
*
|
|
33
|
+
* Resolution order — the first match wins:
|
|
34
|
+
* 1. Rules whose `host` is the request host, longest matching `pathPrefix` first (ties: earlier rule).
|
|
35
|
+
* 2. Host-less rules, ranked the same way; one with no `pathPrefix` is the global catch-all (at most one).
|
|
36
|
+
* 3. No match throws.
|
|
37
|
+
*
|
|
38
|
+
* `config` is type-checked to supply the keys `base` lacks, but only when `base` is written
|
|
39
|
+
* `satisfies Partial<MOSConfigInput>` (not annotated). `normalizeMOSConfig` re-checks at runtime.
|
|
40
|
+
*/
|
|
41
|
+
export function hostPathMatcher(rules, base) {
|
|
42
|
+
const compiled = rules.map((rule) => ({
|
|
43
|
+
host: rule.host?.toLowerCase(),
|
|
44
|
+
prefix: normalizePrefix(rule.pathPrefix),
|
|
45
|
+
config: rule.config,
|
|
46
|
+
}));
|
|
47
|
+
const globalCatchAlls = compiled.filter((rule) => rule.host === undefined && rule.prefix === undefined);
|
|
48
|
+
if (globalCatchAlls.length > 1) {
|
|
49
|
+
throw new Error('hostPathMatcher: only one host-less, path-less global catch-all rule is allowed');
|
|
50
|
+
}
|
|
51
|
+
return (request) => {
|
|
52
|
+
const url = new URL(request.url);
|
|
53
|
+
// hostname is port-free; url.host would keep the port (acme.com:8443).
|
|
54
|
+
const host = url.hostname;
|
|
55
|
+
const pathname = url.pathname;
|
|
56
|
+
// Host tier wins over host-less.
|
|
57
|
+
const matchedRule = selectBestRule(compiled, pathname, (rule) => rule.host === host) ??
|
|
58
|
+
selectBestRule(compiled, pathname, (rule) => rule.host === undefined);
|
|
59
|
+
if (matchedRule === undefined) {
|
|
60
|
+
throw new Error(`hostPathMatcher: no rule matched ${host}${pathname}`);
|
|
61
|
+
}
|
|
62
|
+
return { ...base, ...matchedRule.config };
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=hostPathMatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostPathMatcher.js","sourceRoot":"","sources":["../src/hostPathMatcher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConfigFactory } from './adapters/ConfigFactory'\nimport type { MOSConfigInput } from './types'\n\n/** Non-optional keys of `T`. */\ntype RequiredKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T]\n/** Required config keys not supplied by base `B`. */\ntype MissingRequiredConfigKeys<B> = Exclude<RequiredKeys<MOSConfigInput>, keyof B>\n/** Rule config: the required keys `base` lacks, plus any other field optionally. */\ntype RuleConfig<B> = Partial<MOSConfigInput> & Required<Pick<MOSConfigInput, MissingRequiredConfigKeys<B>>>\n\n/** A host/path → config rule for {@link hostPathMatcher}. */\nexport interface HostPathRule<B = Record<never, never>> {\n /** Exact request hostname (case-insensitive, port-free, no wildcards). Omit to match any host. */\n host?: string\n /** Path prefix, matched on segment boundaries (`/news` matches `/news/x`, not `/newsletter`). Omit, or pass `/`, for any path. */\n pathPrefix?: string\n /** This rule's config, shallow-merged over `base`. Must contain every required field `base` lacks. */\n config: RuleConfig<B>\n}\n\ninterface CompiledRule {\n host: string | undefined\n prefix: string | undefined\n config: Partial<MOSConfigInput>\n}\n\nfunction normalizePrefix(pathPrefix: string | undefined): string | undefined {\n if (pathPrefix === undefined) {\n return undefined\n }\n const trimmed = pathPrefix.replace(/^\\/+/, '').replace(/\\/+$/, '')\n return trimmed === '' ? undefined : `/${trimmed}`\n}\n\n/** Whether `prefix` matches `pathname`: absent, equal, or on a segment boundary. */\nfunction prefixMatches(prefix: string | undefined, pathname: string): boolean {\n return prefix === undefined || pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\n/** Best `eligible` rule by longest matching prefix; ties go to the first declared. */\nfunction selectBestRule(rules: CompiledRule[], pathname: string, eligible: (rule: CompiledRule) => boolean): CompiledRule | undefined {\n let best: CompiledRule | undefined\n let bestPrefixLength = -1\n for (const rule of rules) {\n if (!eligible(rule) || !prefixMatches(rule.prefix, pathname)) {\n continue\n }\n const prefixLength = rule.prefix === undefined ? 0 : rule.prefix.length\n // Strict > keeps the first rule on a tie.\n if (best === undefined || prefixLength > bestPrefixLength) {\n best = rule\n bestPrefixLength = prefixLength\n }\n }\n return best\n}\n\n/**\n * Builds a {@link ConfigFactory} from host/path rules. Each rule's `config` is shallow-merged over\n * `base`; the result must be a complete {@link MOSConfigInput}.\n *\n * Resolution order — the first match wins:\n * 1. Rules whose `host` is the request host, longest matching `pathPrefix` first (ties: earlier rule).\n * 2. Host-less rules, ranked the same way; one with no `pathPrefix` is the global catch-all (at most one).\n * 3. No match throws.\n *\n * `config` is type-checked to supply the keys `base` lacks, but only when `base` is written\n * `satisfies Partial<MOSConfigInput>` (not annotated). `normalizeMOSConfig` re-checks at runtime.\n */\nexport function hostPathMatcher<const B extends Partial<MOSConfigInput> = Record<never, never>>(\n rules: HostPathRule<B>[],\n base?: B,\n): ConfigFactory {\n const compiled: CompiledRule[] = rules.map((rule) => ({\n host: rule.host?.toLowerCase(),\n prefix: normalizePrefix(rule.pathPrefix),\n config: rule.config,\n }))\n\n const globalCatchAlls = compiled.filter((rule) => rule.host === undefined && rule.prefix === undefined)\n if (globalCatchAlls.length > 1) {\n throw new Error('hostPathMatcher: only one host-less, path-less global catch-all rule is allowed')\n }\n\n return (request) => {\n const url = new URL(request.url)\n // hostname is port-free; url.host would keep the port (acme.com:8443).\n const host = url.hostname\n const pathname = url.pathname\n\n // Host tier wins over host-less.\n const matchedRule =\n selectBestRule(compiled, pathname, (rule) => rule.host === host) ??\n selectBestRule(compiled, pathname, (rule) => rule.host === undefined)\n if (matchedRule === undefined) {\n throw new Error(`hostPathMatcher: no rule matched ${host}${pathname}`)\n }\n\n return { ...base, ...matchedRule.config } as MOSConfigInput\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export type { ClientMetadataProvider, ContentOptions, ElementHandlers, Fetcher, HtmlRewriterAdapter, HtmlRewriterCapabilities, HtmlRewriterSession, Identity, IdentityProvider, PersistIdentityArgs, ResolveIdentityArgs, Resource, ResourceProvider, RewriterElement, RewriterText, } from './adapters';
|
|
1
|
+
export type { ClientMetadataProvider, ConfigFactory, ContentOptions, ElementHandlers, Fetcher, HtmlRewriterAdapter, HtmlRewriterCapabilities, HtmlRewriterSession, Identity, IdentityProvider, PersistIdentityArgs, ResolveIdentityArgs, Resource, ResourceProvider, RewriterElement, RewriterText, UnresolvedConfigContext, UnresolvedConfigHandler, UnresolvedConfigReason, } from './adapters';
|
|
2
2
|
export { buildIdentity, defaultPersistIdentity, defaultResolveIdentity, getExistingCookies, } from './adapters';
|
|
3
3
|
export type { MOSConfig } from './config';
|
|
4
|
+
export { ConfigUnresolvableError } from './configResolution';
|
|
5
|
+
export { type HostPathRule, hostPathMatcher } from './hostPathMatcher';
|
|
4
6
|
export type { MOSProxyLogCode, MOSProxyLogEvent, MOSProxyLogger, MOSProxyLogLevel } from './logger';
|
|
5
7
|
export { MOSProxy, type MOSProxyHtmlPipelineErrorContext, type MOSProxyHtmlPipelineErrorHandler, type MOSProxyHtmlPipelineStage, type MOSProxyOptions, } from './MOSProxy';
|
|
6
8
|
export { MOSProxyBuilder } from './MOSProxyBuilder';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { buildIdentity, defaultPersistIdentity, defaultResolveIdentity, getExistingCookies, } from './adapters';
|
|
2
|
+
export { ConfigUnresolvableError } from './configResolution';
|
|
3
|
+
export { hostPathMatcher } from './hostPathMatcher';
|
|
2
4
|
export { MOSProxy, } from './MOSProxy';
|
|
3
5
|
export { MOSProxyBuilder } from './MOSProxyBuilder';
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,GACrB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAqB,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEtE,OAAO,EACH,QAAQ,GAKX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA","sourcesContent":["export type {\n ClientMetadataProvider,\n ConfigFactory,\n ContentOptions,\n ElementHandlers,\n Fetcher,\n HtmlRewriterAdapter,\n HtmlRewriterCapabilities,\n HtmlRewriterSession,\n Identity,\n IdentityProvider,\n PersistIdentityArgs,\n ResolveIdentityArgs,\n Resource,\n ResourceProvider,\n RewriterElement,\n RewriterText,\n UnresolvedConfigContext,\n UnresolvedConfigHandler,\n UnresolvedConfigReason,\n} from './adapters'\nexport {\n buildIdentity,\n defaultPersistIdentity,\n defaultResolveIdentity,\n getExistingCookies,\n} from './adapters'\nexport type { MOSConfig } from './config'\nexport { ConfigUnresolvableError } from './configResolution'\nexport { type HostPathRule, hostPathMatcher } from './hostPathMatcher'\nexport type { MOSProxyLogCode, MOSProxyLogEvent, MOSProxyLogger, MOSProxyLogLevel } from './logger'\nexport {\n MOSProxy,\n type MOSProxyHtmlPipelineErrorContext,\n type MOSProxyHtmlPipelineErrorHandler,\n type MOSProxyHtmlPipelineStage,\n type MOSProxyOptions,\n} from './MOSProxy'\nexport { MOSProxyBuilder } from './MOSProxyBuilder'\nexport type {\n Feature,\n FeatureMeterableProperty,\n FeatureNumberProperty,\n MOSConfigInput,\n ModifyHttpResponse,\n PageMetadata,\n SetHttpResponse,\n SubSurfaceBehaviorApi,\n SubSurfaceMetadataApi,\n SurfaceBehaviorApi,\n SurfaceDecisionError,\n SurfaceDecisionResponse,\n WebComponentElement,\n WebComponentRangeReplacement,\n WebContentSurfaceBehavior,\n WebElement,\n} from './types'\n"]}
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type MOSProxyLogLevel = 'warn' | 'error';
|
|
2
|
-
export type MOSProxyLogCode = 'html-pipeline-failed' | 'html-pipeline-error-handler-invalid' | 'html-pipeline-error-handler-threw' | 'invalid-ignore-path-pattern' | 'response-clone-failed' | 'surface-decisions-api-failed' | 'identity-resolve-failed' | 'identity-persist-failed' | 'link-rewriting-header-failed' | 'link-rewriting-body-failed' | 'marker-pass-invalid-state' | 'marker-pass-selector-failed' | 'replacement-markers-identical' | 'replacement-state-missing' | 'element-render-failed' | 'element-type-unsupported' | 'page-metadata-parse-failed' | 'component-selector-unsupported' | 'component-range-replacement-skipped' | 'component-transform-failed';
|
|
2
|
+
export type MOSProxyLogCode = 'html-pipeline-failed' | 'html-pipeline-error-handler-invalid' | 'html-pipeline-error-handler-threw' | 'invalid-ignore-path-pattern' | 'invalid-surface-decisions-cookie-pattern' | 'config-resolution-failed' | 'config-resolution-served-last-known-good' | 'unresolved-config-handler-invalid' | 'unresolved-config-handler-threw' | 'response-clone-failed' | 'surface-decisions-api-failed' | 'identity-resolve-failed' | 'identity-persist-failed' | 'link-rewriting-header-failed' | 'link-rewriting-body-failed' | 'marker-pass-invalid-state' | 'marker-pass-selector-failed' | 'replacement-markers-identical' | 'replacement-state-missing' | 'element-render-failed' | 'element-type-unsupported' | 'page-metadata-parse-failed' | 'component-selector-unsupported' | 'component-range-replacement-skipped' | 'component-transform-failed';
|
|
3
3
|
export interface MOSProxyLogEvent {
|
|
4
4
|
level: MOSProxyLogLevel;
|
|
5
5
|
code: MOSProxyLogCode;
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAyCA,MAAM,CAAC,MAAM,aAAa,GAAmB;IACzC,GAAG,CAAC,KAAK;QACL,MAAM,OAAO,GAAc,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAClF,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC7B,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAA;YACzB,OAAM;QACV,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAA;IAC5B,CAAC;CACJ,CAAA","sourcesContent":["export type MOSProxyLogLevel = 'warn' | 'error'\n\nexport type MOSProxyLogCode =\n | 'html-pipeline-failed'\n | 'html-pipeline-error-handler-invalid'\n | 'html-pipeline-error-handler-threw'\n | 'invalid-ignore-path-pattern'\n | 'invalid-surface-decisions-cookie-pattern'\n | 'config-resolution-failed'\n | 'config-resolution-served-last-known-good'\n | 'unresolved-config-handler-invalid'\n | 'unresolved-config-handler-threw'\n | 'response-clone-failed'\n | 'surface-decisions-api-failed'\n | 'identity-resolve-failed'\n | 'identity-persist-failed'\n | 'link-rewriting-header-failed'\n | 'link-rewriting-body-failed'\n | 'marker-pass-invalid-state'\n | 'marker-pass-selector-failed'\n | 'replacement-markers-identical'\n | 'replacement-state-missing'\n | 'element-render-failed'\n | 'element-type-unsupported'\n | 'page-metadata-parse-failed'\n | 'component-selector-unsupported'\n | 'component-range-replacement-skipped'\n | 'component-transform-failed'\n\nexport interface MOSProxyLogEvent {\n level: MOSProxyLogLevel\n code: MOSProxyLogCode\n message: string\n context?: Record<string, unknown>\n error?: unknown\n}\n\nexport interface MOSProxyLogger {\n log(event: MOSProxyLogEvent): void\n}\n\nexport const consoleLogger: MOSProxyLogger = {\n log(event) {\n const details: unknown[] = [event.message, { code: event.code, ...event.context }]\n if (event.error) {\n details.push(event.error)\n }\n\n if (event.level === 'error') {\n console.error(...details)\n return\n }\n\n console.warn(...details)\n },\n}\n"]}
|
|
@@ -10,6 +10,7 @@ export type FetchSurfaceDecisionsArgs = {
|
|
|
10
10
|
resource: Resource;
|
|
11
11
|
userAgent?: string | undefined;
|
|
12
12
|
referer?: string | undefined;
|
|
13
|
+
cookies?: Record<string, string> | undefined;
|
|
13
14
|
originStatus: number;
|
|
14
15
|
};
|
|
15
16
|
export type FetchSurfaceDecisionsFailureReason = 'request-failed' | 'invalid-json' | 'api-error' | 'http-error' | 'invalid-response';
|
|
@@ -23,4 +24,4 @@ export type FetchSurfaceDecisionsResult = {
|
|
|
23
24
|
status?: number;
|
|
24
25
|
statusCode?: number;
|
|
25
26
|
};
|
|
26
|
-
export default function fetchSurfaceDecisions(ctx: PipelineContext, { identity, url, clientMetadata, resource, userAgent, referer, originStatus }: FetchSurfaceDecisionsArgs, apiFetcher: Fetcher): Promise<FetchSurfaceDecisionsResult>;
|
|
27
|
+
export default function fetchSurfaceDecisions(ctx: PipelineContext, { identity, url, clientMetadata, resource, userAgent, referer, cookies, originStatus }: FetchSurfaceDecisionsArgs, apiFetcher: Fetcher): Promise<FetchSurfaceDecisionsResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { withMosProxyHeaders } from '../apiRequestHeaders';
|
|
2
|
-
export default async function fetchSurfaceDecisions(ctx, { identity, url, clientMetadata, resource, userAgent, referer, originStatus }, apiFetcher) {
|
|
2
|
+
export default async function fetchSurfaceDecisions(ctx, { identity, url, clientMetadata, resource, userAgent, referer, cookies, originStatus }, apiFetcher) {
|
|
3
3
|
const { config } = ctx;
|
|
4
4
|
const body = JSON.stringify({
|
|
5
5
|
...clientMetadata,
|
|
@@ -10,6 +10,7 @@ export default async function fetchSurfaceDecisions(ctx, { identity, url, client
|
|
|
10
10
|
url,
|
|
11
11
|
userAgent,
|
|
12
12
|
referer,
|
|
13
|
+
cookies,
|
|
13
14
|
proxyOrigin: {
|
|
14
15
|
status: originStatus,
|
|
15
16
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchSurfaceDecisions.js","sourceRoot":"","sources":["../../src/stages/fetchSurfaceDecisions.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"fetchSurfaceDecisions.js","sourceRoot":"","sources":["../../src/stages/fetchSurfaceDecisions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Fetcher } from '../adapters/Fetcher'\nimport type { Identity } from '../adapters/IdentityProvider'\nimport type { Resource } from '../adapters/ResourceProvider'\nimport { withMosProxyHeaders } from '../apiRequestHeaders'\nimport type { PipelineContext } from '../context'\nimport type { SurfaceDecisionError, SurfaceDecisionResponse } from '../types'\n\nexport type FetchSurfaceDecisionsArgs = {\n identity: Identity\n url: string\n clientMetadata: Record<string, unknown>\n resource: Resource\n userAgent?: string | undefined\n referer?: string | undefined\n cookies?: Record<string, string> | undefined\n originStatus: number\n}\n\nexport type FetchSurfaceDecisionsFailureReason = 'request-failed' | 'invalid-json' | 'api-error' | 'http-error' | 'invalid-response'\n\nexport type FetchSurfaceDecisionsResult =\n | {\n ok: true\n data: SurfaceDecisionResponse\n }\n | {\n ok: false\n reason: FetchSurfaceDecisionsFailureReason\n error: unknown\n status?: number\n statusCode?: number\n }\n\nexport default async function fetchSurfaceDecisions(\n ctx: PipelineContext,\n { identity, url, clientMetadata, resource, userAgent, referer, cookies, originStatus }: FetchSurfaceDecisionsArgs,\n apiFetcher: Fetcher,\n): Promise<FetchSurfaceDecisionsResult> {\n const { config } = ctx\n const body = JSON.stringify({\n ...clientMetadata,\n surfaceSlug: config.surfaceSlug,\n identity,\n resource,\n http: {\n url,\n userAgent,\n referer,\n cookies,\n proxyOrigin: {\n status: originStatus,\n },\n },\n })\n\n const request = new Request(new URL('/api/v1/surface-decisions', config.mosHost), {\n method: 'POST',\n body,\n headers: withMosProxyHeaders({\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.mosSecretKey}`,\n }),\n })\n\n let response: Response\n try {\n response = await apiFetcher(request)\n } catch (error) {\n return { ok: false, reason: 'request-failed', error }\n }\n\n let data: unknown\n try {\n data = await response.json()\n } catch (error) {\n return { ok: false, reason: 'invalid-json', error, status: response.status }\n }\n\n if (isSurfaceDecisionError(data)) {\n return {\n ok: false,\n reason: 'api-error',\n error: new Error(data.message),\n status: response.status,\n statusCode: data.statusCode,\n }\n }\n\n if (!response.ok) {\n return {\n ok: false,\n reason: 'http-error',\n error: new Error(`Surface decisions API returned HTTP ${response.status}`),\n status: response.status,\n }\n }\n\n if (!isSurfaceDecisionResponse(data)) {\n return {\n ok: false,\n reason: 'invalid-response',\n error: new Error('Surface decisions API returned an invalid response shape'),\n status: response.status,\n }\n }\n\n return { ok: true, data }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null\n\nconst isSurfaceDecisionError = (value: unknown): value is SurfaceDecisionError =>\n isRecord(value) && value.status === 'error' && typeof value.message === 'string' && typeof value.statusCode === 'number'\n\nconst isSurfaceDecisionResponse = (value: unknown): value is SurfaceDecisionResponse =>\n isRecord(value) &&\n value.status === 'success' &&\n isRecord(value.identity) &&\n isRecord(value.features) &&\n isRecord(value.customer) &&\n isRecord(value.surfaceBehavior) &&\n typeof value.componentsSkipped === 'boolean' &&\n isRecord(value.componentBehaviors)\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defaultPersistIdentity, defaultResolveIdentity } from '../adapters/IdentityProvider';
|
|
2
|
+
import { selectSurfaceDecisionCookies } from '../surfaceDecisionCookies';
|
|
2
3
|
import fetchSurfaceDecisions from './fetchSurfaceDecisions';
|
|
3
4
|
import { parsePageMetadata } from './pageMetadata';
|
|
4
5
|
export default async function getSurfaceDecisions(ctx, request, response, apiFetcher, rewriter, clientMetadataProvider, identityProvider, resourceProvider) {
|
|
@@ -29,6 +30,7 @@ export default async function getSurfaceDecisions(ctx, request, response, apiFet
|
|
|
29
30
|
resource,
|
|
30
31
|
userAgent: request.headers.get('User-Agent') ?? undefined,
|
|
31
32
|
referer: request.headers.get('Referer') ?? undefined,
|
|
33
|
+
cookies: selectSurfaceDecisionCookies(request.headers.get('Cookie'), response, config.surfaceDecisionsCookiePatterns),
|
|
32
34
|
originStatus: response.status,
|
|
33
35
|
}, apiFetcher);
|
|
34
36
|
if (!result.ok) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surfaceDecisions.js","sourceRoot":"","sources":["../../src/stages/surfaceDecisions.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAwC,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"surfaceDecisions.js","sourceRoot":"","sources":["../../src/stages/surfaceDecisions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientMetadataProvider } from '../adapters/ClientMetadataProvider'\nimport type { Fetcher } from '../adapters/Fetcher'\nimport type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter'\nimport { defaultPersistIdentity, defaultResolveIdentity, type Identity, type IdentityProvider } from '../adapters/IdentityProvider'\nimport type { ResourceProvider } from '../adapters/ResourceProvider'\nimport type { PipelineContext } from '../context'\nimport { selectSurfaceDecisionCookies } from '../surfaceDecisionCookies'\nimport type { SurfaceDecisionResponse } from '../types'\nimport fetchSurfaceDecisions from './fetchSurfaceDecisions'\nimport { parsePageMetadata } from './pageMetadata'\n\nexport default async function getSurfaceDecisions(\n ctx: PipelineContext,\n request: Request,\n response: Response,\n apiFetcher: Fetcher,\n rewriter: HtmlRewriterAdapter | null,\n clientMetadataProvider: ClientMetadataProvider | null,\n identityProvider: IdentityProvider | null,\n resourceProvider: ResourceProvider | null,\n): Promise<[Response, SurfaceDecisionResponse | null]> {\n const { config, logger } = ctx\n\n const resolve = identityProvider?.resolve ?? defaultResolveIdentity\n let identity: Identity\n try {\n identity = await resolve({ request, originResponse: response, config, logger })\n } catch (error) {\n logger.log({\n level: 'warn',\n code: 'identity-resolve-failed',\n message: 'Identity provider resolve threw; skipping surface decisions and returning origin response.',\n error,\n })\n return [response, null]\n }\n\n const [metadataStream, passThroughStream] = response.body?.tee() ?? [null, null]\n const pageMetadata = metadataStream && rewriter ? await parsePageMetadata(ctx, new Response(metadataStream, response), rewriter) : {}\n let modifiedResponse = passThroughStream ? new Response(passThroughStream, response) : response\n\n const clientMetadata = clientMetadataProvider?.build(request) ?? {}\n\n const resource = { id: new URL(request.url).pathname, meta: pageMetadata, ...resourceProvider?.build(request) }\n\n const result = await fetchSurfaceDecisions(\n ctx,\n {\n identity,\n url: request.url,\n clientMetadata,\n resource,\n userAgent: request.headers.get('User-Agent') ?? undefined,\n referer: request.headers.get('Referer') ?? undefined,\n cookies: selectSurfaceDecisionCookies(request.headers.get('Cookie'), response, config.surfaceDecisionsCookiePatterns),\n originStatus: response.status,\n },\n apiFetcher,\n )\n\n if (!result.ok) {\n logger.log({\n level: 'warn',\n code: 'surface-decisions-api-failed',\n message: 'Surface decisions API failed; continuing with the origin response.',\n context: {\n reason: result.reason,\n status: result.status,\n statusCode: result.statusCode,\n },\n error: result.error,\n })\n return [modifiedResponse, null]\n }\n\n const surfaceDecisions = result.data\n\n const persist = identityProvider?.persist ?? defaultPersistIdentity\n try {\n modifiedResponse = await persist({\n resolved: identity,\n decisions: surfaceDecisions,\n response: modifiedResponse,\n request,\n config,\n logger,\n })\n } catch (error) {\n logger.log({\n level: 'warn',\n code: 'identity-persist-failed',\n message: 'Identity provider persist threw; keeping pre-persist response.',\n error,\n })\n }\n\n return [modifiedResponse, surfaceDecisions]\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function selectSurfaceDecisionCookies(cookieHeader: string | null, originResponse: Response | undefined, patterns: RegExp[]): Record<string, string> | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parse, parseSetCookie } from 'cookie';
|
|
2
|
+
export function selectSurfaceDecisionCookies(cookieHeader, originResponse, patterns) {
|
|
3
|
+
if (patterns.length === 0) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const selected = {};
|
|
7
|
+
for (const [name, value] of Object.entries(parse(cookieHeader || ''))) {
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
continue;
|
|
10
|
+
if (patterns.some((pattern) => pattern.test(name))) {
|
|
11
|
+
selected[name] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const setCookies = originResponse?.headers.getSetCookie().map((header) => parseSetCookie(header)) ?? [];
|
|
15
|
+
for (const cookie of setCookies) {
|
|
16
|
+
if (!cookie.name || cookie.value === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
if (patterns.some((pattern) => pattern.test(cookie.name))) {
|
|
19
|
+
selected[cookie.name] = cookie.value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return Object.keys(selected).length > 0 ? selected : undefined;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=surfaceDecisionCookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surfaceDecisionCookies.js","sourceRoot":"","sources":["../src/surfaceDecisionCookies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE9C,MAAM,UAAU,4BAA4B,CACxC,YAA2B,EAC3B,cAAoC,EACpC,QAAkB;IAElB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,SAAS,CAAA;IACpB,CAAC;IAED,MAAM,QAAQ,GAA2B,EAAE,CAAA;IAE3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACjD,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QAC1B,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG,cAAc,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACvG,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;YAAE,SAAQ;QACxD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACxD,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAA;QACxC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;AAClE,CAAC","sourcesContent":["import { parse, parseSetCookie } from 'cookie'\n\nexport function selectSurfaceDecisionCookies(\n cookieHeader: string | null,\n originResponse: Response | undefined,\n patterns: RegExp[],\n): Record<string, string> | undefined {\n if (patterns.length === 0) {\n return undefined\n }\n\n const selected: Record<string, string> = {}\n\n for (const [name, value] of Object.entries(parse(cookieHeader || ''))) {\n if (value === undefined) continue\n if (patterns.some((pattern) => pattern.test(name))) {\n selected[name] = value\n }\n }\n\n const setCookies = originResponse?.headers.getSetCookie().map((header) => parseSetCookie(header)) ?? []\n for (const cookie of setCookies) {\n if (!cookie.name || cookie.value === undefined) continue\n if (patterns.some((pattern) => pattern.test(cookie.name))) {\n selected[cookie.name] = cookie.value\n }\n }\n\n return Object.keys(selected).length > 0 ? selected : undefined\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -116,6 +116,8 @@ export interface MOSConfigInput {
|
|
|
116
116
|
injectScriptUrl?: string;
|
|
117
117
|
/** Comma-separated regex patterns; matching pathnames skip surface decisions. */
|
|
118
118
|
surfaceDecisionsIgnorePaths?: string;
|
|
119
|
+
/** Comma-separated regex patterns; matching request and origin Set-Cookie values are forwarded in the surface-decisions http payload. */
|
|
120
|
+
surfaceDecisionsCookies?: string;
|
|
119
121
|
/** Optional headers added to or replacing client headers on every origin request. */
|
|
120
122
|
originRequestHeaders?: Record<string, string>;
|
|
121
123
|
/**
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type PageMetadata = Record<string, string>\n\nexport interface FeatureMeterableProperty {\n type: 'meterable'\n hasAccess: boolean\n remainingUnits?: number\n}\n\nexport interface FeatureNumberProperty {\n type: 'number'\n value: number\n}\n\nexport interface Feature {\n featureSlug: string\n properties: Record<string, FeatureMeterableProperty | FeatureNumberProperty>\n sideEffects: unknown[]\n}\n\nexport interface SubSurfaceMetadataApi {\n cssSelector?: string | null\n}\n\nexport type SetHttpResponse = {\n headers?: Record<string, string>\n cookies?: string[]\n status: number\n statusText?: string\n body: string | null\n}\n\nexport type ModifyHttpResponse = {\n addHeaders?: { name: string; value: string }[]\n removeHeaders?: string[]\n addCookies?: string[]\n status?: number\n statusText?: string\n body?: string | null\n}\n\nexport type SurfaceBehaviorApi = {\n http?: ModifyHttpResponse | SetHttpResponse\n properties?: Record<string, unknown>\n} & Record<string, unknown>\n\nexport type WebComponentElement<T = Record<string, unknown>> = {\n schema: string\n props: T\n}\n\nexport type WebElement =\n | {\n type: 'html'\n content: string\n }\n | {\n type: 'text'\n content: string\n }\n | ({\n type: 'element'\n } & WebComponentElement)\n | ({\n type: 'custom'\n } & Record<string, unknown>)\n\nexport type WebComponentRangeReplacement = {\n fromMarker?: string\n toMarker?: string\n replaceWith?: WebElement[] | null\n}\n\nexport type WebContentSurfaceBehavior = {\n before?: WebElement[]\n prepend?: WebElement[]\n remove?: boolean\n replaceRange?: WebComponentRangeReplacement | null\n append?: WebElement[]\n after?: WebElement[]\n}\n\nexport type SubSurfaceBehaviorApi = {\n content?: WebContentSurfaceBehavior\n properties?: Record<string, unknown>\n metadata: SubSurfaceMetadataApi\n} & Record<string, unknown>\n\nexport interface SurfaceDecisionResponse {\n status: 'success'\n identity: {\n identifier: string\n isAuthenticated: boolean\n authType: string\n jwtClaims: Record<string, unknown>\n }\n features: Record<string, Feature>\n customer: {\n hasProducts: boolean\n }\n surfaceBehavior: SurfaceBehaviorApi\n componentsSkipped: boolean\n componentBehaviors: Record<string, SubSurfaceBehaviorApi>\n}\n\nexport interface SurfaceDecisionError {\n message: string\n status: 'error'\n statusCode: number\n}\n\nexport type MosAuthenticatedApiRoute = {\n matchPath: string\n method: 'POST' | 'PUT' | 'PATCH'\n mosPath: string\n}\n\nexport interface MOSConfigInput {\n /** Origin website base URL, e.g. `https://news.example.com`. */\n originUrl: string\n /** Surface slug identifying the MonetizationOS surface. */\n surfaceSlug: string\n /** MonetizationOS API base URL. */\n mosHost: string\n /** MonetizationOS secret key; expected format `sk_<env>_<suffix>` */\n mosSecretKey: string\n /** Path prefix routed to the MonetizationOS endpoint proxy. Default: `/mos-endpoints/`. */\n mosEndpointsPrefix?: string\n /** Cookie name storing the anonymous session id. */\n anonymousSessionCookieName: string\n /** Cookie name storing the authenticated user JWT. */\n authenticatedUserJwtCookieName: string\n /** Optional script URL injected into the `<head>` of HTML responses. */\n injectScriptUrl?: string\n /** Comma-separated regex patterns; matching pathnames skip surface decisions. */\n surfaceDecisionsIgnorePaths?: string\n /** Optional headers added to or replacing client headers on every origin request. */\n originRequestHeaders?: Record<string, string>\n /**\n * When true (default), JWT surface-decision requests include `createAnonymousIdentifierFallback: true`\n * so MonetizationOS can mint an anonymous identifier if JWT authentication fails.\n * Set to `false` to opt out.\n */\n createAnonymousIdentifierFallback?: boolean\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type PageMetadata = Record<string, string>\n\nexport interface FeatureMeterableProperty {\n type: 'meterable'\n hasAccess: boolean\n remainingUnits?: number\n}\n\nexport interface FeatureNumberProperty {\n type: 'number'\n value: number\n}\n\nexport interface Feature {\n featureSlug: string\n properties: Record<string, FeatureMeterableProperty | FeatureNumberProperty>\n sideEffects: unknown[]\n}\n\nexport interface SubSurfaceMetadataApi {\n cssSelector?: string | null\n}\n\nexport type SetHttpResponse = {\n headers?: Record<string, string>\n cookies?: string[]\n status: number\n statusText?: string\n body: string | null\n}\n\nexport type ModifyHttpResponse = {\n addHeaders?: { name: string; value: string }[]\n removeHeaders?: string[]\n addCookies?: string[]\n status?: number\n statusText?: string\n body?: string | null\n}\n\nexport type SurfaceBehaviorApi = {\n http?: ModifyHttpResponse | SetHttpResponse\n properties?: Record<string, unknown>\n} & Record<string, unknown>\n\nexport type WebComponentElement<T = Record<string, unknown>> = {\n schema: string\n props: T\n}\n\nexport type WebElement =\n | {\n type: 'html'\n content: string\n }\n | {\n type: 'text'\n content: string\n }\n | ({\n type: 'element'\n } & WebComponentElement)\n | ({\n type: 'custom'\n } & Record<string, unknown>)\n\nexport type WebComponentRangeReplacement = {\n fromMarker?: string\n toMarker?: string\n replaceWith?: WebElement[] | null\n}\n\nexport type WebContentSurfaceBehavior = {\n before?: WebElement[]\n prepend?: WebElement[]\n remove?: boolean\n replaceRange?: WebComponentRangeReplacement | null\n append?: WebElement[]\n after?: WebElement[]\n}\n\nexport type SubSurfaceBehaviorApi = {\n content?: WebContentSurfaceBehavior\n properties?: Record<string, unknown>\n metadata: SubSurfaceMetadataApi\n} & Record<string, unknown>\n\nexport interface SurfaceDecisionResponse {\n status: 'success'\n identity: {\n identifier: string\n isAuthenticated: boolean\n authType: string\n jwtClaims: Record<string, unknown>\n }\n features: Record<string, Feature>\n customer: {\n hasProducts: boolean\n }\n surfaceBehavior: SurfaceBehaviorApi\n componentsSkipped: boolean\n componentBehaviors: Record<string, SubSurfaceBehaviorApi>\n}\n\nexport interface SurfaceDecisionError {\n message: string\n status: 'error'\n statusCode: number\n}\n\nexport type MosAuthenticatedApiRoute = {\n matchPath: string\n method: 'POST' | 'PUT' | 'PATCH'\n mosPath: string\n}\n\nexport interface MOSConfigInput {\n /** Origin website base URL, e.g. `https://news.example.com`. */\n originUrl: string\n /** Surface slug identifying the MonetizationOS surface. */\n surfaceSlug: string\n /** MonetizationOS API base URL. */\n mosHost: string\n /** MonetizationOS secret key; expected format `sk_<env>_<suffix>` */\n mosSecretKey: string\n /** Path prefix routed to the MonetizationOS endpoint proxy. Default: `/mos-endpoints/`. */\n mosEndpointsPrefix?: string\n /** Cookie name storing the anonymous session id. */\n anonymousSessionCookieName: string\n /** Cookie name storing the authenticated user JWT. */\n authenticatedUserJwtCookieName: string\n /** Optional script URL injected into the `<head>` of HTML responses. */\n injectScriptUrl?: string\n /** Comma-separated regex patterns; matching pathnames skip surface decisions. */\n surfaceDecisionsIgnorePaths?: string\n /** Comma-separated regex patterns; matching request and origin Set-Cookie values are forwarded in the surface-decisions http payload. */\n surfaceDecisionsCookies?: string\n /** Optional headers added to or replacing client headers on every origin request. */\n originRequestHeaders?: Record<string, string>\n /**\n * When true (default), JWT surface-decision requests include `createAnonymousIdentifierFallback: true`\n * so MonetizationOS can mint an anonymous identifier if JWT authentication fails.\n * Set to `false` to opt out.\n */\n createAnonymousIdentifierFallback?: boolean\n}\n"]}
|