@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
@@ -0,0 +1 @@
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 { withMosProxyVersionHeader } from '../apiRequestHeaders'\nimport type { PipelineContext } from '../context'\nimport type { PageMetadata, SurfaceDecisionError, SurfaceDecisionResponse } from '../types'\n\nexport type FetchSurfaceDecisionsArgs = {\n anonymousIdentifier?: string | undefined\n userJwt?: string | undefined\n path: string\n url: string\n clientMetadata: Record<string, unknown>\n pageMetadata?: PageMetadata\n userAgent?: 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 { anonymousIdentifier, userJwt, path, url, clientMetadata, pageMetadata, userAgent, originStatus }: FetchSurfaceDecisionsArgs,\n apiFetcher: Fetcher,\n): Promise<FetchSurfaceDecisionsResult> {\n const { config } = ctx\n const body = JSON.stringify({\n surfaceSlug: config.surfaceSlug,\n identity: buildIdentity({ anonymousIdentifier, userJwt }),\n resource: {\n id: path,\n meta: pageMetadata,\n },\n http: {\n url,\n userAgent,\n proxyOrigin: {\n status: originStatus,\n },\n },\n // Legacy field name kept to avoid breaking the API schema; opaque to the proxy core.\n cloudflare: clientMetadata,\n })\n\n const request = new Request(new URL('/api/v1/surface-decisions', config.mosHost), {\n method: 'POST',\n body,\n headers: withMosProxyVersionHeader({\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 buildIdentity = ({ anonymousIdentifier, userJwt }: Pick<FetchSurfaceDecisionsArgs, 'anonymousIdentifier' | 'userJwt'>) => {\n if (!anonymousIdentifier && !userJwt) {\n return { createAnonymousIdentifier: true }\n }\n return userJwt ? { userJwt } : { anonymousIdentifier }\n}\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"]}
@@ -0,0 +1,21 @@
1
+ import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter';
2
+ import type { PipelineContext } from '../context';
3
+ import type { SurfaceDecisionResponse } from '../types';
4
+ export type SurfaceMarkers = {
5
+ componentsWithInvalidSelectors: string[];
6
+ markers: Record<string, ComponentRange[]>;
7
+ };
8
+ export type ComponentRange = {
9
+ markers: Marker[];
10
+ };
11
+ export type Marker = {
12
+ endTag: number | null;
13
+ startMarker: boolean;
14
+ endMarker: boolean;
15
+ nextEndMarker: number | null;
16
+ };
17
+ /**
18
+ * For any modifications with start or end markers, find the positions of those markers.
19
+ * Runs a single pass of the rewriter over a cloned response to build per-component marker maps.
20
+ */
21
+ export declare const findMarkerPositions: (ctx: PipelineContext, response: Response, surfaceDecisions: SurfaceDecisionResponse, rewriter: HtmlRewriterAdapter) => Promise<SurfaceMarkers>;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * For any modifications with start or end markers, find the positions of those markers.
3
+ * Runs a single pass of the rewriter over a cloned response to build per-component marker maps.
4
+ */
5
+ export const findMarkerPositions = async (ctx, response, surfaceDecisions, rewriter) => {
6
+ const logInvalidState = (location, componentKey) => {
7
+ ctx.logger.log({
8
+ level: 'warn',
9
+ code: 'marker-pass-invalid-state',
10
+ message: 'Marker handler invoked with no active range; skipping.',
11
+ context: { location, componentKey },
12
+ });
13
+ };
14
+ const markers = Object.fromEntries(Object.entries(surfaceDecisions.componentBehaviors).map(([key]) => [key, []]));
15
+ const handlers = Object.entries(surfaceDecisions.componentBehaviors).flatMap(([componentKey, componentBehavior]) => {
16
+ const modification = componentBehavior.content?.replaceRange;
17
+ if (!componentBehavior.metadata?.cssSelector || !modification) {
18
+ return [];
19
+ }
20
+ const { toMarker, fromMarker } = modification;
21
+ let elementCounter = 0;
22
+ let currentRange = null;
23
+ const entries = [
24
+ {
25
+ selector: `${componentBehavior.metadata.cssSelector}`,
26
+ componentKey,
27
+ handler: {
28
+ element: () => {
29
+ currentRange = { markers: [] };
30
+ markers[componentKey]?.push(currentRange);
31
+ },
32
+ },
33
+ },
34
+ {
35
+ selector: `${componentBehavior.metadata.cssSelector} *`,
36
+ componentKey,
37
+ handler: {
38
+ element(element) {
39
+ if (!currentRange) {
40
+ logInvalidState('child-element', componentKey);
41
+ return;
42
+ }
43
+ ++elementCounter;
44
+ const marker = {
45
+ endMarker: false,
46
+ startMarker: false,
47
+ endTag: null,
48
+ nextEndMarker: null,
49
+ };
50
+ currentRange.markers[elementCounter] = marker;
51
+ if (element.onEndTag) {
52
+ try {
53
+ element.onEndTag(() => {
54
+ marker.endTag = elementCounter;
55
+ });
56
+ }
57
+ catch {
58
+ marker.endTag = elementCounter;
59
+ }
60
+ }
61
+ else {
62
+ marker.endTag = elementCounter;
63
+ }
64
+ },
65
+ },
66
+ },
67
+ toMarker
68
+ ? {
69
+ selector: `${componentBehavior.metadata.cssSelector} ${toMarker}`,
70
+ componentKey,
71
+ handler: {
72
+ element() {
73
+ if (!currentRange) {
74
+ logInvalidState('to-marker', componentKey);
75
+ return;
76
+ }
77
+ const marker = currentRange.markers[elementCounter];
78
+ if (marker) {
79
+ marker.endMarker = true;
80
+ }
81
+ for (let i = elementCounter; i >= 1; i--) {
82
+ const m = currentRange.markers[i];
83
+ if (m && !m.nextEndMarker) {
84
+ m.nextEndMarker = elementCounter;
85
+ }
86
+ else {
87
+ break;
88
+ }
89
+ }
90
+ },
91
+ },
92
+ }
93
+ : null,
94
+ fromMarker
95
+ ? {
96
+ selector: `${componentBehavior.metadata.cssSelector} ${fromMarker}`,
97
+ componentKey,
98
+ handler: {
99
+ element() {
100
+ if (!currentRange) {
101
+ logInvalidState('from-marker', componentKey);
102
+ return;
103
+ }
104
+ const marker = currentRange.markers[elementCounter];
105
+ if (marker) {
106
+ marker.startMarker = true;
107
+ }
108
+ },
109
+ },
110
+ }
111
+ : null,
112
+ ];
113
+ return entries.filter((e) => !!e);
114
+ });
115
+ if (!handlers.length || !response.body) {
116
+ return { markers, componentsWithInvalidSelectors: [] };
117
+ }
118
+ const componentsWithInvalidSelectors = [];
119
+ let session = rewriter.create();
120
+ for (const { selector, handler, componentKey } of handlers) {
121
+ try {
122
+ session = session.on(selector, handler);
123
+ }
124
+ catch (error) {
125
+ ctx.logger.log({
126
+ level: 'error',
127
+ code: 'marker-pass-selector-failed',
128
+ message: 'Failed to register marker-pass handler for component selector; skipping range replacement for this component.',
129
+ context: { selector, componentKey },
130
+ error,
131
+ });
132
+ componentsWithInvalidSelectors.push(componentKey);
133
+ }
134
+ }
135
+ await session.transform(response.clone()).text();
136
+ return { markers, componentsWithInvalidSelectors };
137
+ };
138
+ //# sourceMappingURL=findMarkerPositions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findMarkerPositions.js","sourceRoot":"","sources":["../../src/stages/findMarkerPositions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ElementHandlers, HtmlRewriterAdapter, RewriterElement } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { SurfaceDecisionResponse } from '../types'\n\nexport type SurfaceMarkers = {\n componentsWithInvalidSelectors: string[]\n markers: Record<string, ComponentRange[]>\n}\n\nexport type ComponentRange = {\n markers: Marker[]\n}\n\nexport type Marker = {\n endTag: number | null\n startMarker: boolean\n endMarker: boolean\n nextEndMarker: number | null\n}\n\ntype Handler = { selector: string; componentKey: string; handler: ElementHandlers }\n\n/**\n * For any modifications with start or end markers, find the positions of those markers.\n * Runs a single pass of the rewriter over a cloned response to build per-component marker maps.\n */\nexport const findMarkerPositions = async (\n ctx: PipelineContext,\n response: Response,\n surfaceDecisions: SurfaceDecisionResponse,\n rewriter: HtmlRewriterAdapter,\n): Promise<SurfaceMarkers> => {\n const logInvalidState = (location: string, componentKey: string) => {\n ctx.logger.log({\n level: 'warn',\n code: 'marker-pass-invalid-state',\n message: 'Marker handler invoked with no active range; skipping.',\n context: { location, componentKey },\n })\n }\n\n const markers: SurfaceMarkers['markers'] = Object.fromEntries(\n Object.entries(surfaceDecisions.componentBehaviors).map(([key]) => [key, []]),\n )\n\n const handlers: Handler[] = Object.entries(surfaceDecisions.componentBehaviors).flatMap<Handler>(\n ([componentKey, componentBehavior]) => {\n const modification = componentBehavior.content?.replaceRange\n if (!componentBehavior.metadata?.cssSelector || !modification) {\n return []\n }\n const { toMarker, fromMarker } = modification\n let elementCounter = 0\n let currentRange: ComponentRange | null = null\n\n const entries: (Handler | null)[] = [\n {\n selector: `${componentBehavior.metadata.cssSelector}`,\n componentKey,\n handler: {\n element: () => {\n currentRange = { markers: [] }\n markers[componentKey]?.push(currentRange)\n },\n },\n },\n {\n selector: `${componentBehavior.metadata.cssSelector} *`,\n componentKey,\n handler: {\n element(element: RewriterElement) {\n if (!currentRange) {\n logInvalidState('child-element', componentKey)\n return\n }\n ++elementCounter\n const marker: Marker = {\n endMarker: false,\n startMarker: false,\n endTag: null,\n nextEndMarker: null,\n }\n currentRange.markers[elementCounter] = marker\n if (element.onEndTag) {\n try {\n element.onEndTag(() => {\n marker.endTag = elementCounter\n })\n } catch {\n marker.endTag = elementCounter\n }\n } else {\n marker.endTag = elementCounter\n }\n },\n },\n },\n toMarker\n ? {\n selector: `${componentBehavior.metadata.cssSelector} ${toMarker}`,\n componentKey,\n handler: {\n element() {\n if (!currentRange) {\n logInvalidState('to-marker', componentKey)\n return\n }\n const marker = currentRange.markers[elementCounter]\n if (marker) {\n marker.endMarker = true\n }\n for (let i = elementCounter; i >= 1; i--) {\n const m = currentRange.markers[i]\n if (m && !m.nextEndMarker) {\n m.nextEndMarker = elementCounter\n } else {\n break\n }\n }\n },\n },\n }\n : null,\n fromMarker\n ? {\n selector: `${componentBehavior.metadata.cssSelector} ${fromMarker}`,\n componentKey,\n handler: {\n element() {\n if (!currentRange) {\n logInvalidState('from-marker', componentKey)\n return\n }\n const marker = currentRange.markers[elementCounter]\n if (marker) {\n marker.startMarker = true\n }\n },\n },\n }\n : null,\n ]\n return entries.filter((e): e is Handler => !!e)\n },\n )\n\n if (!handlers.length || !response.body) {\n return { markers, componentsWithInvalidSelectors: [] }\n }\n\n const componentsWithInvalidSelectors: string[] = []\n let session = rewriter.create()\n for (const { selector, handler, componentKey } of handlers) {\n try {\n session = session.on(selector, handler)\n } catch (error) {\n ctx.logger.log({\n level: 'error',\n code: 'marker-pass-selector-failed',\n message: 'Failed to register marker-pass handler for component selector; skipping range replacement for this component.',\n context: { selector, componentKey },\n error,\n })\n componentsWithInvalidSelectors.push(componentKey)\n }\n }\n\n await session.transform(response.clone()).text()\n return { markers, componentsWithInvalidSelectors }\n}\n"]}
@@ -0,0 +1 @@
1
+ export default function getTargetUrl(requestUrl: URL, originUrl: URL): URL;
@@ -0,0 +1,9 @@
1
+ export default function getTargetUrl(requestUrl, originUrl) {
2
+ const target = new URL(requestUrl);
3
+ target.protocol = originUrl.protocol;
4
+ target.host = originUrl.host;
5
+ target.port = originUrl.port;
6
+ target.pathname = originUrl.pathname.replace(/\/$/, '') + target.pathname;
7
+ return target;
8
+ }
9
+ //# sourceMappingURL=getTargetUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getTargetUrl.js","sourceRoot":"","sources":["../../src/stages/getTargetUrl.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,UAAe,EAAE,SAAc;IAChE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAA;IACpC,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAA;IAC5B,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAA;IAC5B,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAA;IACzE,OAAO,MAAM,CAAA;AACjB,CAAC","sourcesContent":["export default function getTargetUrl(requestUrl: URL, originUrl: URL): URL {\n const target = new URL(requestUrl)\n target.protocol = originUrl.protocol\n target.host = originUrl.host\n target.port = originUrl.port\n target.pathname = originUrl.pathname.replace(/\\/$/, '') + target.pathname\n return target\n}\n"]}
@@ -0,0 +1 @@
1
+ export default function isRedirectResponse(response: Response): boolean;
@@ -0,0 +1,4 @@
1
+ export default function isRedirectResponse(response) {
2
+ return response.status >= 300 && response.status < 400;
3
+ }
4
+ //# sourceMappingURL=isRedirectResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isRedirectResponse.js","sourceRoot":"","sources":["../../src/stages/isRedirectResponse.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,QAAkB;IACzD,OAAO,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAA;AAC1D,CAAC","sourcesContent":["export default function isRedirectResponse(response: Response): boolean {\n return response.status >= 300 && response.status < 400\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { PipelineContext } from '../context';
2
+ export default function rewriteOriginResponse(ctx: PipelineContext, request: Request, response: Response): Promise<Response>;
@@ -0,0 +1,37 @@
1
+ import { statusAllowsBody } from '../http';
2
+ import transformOriginLinks from './transformOriginLinks';
3
+ export default async function rewriteOriginResponse(ctx, request, response) {
4
+ const { config, logger } = ctx;
5
+ const requestUrl = new URL(request.url);
6
+ const originUrl = config.originUrl;
7
+ const headers = new Headers();
8
+ response.headers.forEach((value, name) => {
9
+ try {
10
+ headers.append(name, transformOriginLinks(requestUrl, originUrl, value));
11
+ }
12
+ catch (error) {
13
+ logger.log({
14
+ level: 'error',
15
+ code: 'link-rewriting-header-failed',
16
+ message: 'Failed to rewrite origin link in response header; keeping original value.',
17
+ context: { header: name },
18
+ error,
19
+ });
20
+ headers.append(name, value);
21
+ }
22
+ });
23
+ headers.set('Cache-Control', 'no-store');
24
+ const init = { status: response.status, statusText: response.statusText, headers };
25
+ if (!statusAllowsBody(response.status) || !response.body) {
26
+ return new Response(null, init);
27
+ }
28
+ // Mutating the body invalidates the origin's byte-length/encoding metadata.
29
+ headers.delete('Content-Length');
30
+ headers.delete('Content-Encoding');
31
+ const body = await response.text();
32
+ if (!body) {
33
+ return new Response(null, init);
34
+ }
35
+ return new Response(transformOriginLinks(requestUrl, originUrl, body), init);
36
+ }
37
+ //# sourceMappingURL=linkRewriting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkRewriting.js","sourceRoot":"","sources":["../../src/stages/linkRewriting.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAoB,EAAE,OAAgB,EAAE,QAAkB;IAC1G,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAA;IAC9B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;IAElC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;IAC7B,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrC,IAAI,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAA;QAC5E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC;gBACP,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,2EAA2E;gBACpF,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;gBACzB,KAAK;aACR,CAAC,CAAA;YACF,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;IACL,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAA;IAExC,MAAM,IAAI,GAAiB,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAA;IAEhG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACnC,CAAC;IAED,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAChC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAElC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAClC,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACnC,CAAC;IAED,OAAO,IAAI,QAAQ,CAAC,oBAAoB,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;AAChF,CAAC","sourcesContent":["import type { PipelineContext } from '../context'\nimport { statusAllowsBody } from '../http'\nimport transformOriginLinks from './transformOriginLinks'\n\nexport default async function rewriteOriginResponse(ctx: PipelineContext, request: Request, response: Response): Promise<Response> {\n const { config, logger } = ctx\n const requestUrl = new URL(request.url)\n const originUrl = config.originUrl\n\n const headers = new Headers()\n response.headers.forEach((value, name) => {\n try {\n headers.append(name, transformOriginLinks(requestUrl, originUrl, value))\n } catch (error) {\n logger.log({\n level: 'error',\n code: 'link-rewriting-header-failed',\n message: 'Failed to rewrite origin link in response header; keeping original value.',\n context: { header: name },\n error,\n })\n headers.append(name, value)\n }\n })\n headers.set('Cache-Control', 'no-store')\n\n const init: ResponseInit = { status: response.status, statusText: response.statusText, headers }\n\n if (!statusAllowsBody(response.status) || !response.body) {\n return new Response(null, init)\n }\n\n // Mutating the body invalidates the origin's byte-length/encoding metadata.\n headers.delete('Content-Length')\n headers.delete('Content-Encoding')\n\n const body = await response.text()\n if (!body) {\n return new Response(null, init)\n }\n\n return new Response(transformOriginLinks(requestUrl, originUrl, body), init)\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Fetcher } from '../adapters/Fetcher';
2
+ import type { PipelineContext } from '../context';
3
+ export default function performOriginRequest(ctx: PipelineContext, request: Request, originFetcher: Fetcher): Promise<Response>;
@@ -0,0 +1,10 @@
1
+ import getTargetUrl from './getTargetUrl';
2
+ export default function performOriginRequest(ctx, request, originFetcher) {
3
+ const targetUrl = getTargetUrl(new URL(request.url), ctx.config.originUrl);
4
+ const originRequest = new Request(targetUrl, request);
5
+ for (const [name, value] of Object.entries(ctx.config.originRequestHeaders)) {
6
+ originRequest.headers.set(name, value);
7
+ }
8
+ return originFetcher(originRequest);
9
+ }
10
+ //# sourceMappingURL=originRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"originRequest.js","sourceRoot":"","sources":["../../src/stages/originRequest.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAEzC,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,GAAoB,EAAE,OAAgB,EAAE,aAAsB;IACvG,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC1E,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAErD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC1E,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC1C,CAAC;IAED,OAAO,aAAa,CAAC,aAAa,CAAC,CAAA;AACvC,CAAC","sourcesContent":["import type { Fetcher } from '../adapters/Fetcher'\nimport type { PipelineContext } from '../context'\nimport getTargetUrl from './getTargetUrl'\n\nexport default function performOriginRequest(ctx: PipelineContext, request: Request, originFetcher: Fetcher): Promise<Response> {\n const targetUrl = getTargetUrl(new URL(request.url), ctx.config.originUrl)\n const originRequest = new Request(targetUrl, request)\n\n for (const [name, value] of Object.entries(ctx.config.originRequestHeaders)) {\n originRequest.headers.set(name, value)\n }\n\n return originFetcher(originRequest)\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter';
2
+ import type { PipelineContext } from '../context';
3
+ import type { PageMetadata } from '../types';
4
+ export declare function parsePageMetadata(ctx: PipelineContext, response: Response, rewriter: HtmlRewriterAdapter): Promise<PageMetadata>;
@@ -0,0 +1,25 @@
1
+ export async function parsePageMetadata(ctx, response, rewriter) {
2
+ const metadata = {};
3
+ try {
4
+ const session = rewriter.create().on('meta', {
5
+ element(element) {
6
+ const key = element.getAttribute('name') ?? element.getAttribute('property');
7
+ const value = element.getAttribute('content');
8
+ if (key && value !== null) {
9
+ metadata[key] = value;
10
+ }
11
+ },
12
+ });
13
+ await session.transform(response).text();
14
+ }
15
+ catch (error) {
16
+ ctx.logger.log({
17
+ level: 'error',
18
+ code: 'page-metadata-parse-failed',
19
+ message: 'Failed to parse page metadata; returning empty metadata.',
20
+ error,
21
+ });
22
+ }
23
+ return metadata;
24
+ }
25
+ //# sourceMappingURL=pageMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageMetadata.js","sourceRoot":"","sources":["../../src/stages/pageMetadata.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAoB,EAAE,QAAkB,EAAE,QAA6B;IAC3G,MAAM,QAAQ,GAAiB,EAAE,CAAA;IAEjC,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE;YACzC,OAAO,CAAC,OAAO;gBACX,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;gBAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;gBAC7C,IAAI,GAAG,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACxB,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;gBACzB,CAAC;YACL,CAAC;SACJ,CAAC,CAAA;QACF,MAAM,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YACX,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,4BAA4B;YAClC,OAAO,EAAE,0DAA0D;YACnE,KAAK;SACR,CAAC,CAAA;IACN,CAAC;IAED,OAAO,QAAQ,CAAA;AACnB,CAAC","sourcesContent":["import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { PageMetadata } from '../types'\n\nexport async function parsePageMetadata(ctx: PipelineContext, response: Response, rewriter: HtmlRewriterAdapter): Promise<PageMetadata> {\n const metadata: PageMetadata = {}\n\n try {\n const session = rewriter.create().on('meta', {\n element(element) {\n const key = element.getAttribute('name') ?? element.getAttribute('property')\n const value = element.getAttribute('content')\n if (key && value !== null) {\n metadata[key] = value\n }\n },\n })\n await session.transform(response).text()\n } catch (error) {\n ctx.logger.log({\n level: 'error',\n code: 'page-metadata-parse-failed',\n message: 'Failed to parse page metadata; returning empty metadata.',\n error,\n })\n }\n\n return metadata\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { PipelineContext } from '../context';
2
+ export default function shouldIgnorePath(ctx: PipelineContext, request: Request): boolean;
@@ -0,0 +1,9 @@
1
+ export default function shouldIgnorePath(ctx, request) {
2
+ const patterns = ctx.config.surfaceDecisionsIgnorePathPatterns;
3
+ if (patterns.length === 0) {
4
+ return false;
5
+ }
6
+ const { pathname } = new URL(request.url);
7
+ return patterns.some((pattern) => pattern.test(pathname));
8
+ }
9
+ //# sourceMappingURL=shouldIgnorePath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldIgnorePath.js","sourceRoot":"","sources":["../../src/stages/shouldIgnorePath.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,GAAoB,EAAE,OAAgB;IAC3E,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,kCAAkC,CAAA;IAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAA;IAChB,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACzC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC7D,CAAC","sourcesContent":["import type { PipelineContext } from '../context'\n\nexport default function shouldIgnorePath(ctx: PipelineContext, request: Request): boolean {\n const patterns = ctx.config.surfaceDecisionsIgnorePathPatterns\n if (patterns.length === 0) {\n return false\n }\n const { pathname } = new URL(request.url)\n return patterns.some((pattern) => pattern.test(pathname))\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { SurfaceDecisionResponse } from '../types';
2
+ type ReturnImmediately = boolean;
3
+ export default function handleSurfaceBehavior(response: Response, surfaceDecisions: SurfaceDecisionResponse): [Response, ReturnImmediately];
4
+ export {};
@@ -0,0 +1,60 @@
1
+ import { statusAllowsBody } from '../http';
2
+ export default function handleSurfaceBehavior(response, surfaceDecisions) {
3
+ const http = surfaceDecisions.surfaceBehavior?.http;
4
+ if (!http || Object.keys(http).length === 0) {
5
+ return [response, false];
6
+ }
7
+ let headers = new Headers(response.headers);
8
+ let status = response.status;
9
+ let statusText = response.statusText;
10
+ let body = response.body;
11
+ let returnImmediately = false;
12
+ if ('headers' in http && http.headers) {
13
+ headers = new Headers(http.headers);
14
+ }
15
+ if ('cookies' in http && http.cookies) {
16
+ headers.delete('Set-Cookie');
17
+ http.cookies.forEach((cookie) => {
18
+ headers.append('Set-Cookie', cookie);
19
+ });
20
+ }
21
+ if ('addHeaders' in http && http.addHeaders?.length) {
22
+ // Intentional set-not-append: Set-Cookie has its own field (`cookies`/`addCookies`), so
23
+ // addHeaders is only used for non-multi-valued headers where replace is the safer default.
24
+ http.addHeaders.forEach(({ name, value }) => {
25
+ headers.set(name, value);
26
+ });
27
+ }
28
+ if ('removeHeaders' in http && http.removeHeaders?.length) {
29
+ http.removeHeaders.forEach((name) => {
30
+ headers.delete(name);
31
+ });
32
+ }
33
+ if ('addCookies' in http && http.addCookies?.length) {
34
+ http.addCookies.forEach((cookie) => {
35
+ headers.append('Set-Cookie', cookie);
36
+ });
37
+ }
38
+ if ('status' in http && http.status) {
39
+ status = http.status;
40
+ }
41
+ if ('statusText' in http && http.statusText) {
42
+ statusText = http.statusText;
43
+ }
44
+ if ('body' in http && http.body !== undefined) {
45
+ body = http.body;
46
+ returnImmediately = true;
47
+ }
48
+ if (!statusAllowsBody(status)) {
49
+ body = null;
50
+ }
51
+ return [
52
+ new Response(body, {
53
+ status,
54
+ statusText,
55
+ headers,
56
+ }),
57
+ returnImmediately,
58
+ ];
59
+ }
60
+ //# sourceMappingURL=surfaceBehavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceBehavior.js","sourceRoot":"","sources":["../../src/stages/surfaceBehavior.ts"],"names":[],"mappings":"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","sourcesContent":["import { statusAllowsBody } from '../http'\nimport type { SurfaceDecisionResponse } from '../types'\n\ntype ReturnImmediately = boolean\n\nexport default function handleSurfaceBehavior(\n response: Response,\n surfaceDecisions: SurfaceDecisionResponse,\n): [Response, ReturnImmediately] {\n const http = surfaceDecisions.surfaceBehavior?.http\n if (!http || Object.keys(http).length === 0) {\n return [response, false]\n }\n\n let headers = new Headers(response.headers)\n let status = response.status\n let statusText = response.statusText\n let body: BodyInit | null = response.body\n let returnImmediately = false\n\n if ('headers' in http && http.headers) {\n headers = new Headers(http.headers)\n }\n\n if ('cookies' in http && http.cookies) {\n headers.delete('Set-Cookie')\n http.cookies.forEach((cookie) => {\n headers.append('Set-Cookie', cookie)\n })\n }\n\n if ('addHeaders' in http && http.addHeaders?.length) {\n // Intentional set-not-append: Set-Cookie has its own field (`cookies`/`addCookies`), so\n // addHeaders is only used for non-multi-valued headers where replace is the safer default.\n http.addHeaders.forEach(({ name, value }) => {\n headers.set(name, value)\n })\n }\n\n if ('removeHeaders' in http && http.removeHeaders?.length) {\n http.removeHeaders.forEach((name) => {\n headers.delete(name)\n })\n }\n\n if ('addCookies' in http && http.addCookies?.length) {\n http.addCookies.forEach((cookie) => {\n headers.append('Set-Cookie', cookie)\n })\n }\n\n if ('status' in http && http.status) {\n status = http.status\n }\n\n if ('statusText' in http && http.statusText) {\n statusText = http.statusText\n }\n\n if ('body' in http && http.body !== undefined) {\n body = http.body\n returnImmediately = true\n }\n\n if (!statusAllowsBody(status)) {\n body = null\n }\n\n return [\n new Response(body, {\n status,\n statusText,\n headers,\n }),\n returnImmediately,\n ]\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter';
2
+ import type { PipelineContext } from '../context';
3
+ import type { SurfaceDecisionResponse } from '../types';
4
+ export default function handleSurfaceComponents(ctx: PipelineContext, response: Response, surfaceDecisions: SurfaceDecisionResponse, rewriter: HtmlRewriterAdapter): Promise<Response>;
@@ -0,0 +1,71 @@
1
+ import { buildReplacementHandlers } from './buildReplacementHandlers';
2
+ import { ContentElementHandler } from './ContentElementHandler';
3
+ import { findMarkerPositions } from './findMarkerPositions';
4
+ export default async function handleSurfaceComponents(ctx, response, surfaceDecisions, rewriter) {
5
+ if (surfaceDecisions.componentsSkipped) {
6
+ return response;
7
+ }
8
+ const { config, logger } = ctx;
9
+ const supportsRangeReplacement = rewriter.capabilities.onEndTag;
10
+ const { markers, componentsWithInvalidSelectors } = supportsRangeReplacement
11
+ ? await findMarkerPositions(ctx, response, surfaceDecisions, rewriter)
12
+ : { markers: {}, componentsWithInvalidSelectors: [] };
13
+ let doRewrite = false;
14
+ let session = rewriter.create();
15
+ Object.entries(surfaceDecisions.componentBehaviors).forEach(([componentKey, componentBehavior]) => {
16
+ if (!componentBehavior.metadata.cssSelector || !componentBehavior.content) {
17
+ return;
18
+ }
19
+ if (componentBehavior.metadata.cssSelector.includes(':last-child')) {
20
+ logger.log({
21
+ level: 'warn',
22
+ code: 'component-selector-unsupported',
23
+ message: 'Ignoring component with unsupported CSS selector.',
24
+ context: { componentKey, selector: componentBehavior.metadata.cssSelector },
25
+ });
26
+ return;
27
+ }
28
+ const handlers = [
29
+ {
30
+ selector: componentBehavior.metadata.cssSelector,
31
+ handler: new ContentElementHandler(ctx, componentBehavior.content),
32
+ },
33
+ ];
34
+ if (supportsRangeReplacement && !componentsWithInvalidSelectors.includes(componentKey)) {
35
+ handlers.push(...buildReplacementHandlers(ctx, componentBehavior, markers[componentKey]));
36
+ }
37
+ else if (!supportsRangeReplacement && componentBehavior.content.replaceRange) {
38
+ logger.log({
39
+ level: 'warn',
40
+ code: 'component-range-replacement-skipped',
41
+ message: 'Range replacement skipped: HTML rewriter adapter does not support onEndTag.',
42
+ context: { componentKey },
43
+ });
44
+ }
45
+ for (const { selector, handler } of handlers) {
46
+ try {
47
+ session = session.on(selector, handler);
48
+ doRewrite = true;
49
+ }
50
+ catch (error) {
51
+ logger.log({
52
+ level: 'error',
53
+ code: 'component-transform-failed',
54
+ message: 'Failed to register component transform; skipping this handler.',
55
+ context: { componentKey, selector: componentBehavior.metadata.cssSelector },
56
+ error,
57
+ });
58
+ }
59
+ }
60
+ });
61
+ if (config.injectScriptUrl) {
62
+ const scriptUrl = config.injectScriptUrl;
63
+ session = session.on('head', {
64
+ element(element) {
65
+ element.append(`<script src="${scriptUrl}" async defer></script>`, { html: true });
66
+ },
67
+ });
68
+ }
69
+ return doRewrite ? session.transform(response) : response;
70
+ }
71
+ //# sourceMappingURL=surfaceComponents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceComponents.js","sourceRoot":"","sources":["../../src/stages/surfaceComponents.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAE3D,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,uBAAuB,CACjD,GAAoB,EACpB,QAAkB,EAClB,gBAAyC,EACzC,QAA6B;IAE7B,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,CAAC;QACrC,OAAO,QAAQ,CAAA;IACnB,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAA;IAC9B,MAAM,wBAAwB,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAA;IAC/D,MAAM,EAAE,OAAO,EAAE,8BAA8B,EAAE,GAAG,wBAAwB;QACxE,CAAC,CAAC,MAAM,mBAAmB,CAAC,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,CAAC;QACtE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,8BAA8B,EAAE,EAAE,EAAE,CAAA;IAEzD,IAAI,SAAS,GAAG,KAAK,CAAA;IACrB,IAAI,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAA;IAE/B,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,iBAAiB,CAAC,EAAE,EAAE;QAC9F,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YACxE,OAAM;QACV,CAAC;QAED,IAAI,iBAAiB,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACjE,MAAM,CAAC,GAAG,CAAC;gBACP,KAAK,EAAE,MAAM;gBACb,IAAI,EAAE,gCAAgC;gBACtC,OAAO,EAAE,mDAAmD;gBAC5D,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,WAAW,EAAE;aAC9E,CAAC,CAAA;YACF,OAAM;QACV,CAAC;QAED,MAAM,QAAQ,GAAsE;YAChF;gBACI,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,WAAW;gBAChD,OAAO,EAAE,IAAI,qBAAqB,CAAC,GAAG,EAAE,iBAAiB,CAAC,OAAO,CAAC;aACrE;SACJ,CAAA;QAED,IAAI,wBAAwB,IAAI,CAAC,8BAA8B,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACrF,QAAQ,CAAC,IAAI,CAAC,GAAG,wBAAwB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAC7F,CAAC;aAAM,IAAI,CAAC,wBAAwB,IAAI,iBAAiB,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC7E,MAAM,CAAC,GAAG,CAAC;gBACP,KAAK,EAAE,MAAM;gBACb,IAAI,EAAE,qCAAqC;gBAC3C,OAAO,EAAE,6EAA6E;gBACtF,OAAO,EAAE,EAAE,YAAY,EAAE;aAC5B,CAAC,CAAA;QACN,CAAC;QAED,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC3C,IAAI,CAAC;gBACD,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;gBACvC,SAAS,GAAG,IAAI,CAAA;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,CAAC,GAAG,CAAC;oBACP,KAAK,EAAE,OAAO;oBACd,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,gEAAgE;oBACzE,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,WAAW,EAAE;oBAC3E,KAAK;iBACR,CAAC,CAAA;YACN,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,MAAM,CAAC,eAAe,CAAA;QACxC,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE;YACzB,OAAO,CAAC,OAAO;gBACX,OAAO,CAAC,MAAM,CAAC,gBAAgB,SAAS,yBAAyB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YACtF,CAAC;SACJ,CAAC,CAAA;IACN,CAAC;IAED,OAAO,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC7D,CAAC","sourcesContent":["import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { SurfaceDecisionResponse } from '../types'\nimport { buildReplacementHandlers } from './buildReplacementHandlers'\nimport { ContentElementHandler } from './ContentElementHandler'\nimport { findMarkerPositions } from './findMarkerPositions'\n\nexport default async function handleSurfaceComponents(\n ctx: PipelineContext,\n response: Response,\n surfaceDecisions: SurfaceDecisionResponse,\n rewriter: HtmlRewriterAdapter,\n): Promise<Response> {\n if (surfaceDecisions.componentsSkipped) {\n return response\n }\n\n const { config, logger } = ctx\n const supportsRangeReplacement = rewriter.capabilities.onEndTag\n const { markers, componentsWithInvalidSelectors } = supportsRangeReplacement\n ? await findMarkerPositions(ctx, response, surfaceDecisions, rewriter)\n : { markers: {}, componentsWithInvalidSelectors: [] }\n\n let doRewrite = false\n let session = rewriter.create()\n\n Object.entries(surfaceDecisions.componentBehaviors).forEach(([componentKey, componentBehavior]) => {\n if (!componentBehavior.metadata.cssSelector || !componentBehavior.content) {\n return\n }\n\n if (componentBehavior.metadata.cssSelector.includes(':last-child')) {\n logger.log({\n level: 'warn',\n code: 'component-selector-unsupported',\n message: 'Ignoring component with unsupported CSS selector.',\n context: { componentKey, selector: componentBehavior.metadata.cssSelector },\n })\n return\n }\n\n const handlers: { selector: string; handler: Parameters<typeof session.on>[1] }[] = [\n {\n selector: componentBehavior.metadata.cssSelector,\n handler: new ContentElementHandler(ctx, componentBehavior.content),\n },\n ]\n\n if (supportsRangeReplacement && !componentsWithInvalidSelectors.includes(componentKey)) {\n handlers.push(...buildReplacementHandlers(ctx, componentBehavior, markers[componentKey]))\n } else if (!supportsRangeReplacement && componentBehavior.content.replaceRange) {\n logger.log({\n level: 'warn',\n code: 'component-range-replacement-skipped',\n message: 'Range replacement skipped: HTML rewriter adapter does not support onEndTag.',\n context: { componentKey },\n })\n }\n\n for (const { selector, handler } of handlers) {\n try {\n session = session.on(selector, handler)\n doRewrite = true\n } catch (error) {\n logger.log({\n level: 'error',\n code: 'component-transform-failed',\n message: 'Failed to register component transform; skipping this handler.',\n context: { componentKey, selector: componentBehavior.metadata.cssSelector },\n error,\n })\n }\n }\n })\n\n if (config.injectScriptUrl) {\n const scriptUrl = config.injectScriptUrl\n session = session.on('head', {\n element(element) {\n element.append(`<script src=\"${scriptUrl}\" async defer></script>`, { html: true })\n },\n })\n }\n\n return doRewrite ? session.transform(response) : response\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { ClientMetadataProvider } from '../adapters/ClientMetadataProvider';
2
+ import type { Fetcher } from '../adapters/Fetcher';
3
+ import type { HtmlRewriterAdapter } from '../adapters/HtmlRewriterAdapter';
4
+ import type { PipelineContext } from '../context';
5
+ import type { SurfaceDecisionResponse } from '../types';
6
+ export default function getSurfaceDecisions(ctx: PipelineContext, request: Request, response: Response, apiFetcher: Fetcher, rewriter: HtmlRewriterAdapter | null, clientMetadataProvider: ClientMetadataProvider | null): Promise<[Response, SurfaceDecisionResponse | null]>;
@@ -0,0 +1,59 @@
1
+ import { parse, parseSetCookie } from 'cookie';
2
+ import fetchSurfaceDecisions from './fetchSurfaceDecisions';
3
+ import { parsePageMetadata } from './pageMetadata';
4
+ export default async function getSurfaceDecisions(ctx, request, response, apiFetcher, rewriter, clientMetadataProvider) {
5
+ const { config, logger } = ctx;
6
+ const { anonymousIdentifier, userJwt } = getExistingCookies(request, response, config);
7
+ const [metadataStream, passThroughStream] = response.body?.tee() ?? [null, null];
8
+ const pageMetadata = metadataStream && rewriter ? await parsePageMetadata(ctx, new Response(metadataStream, response), rewriter) : {};
9
+ let modifiedResponse = passThroughStream ? new Response(passThroughStream, response) : response;
10
+ const clientMetadata = clientMetadataProvider?.build(request) ?? {};
11
+ const result = await fetchSurfaceDecisions(ctx, {
12
+ anonymousIdentifier,
13
+ userJwt,
14
+ path: new URL(request.url).pathname,
15
+ url: request.url,
16
+ clientMetadata,
17
+ pageMetadata,
18
+ userAgent: request.headers.get('User-Agent') ?? undefined,
19
+ originStatus: response.status,
20
+ }, apiFetcher);
21
+ if (!result.ok) {
22
+ logger.log({
23
+ level: 'warn',
24
+ code: 'surface-decisions-api-failed',
25
+ message: 'Surface decisions API failed; continuing with the origin response.',
26
+ context: {
27
+ reason: result.reason,
28
+ status: result.status,
29
+ statusCode: result.statusCode,
30
+ },
31
+ error: result.error,
32
+ });
33
+ return [modifiedResponse, null];
34
+ }
35
+ const surfaceDecisions = result.data;
36
+ if (!anonymousIdentifier && !userJwt && surfaceDecisions?.identity?.identifier) {
37
+ const headers = new Headers(modifiedResponse.headers);
38
+ headers.append('Set-Cookie', `${config.anonymousSessionCookieName}=${surfaceDecisions.identity.identifier}; Path=/`);
39
+ modifiedResponse = new Response(modifiedResponse.body, {
40
+ status: modifiedResponse.status,
41
+ statusText: modifiedResponse.statusText,
42
+ headers,
43
+ });
44
+ }
45
+ return [modifiedResponse, surfaceDecisions];
46
+ }
47
+ const getExistingCookies = (request, originResponse, config) => {
48
+ const setCookies = originResponse.headers.getSetCookie().map((header) => parseSetCookie(header));
49
+ const originAnonymousCookie = setCookies.find((s) => s.name === config.anonymousSessionCookieName);
50
+ const originUserJwtCookie = setCookies.find((s) => s.name === config.authenticatedUserJwtCookieName);
51
+ if (originAnonymousCookie || originUserJwtCookie) {
52
+ return { anonymousIdentifier: originAnonymousCookie?.value, userJwt: originUserJwtCookie?.value };
53
+ }
54
+ const cookies = parse(request.headers.get('Cookie') || '');
55
+ const anonymousIdentifier = cookies[config.anonymousSessionCookieName];
56
+ const userJwt = cookies[config.authenticatedUserJwtCookieName];
57
+ return { anonymousIdentifier, userJwt };
58
+ };
59
+ //# sourceMappingURL=surfaceDecisions.js.map