@monetizationos/proxy 1.0.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/dist/MOSProxy.d.ts +43 -0
  4. package/dist/MOSProxy.js +140 -0
  5. package/dist/MOSProxy.js.map +1 -0
  6. package/dist/MOSProxyBuilder.d.ts +75 -0
  7. package/dist/MOSProxyBuilder.js +126 -0
  8. package/dist/MOSProxyBuilder.js.map +1 -0
  9. package/dist/adapters/ClientMetadataProvider.d.ts +12 -0
  10. package/dist/adapters/ClientMetadataProvider.js +2 -0
  11. package/dist/adapters/ClientMetadataProvider.js.map +1 -0
  12. package/dist/adapters/Fetcher.d.ts +10 -0
  13. package/dist/adapters/Fetcher.js +2 -0
  14. package/dist/adapters/Fetcher.js.map +1 -0
  15. package/dist/adapters/HtmlRewriterAdapter.d.ts +60 -0
  16. package/dist/adapters/HtmlRewriterAdapter.js +2 -0
  17. package/dist/adapters/HtmlRewriterAdapter.js.map +1 -0
  18. package/dist/adapters/index.d.ts +3 -0
  19. package/dist/adapters/index.js +2 -0
  20. package/dist/adapters/index.js.map +1 -0
  21. package/dist/apiRequestHeaders.d.ts +4 -0
  22. package/dist/apiRequestHeaders.js +11 -0
  23. package/dist/apiRequestHeaders.js.map +1 -0
  24. package/dist/config.d.ts +16 -0
  25. package/dist/config.js +39 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/context.d.ts +12 -0
  28. package/dist/context.js +2 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/http.d.ts +1 -0
  31. package/dist/http.js +3 -0
  32. package/dist/http.js.map +1 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.js +3 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/logger.d.ts +13 -0
  37. package/dist/logger.js +14 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/stages/ContentElementHandler.d.ts +9 -0
  40. package/dist/stages/ContentElementHandler.js +37 -0
  41. package/dist/stages/ContentElementHandler.js.map +1 -0
  42. package/dist/stages/buildReplacementHandlers.d.ts +14 -0
  43. package/dist/stages/buildReplacementHandlers.js +116 -0
  44. package/dist/stages/buildReplacementHandlers.js.map +1 -0
  45. package/dist/stages/customEndpoint.d.ts +3 -0
  46. package/dist/stages/customEndpoint.js +18 -0
  47. package/dist/stages/customEndpoint.js.map +1 -0
  48. package/dist/stages/elements.d.ts +5 -0
  49. package/dist/stages/elements.js +42 -0
  50. package/dist/stages/elements.js.map +1 -0
  51. package/dist/stages/fetchSurfaceDecisions.d.ts +25 -0
  52. package/dist/stages/fetchSurfaceDecisions.js +86 -0
  53. package/dist/stages/fetchSurfaceDecisions.js.map +1 -0
  54. package/dist/stages/findMarkerPositions.d.ts +21 -0
  55. package/dist/stages/findMarkerPositions.js +138 -0
  56. package/dist/stages/findMarkerPositions.js.map +1 -0
  57. package/dist/stages/getTargetUrl.d.ts +1 -0
  58. package/dist/stages/getTargetUrl.js +9 -0
  59. package/dist/stages/getTargetUrl.js.map +1 -0
  60. package/dist/stages/isRedirectResponse.d.ts +1 -0
  61. package/dist/stages/isRedirectResponse.js +4 -0
  62. package/dist/stages/isRedirectResponse.js.map +1 -0
  63. package/dist/stages/linkRewriting.d.ts +2 -0
  64. package/dist/stages/linkRewriting.js +37 -0
  65. package/dist/stages/linkRewriting.js.map +1 -0
  66. package/dist/stages/originRequest.d.ts +3 -0
  67. package/dist/stages/originRequest.js +10 -0
  68. package/dist/stages/originRequest.js.map +1 -0
  69. package/dist/stages/pageMetadata.d.ts +4 -0
  70. package/dist/stages/pageMetadata.js +25 -0
  71. package/dist/stages/pageMetadata.js.map +1 -0
  72. package/dist/stages/shouldIgnorePath.d.ts +2 -0
  73. package/dist/stages/shouldIgnorePath.js +9 -0
  74. package/dist/stages/shouldIgnorePath.js.map +1 -0
  75. package/dist/stages/surfaceBehavior.d.ts +4 -0
  76. package/dist/stages/surfaceBehavior.js +60 -0
  77. package/dist/stages/surfaceBehavior.js.map +1 -0
  78. package/dist/stages/surfaceComponents.d.ts +4 -0
  79. package/dist/stages/surfaceComponents.js +71 -0
  80. package/dist/stages/surfaceComponents.js.map +1 -0
  81. package/dist/stages/surfaceDecisions.d.ts +6 -0
  82. package/dist/stages/surfaceDecisions.js +59 -0
  83. package/dist/stages/surfaceDecisions.js.map +1 -0
  84. package/dist/stages/transformOriginLinks.d.ts +1 -0
  85. package/dist/stages/transformOriginLinks.js +14 -0
  86. package/dist/stages/transformOriginLinks.js.map +1 -0
  87. package/dist/types.d.ts +116 -0
  88. package/dist/types.js +2 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MonetizationOS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ <div align="center">
