@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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/MOSProxy.d.ts +43 -0
- package/dist/MOSProxy.js +140 -0
- package/dist/MOSProxy.js.map +1 -0
- package/dist/MOSProxyBuilder.d.ts +75 -0
- package/dist/MOSProxyBuilder.js +126 -0
- package/dist/MOSProxyBuilder.js.map +1 -0
- package/dist/adapters/ClientMetadataProvider.d.ts +12 -0
- package/dist/adapters/ClientMetadataProvider.js +2 -0
- package/dist/adapters/ClientMetadataProvider.js.map +1 -0
- package/dist/adapters/Fetcher.d.ts +10 -0
- package/dist/adapters/Fetcher.js +2 -0
- package/dist/adapters/Fetcher.js.map +1 -0
- package/dist/adapters/HtmlRewriterAdapter.d.ts +60 -0
- package/dist/adapters/HtmlRewriterAdapter.js +2 -0
- package/dist/adapters/HtmlRewriterAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/apiRequestHeaders.d.ts +4 -0
- package/dist/apiRequestHeaders.js +11 -0
- package/dist/apiRequestHeaders.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +39 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +3 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/stages/ContentElementHandler.d.ts +9 -0
- package/dist/stages/ContentElementHandler.js +37 -0
- package/dist/stages/ContentElementHandler.js.map +1 -0
- package/dist/stages/buildReplacementHandlers.d.ts +14 -0
- package/dist/stages/buildReplacementHandlers.js +116 -0
- package/dist/stages/buildReplacementHandlers.js.map +1 -0
- package/dist/stages/customEndpoint.d.ts +3 -0
- package/dist/stages/customEndpoint.js +18 -0
- package/dist/stages/customEndpoint.js.map +1 -0
- package/dist/stages/elements.d.ts +5 -0
- package/dist/stages/elements.js +42 -0
- package/dist/stages/elements.js.map +1 -0
- package/dist/stages/fetchSurfaceDecisions.d.ts +25 -0
- package/dist/stages/fetchSurfaceDecisions.js +86 -0
- package/dist/stages/fetchSurfaceDecisions.js.map +1 -0
- package/dist/stages/findMarkerPositions.d.ts +21 -0
- package/dist/stages/findMarkerPositions.js +138 -0
- package/dist/stages/findMarkerPositions.js.map +1 -0
- package/dist/stages/getTargetUrl.d.ts +1 -0
- package/dist/stages/getTargetUrl.js +9 -0
- package/dist/stages/getTargetUrl.js.map +1 -0
- package/dist/stages/isRedirectResponse.d.ts +1 -0
- package/dist/stages/isRedirectResponse.js +4 -0
- package/dist/stages/isRedirectResponse.js.map +1 -0
- package/dist/stages/linkRewriting.d.ts +2 -0
- package/dist/stages/linkRewriting.js +37 -0
- package/dist/stages/linkRewriting.js.map +1 -0
- package/dist/stages/originRequest.d.ts +3 -0
- package/dist/stages/originRequest.js +10 -0
- package/dist/stages/originRequest.js.map +1 -0
- package/dist/stages/pageMetadata.d.ts +4 -0
- package/dist/stages/pageMetadata.js +25 -0
- package/dist/stages/pageMetadata.js.map +1 -0
- package/dist/stages/shouldIgnorePath.d.ts +2 -0
- package/dist/stages/shouldIgnorePath.js +9 -0
- package/dist/stages/shouldIgnorePath.js.map +1 -0
- package/dist/stages/surfaceBehavior.d.ts +4 -0
- package/dist/stages/surfaceBehavior.js +60 -0
- package/dist/stages/surfaceBehavior.js.map +1 -0
- package/dist/stages/surfaceComponents.d.ts +4 -0
- package/dist/stages/surfaceComponents.js +71 -0
- package/dist/stages/surfaceComponents.js.map +1 -0
- package/dist/stages/surfaceDecisions.d.ts +6 -0
- package/dist/stages/surfaceDecisions.js +59 -0
- package/dist/stages/surfaceDecisions.js.map +1 -0
- package/dist/stages/transformOriginLinks.d.ts +1 -0
- package/dist/stages/transformOriginLinks.js +14 -0
- package/dist/stages/transformOriginLinks.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HtmlRewriterAdapter.js","sourceRoot":"","sources":["../../src/adapters/HtmlRewriterAdapter.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Platform-agnostic contract over lol-html-style streaming HTML rewriters\n * (Cloudflare's `HTMLRewriter`, Fastly's `HTMLRewritingStream`, Akamai's `HtmlRewritingStream`).\n *\n * An adapter creates a new rewriter session per call to `create()`; the session is a builder\n * that accepts selectors + handlers via `.on(...)` and produces a transformed `Response` via\n * `.transform(response)`.\n *\n * `capabilities` advertises which lol-html features the underlying implementation supports.\n * Consumers dispatch on these flags to enable or fall back paths (e.g., range-replacement\n * via `element.onEndTag` vs. a sentinel post-process).\n */\nexport interface HtmlRewriterAdapter {\n readonly capabilities: HtmlRewriterCapabilities\n create(): HtmlRewriterSession\n}\n\nexport interface HtmlRewriterCapabilities {\n /** Element handlers can register an end-tag callback via `element.onEndTag(cb)`. */\n onEndTag: boolean\n /** `:nth-child()` CSS pseudo-class is supported in selectors. */\n nthChild: boolean\n}\n\nexport interface HtmlRewriterSession {\n on(selector: string, handlers: ElementHandlers): HtmlRewriterSession\n transform(response: Response): Response\n}\n\nexport interface ElementHandlers {\n element?(element: RewriterElement): void | Promise<void>\n text?(text: RewriterText): void | Promise<void>\n}\n\nexport interface ContentOptions {\n html?: boolean\n}\n\n/**\n * Mirrors the Cloudflare `Element` API (a lol-html `HTMLRewriterElement`).\n *\n * Adapters may throw from `onEndTag` on runtimes that don't support it; callers should\n * check `capabilities.onEndTag` before relying on it, or catch the error.\n */\nexport interface RewriterElement {\n readonly removed: boolean\n readonly tagName: string\n getAttribute(name: string): string | null\n hasAttribute(name: string): boolean\n setAttribute(name: string, value: string): void\n removeAttribute(name: string): void\n before(content: string, options?: ContentOptions): void\n after(content: string, options?: ContentOptions): void\n prepend(content: string, options?: ContentOptions): void\n append(content: string, options?: ContentOptions): void\n replace(content: string, options?: ContentOptions): void\n remove(): void\n onEndTag?(callback: () => void | Promise<void>): void\n}\n\nexport interface RewriterText {\n readonly text: string\n readonly lastInTextNode: boolean\n readonly removed: boolean\n remove(): void\n}\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { ClientMetadataProvider } from './ClientMetadataProvider';
|
|
2
|
+
export type { Fetcher } from './Fetcher';
|
|
3
|
+
export type { ContentOptions, ElementHandlers, HtmlRewriterAdapter, HtmlRewriterCapabilities, HtmlRewriterSession, RewriterElement, RewriterText, } from './HtmlRewriterAdapter';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"","sourcesContent":["export type { ClientMetadataProvider } from './ClientMetadataProvider'\nexport type { Fetcher } from './Fetcher'\nexport type {\n ContentOptions,\n ElementHandlers,\n HtmlRewriterAdapter,\n HtmlRewriterCapabilities,\n HtmlRewriterSession,\n RewriterElement,\n RewriterText,\n} from './HtmlRewriterAdapter'\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const MOS_PROXY_VERSION_HEADER = "X-MOS-Proxy-Version";
|
|
2
|
+
export declare const MOS_PROXY_PACKAGE_VERSION = "1.0.0";
|
|
3
|
+
export declare const setMosProxyVersionHeader: (headers: Headers) => void;
|
|
4
|
+
export declare const withMosProxyVersionHeader: (headers?: HeadersInit) => Headers;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const MOS_PROXY_VERSION_HEADER = 'X-MOS-Proxy-Version';
|
|
2
|
+
export const MOS_PROXY_PACKAGE_VERSION = '1.0.0';
|
|
3
|
+
export const setMosProxyVersionHeader = (headers) => {
|
|
4
|
+
headers.set(MOS_PROXY_VERSION_HEADER, MOS_PROXY_PACKAGE_VERSION);
|
|
5
|
+
};
|
|
6
|
+
export const withMosProxyVersionHeader = (headers) => {
|
|
7
|
+
const next = new Headers(headers);
|
|
8
|
+
setMosProxyVersionHeader(next);
|
|
9
|
+
return next;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=apiRequestHeaders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiRequestHeaders.js","sourceRoot":"","sources":["../src/apiRequestHeaders.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAA;AAC7D,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAA;AAEhD,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,OAAgB,EAAQ,EAAE;IAC/D,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,CAAA;AACpE,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAqB,EAAW,EAAE;IACxE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,wBAAwB,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,IAAI,CAAA;AACf,CAAC,CAAA","sourcesContent":["export const MOS_PROXY_VERSION_HEADER = 'X-MOS-Proxy-Version'\nexport const MOS_PROXY_PACKAGE_VERSION = '1.0.0'\n\nexport const setMosProxyVersionHeader = (headers: Headers): void => {\n headers.set(MOS_PROXY_VERSION_HEADER, MOS_PROXY_PACKAGE_VERSION)\n}\n\nexport const withMosProxyVersionHeader = (headers?: HeadersInit): Headers => {\n const next = new Headers(headers)\n setMosProxyVersionHeader(next)\n return next\n}\n"]}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type MOSProxyLogger } from './logger';
|
|
2
|
+
import type { MOSConfigInput } from './types';
|
|
3
|
+
export interface MOSConfig {
|
|
4
|
+
originUrl: URL;
|
|
5
|
+
surfaceSlug: string;
|
|
6
|
+
mosHost: URL;
|
|
7
|
+
mosSecretKey: string;
|
|
8
|
+
mosEnvironment: string;
|
|
9
|
+
mosEndpointsPrefix: string;
|
|
10
|
+
anonymousSessionCookieName: string;
|
|
11
|
+
authenticatedUserJwtCookieName: string;
|
|
12
|
+
injectScriptUrl: string | undefined;
|
|
13
|
+
surfaceDecisionsIgnorePathPatterns: RegExp[];
|
|
14
|
+
originRequestHeaders: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizeMOSConfig(config: MOSConfigInput, logger?: MOSProxyLogger): MOSConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { consoleLogger } from './logger';
|
|
2
|
+
const DEFAULT_ENDPOINTS_PREFIX = '/mos-endpoints/';
|
|
3
|
+
export function normalizeMOSConfig(config, logger = consoleLogger) {
|
|
4
|
+
const ignorePathPatterns = [];
|
|
5
|
+
if (config.surfaceDecisionsIgnorePaths) {
|
|
6
|
+
for (const raw of config.surfaceDecisionsIgnorePaths.split(',')) {
|
|
7
|
+
const pattern = raw.trim();
|
|
8
|
+
if (!pattern)
|
|
9
|
+
continue;
|
|
10
|
+
try {
|
|
11
|
+
ignorePathPatterns.push(new RegExp(pattern));
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
logger.log({
|
|
15
|
+
level: 'warn',
|
|
16
|
+
code: 'invalid-ignore-path-pattern',
|
|
17
|
+
message: `Invalid surfaceDecisionsIgnorePaths regex pattern: ${pattern}`,
|
|
18
|
+
context: { pattern },
|
|
19
|
+
error,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const secretKeyParts = config.mosSecretKey.split('_');
|
|
25
|
+
return {
|
|
26
|
+
originUrl: new URL(config.originUrl),
|
|
27
|
+
surfaceSlug: config.surfaceSlug,
|
|
28
|
+
mosHost: new URL(config.mosHost),
|
|
29
|
+
mosSecretKey: config.mosSecretKey,
|
|
30
|
+
mosEnvironment: `${secretKeyParts[1] ?? ''}_${secretKeyParts[2] ?? ''}`,
|
|
31
|
+
mosEndpointsPrefix: config.mosEndpointsPrefix || DEFAULT_ENDPOINTS_PREFIX,
|
|
32
|
+
anonymousSessionCookieName: config.anonymousSessionCookieName,
|
|
33
|
+
authenticatedUserJwtCookieName: config.authenticatedUserJwtCookieName,
|
|
34
|
+
injectScriptUrl: config.injectScriptUrl,
|
|
35
|
+
surfaceDecisionsIgnorePathPatterns: ignorePathPatterns,
|
|
36
|
+
originRequestHeaders: { ...(config.originRequestHeaders ?? {}) },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAuB,MAAM,UAAU,CAAA;AAG7D,MAAM,wBAAwB,GAAG,iBAAiB,CAAA;AAgBlD,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,SAAyB,aAAa;IAC7F,MAAM,kBAAkB,GAAa,EAAE,CAAA;IACvC,IAAI,MAAM,CAAC,2BAA2B,EAAE,CAAC;QACrC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,2BAA2B,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;YAC1B,IAAI,CAAC,OAAO;gBAAE,SAAQ;YACtB,IAAI,CAAC;gBACD,kBAAkB,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,CAAC,GAAG,CAAC;oBACP,KAAK,EAAE,MAAM;oBACb,IAAI,EAAE,6BAA6B;oBACnC,OAAO,EAAE,sDAAsD,OAAO,EAAE;oBACxE,OAAO,EAAE,EAAE,OAAO,EAAE;oBACpB,KAAK;iBACR,CAAC,CAAA;YACN,CAAC;QACL,CAAC;IACL,CAAC;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,oBAAoB,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,oBAAoB,IAAI,EAAE,CAAC,EAAE;KACnE,CAAA;AACL,CAAC","sourcesContent":["import { consoleLogger, type MOSProxyLogger } from './logger'\nimport type { MOSConfigInput } from './types'\n\nconst DEFAULT_ENDPOINTS_PREFIX = '/mos-endpoints/'\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 originRequestHeaders: Record<string, string>\n}\n\nexport function normalizeMOSConfig(config: MOSConfigInput, logger: MOSProxyLogger = consoleLogger): MOSConfig {\n const ignorePathPatterns: RegExp[] = []\n if (config.surfaceDecisionsIgnorePaths) {\n for (const raw of config.surfaceDecisionsIgnorePaths.split(',')) {\n const pattern = raw.trim()\n if (!pattern) continue\n try {\n ignorePathPatterns.push(new RegExp(pattern))\n } catch (error) {\n logger.log({\n level: 'warn',\n code: 'invalid-ignore-path-pattern',\n message: `Invalid surfaceDecisionsIgnorePaths regex pattern: ${pattern}`,\n context: { pattern },\n error,\n })\n }\n }\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 originRequestHeaders: { ...(config.originRequestHeaders ?? {}) },\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MOSConfig } from './config';
|
|
2
|
+
import type { MOSProxyLogger } from './logger';
|
|
3
|
+
/**
|
|
4
|
+
* Shared environment threaded into every pipeline stage that needs cross-cutting infrastructure
|
|
5
|
+
* (normalized config, logger, and — when added — metrics/tracer/clock). Built once per request in
|
|
6
|
+
* `MOSProxy.handle`. Stages that only operate on a response and decision payload (e.g.
|
|
7
|
+
* `handleSurfaceBehavior`, `isRedirectResponse`) deliberately do not take a context.
|
|
8
|
+
*/
|
|
9
|
+
export interface PipelineContext {
|
|
10
|
+
config: MOSConfig;
|
|
11
|
+
logger: MOSProxyLogger;
|
|
12
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"","sourcesContent":["import type { MOSConfig } from './config'\nimport type { MOSProxyLogger } from './logger'\n\n/**\n * Shared environment threaded into every pipeline stage that needs cross-cutting infrastructure\n * (normalized config, logger, and — when added — metrics/tracer/clock). Built once per request in\n * `MOSProxy.handle`. Stages that only operate on a response and decision payload (e.g.\n * `handleSurfaceBehavior`, `isRedirectResponse`) deliberately do not take a context.\n */\nexport interface PipelineContext {\n config: MOSConfig\n logger: MOSProxyLogger\n}\n"]}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const statusAllowsBody: (status: number) => boolean;
|
package/dist/http.js
ADDED
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;AAEjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA","sourcesContent":["const NO_BODY_STATUSES = new Set([204, 205, 304])\n\nexport const statusAllowsBody = (status: number) => status >= 200 && !NO_BODY_STATUSES.has(status)\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ClientMetadataProvider, ContentOptions, ElementHandlers, Fetcher, HtmlRewriterAdapter, HtmlRewriterCapabilities, HtmlRewriterSession, RewriterElement, RewriterText, } from './adapters';
|
|
2
|
+
export type { MOSProxyLogCode, MOSProxyLogEvent, MOSProxyLogger, MOSProxyLogLevel } from './logger';
|
|
3
|
+
export { MOSProxy, type MOSProxyHtmlPipelineErrorContext, type MOSProxyHtmlPipelineErrorHandler, type MOSProxyHtmlPipelineStage, type MOSProxyOptions, } from './MOSProxy';
|
|
4
|
+
export { MOSProxyBuilder } from './MOSProxyBuilder';
|
|
5
|
+
export type { Feature, FeatureMeterableProperty, FeatureNumberProperty, MOSConfigInput, ModifyHttpResponse, PageMetadata, SetHttpResponse, SubSurfaceBehaviorApi, SubSurfaceMetadataApi, SurfaceBehaviorApi, SurfaceDecisionError, SurfaceDecisionResponse, WebComponentElement, WebComponentRangeReplacement, WebContentSurfaceBehavior, WebElement, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACH,QAAQ,GAKX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA","sourcesContent":["export type {\n ClientMetadataProvider,\n ContentOptions,\n ElementHandlers,\n Fetcher,\n HtmlRewriterAdapter,\n HtmlRewriterCapabilities,\n HtmlRewriterSession,\n RewriterElement,\n RewriterText,\n} from './adapters'\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
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
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' | '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
|
+
export interface MOSProxyLogEvent {
|
|
4
|
+
level: MOSProxyLogLevel;
|
|
5
|
+
code: MOSProxyLogCode;
|
|
6
|
+
message: string;
|
|
7
|
+
context?: Record<string, unknown>;
|
|
8
|
+
error?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface MOSProxyLogger {
|
|
11
|
+
log(event: MOSProxyLogEvent): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const consoleLogger: MOSProxyLogger;
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const consoleLogger = {
|
|
2
|
+
log(event) {
|
|
3
|
+
const details = [event.message, { code: event.code, ...event.context }];
|
|
4
|
+
if (event.error) {
|
|
5
|
+
details.push(event.error);
|
|
6
|
+
}
|
|
7
|
+
if (event.level === 'error') {
|
|
8
|
+
console.error(...details);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
console.warn(...details);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAkCA,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 | 'response-clone-failed'\n | 'surface-decisions-api-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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ElementHandlers, RewriterElement } from '../adapters/HtmlRewriterAdapter';
|
|
2
|
+
import type { PipelineContext } from '../context';
|
|
3
|
+
import type { WebContentSurfaceBehavior } from '../types';
|
|
4
|
+
export declare class ContentElementHandler implements ElementHandlers {
|
|
5
|
+
content: WebContentSurfaceBehavior;
|
|
6
|
+
private readonly ctx;
|
|
7
|
+
constructor(ctx: PipelineContext, content: WebContentSurfaceBehavior);
|
|
8
|
+
element(element: RewriterElement): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { renderElement } from './elements';
|
|
2
|
+
const transformPositions = ['before', 'prepend', 'append', 'after'];
|
|
3
|
+
const reverseTransformPositions = ['after', 'prepend'];
|
|
4
|
+
export class ContentElementHandler {
|
|
5
|
+
content;
|
|
6
|
+
ctx;
|
|
7
|
+
constructor(ctx, content) {
|
|
8
|
+
this.ctx = ctx;
|
|
9
|
+
this.content = content;
|
|
10
|
+
}
|
|
11
|
+
element(element) {
|
|
12
|
+
if (element.removed) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let retainElement = false;
|
|
16
|
+
for (const key of transformPositions) {
|
|
17
|
+
const list = this.content[key];
|
|
18
|
+
if (list?.length) {
|
|
19
|
+
const ordered = reverseTransformPositions.includes(key) ? [...list].reverse() : list;
|
|
20
|
+
for (const transformation of ordered) {
|
|
21
|
+
const [content, options] = renderElement(this.ctx, transformation);
|
|
22
|
+
element[key](content, options);
|
|
23
|
+
}
|
|
24
|
+
retainElement = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (this.content.remove) {
|
|
28
|
+
if (retainElement) {
|
|
29
|
+
element.replace('', { html: true });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
element.remove();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=ContentElementHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentElementHandler.js","sourceRoot":"","sources":["../../src/stages/ContentElementHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,kBAAkB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAA;AAC5E,MAAM,yBAAyB,GAAuD,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAE1G,MAAM,OAAO,qBAAqB;IAC9B,OAAO,CAA2B;IACjB,GAAG,CAAiB;IAErC,YAAY,GAAoB,EAAE,OAAkC;QAChE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IAC1B,CAAC;IAED,OAAO,CAAC,OAAwB;QAC5B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAClB,OAAM;QACV,CAAC;QAED,IAAI,aAAa,GAAG,KAAK,CAAA;QACzB,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;gBACf,MAAM,OAAO,GAAG,yBAAyB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;gBACpF,KAAK,MAAM,cAAc,IAAI,OAAO,EAAE,CAAC;oBACnC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;oBAClE,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;gBAClC,CAAC;gBACD,aAAa,GAAG,IAAI,CAAA;YACxB,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACtB,IAAI,aAAa,EAAE,CAAC;gBAChB,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YACvC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,MAAM,EAAE,CAAA;YACpB,CAAC;QACL,CAAC;IACL,CAAC;CACJ","sourcesContent":["import type { ElementHandlers, RewriterElement } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { WebContentSurfaceBehavior } from '../types'\nimport { renderElement } from './elements'\n\nconst transformPositions = ['before', 'prepend', 'append', 'after'] as const\nconst reverseTransformPositions: ReadonlyArray<(typeof transformPositions)[number]> = ['after', 'prepend']\n\nexport class ContentElementHandler implements ElementHandlers {\n content: WebContentSurfaceBehavior\n private readonly ctx: PipelineContext\n\n constructor(ctx: PipelineContext, content: WebContentSurfaceBehavior) {\n this.ctx = ctx\n this.content = content\n }\n\n element(element: RewriterElement) {\n if (element.removed) {\n return\n }\n\n let retainElement = false\n for (const key of transformPositions) {\n const list = this.content[key]\n if (list?.length) {\n const ordered = reverseTransformPositions.includes(key) ? [...list].reverse() : list\n for (const transformation of ordered) {\n const [content, options] = renderElement(this.ctx, transformation)\n element[key](content, options)\n }\n retainElement = true\n }\n }\n\n if (this.content.remove) {\n if (retainElement) {\n element.replace('', { html: true })\n } else {\n element.remove()\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ElementHandlers } from '../adapters/HtmlRewriterAdapter';
|
|
2
|
+
import type { PipelineContext } from '../context';
|
|
3
|
+
import type { SubSurfaceBehaviorApi } from '../types';
|
|
4
|
+
import type { ComponentRange } from './findMarkerPositions';
|
|
5
|
+
export type ReplacementHandler = {
|
|
6
|
+
selector: string;
|
|
7
|
+
handler: ElementHandlers;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Requires a prior pass by `findMarkerPositions` to populate `ranges`. Depends on
|
|
11
|
+
* `element.onEndTag` to detect element boundaries — on adapters without `capabilities.onEndTag`,
|
|
12
|
+
* the caller should skip this step.
|
|
13
|
+
*/
|
|
14
|
+
export declare const buildReplacementHandlers: (ctx: PipelineContext, subSurface: SubSurfaceBehaviorApi, ranges: ComponentRange[] | undefined) => ReplacementHandler[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { renderElement } from './elements';
|
|
2
|
+
/**
|
|
3
|
+
* Requires a prior pass by `findMarkerPositions` to populate `ranges`. Depends on
|
|
4
|
+
* `element.onEndTag` to detect element boundaries — on adapters without `capabilities.onEndTag`,
|
|
5
|
+
* the caller should skip this step.
|
|
6
|
+
*/
|
|
7
|
+
export const buildReplacementHandlers = (ctx, subSurface, ranges) => {
|
|
8
|
+
const { logger } = ctx;
|
|
9
|
+
const modification = subSurface.content?.replaceRange;
|
|
10
|
+
if (!modification || !ranges) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
if (modification.fromMarker && modification.toMarker && modification.fromMarker === modification.toMarker) {
|
|
14
|
+
logger.log({
|
|
15
|
+
level: 'warn',
|
|
16
|
+
code: 'replacement-markers-identical',
|
|
17
|
+
message: 'Range replacement skipped: fromMarker and toMarker are identical.',
|
|
18
|
+
context: { marker: modification.fromMarker, selector: subSurface.metadata.cssSelector },
|
|
19
|
+
});
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
let elementCounter = 0;
|
|
23
|
+
let rangeIndex = -1;
|
|
24
|
+
const states = [];
|
|
25
|
+
const getCurrentState = () => {
|
|
26
|
+
const state = states[states.length - 1];
|
|
27
|
+
if (!state) {
|
|
28
|
+
logger.log({
|
|
29
|
+
level: 'warn',
|
|
30
|
+
code: 'replacement-state-missing',
|
|
31
|
+
message: 'Range replacement handler invoked with no active replacement state; skipping.',
|
|
32
|
+
context: { selector: subSurface.metadata.cssSelector },
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
return state;
|
|
37
|
+
};
|
|
38
|
+
const onChildElement = (element) => {
|
|
39
|
+
elementCounter++;
|
|
40
|
+
if (element.removed) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const state = getCurrentState();
|
|
44
|
+
const range = ranges[rangeIndex];
|
|
45
|
+
const marker = range?.markers[elementCounter];
|
|
46
|
+
if (state?.operation === 'SCANNING') {
|
|
47
|
+
if (marker?.startMarker) {
|
|
48
|
+
for (const after of (modification.replaceWith ?? []).slice().reverse()) {
|
|
49
|
+
const [content, options] = renderElement(ctx, after);
|
|
50
|
+
element.after(content, options);
|
|
51
|
+
}
|
|
52
|
+
if (element.onEndTag) {
|
|
53
|
+
try {
|
|
54
|
+
element.onEndTag(() => {
|
|
55
|
+
state.operation = 'REMOVING';
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
state.operation = 'REMOVING';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
state.operation = 'REMOVING';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (state?.operation === 'REMOVING') {
|
|
69
|
+
if (marker?.endMarker) {
|
|
70
|
+
state.operation = 'REPLACED';
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (marker?.endTag !== elementCounter && marker?.nextEndMarker && marker?.endTag && marker.endTag >= marker.nextEndMarker) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
element.remove();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const parentHandler = {
|
|
81
|
+
element(element) {
|
|
82
|
+
rangeIndex++;
|
|
83
|
+
if (element.removed) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const state = { operation: modification.fromMarker ? 'SCANNING' : 'REMOVING' };
|
|
87
|
+
states.push(state);
|
|
88
|
+
if (state.operation === 'REMOVING') {
|
|
89
|
+
for (const prepend of (modification.replaceWith ?? []).slice().reverse()) {
|
|
90
|
+
const [content, options] = renderElement(ctx, prepend);
|
|
91
|
+
element.prepend(content, options);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (element.onEndTag) {
|
|
95
|
+
element.onEndTag(() => {
|
|
96
|
+
states.pop();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
text: (text) => {
|
|
101
|
+
const state = getCurrentState();
|
|
102
|
+
if (state?.operation === 'REMOVING') {
|
|
103
|
+
text.remove();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const selector = subSurface.metadata.cssSelector;
|
|
108
|
+
if (!selector) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
{ selector, handler: parentHandler },
|
|
113
|
+
{ selector: `${selector} *`, handler: { element: onChildElement } },
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=buildReplacementHandlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildReplacementHandlers.js","sourceRoot":"","sources":["../../src/stages/buildReplacementHandlers.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ElementHandlers, RewriterElement, RewriterText } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { SubSurfaceBehaviorApi } from '../types'\nimport { renderElement } from './elements'\nimport type { ComponentRange } from './findMarkerPositions'\n\ntype ReplacementState = {\n operation: 'SCANNING' | 'REMOVING' | 'REPLACED'\n}\n\nexport type ReplacementHandler = { selector: string; handler: ElementHandlers }\n\n/**\n * Requires a prior pass by `findMarkerPositions` to populate `ranges`. Depends on\n * `element.onEndTag` to detect element boundaries — on adapters without `capabilities.onEndTag`,\n * the caller should skip this step.\n */\nexport const buildReplacementHandlers = (\n ctx: PipelineContext,\n subSurface: SubSurfaceBehaviorApi,\n ranges: ComponentRange[] | undefined,\n): ReplacementHandler[] => {\n const { logger } = ctx\n const modification = subSurface.content?.replaceRange\n if (!modification || !ranges) {\n return []\n }\n\n if (modification.fromMarker && modification.toMarker && modification.fromMarker === modification.toMarker) {\n logger.log({\n level: 'warn',\n code: 'replacement-markers-identical',\n message: 'Range replacement skipped: fromMarker and toMarker are identical.',\n context: { marker: modification.fromMarker, selector: subSurface.metadata.cssSelector },\n })\n return []\n }\n\n let elementCounter = 0\n let rangeIndex = -1\n const states: ReplacementState[] = []\n const getCurrentState = () => {\n const state = states[states.length - 1]\n if (!state) {\n logger.log({\n level: 'warn',\n code: 'replacement-state-missing',\n message: 'Range replacement handler invoked with no active replacement state; skipping.',\n context: { selector: subSurface.metadata.cssSelector },\n })\n return\n }\n return state\n }\n\n const onChildElement = (element: RewriterElement) => {\n elementCounter++\n if (element.removed) {\n return\n }\n\n const state = getCurrentState()\n const range = ranges[rangeIndex]\n const marker = range?.markers[elementCounter]\n\n if (state?.operation === 'SCANNING') {\n if (marker?.startMarker) {\n for (const after of (modification.replaceWith ?? []).slice().reverse()) {\n const [content, options] = renderElement(ctx, after)\n element.after(content, options)\n }\n if (element.onEndTag) {\n try {\n element.onEndTag(() => {\n state.operation = 'REMOVING'\n })\n } catch {\n state.operation = 'REMOVING'\n }\n } else {\n state.operation = 'REMOVING'\n }\n }\n return\n }\n\n if (state?.operation === 'REMOVING') {\n if (marker?.endMarker) {\n state.operation = 'REPLACED'\n return\n }\n\n if (marker?.endTag !== elementCounter && marker?.nextEndMarker && marker?.endTag && marker.endTag >= marker.nextEndMarker) {\n return\n }\n\n element.remove()\n return\n }\n }\n\n const parentHandler: ElementHandlers = {\n element(element: RewriterElement) {\n rangeIndex++\n if (element.removed) {\n return\n }\n\n const state: ReplacementState = { operation: modification.fromMarker ? 'SCANNING' : 'REMOVING' }\n states.push(state)\n\n if (state.operation === 'REMOVING') {\n for (const prepend of (modification.replaceWith ?? []).slice().reverse()) {\n const [content, options] = renderElement(ctx, prepend)\n element.prepend(content, options)\n }\n }\n\n if (element.onEndTag) {\n element.onEndTag(() => {\n states.pop()\n })\n }\n },\n\n text: (text: RewriterText) => {\n const state = getCurrentState()\n if (state?.operation === 'REMOVING') {\n text.remove()\n }\n },\n }\n\n const selector = subSurface.metadata.cssSelector\n if (!selector) {\n return []\n }\n return [\n { selector, handler: parentHandler },\n { selector: `${selector} *`, handler: { element: onChildElement } },\n ]\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { setMosProxyVersionHeader } from '../apiRequestHeaders';
|
|
2
|
+
export default async function customEndpointRequest(ctx, request, apiFetcher) {
|
|
3
|
+
const { config } = ctx;
|
|
4
|
+
const requestUrl = new URL(request.url);
|
|
5
|
+
const prefix = config.mosEndpointsPrefix;
|
|
6
|
+
if (!requestUrl.pathname.startsWith(prefix)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const target = new URL(request.url);
|
|
10
|
+
target.protocol = config.mosHost.protocol;
|
|
11
|
+
target.host = config.mosHost.host;
|
|
12
|
+
target.port = config.mosHost.port;
|
|
13
|
+
target.pathname = `/api/v1/envs/${config.mosEnvironment}/endpoints/${requestUrl.pathname.slice(prefix.length).replace(/^\//, '')}`;
|
|
14
|
+
const apiRequest = new Request(target, request);
|
|
15
|
+
setMosProxyVersionHeader(apiRequest.headers);
|
|
16
|
+
return apiFetcher(apiRequest);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=customEndpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customEndpoint.js","sourceRoot":"","sources":["../../src/stages/customEndpoint.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAG/D,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAoB,EAAE,OAAgB,EAAE,UAAmB;IAC3G,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAA;IACtB,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAA;IAExC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAA;IACf,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAA;IACzC,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAA;IACjC,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAA;IACjC,MAAM,CAAC,QAAQ,GAAG,gBAAgB,MAAM,CAAC,cAAc,cAAc,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAA;IAElI,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/C,wBAAwB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IAC5C,OAAO,UAAU,CAAC,UAAU,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import type { Fetcher } from '../adapters/Fetcher'\nimport { setMosProxyVersionHeader } from '../apiRequestHeaders'\nimport type { PipelineContext } from '../context'\n\nexport default async function customEndpointRequest(ctx: PipelineContext, request: Request, apiFetcher: Fetcher): Promise<Response | null> {\n const { config } = ctx\n const requestUrl = new URL(request.url)\n const prefix = config.mosEndpointsPrefix\n\n if (!requestUrl.pathname.startsWith(prefix)) {\n return null\n }\n\n const target = new URL(request.url)\n target.protocol = config.mosHost.protocol\n target.host = config.mosHost.host\n target.port = config.mosHost.port\n target.pathname = `/api/v1/envs/${config.mosEnvironment}/endpoints/${requestUrl.pathname.slice(prefix.length).replace(/^\\//, '')}`\n\n const apiRequest = new Request(target, request)\n setMosProxyVersionHeader(apiRequest.headers)\n return apiFetcher(apiRequest)\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContentOptions } from '../adapters/HtmlRewriterAdapter';
|
|
2
|
+
import type { PipelineContext } from '../context';
|
|
3
|
+
import type { WebComponentElement, WebElement } from '../types';
|
|
4
|
+
export declare const renderElement: (ctx: PipelineContext, element: WebElement) => [string, ContentOptions];
|
|
5
|
+
export declare const renderComponentElement: (component: WebComponentElement) => string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const renderElement = (ctx, element) => {
|
|
2
|
+
try {
|
|
3
|
+
const mapped = {
|
|
4
|
+
...element,
|
|
5
|
+
type: element.type?.toLowerCase(),
|
|
6
|
+
};
|
|
7
|
+
if (mapped.type === 'html') {
|
|
8
|
+
return [mapped.content, { html: true }];
|
|
9
|
+
}
|
|
10
|
+
if (mapped.type === 'text') {
|
|
11
|
+
return [mapped.content, { html: false }];
|
|
12
|
+
}
|
|
13
|
+
if (mapped.type === 'element') {
|
|
14
|
+
return [renderComponentElement(mapped), { html: true }];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
ctx.logger.log({
|
|
19
|
+
level: 'error',
|
|
20
|
+
code: 'element-render-failed',
|
|
21
|
+
message: 'Failed to render element; substituting empty content.',
|
|
22
|
+
context: { type: element.type },
|
|
23
|
+
error,
|
|
24
|
+
});
|
|
25
|
+
return ['', { html: false }];
|
|
26
|
+
}
|
|
27
|
+
ctx.logger.log({
|
|
28
|
+
level: 'warn',
|
|
29
|
+
code: 'element-type-unsupported',
|
|
30
|
+
message: 'Unsupported element type; substituting empty content.',
|
|
31
|
+
context: { type: element.type },
|
|
32
|
+
});
|
|
33
|
+
return ['', { html: false }];
|
|
34
|
+
};
|
|
35
|
+
export const renderComponentElement = (component) => {
|
|
36
|
+
const [schemaSource, versionedSchemaId] = component.schema.split(':');
|
|
37
|
+
const [schemaId, schemaVersion] = versionedSchemaId?.split('@') ?? [];
|
|
38
|
+
const webComponentTag = `${schemaSource}-${schemaId}`;
|
|
39
|
+
const escapedPropsAttribute = JSON.stringify(component.props).replace(/"/g, '"');
|
|
40
|
+
return `<${webComponentTag} version="${schemaVersion ?? ''}" props="${escapedPropsAttribute}"></${webComponentTag}>`;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=elements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elements.js","sourceRoot":"","sources":["../../src/stages/elements.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAoB,EAAE,OAAmB,EAA4B,EAAE;IACjG,IAAI,CAAC;QACD,MAAM,MAAM,GAAG;YACX,GAAG,OAAO;YACV,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE;SACtB,CAAA;QAEf,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3C,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC5C,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3D,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YACX,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,uDAAuD;YAChE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;YAC/B,KAAK;SACR,CAAC,CAAA;QACF,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAChC,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;QACX,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,uDAAuD;QAChE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;KAClC,CAAC,CAAA;IACF,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AAChC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,SAA8B,EAAU,EAAE;IAC7E,MAAM,CAAC,YAAY,EAAE,iBAAiB,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACrE,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,GAAG,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;IACrE,MAAM,eAAe,GAAG,GAAG,YAAY,IAAI,QAAQ,EAAE,CAAA;IACrD,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IAErF,OAAO,IAAI,eAAe,aAAa,aAAa,IAAI,EAAE,YAAY,qBAAqB,OAAO,eAAe,GAAG,CAAA;AACxH,CAAC,CAAA","sourcesContent":["import type { ContentOptions } from '../adapters/HtmlRewriterAdapter'\nimport type { PipelineContext } from '../context'\nimport type { WebComponentElement, WebElement } from '../types'\n\nexport const renderElement = (ctx: PipelineContext, element: WebElement): [string, ContentOptions] => {\n try {\n const mapped = {\n ...element,\n type: element.type?.toLowerCase(),\n } as WebElement\n\n if (mapped.type === 'html') {\n return [mapped.content, { html: true }]\n }\n\n if (mapped.type === 'text') {\n return [mapped.content, { html: false }]\n }\n\n if (mapped.type === 'element') {\n return [renderComponentElement(mapped), { html: true }]\n }\n } catch (error) {\n ctx.logger.log({\n level: 'error',\n code: 'element-render-failed',\n message: 'Failed to render element; substituting empty content.',\n context: { type: element.type },\n error,\n })\n return ['', { html: false }]\n }\n\n ctx.logger.log({\n level: 'warn',\n code: 'element-type-unsupported',\n message: 'Unsupported element type; substituting empty content.',\n context: { type: element.type },\n })\n return ['', { html: false }]\n}\n\nexport const renderComponentElement = (component: WebComponentElement): string => {\n const [schemaSource, versionedSchemaId] = component.schema.split(':')\n const [schemaId, schemaVersion] = versionedSchemaId?.split('@') ?? []\n const webComponentTag = `${schemaSource}-${schemaId}`\n const escapedPropsAttribute = JSON.stringify(component.props).replace(/\"/g, '"')\n\n return `<${webComponentTag} version=\"${schemaVersion ?? ''}\" props=\"${escapedPropsAttribute}\"></${webComponentTag}>`\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Fetcher } from '../adapters/Fetcher';
|
|
2
|
+
import type { PipelineContext } from '../context';
|
|
3
|
+
import type { PageMetadata, SurfaceDecisionResponse } from '../types';
|
|
4
|
+
export type FetchSurfaceDecisionsArgs = {
|
|
5
|
+
anonymousIdentifier?: string | undefined;
|
|
6
|
+
userJwt?: string | undefined;
|
|
7
|
+
path: string;
|
|
8
|
+
url: string;
|
|
9
|
+
clientMetadata: Record<string, unknown>;
|
|
10
|
+
pageMetadata?: PageMetadata;
|
|
11
|
+
userAgent?: string | undefined;
|
|
12
|
+
originStatus: number;
|
|
13
|
+
};
|
|
14
|
+
export type FetchSurfaceDecisionsFailureReason = 'request-failed' | 'invalid-json' | 'api-error' | 'http-error' | 'invalid-response';
|
|
15
|
+
export type FetchSurfaceDecisionsResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
data: SurfaceDecisionResponse;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: FetchSurfaceDecisionsFailureReason;
|
|
21
|
+
error: unknown;
|
|
22
|
+
status?: number;
|
|
23
|
+
statusCode?: number;
|
|
24
|
+
};
|
|
25
|
+
export default function fetchSurfaceDecisions(ctx: PipelineContext, { anonymousIdentifier, userJwt, path, url, clientMetadata, pageMetadata, userAgent, originStatus }: FetchSurfaceDecisionsArgs, apiFetcher: Fetcher): Promise<FetchSurfaceDecisionsResult>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { withMosProxyVersionHeader } from '../apiRequestHeaders';
|
|
2
|
+
export default async function fetchSurfaceDecisions(ctx, { anonymousIdentifier, userJwt, path, url, clientMetadata, pageMetadata, userAgent, originStatus }, apiFetcher) {
|
|
3
|
+
const { config } = ctx;
|
|
4
|
+
const body = JSON.stringify({
|
|
5
|
+
surfaceSlug: config.surfaceSlug,
|
|
6
|
+
identity: buildIdentity({ anonymousIdentifier, userJwt }),
|
|
7
|
+
resource: {
|
|
8
|
+
id: path,
|
|
9
|
+
meta: pageMetadata,
|
|
10
|
+
},
|
|
11
|
+
http: {
|
|
12
|
+
url,
|
|
13
|
+
userAgent,
|
|
14
|
+
proxyOrigin: {
|
|
15
|
+
status: originStatus,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
// Legacy field name kept to avoid breaking the API schema; opaque to the proxy core.
|
|
19
|
+
cloudflare: clientMetadata,
|
|
20
|
+
});
|
|
21
|
+
const request = new Request(new URL('/api/v1/surface-decisions', config.mosHost), {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body,
|
|
24
|
+
headers: withMosProxyVersionHeader({
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
Authorization: `Bearer ${config.mosSecretKey}`,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
let response;
|
|
30
|
+
try {
|
|
31
|
+
response = await apiFetcher(request);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { ok: false, reason: 'request-failed', error };
|
|
35
|
+
}
|
|
36
|
+
let data;
|
|
37
|
+
try {
|
|
38
|
+
data = await response.json();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return { ok: false, reason: 'invalid-json', error, status: response.status };
|
|
42
|
+
}
|
|
43
|
+
if (isSurfaceDecisionError(data)) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: 'api-error',
|
|
47
|
+
error: new Error(data.message),
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusCode: data.statusCode,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: 'http-error',
|
|
56
|
+
error: new Error(`Surface decisions API returned HTTP ${response.status}`),
|
|
57
|
+
status: response.status,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!isSurfaceDecisionResponse(data)) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: 'invalid-response',
|
|
64
|
+
error: new Error('Surface decisions API returned an invalid response shape'),
|
|
65
|
+
status: response.status,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, data };
|
|
69
|
+
}
|
|
70
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
71
|
+
const buildIdentity = ({ anonymousIdentifier, userJwt }) => {
|
|
72
|
+
if (!anonymousIdentifier && !userJwt) {
|
|
73
|
+
return { createAnonymousIdentifier: true };
|
|
74
|
+
}
|
|
75
|
+
return userJwt ? { userJwt } : { anonymousIdentifier };
|
|
76
|
+
};
|
|
77
|
+
const isSurfaceDecisionError = (value) => isRecord(value) && value.status === 'error' && typeof value.message === 'string' && typeof value.statusCode === 'number';
|
|
78
|
+
const isSurfaceDecisionResponse = (value) => isRecord(value) &&
|
|
79
|
+
value.status === 'success' &&
|
|
80
|
+
isRecord(value.identity) &&
|
|
81
|
+
isRecord(value.features) &&
|
|
82
|
+
isRecord(value.customer) &&
|
|
83
|
+
isRecord(value.surfaceBehavior) &&
|
|
84
|
+
typeof value.componentsSkipped === 'boolean' &&
|
|
85
|
+
isRecord(value.componentBehaviors);
|
|
86
|
+
//# sourceMappingURL=fetchSurfaceDecisions.js.map
|