2
+ <a href="https://monetizationos.com">
3
+ <img alt="MonetizationOS logo" src="https://app.monetizationos.com/static/monetizationos-logo.png" height="48">
4
+ </a>
5
+ <h1>MonetizationOS Proxy Core</h1>
6
+ </div>
7
+
8
+ A Fetch-based [MonetizationOS Proxy](https://docs.monetizationos.com/docs/api-reference/proxies) core: `Request` in, `Response` out, runtime-agnostic.
9
+
10
+ This package contains the shared pipeline that powers the MonetizationOS proxy workers on Cloudflare, Fastly, Akamai, and any other runtime that speaks the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Platform-specific concerns — origin dispatch, config/secret loading, HTML rewriting — are supplied by each consumer as adapters.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install @monetizationos/proxy
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { MOSProxyBuilder } from "@monetizationos/proxy";
22
+
23
+ const proxy = new MOSProxyBuilder()
24
+ .withConfig({
25
+ originUrl: "https://news.example.com",
26
+ surfaceSlug: "web",
27
+ mosHost: "https://api.monetizationos.com",
28
+ mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,
29
+ mosEndpointsPrefix: "/mos-endpoints/",
30
+ anonymousSessionCookieName: "anon-session-id",
31
+ authenticatedUserJwtCookieName: "__session",
32
+ injectScriptUrl: "https://assets.monetizationos.com/web-components-latest.js",
33
+ originRequestHeaders: { "X-Api-Key": process.env.ORIGIN_API_KEY! },
34
+ })
35
+ .withHtmlRewriter(myHtmlRewriterAdapter)
36
+ .build();
37
+
38
+ export default {
39
+ fetch: (request: Request) => proxy.handle(request),
40
+ };
41
+ ```
42
+
43
+ ## Pipeline
44
+
45
+ 1. Custom endpoint routing (`/mos-endpoints/*` → MOS API)
46
+ 2. Origin fetch
47
+ 3. Link rewriting and `<meta>` extraction
48
+ 4. Surface decisions
49
+ 5. Surface behavior (HTTP-level mutations)
50
+ 6. Surface components (DOM-level transforms)
51
+
52
+ Stages 3–6 run on HTML responses only and auto-skip for everything else. Call `.withoutHtmlTransformation()` to disable them entirely.
53
+
54
+ ## Adapters
55
+
56
+ - `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.
57
+ - `HtmlRewriterAdapter` — wraps the platform's lol-html binding.
58
+ - `ClientMetadataProvider` — optional; exposes platform-specific request metadata (Cloudflare's `cf` object, Fastly's `event.client`, etc.).
59
+
60
+ ## API-only mode
61
+
62
+ Skip HTML transformation entirely when the proxy only needs to handle API traffic:
63
+
64
+ ```ts
65
+ const proxy = new MOSProxyBuilder().withConfig(config).withoutHtmlTransformation().build();
66
+ ```
67
+
68
+ ## Error handling and logging
69
+
70
+ The HTML pipeline fails open by default: on error, the proxy logs a structured event and returns the last safe response so the origin page still gets served. Register an error handler to shape that response yourself — render a custom error page, return a 503, or rethrow so the host runtime's error middleware takes over.
71
+
72
+ ```ts
73
+ const proxy = new MOSProxyBuilder()
74
+ .withConfig(config)
75
+ .withHtmlRewriter(myHtmlRewriterAdapter)
76
+ .withHtmlPipelineErrorHandler(({ error, stage, lastSafeResponse }) => {
77
+ // Inspect `error` / `stage`, or return your own Response.
78
+ return lastSafeResponse;
79
+ })
80
+ .withLogger({
81
+ log(event) {
82
+ console[event.level](event.message, event.context, event.error);
83
+ },
84
+ })
85
+ .build();
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT
@@ -0,0 +1,43 @@
1
+ import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider';
2
+ import type { Fetcher } from './adapters/Fetcher';
3
+ import type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter';
4
+ import { type MOSProxyLogger } from './logger';
5
+ import type { MOSConfigInput } from './types';
6
+ export type MOSProxyHtmlPipelineStage = 'origin-response' | 'link-rewriting' | 'surface-decisions' | 'surface-behavior' | 'surface-components';
7
+ export interface MOSProxyHtmlPipelineErrorContext {
8
+ error: unknown;
9
+ stage: MOSProxyHtmlPipelineStage;
10
+ request: Request;
11
+ lastSafeResponse: Response;
12
+ }
13
+ /**
14
+ * Called when the HTML pipeline throws. Return a `Response` to fully control the proxy's reply, or
15
+ * re-throw to surface the error to the platform handler. If omitted, the proxy fails open and
16
+ * returns `lastSafeResponse`. If the callback itself throws or returns a non-Response value, the
17
+ * proxy logs a warning and falls back to `lastSafeResponse` so the site cannot be taken down by a
18
+ * bug in the handler.
19
+ */
20
+ export type MOSProxyHtmlPipelineErrorHandler = (ctx: MOSProxyHtmlPipelineErrorContext) => Response | Promise<Response>;
21
+ export interface MOSProxyOptions {
22
+ config: MOSConfigInput;
23
+ originFetcher: Fetcher;
24
+ apiFetcher: Fetcher | null;
25
+ htmlRewriter: HtmlRewriterAdapter | null;
26
+ clientMetadataProvider: ClientMetadataProvider | null;
27
+ logger?: MOSProxyLogger;
28
+ onHtmlPipelineError?: MOSProxyHtmlPipelineErrorHandler;
29
+ customEndpointsEnabled: boolean;
30
+ linkRewritingEnabled: boolean;
31
+ surfaceDecisionsEnabled: boolean;
32
+ htmlTransformationEnabled: boolean;
33
+ }
34
+ /**
35
+ * Platform-agnostic MonetizationOS proxy. Build one with `MOSProxyBuilder`, then call
36
+ * `proxy.handle(request)` from your platform's fetch entry point.
37
+ */
38
+ export declare class MOSProxy {
39
+ private readonly opts;
40
+ private readonly config;
41
+ constructor(opts: MOSProxyOptions);
42
+ handle(request: Request): Promise<Response>;
43
+ }
@@ -0,0 +1,140 @@
1
+ import { normalizeMOSConfig } from './config';
2
+ import { consoleLogger } from './logger';
3
+ import customEndpointRequest from './stages/customEndpoint';
4
+ import isRedirectResponse from './stages/isRedirectResponse';
5
+ import rewriteOriginResponse from './stages/linkRewriting';
6
+ import performOriginRequest from './stages/originRequest';
7
+ import shouldIgnorePath from './stages/shouldIgnorePath';
8
+ import handleSurfaceBehavior from './stages/surfaceBehavior';
9
+ import handleSurfaceComponents from './stages/surfaceComponents';
10
+ import getSurfaceDecisions from './stages/surfaceDecisions';
11
+ /**
12
+ * Platform-agnostic MonetizationOS proxy. Build one with `MOSProxyBuilder`, then call
13
+ * `proxy.handle(request)` from your platform's fetch entry point.
14
+ */
15
+ export class MOSProxy {
16
+ opts;
17
+ config;
18
+ constructor(opts) {
19
+ this.opts = opts;
20
+ this.config = normalizeMOSConfig(opts.config, opts.logger ?? consoleLogger);
21
+ }
22
+ async handle(request) {
23
+ const { originFetcher, apiFetcher, htmlRewriter, clientMetadataProvider, onHtmlPipelineError } = this.opts;
24
+ const logger = this.opts.logger ?? consoleLogger;
25
+ const ctx = { config: this.config, logger };
26
+ // Stage 1a: custom endpoint routing
27
+ if (this.opts.customEndpointsEnabled) {
28
+ if (!apiFetcher) {
29
+ throw new Error('MOSProxy: customEndpoints is enabled but no API fetcher is configured');
30
+ }
31
+ const customEndpointResponse = await customEndpointRequest(ctx, request, apiFetcher);
32
+ if (customEndpointResponse) {
33
+ return customEndpointResponse;
34
+ }
35
+ }
36
+ // Stage 1b: origin fetch
37
+ const originResponse = await performOriginRequest(ctx, request, originFetcher);
38
+ // Auto-skip HTML pipeline for non-HTML content, or when HTML transformation is disabled entirely
39
+ const isHtml = originResponse.headers.get('Content-Type')?.startsWith('text/html') ?? false;
40
+ if (!isHtml || !this.opts.htmlTransformationEnabled) {
41
+ return originResponse;
42
+ }
43
+ let stage = 'origin-response';
44
+ let failOpenResponse = cloneResponseForFallback(originResponse, logger, stage);
45
+ try {
46
+ // Stage 2: rewrite origin links (optional)
47
+ let rewrittenResponse = originResponse;
48
+ if (this.opts.linkRewritingEnabled) {
49
+ stage = 'link-rewriting';
50
+ rewrittenResponse = await rewriteOriginResponse(ctx, request, originResponse);
51
+ failOpenResponse = cloneResponseForFallback(rewrittenResponse, logger, stage);
52
+ }
53
+ // Stage 3: surface decisions (optional)
54
+ if (!this.opts.surfaceDecisionsEnabled) {
55
+ return rewrittenResponse;
56
+ }
57
+ if (shouldIgnorePath(ctx, request) || isRedirectResponse(rewrittenResponse)) {
58
+ return rewrittenResponse;
59
+ }
60
+ if (!apiFetcher) {
61
+ throw new Error('MOSProxy: surfaceDecisions is enabled but no API fetcher is configured');
62
+ }
63
+ stage = 'surface-decisions';
64
+ const [modifiedResponse, surfaceDecisions] = await getSurfaceDecisions(ctx, request, rewrittenResponse, apiFetcher, htmlRewriter, clientMetadataProvider);
65
+ if (!surfaceDecisions) {
66
+ return modifiedResponse;
67
+ }
68
+ failOpenResponse = cloneResponseForFallback(modifiedResponse, logger, stage);
69
+ // Stage 4: surface behavior (HTTP-level)
70
+ stage = 'surface-behavior';
71
+ const [surfaceDecisionResponse, returnImmediately] = handleSurfaceBehavior(modifiedResponse, surfaceDecisions);
72
+ if (returnImmediately) {
73
+ return surfaceDecisionResponse;
74
+ }
75
+ failOpenResponse = cloneResponseForFallback(surfaceDecisionResponse, logger, stage);
76
+ // Stage 5: surface components (HTML-level) — requires an HTML rewriter
77
+ if (!htmlRewriter) {
78
+ return surfaceDecisionResponse;
79
+ }
80
+ stage = 'surface-components';
81
+ return await handleSurfaceComponents(ctx, surfaceDecisionResponse, surfaceDecisions, htmlRewriter);
82
+ }
83
+ catch (error) {
84
+ logger.log({
85
+ level: 'error',
86
+ code: 'html-pipeline-failed',
87
+ message: 'HTML pipeline failed.',
88
+ context: { stage, hasErrorHandler: Boolean(onHtmlPipelineError) },
89
+ error,
90
+ });
91
+ if (!onHtmlPipelineError) {
92
+ return failOpenResponse;
93
+ }
94
+ try {
95
+ const handlerResponse = await onHtmlPipelineError({
96
+ error,
97
+ stage,
98
+ request,
99
+ lastSafeResponse: failOpenResponse,
100
+ });
101
+ if (!(handlerResponse instanceof Response)) {
102
+ logger.log({
103
+ level: 'warn',
104
+ code: 'html-pipeline-error-handler-invalid',
105
+ message: 'onHtmlPipelineError did not return a Response; falling back to the last safe response.',
106
+ context: { stage },
107
+ });
108
+ return failOpenResponse;
109
+ }
110
+ return handlerResponse;
111
+ }
112
+ catch (handlerError) {
113
+ logger.log({
114
+ level: 'warn',
115
+ code: 'html-pipeline-error-handler-threw',
116
+ message: 'onHtmlPipelineError threw; falling back to the last safe response.',
117
+ context: { stage },
118
+ error: handlerError,
119
+ });
120
+ return failOpenResponse;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ const cloneResponseForFallback = (response, logger, stage) => {
126
+ try {
127
+ return response.clone();
128
+ }
129
+ catch (error) {
130
+ logger.log({
131
+ level: 'warn',
132
+ code: 'response-clone-failed',
133
+ message: 'Could not clone response for fail-open fallback.',
134
+ context: { stage },
135
+ error,
136
+ });
137
+ return response;
138
+ }
139
+ };
140
+ //# sourceMappingURL=MOSProxy.js.map
@@ -0,0 +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 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 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 } 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 logger?: MOSProxyLogger\n onHtmlPipelineError?: MOSProxyHtmlPipelineErrorHandler\n customEndpointsEnabled: boolean\n linkRewritingEnabled: boolean\n surfaceDecisionsEnabled: boolean\n htmlTransformationEnabled: boolean\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, onHtmlPipelineError } = 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: 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 )\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"]}
@@ -0,0 +1,75 @@
1
+ import type { ClientMetadataProvider } from './adapters/ClientMetadataProvider';
2
+ import type { Fetcher } from './adapters/Fetcher';
3
+ import type { HtmlRewriterAdapter } from './adapters/HtmlRewriterAdapter';
4
+ import type { MOSProxyLogger } from './logger';
5
+ import { MOSProxy, type MOSProxyHtmlPipelineErrorHandler } from './MOSProxy';
6
+ import type { MOSConfigInput } from './types';
7
+ /**
8
+ * Fluent builder for `MOSProxy`. Provide configuration and platform adapters, then `build()`.
9
+ *
10
+ * Defaults:
11
+ * - custom endpoint routing: enabled
12
+ * - link rewriting: enabled
13
+ * - surface decisions: enabled
14
+ * - HTML transformation: enabled (auto-skipped per request for non-HTML responses)
15
+ * - origin/API fetcher: `globalThis.fetch` (override on runtimes that need a backend binding, e.g. Fastly)
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const proxy = new MOSProxyBuilder()
20
+ * .withConfig({
21
+ * originUrl: 'https://news.example.com',
22
+ * surfaceSlug: 'web',
23
+ * mosHost: 'https://api.monetizationos.com',
24
+ * mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,
25
+ * mosEndpointsPrefix: '/mos-endpoints/',
26
+ * anonymousSessionCookieName: 'anon-session-id',
27
+ * authenticatedUserJwtCookieName: '__session',
28
+ * injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',
29
+ * surfaceDecisionsIgnorePaths: '',
30
+ * originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },
31
+ * })
32
+ * .withHtmlRewriter(myHtmlRewriterAdapter)
33
+ * .build()
34
+ *
35
+ * export default { fetch: (request: Request) => proxy.handle(request) }
36
+ * ```
37
+ */
38
+ export declare class MOSProxyBuilder {
39
+ private _config;
40
+ private _originFetcher;
41
+ private _apiFetcher;
42
+ private _htmlRewriter;
43
+ private _clientMetadataProvider;
44
+ private _logger;
45
+ private _onHtmlPipelineError;
46
+ private _customEndpoints;
47
+ private _linkRewriting;
48
+ private _surfaceDecisions;
49
+ private _htmlTransformation;
50
+ withConfig(config: MOSConfigInput): this;
51
+ withOriginFetcher(fetcher: Fetcher): this;
52
+ withApiFetcher(fetcher: Fetcher): this;
53
+ withHtmlRewriter(rewriter: HtmlRewriterAdapter): this;
54
+ withClientMetadata(provider: ClientMetadataProvider): this;
55
+ withLogger(logger: MOSProxyLogger): this;
56
+ /**
57
+ * Register a callback to handle HTML pipeline errors. The proxy fails open by default and
58
+ * returns the last safe (unconsumed) response — provide this if you want to shape that
59
+ * response yourself (e.g. render a custom error page, return a 503, or re-throw so your
60
+ * platform's error middleware handles it). If the callback throws or returns a non-Response
61
+ * value, the proxy logs a warning and falls back to the last safe response.
62
+ */
63
+ withHtmlPipelineErrorHandler(handler: MOSProxyHtmlPipelineErrorHandler): this;
64
+ withoutCustomEndpoints(): this;
65
+ withoutLinkRewriting(): this;
66
+ withoutSurfaceDecisions(): this;
67
+ /**
68
+ * Disable the HTML transformation pipeline entirely (stages 3–6).
69
+ *
70
+ * Use for API-only proxies: neither an HTML rewriter adapter nor an API fetcher is required
71
+ * if custom endpoints and surface decisions are both disabled alongside this.
72
+ */
73
+ withoutHtmlTransformation(): this;
74
+ build(): MOSProxy;
75
+ }
@@ -0,0 +1,126 @@
1
+ import { MOSProxy } from './MOSProxy';
2
+ /**
3
+ * Fluent builder for `MOSProxy`. Provide configuration and platform adapters, then `build()`.
4
+ *
5
+ * Defaults:
6
+ * - custom endpoint routing: enabled
7
+ * - link rewriting: enabled
8
+ * - surface decisions: enabled
9
+ * - HTML transformation: enabled (auto-skipped per request for non-HTML responses)
10
+ * - origin/API fetcher: `globalThis.fetch` (override on runtimes that need a backend binding, e.g. Fastly)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const proxy = new MOSProxyBuilder()
15
+ * .withConfig({
16
+ * originUrl: 'https://news.example.com',
17
+ * surfaceSlug: 'web',
18
+ * mosHost: 'https://api.monetizationos.com',
19
+ * mosSecretKey: process.env.MONETIZATION_OS_SECRET_KEY!,
20
+ * mosEndpointsPrefix: '/mos-endpoints/',
21
+ * anonymousSessionCookieName: 'anon-session-id',
22
+ * authenticatedUserJwtCookieName: '__session',
23
+ * injectScriptUrl: 'https://assets.monetizationos.com/web-components-latest.js',
24
+ * surfaceDecisionsIgnorePaths: '',
25
+ * originRequestHeaders: { 'X-Api-Key': process.env.ORIGIN_API_KEY! },
26
+ * })
27
+ * .withHtmlRewriter(myHtmlRewriterAdapter)
28
+ * .build()
29
+ *
30
+ * export default { fetch: (request: Request) => proxy.handle(request) }
31
+ * ```
32
+ */
33
+ export class MOSProxyBuilder {
34
+ _config = null;
35
+ _originFetcher = globalThis.fetch;
36
+ _apiFetcher = globalThis.fetch;
37
+ _htmlRewriter = null;
38
+ _clientMetadataProvider = null;
39
+ _logger = null;
40
+ _onHtmlPipelineError = null;
41
+ _customEndpoints = true;
42
+ _linkRewriting = true;
43
+ _surfaceDecisions = true;
44
+ _htmlTransformation = true;
45
+ withConfig(config) {
46
+ this._config = config;
47
+ return this;
48
+ }
49
+ withOriginFetcher(fetcher) {
50
+ this._originFetcher = fetcher;
51
+ return this;
52
+ }
53
+ withApiFetcher(fetcher) {
54
+ this._apiFetcher = fetcher;
55
+ return this;
56
+ }
57
+ withHtmlRewriter(rewriter) {
58
+ this._htmlRewriter = rewriter;
59
+ return this;
60
+ }
61
+ withClientMetadata(provider) {
62
+ this._clientMetadataProvider = provider;
63
+ return this;
64
+ }
65
+ withLogger(logger) {
66
+ this._logger = logger;
67
+ return this;
68
+ }
69
+ /**
70
+ * Register a callback to handle HTML pipeline errors. The proxy fails open by default and
71
+ * returns the last safe (unconsumed) response — provide this if you want to shape that
72
+ * response yourself (e.g. render a custom error page, return a 503, or re-throw so your
73
+ * platform's error middleware handles it). If the callback throws or returns a non-Response
74
+ * value, the proxy logs a warning and falls back to the last safe response.
75
+ */
76
+ withHtmlPipelineErrorHandler(handler) {
77
+ this._onHtmlPipelineError = handler;
78
+ return this;
79
+ }
80
+ withoutCustomEndpoints() {
81
+ this._customEndpoints = false;
82
+ return this;
83
+ }
84
+ withoutLinkRewriting() {
85
+ this._linkRewriting = false;
86
+ return this;
87
+ }
88
+ withoutSurfaceDecisions() {
89
+ this._surfaceDecisions = false;
90
+ return this;
91
+ }
92
+ /**
93
+ * Disable the HTML transformation pipeline entirely (stages 3–6).
94
+ *
95
+ * Use for API-only proxies: neither an HTML rewriter adapter nor an API fetcher is required
96
+ * if custom endpoints and surface decisions are both disabled alongside this.
97
+ */
98
+ withoutHtmlTransformation() {
99
+ this._htmlTransformation = false;
100
+ return this;
101
+ }
102
+ build() {
103
+ if (!this._config) {
104
+ throw new Error('MOSProxyBuilder: withConfig(...) is required');
105
+ }
106
+ const needsHtmlRewriter = this._htmlTransformation && this._surfaceDecisions;
107
+ if (needsHtmlRewriter && !this._htmlRewriter) {
108
+ throw new Error('MOSProxyBuilder: withHtmlRewriter(...) is required when surface decisions is enabled; call withoutSurfaceDecisions() or withoutHtmlTransformation() if you do not need it');
109
+ }
110
+ const opts = {
111
+ config: this._config,
112
+ originFetcher: this._originFetcher,
113
+ apiFetcher: this._apiFetcher,
114
+ htmlRewriter: this._htmlRewriter,
115
+ clientMetadataProvider: this._clientMetadataProvider,
116
+ logger: this._logger ?? undefined,
117
+ onHtmlPipelineError: this._onHtmlPipelineError ?? undefined,
118
+ customEndpointsEnabled: this._customEndpoints,
119
+ linkRewritingEnabled: this._linkRewriting,
120
+ surfaceDecisionsEnabled: this._surfaceDecisions,
121
+ htmlTransformationEnabled: this._htmlTransformation,
122
+ };
123
+ return new MOSProxy(opts);
124
+ }
125
+ }
126
+ //# sourceMappingURL=MOSProxyBuilder.js.map
@@ -0,0 +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 { MOSProxyLogger } from './logger'\nimport { MOSProxy, type MOSProxyHtmlPipelineErrorHandler, type MOSProxyOptions } from './MOSProxy'\nimport type { MOSConfigInput } 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 * })\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 _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\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 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 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 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 }\n return new MOSProxy(opts)\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Supplies platform-specific request metadata forwarded to the surface-decisions API
3
+ * under the `cloudflare` field of the request payload (kept for backwards-compat;
4
+ * future schema may rename).
5
+ *
6
+ * - Cloudflare: `{ cf: request.cf }`
7
+ * - Fastly: `{ fastly: { client: event.client, sigsci: {...} } }`
8
+ * - Akamai / unknown: `{}` is fine
9
+ */
10
+ export interface ClientMetadataProvider {
11
+ build(request: Request): Record<string, unknown>;
12
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ClientMetadataProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientMetadataProvider.js","sourceRoot":"","sources":["../../src/adapters/ClientMetadataProvider.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Supplies platform-specific request metadata forwarded to the surface-decisions API\n * under the `cloudflare` field of the request payload (kept for backwards-compat;\n * future schema may rename).\n *\n * - Cloudflare: `{ cf: request.cf }`\n * - Fastly: `{ fastly: { client: event.client, sigsci: {...} } }`\n * - Akamai / unknown: `{}` is fine\n */\nexport interface ClientMetadataProvider {\n build(request: Request): Record<string, unknown>\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dispatches an outbound HTTP request and returns its response.
3
+ *
4
+ * Used for both origin and MOS API traffic. Defaults to `globalThis.fetch`; override on
5
+ * runtimes that need a backend binding or custom dispatch:
6
+ * - Cloudflare: `fetch` (global fetch)
7
+ * - Fastly: `(req) => fetch(req, { backend: 'origin' })` (separate backend per use)
8
+ * - Akamai: wrap `httpRequest()` so surrounding code keeps talking web-standard `Request`/`Response`
9
+ */
10
+ export type Fetcher = (request: Request) => Promise<Response>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fetcher.js","sourceRoot":"","sources":["../../src/adapters/Fetcher.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Dispatches an outbound HTTP request and returns its response.\n *\n * Used for both origin and MOS API traffic. Defaults to `globalThis.fetch`; override on\n * runtimes that need a backend binding or custom dispatch:\n * - Cloudflare: `fetch` (global fetch)\n * - Fastly: `(req) => fetch(req, { backend: 'origin' })` (separate backend per use)\n * - Akamai: wrap `httpRequest()` so surrounding code keeps talking web-standard `Request`/`Response`\n */\nexport type Fetcher = (request: Request) => Promise<Response>\n"]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Platform-agnostic contract over lol-html-style streaming HTML rewriters
3
+ * (Cloudflare's `HTMLRewriter`, Fastly's `HTMLRewritingStream`, Akamai's `HtmlRewritingStream`).
4
+ *
5
+ * An adapter creates a new rewriter session per call to `create()`; the session is a builder
6
+ * that accepts selectors + handlers via `.on(...)` and produces a transformed `Response` via
7
+ * `.transform(response)`.
8
+ *
9
+ * `capabilities` advertises which lol-html features the underlying implementation supports.
10
+ * Consumers dispatch on these flags to enable or fall back paths (e.g., range-replacement
11
+ * via `element.onEndTag` vs. a sentinel post-process).
12
+ */
13
+ export interface HtmlRewriterAdapter {
14
+ readonly capabilities: HtmlRewriterCapabilities;
15
+ create(): HtmlRewriterSession;
16
+ }
17
+ export interface HtmlRewriterCapabilities {
18
+ /** Element handlers can register an end-tag callback via `element.onEndTag(cb)`. */
19
+ onEndTag: boolean;
20
+ /** `:nth-child()` CSS pseudo-class is supported in selectors. */
21
+ nthChild: boolean;
22
+ }
23
+ export interface HtmlRewriterSession {
24
+ on(selector: string, handlers: ElementHandlers): HtmlRewriterSession;
25
+ transform(response: Response): Response;
26
+ }
27
+ export interface ElementHandlers {
28
+ element?(element: RewriterElement): void | Promise<void>;
29
+ text?(text: RewriterText): void | Promise<void>;
30
+ }
31
+ export interface ContentOptions {
32
+ html?: boolean;
33
+ }
34
+ /**
35
+ * Mirrors the Cloudflare `Element` API (a lol-html `HTMLRewriterElement`).
36
+ *
37
+ * Adapters may throw from `onEndTag` on runtimes that don't support it; callers should
38
+ * check `capabilities.onEndTag` before relying on it, or catch the error.
39
+ */
40
+ export interface RewriterElement {
41
+ readonly removed: boolean;
42
+ readonly tagName: string;
43
+ getAttribute(name: string): string | null;
44
+ hasAttribute(name: string): boolean;
45
+ setAttribute(name: string, value: string): void;
46
+ removeAttribute(name: string): void;
47
+ before(content: string, options?: ContentOptions): void;
48
+ after(content: string, options?: ContentOptions): void;
49
+ prepend(content: string, options?: ContentOptions): void;
50
+ append(content: string, options?: ContentOptions): void;
51
+ replace(content: string, options?: ContentOptions): void;
52
+ remove(): void;
53
+ onEndTag?(callback: () => void | Promise<void>): void;
54
+ }
55
+ export interface RewriterText {
56
+ readonly text: string;
57
+ readonly lastInTextNode: boolean;
58
+ readonly removed: boolean;
59
+ remove(): void;
60
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=HtmlRewriterAdapter.js.map