@sparkletree/core 0.1.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 +105 -0
- package/dist/analytics.d.ts +75 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +170 -0
- package/dist/analytics.js.map +1 -0
- package/dist/audio.d.ts +85 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +465 -0
- package/dist/audio.js.map +1 -0
- package/dist/client.d.ts +79 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +251 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +104 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/fragments.d.ts +108 -0
- package/dist/fragments.d.ts.map +1 -0
- package/dist/fragments.js +223 -0
- package/dist/fragments.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +70 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +97 -0
- package/dist/protocol.js.map +1 -0
- package/dist/publishableKey.d.ts +38 -0
- package/dist/publishableKey.d.ts.map +1 -0
- package/dist/publishableKey.js +69 -0
- package/dist/publishableKey.js.map +1 -0
- package/dist/safeUrl.d.ts +56 -0
- package/dist/safeUrl.d.ts.map +1 -0
- package/dist/safeUrl.js +117 -0
- package/dist/safeUrl.js.map +1 -0
- package/dist/sse.d.ts +51 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +137 -0
- package/dist/sse.js.map +1 -0
- package/dist/state.d.ts +321 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +594 -0
- package/dist/state.js.map +1 -0
- package/dist/text.d.ts +10 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +29 -0
- package/dist/text.js.map +1 -0
- package/dist/theme.d.ts +52 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +88 -0
- package/dist/theme.js.map +1 -0
- package/dist/trust.d.ts +52 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +95 -0
- package/dist/trust.js.map +1 -0
- package/dist/variant.d.ts +117 -0
- package/dist/variant.d.ts.map +1 -0
- package/dist/variant.js +167 -0
- package/dist/variant.js.map +1 -0
- package/dist/wire.d.ts +564 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +133 -0
- package/dist/wire.js.map +1 -0
- package/package.json +55 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client: fetch `/content` for first paint, then open the stream and feed
|
|
3
|
+
* every event into the choreographer.
|
|
4
|
+
*
|
|
5
|
+
* The event dispatch below is the ONLY place in the SDK that knows event
|
|
6
|
+
* names, and every name it knows comes from the generated contract. A server
|
|
7
|
+
* that starts emitting something newer than this build is not an error — it is
|
|
8
|
+
* an older client, which the protocol explicitly supports — so unknown events
|
|
9
|
+
* are ignored rather than warned about.
|
|
10
|
+
*/
|
|
11
|
+
import { CONTENT_SOURCES, COPY_MODES, EVENT_PROTOCOL_UNSUPPORTED, PROTOCOL_CURRENT, isWireEvent, } from "./wire.js";
|
|
12
|
+
import { isSecureApiBase, safeActionUrl } from "./safeUrl.js";
|
|
13
|
+
import { buildContentUrl, buildStreamUrl, negotiatedProtocol, } from "./protocol.js";
|
|
14
|
+
import { readSseStream } from "./sse.js";
|
|
15
|
+
import { CopyChoreographer, } from "./state.js";
|
|
16
|
+
/** Which copy field an event name addresses, if any. */
|
|
17
|
+
function copyFieldOf(event) {
|
|
18
|
+
const [field] = event.split("-");
|
|
19
|
+
if (field === "greeting" || field === "headline" || field === "body" || field === "cta") {
|
|
20
|
+
return field;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function parseData(raw) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function str(value, fallback = "") {
|
|
34
|
+
return typeof value === "string" ? value : fallback;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fetch the first-paint content.
|
|
38
|
+
*
|
|
39
|
+
* Content-first, rewrite-once: this is what the SDK paints BEFORE any stream
|
|
40
|
+
* exists, so there is never a skeleton on screen. If it fails, the consumer's
|
|
41
|
+
* `fallback` prop is what stands in — which is why that prop is required
|
|
42
|
+
* rather than optional.
|
|
43
|
+
*/
|
|
44
|
+
export async function fetchContent(target, options = {}) {
|
|
45
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
46
|
+
// An apiBase that is neither https nor loopback http is a misconfiguration,
|
|
47
|
+
// not a request worth making: the content path degrades to the consumer's
|
|
48
|
+
// fallback, which is real copy by contract.
|
|
49
|
+
if (!isSecureApiBase(target.apiBase))
|
|
50
|
+
return null;
|
|
51
|
+
try {
|
|
52
|
+
// No custom headers: the protocol version already rides the URL
|
|
53
|
+
// (buildContentUrl), and a bare GET is a CORS "simple request" — no
|
|
54
|
+
// preflight to allowlist, no OPTIONS round-trip before first paint.
|
|
55
|
+
const response = await doFetch(buildContentUrl(target), {
|
|
56
|
+
signal: options.signal,
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok)
|
|
59
|
+
return null;
|
|
60
|
+
// The body is untrusted: a proxy or a misconfigured edge can answer 200
|
|
61
|
+
// with HTML or a bare string, and casting that to ContentSnapshot would
|
|
62
|
+
// paint garbage instead of falling back.
|
|
63
|
+
const body = await response.json();
|
|
64
|
+
return body && typeof body === "object" ? body : null;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// A failed first-paint fetch is not an error state: the consumer's
|
|
68
|
+
// fallback is real copy and the stream may still arrive.
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Open an island stream and choreograph it.
|
|
74
|
+
*
|
|
75
|
+
* Returns immediately with a handle; `finished` resolves with the terminal
|
|
76
|
+
* state. It never rejects — a broken stream leaves whatever was painted on
|
|
77
|
+
* screen and reports `phase: "failed"`, because throwing from inside a
|
|
78
|
+
* customer's render tree is a worse outcome than a creative that stopped
|
|
79
|
+
* adapting.
|
|
80
|
+
*/
|
|
81
|
+
export function openStream(options) {
|
|
82
|
+
const { target, onState, onProtocolUnsupported } = options;
|
|
83
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
84
|
+
const choreographer = new CopyChoreographer(options);
|
|
85
|
+
if (onState)
|
|
86
|
+
choreographer.subscribe(onState);
|
|
87
|
+
// Refuse to open a stream to an apiBase that is neither https nor loopback
|
|
88
|
+
// http. Not a throw — a throw inside a customer's render tree is the one
|
|
89
|
+
// outcome this package exists to avoid — just the failed state the same
|
|
90
|
+
// consumer already handles for a dead transport.
|
|
91
|
+
if (!isSecureApiBase(target.apiBase)) {
|
|
92
|
+
choreographer.failed("apiBase must be https (http is allowed for localhost only)");
|
|
93
|
+
return {
|
|
94
|
+
choreographer,
|
|
95
|
+
finished: Promise.resolve(choreographer.getState()),
|
|
96
|
+
close: () => choreographer.dispose(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const abort = () => controller.abort();
|
|
101
|
+
// A signal that is ALREADY aborted never fires "abort" again, so wiring the
|
|
102
|
+
// listener alone would leak the stream open. Abort our controller up front.
|
|
103
|
+
if (options.signal?.aborted)
|
|
104
|
+
controller.abort();
|
|
105
|
+
else
|
|
106
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
107
|
+
const finished = (async () => {
|
|
108
|
+
try {
|
|
109
|
+
// `Accept` is a CORS-safelisted header and the protocol version rides
|
|
110
|
+
// the URL, so this stays a simple request: no preflight stands between
|
|
111
|
+
// a page and its stream. (The st-protocol REQUEST header is for
|
|
112
|
+
// consumer-supplied transports; ours never needs it.)
|
|
113
|
+
const response = await doFetch(buildStreamUrl(target), {
|
|
114
|
+
signal: controller.signal,
|
|
115
|
+
headers: { Accept: "text/event-stream" },
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
choreographer.failed(`stream request failed (${response.status})`);
|
|
119
|
+
return choreographer.getState();
|
|
120
|
+
}
|
|
121
|
+
const served = negotiatedProtocol(response);
|
|
122
|
+
choreographer.streamOpened();
|
|
123
|
+
await readSseStream(response, {
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
onMessage: (message) => {
|
|
126
|
+
const data = parseData(message.data);
|
|
127
|
+
if (message.event === EVENT_PROTOCOL_UNSUPPORTED) {
|
|
128
|
+
const supported = Array.isArray(data.supported)
|
|
129
|
+
? data.supported.map(Number).filter(Number.isFinite)
|
|
130
|
+
: [];
|
|
131
|
+
onProtocolUnsupported?.({
|
|
132
|
+
requested: typeof data.requested === "number" ? data.requested : PROTOCOL_CURRENT,
|
|
133
|
+
supported,
|
|
134
|
+
});
|
|
135
|
+
// Not `failed`: nothing went wrong, this deployment is simply
|
|
136
|
+
// older than this build. First paint stands, and we do not retry
|
|
137
|
+
// — the same version would be refused again.
|
|
138
|
+
choreographer.done({ contentSource: "static", protocolUnsupported: true });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Unknown events are ignored by design: a newer server may emit
|
|
142
|
+
// things this build predates, and the contract guarantees they are
|
|
143
|
+
// additive.
|
|
144
|
+
if (!isWireEvent(message.event))
|
|
145
|
+
return;
|
|
146
|
+
dispatch(choreographer, message.event, data, served);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// A stream that ends without `done` ended badly — but quietly, and
|
|
150
|
+
// whatever is painted stays painted. Two exceptions: a stream that
|
|
151
|
+
// already reported WHY it failed keeps that reason ("Campaign not
|
|
152
|
+
// found" tells someone what to fix; overwriting it here would throw
|
|
153
|
+
// away the only useful thing the server said), and a stream ended by
|
|
154
|
+
// our OWN controller — a deliberate `close()` — is not a failure at
|
|
155
|
+
// all, just a component unmounting.
|
|
156
|
+
const phase = choreographer.getState().phase;
|
|
157
|
+
if (!controller.signal.aborted && phase !== "settled" && phase !== "failed") {
|
|
158
|
+
choreographer.failed("stream ended without a done event");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (!controller.signal.aborted) {
|
|
163
|
+
choreographer.failed(error instanceof Error ? error.message : String(error));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
options.signal?.removeEventListener("abort", abort);
|
|
168
|
+
}
|
|
169
|
+
return choreographer.getState();
|
|
170
|
+
})();
|
|
171
|
+
return {
|
|
172
|
+
choreographer,
|
|
173
|
+
finished,
|
|
174
|
+
close: () => {
|
|
175
|
+
abort();
|
|
176
|
+
choreographer.dispose();
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function dispatch(choreographer, event, data, _protocol) {
|
|
181
|
+
switch (event) {
|
|
182
|
+
case "theme-ready":
|
|
183
|
+
choreographer.themeReady(data.colors ?? {}, data.sources ?? {});
|
|
184
|
+
return;
|
|
185
|
+
case "experience-ready":
|
|
186
|
+
choreographer.layoutReady(str(data.layout), data.experienceManifest);
|
|
187
|
+
return;
|
|
188
|
+
case "layout-ready":
|
|
189
|
+
choreographer.layoutReady(str(data.layout));
|
|
190
|
+
return;
|
|
191
|
+
case "background-ready":
|
|
192
|
+
choreographer.backgroundReady(str(data.bgImage));
|
|
193
|
+
return;
|
|
194
|
+
case "sequence-ready":
|
|
195
|
+
choreographer.sequenceReady(Array.isArray(data.frames) ? data.frames : [], typeof data.heroIndex === "number" ? data.heroIndex : 0, typeof data.fps === "number" ? data.fps : 8);
|
|
196
|
+
return;
|
|
197
|
+
case "video-ready":
|
|
198
|
+
choreographer.videoReady(str(data.videoUrl));
|
|
199
|
+
return;
|
|
200
|
+
case "cta-style-ready":
|
|
201
|
+
choreographer.ctaStyleReady(data.style);
|
|
202
|
+
return;
|
|
203
|
+
case "cta-ready":
|
|
204
|
+
// The action is rendered as an <a href> in the customer's origin, so it
|
|
205
|
+
// is vetted before it ever reaches state: a server-supplied
|
|
206
|
+
// `javascript:` URL is click-XSS, and `undefined` degrades to a plain
|
|
207
|
+
// button rather than a poisoned link.
|
|
208
|
+
choreographer.ctaReady(str(data.text), data.style, safeActionUrl(str(data.action) || undefined));
|
|
209
|
+
return;
|
|
210
|
+
case "done":
|
|
211
|
+
choreographer.done(data.meta ?? {});
|
|
212
|
+
return;
|
|
213
|
+
case "error":
|
|
214
|
+
choreographer.failed(str(data.error, "stream error"));
|
|
215
|
+
return;
|
|
216
|
+
default:
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
const field = copyFieldOf(event);
|
|
220
|
+
if (!field)
|
|
221
|
+
return;
|
|
222
|
+
if (event.endsWith("-start")) {
|
|
223
|
+
// An unlabelled start is an older edge revision mid-rollout: null mode
|
|
224
|
+
// takes the replay path, which is byte-identical to pre-negotiation
|
|
225
|
+
// behaviour. `static` is the honest default for undeclared provenance —
|
|
226
|
+
// silence must never be read as evidence of generation.
|
|
227
|
+
//
|
|
228
|
+
// Both enums are validated against the contract vocabularies rather than
|
|
229
|
+
// cast: the wire is untrusted, and an unknown string landing in the
|
|
230
|
+
// public typed state would drive trust chrome off a value the contract
|
|
231
|
+
// never defined. Unknown mode → null (unlabelled path); unknown source →
|
|
232
|
+
// "static" (the no-claim default).
|
|
233
|
+
const mode = typeof data.mode === "string" && COPY_MODES.includes(data.mode)
|
|
234
|
+
? data.mode
|
|
235
|
+
: null;
|
|
236
|
+
const source = typeof data.contentSource === "string" &&
|
|
237
|
+
CONTENT_SOURCES.includes(data.contentSource)
|
|
238
|
+
? data.contentSource
|
|
239
|
+
: "static";
|
|
240
|
+
choreographer.copyStart(field, mode, source);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (event.endsWith("-chunk")) {
|
|
244
|
+
choreographer.copyChunk(field, str(data.text));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (event.endsWith("-done")) {
|
|
248
|
+
choreographer.copyDone(field, str(data.text));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared-context canonicalization — the TS arm of `ctxhash`.
|
|
3
|
+
*
|
|
4
|
+
* One algorithm, two implementations: this file and
|
|
5
|
+
* `mosaic-edge-serve/internal/ctxhash`. The shared vectors in
|
|
6
|
+
* `sdk/parity/fixtures/ctxhash/vectors.json` are the contract; a change here
|
|
7
|
+
* that is not mirrored in Go fails both parity suites.
|
|
8
|
+
*
|
|
9
|
+
* The vocabulary is CLOSED on purpose: it is simultaneously the cardinality
|
|
10
|
+
* fence (the cache-key space is the product of small enums) and the injection
|
|
11
|
+
* defense (attacker-reachable strings never key a shared cache entry).
|
|
12
|
+
* Unknown keys are dropped at both ends, by contract. An unknown value for a
|
|
13
|
+
* known enum is dropped from the hash — never a new bucket, never a
|
|
14
|
+
* generation.
|
|
15
|
+
*
|
|
16
|
+
* The client's hash is a cache hint. The server re-canonicalizes from raw
|
|
17
|
+
* dims and its own hash is the identity for generation, billing, and
|
|
18
|
+
* admission — nobody gets to pre-poison a bucket.
|
|
19
|
+
*/
|
|
20
|
+
/** Platform intent vocabulary. */
|
|
21
|
+
export declare const CONTEXT_INTENTS: readonly ["signup", "signin", "retain", "upsell", "reassure", "explain", "browse", "convert"];
|
|
22
|
+
/** Dayparts — the timecontext classification, lowercased for the hash. */
|
|
23
|
+
export declare const CONTEXT_DAYPARTS: readonly ["earlymorning", "morning", "latemorning", "midday", "afternoon", "evening", "lateevening", "latenight"];
|
|
24
|
+
export declare const CONTEXT_DAY_TYPES: readonly ["weekday", "weekend"];
|
|
25
|
+
export declare const CONTEXT_WEATHER: readonly ["clear", "cloudy", "rainy", "snowy"];
|
|
26
|
+
export declare const CONTEXT_TEMP_BANDS: readonly ["cold", "mild", "warm", "hot"];
|
|
27
|
+
export declare const CONTEXT_DEVICES: readonly ["mobile", "tablet", "desktop", "signage"];
|
|
28
|
+
/** Existing platform surface-type vocabulary (mosaic/lib/surface-types.ts). */
|
|
29
|
+
export declare const CONTEXT_SURFACE_TYPES: readonly ["vending", "retail", "transit", "stadium", "mall", "outdoor_billboard", "indoor_display", "point_of_sale", "lobby", "speaker", "kiosk", "ar", "web", "other", "grocery", "convenience", "restaurant", "cafe", "bar"];
|
|
30
|
+
export declare const MAX_CUSTOM_DIMS = 4;
|
|
31
|
+
export declare const MAX_NOTES_LENGTH = 280;
|
|
32
|
+
/** Canonical dim map: short keys, normalized lowercase values. */
|
|
33
|
+
export type CanonicalDims = Record<string, string>;
|
|
34
|
+
/** Developer-declared placement, the public shape (long field names). */
|
|
35
|
+
export interface DeclaredContext {
|
|
36
|
+
/** Declared page class, `[a-z0-9-]{1,32}`. Never a URL or path. */
|
|
37
|
+
page?: string;
|
|
38
|
+
/** Platform intent enum. */
|
|
39
|
+
intent?: string;
|
|
40
|
+
/** Placement descriptor, never a user identifier. */
|
|
41
|
+
audience?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Free text, ≤280 chars, describing the placement — never the visitor.
|
|
44
|
+
* Hashed in full: a distinct notes value is a distinct context.
|
|
45
|
+
*/
|
|
46
|
+
notes?: string;
|
|
47
|
+
/** Org-declared custom dims: `{ tier: "pro" }` becomes dim `x.tier`. */
|
|
48
|
+
custom?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Normalizes the free-text `notes` dim. Returns undefined when the field is
|
|
52
|
+
* empty after scrubbing or matches a rejected shape (emails, phone numbers,
|
|
53
|
+
* long token-like runs) — the field is dropped, never partially kept.
|
|
54
|
+
*/
|
|
55
|
+
export declare function normalizeNotes(raw: string): string | undefined;
|
|
56
|
+
export interface NormalizeResult {
|
|
57
|
+
dims: CanonicalDims;
|
|
58
|
+
/** Keys (or `key=value` pairs) removed during normalization, for dev warnings. */
|
|
59
|
+
dropped: string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Normalizes a raw dim map into canonical dims: unknown keys dropped, enum
|
|
63
|
+
* values validated, tokens pattern-checked, custom dims capped at
|
|
64
|
+
* MAX_CUSTOM_DIMS (bytewise-first wins, deterministically), notes scrubbed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function normalizeDims(raw: Record<string, unknown>): NormalizeResult;
|
|
67
|
+
/** Maps the public DeclaredContext shape onto short-key dims. */
|
|
68
|
+
export declare function declaredToDims(declared: DeclaredContext): Record<string, unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* Canonical form: drop absent/unknown keys → lowercase → sort keys bytewise →
|
|
71
|
+
* join "k:v|k:v". Already-normalized dims only; run normalizeDims first.
|
|
72
|
+
*/
|
|
73
|
+
export declare function canonicalContextString(dims: CanonicalDims): string;
|
|
74
|
+
/** sha256(canonical) → hex, truncated to 16 chars (64 bits). */
|
|
75
|
+
export declare function computeContextHash(dims: CanonicalDims): Promise<string>;
|
|
76
|
+
/** Length-budget floors per kind — ceilings on the ceiling (spec §2.6). They
|
|
77
|
+
* cap the case where a developer's fallback is a single word. */
|
|
78
|
+
export declare const FRAGMENT_KIND_FLOORS: Record<string, number>;
|
|
79
|
+
/**
|
|
80
|
+
* ceil(max(len(fallback) × 1.3, floor(kind))). The developer's own fallback
|
|
81
|
+
* defines the layout envelope, which is what makes CLS zero by construction.
|
|
82
|
+
* Server twin: mosaic/services/fragments/fragmentService.ts computeFieldBudget
|
|
83
|
+
* — the server recomputes and never trusts the client's number.
|
|
84
|
+
*/
|
|
85
|
+
export declare function computeFieldBudget(fallback: string, kind: string): number;
|
|
86
|
+
/**
|
|
87
|
+
* Schema hash: keys + kinds + budgets, never the fallback prose (prose in the
|
|
88
|
+
* GET URL would make the CDN object private and delete the cost story). MUST
|
|
89
|
+
* stay byte-identical to mosaic/services/fragments/fragmentService.ts
|
|
90
|
+
* computeSchemaHash; the shared vectors in
|
|
91
|
+
* sdk/parity/fixtures/ctxhash/schema-vectors.json are the contract.
|
|
92
|
+
*/
|
|
93
|
+
export declare function computeSchemaHash(fields: readonly {
|
|
94
|
+
key: string;
|
|
95
|
+
kind: string;
|
|
96
|
+
budget: number;
|
|
97
|
+
}[]): Promise<string>;
|
|
98
|
+
/** Convenience: declared shape → normalized dims → hash. */
|
|
99
|
+
export declare function hashDeclaredContext(declared: DeclaredContext, environment?: Record<string, unknown>): Promise<{
|
|
100
|
+
dims: CanonicalDims;
|
|
101
|
+
hash: string;
|
|
102
|
+
dropped: string[];
|
|
103
|
+
}>;
|
|
104
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,kCAAkC;AAClC,eAAO,MAAM,eAAe,+FASlB,CAAC;AAEX,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,mHASnB,CAAC;AAEX,eAAO,MAAM,iBAAiB,iCAAkC,CAAC;AACjE,eAAO,MAAM,eAAe,gDAAiD,CAAC;AAC9E,eAAO,MAAM,kBAAkB,0CAA2C,CAAC;AAC3E,eAAO,MAAM,eAAe,qDAAsD,CAAC;AAEnF,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,gOAoBxB,CAAC;AAUX,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnD,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAmBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAe9D;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,kFAAkF;IAClF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAiE3E;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUjF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAOlE;AAaD,gEAAgE;AAChE,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7E;AAID;iEACiE;AACjE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMvD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GAC/D,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,4DAA4D;AAC5D,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,eAAe,EACzB,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACxC,OAAO,CAAC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAGnE"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared-context canonicalization — the TS arm of `ctxhash`.
|
|
3
|
+
*
|
|
4
|
+
* One algorithm, two implementations: this file and
|
|
5
|
+
* `mosaic-edge-serve/internal/ctxhash`. The shared vectors in
|
|
6
|
+
* `sdk/parity/fixtures/ctxhash/vectors.json` are the contract; a change here
|
|
7
|
+
* that is not mirrored in Go fails both parity suites.
|
|
8
|
+
*
|
|
9
|
+
* The vocabulary is CLOSED on purpose: it is simultaneously the cardinality
|
|
10
|
+
* fence (the cache-key space is the product of small enums) and the injection
|
|
11
|
+
* defense (attacker-reachable strings never key a shared cache entry).
|
|
12
|
+
* Unknown keys are dropped at both ends, by contract. An unknown value for a
|
|
13
|
+
* known enum is dropped from the hash — never a new bucket, never a
|
|
14
|
+
* generation.
|
|
15
|
+
*
|
|
16
|
+
* The client's hash is a cache hint. The server re-canonicalizes from raw
|
|
17
|
+
* dims and its own hash is the identity for generation, billing, and
|
|
18
|
+
* admission — nobody gets to pre-poison a bucket.
|
|
19
|
+
*/
|
|
20
|
+
/** Platform intent vocabulary. */
|
|
21
|
+
export const CONTEXT_INTENTS = [
|
|
22
|
+
"signup",
|
|
23
|
+
"signin",
|
|
24
|
+
"retain",
|
|
25
|
+
"upsell",
|
|
26
|
+
"reassure",
|
|
27
|
+
"explain",
|
|
28
|
+
"browse",
|
|
29
|
+
"convert",
|
|
30
|
+
];
|
|
31
|
+
/** Dayparts — the timecontext classification, lowercased for the hash. */
|
|
32
|
+
export const CONTEXT_DAYPARTS = [
|
|
33
|
+
"earlymorning",
|
|
34
|
+
"morning",
|
|
35
|
+
"latemorning",
|
|
36
|
+
"midday",
|
|
37
|
+
"afternoon",
|
|
38
|
+
"evening",
|
|
39
|
+
"lateevening",
|
|
40
|
+
"latenight",
|
|
41
|
+
];
|
|
42
|
+
export const CONTEXT_DAY_TYPES = ["weekday", "weekend"];
|
|
43
|
+
export const CONTEXT_WEATHER = ["clear", "cloudy", "rainy", "snowy"];
|
|
44
|
+
export const CONTEXT_TEMP_BANDS = ["cold", "mild", "warm", "hot"];
|
|
45
|
+
export const CONTEXT_DEVICES = ["mobile", "tablet", "desktop", "signage"];
|
|
46
|
+
/** Existing platform surface-type vocabulary (mosaic/lib/surface-types.ts). */
|
|
47
|
+
export const CONTEXT_SURFACE_TYPES = [
|
|
48
|
+
"vending",
|
|
49
|
+
"retail",
|
|
50
|
+
"transit",
|
|
51
|
+
"stadium",
|
|
52
|
+
"mall",
|
|
53
|
+
"outdoor_billboard",
|
|
54
|
+
"indoor_display",
|
|
55
|
+
"point_of_sale",
|
|
56
|
+
"lobby",
|
|
57
|
+
"speaker",
|
|
58
|
+
"kiosk",
|
|
59
|
+
"ar",
|
|
60
|
+
"web",
|
|
61
|
+
"other",
|
|
62
|
+
"grocery",
|
|
63
|
+
"convenience",
|
|
64
|
+
"restaurant",
|
|
65
|
+
"cafe",
|
|
66
|
+
"bar",
|
|
67
|
+
];
|
|
68
|
+
/** Org-declared token dims (`p`, `a`) and custom-dim values. */
|
|
69
|
+
const TOKEN_PATTERN = /^[a-z0-9-]{1,32}$/;
|
|
70
|
+
/** Custom dim keys: `x.` + a short token. */
|
|
71
|
+
const CUSTOM_KEY_PATTERN = /^x\.[a-z0-9-]{1,24}$/;
|
|
72
|
+
/** Loose BCP-47 shape, lowercased. */
|
|
73
|
+
const LOCALE_PATTERN = /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/;
|
|
74
|
+
const COUNTRY_PATTERN = /^[a-z]{2}$/;
|
|
75
|
+
export const MAX_CUSTOM_DIMS = 4;
|
|
76
|
+
export const MAX_NOTES_LENGTH = 280;
|
|
77
|
+
const ENUM_DIMS = {
|
|
78
|
+
i: CONTEXT_INTENTS,
|
|
79
|
+
t: CONTEXT_DAYPARTS,
|
|
80
|
+
y: CONTEXT_DAY_TYPES,
|
|
81
|
+
w: CONTEXT_WEATHER,
|
|
82
|
+
b: CONTEXT_TEMP_BANDS,
|
|
83
|
+
d: CONTEXT_DEVICES,
|
|
84
|
+
s: CONTEXT_SURFACE_TYPES,
|
|
85
|
+
};
|
|
86
|
+
const MARKUP_PATTERN = /<[^>]*>/g;
|
|
87
|
+
const URL_PATTERN = /(?:https?:\/\/|www\.)\S+/gi;
|
|
88
|
+
/** Shapes that indicate visitor data or secrets: reject the whole field. */
|
|
89
|
+
const EMAIL_PATTERN = /\S+@\S+/;
|
|
90
|
+
const PHONE_PATTERN = /(?:\d[\s().+-]*){7,}/;
|
|
91
|
+
const TOKEN_SHAPE_PATTERN = /[a-z0-9_-]{24,}/;
|
|
92
|
+
/**
|
|
93
|
+
* Normalizes the free-text `notes` dim. Returns undefined when the field is
|
|
94
|
+
* empty after scrubbing or matches a rejected shape (emails, phone numbers,
|
|
95
|
+
* long token-like runs) — the field is dropped, never partially kept.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeNotes(raw) {
|
|
98
|
+
let value = raw
|
|
99
|
+
.replace(MARKUP_PATTERN, " ")
|
|
100
|
+
.replace(URL_PATTERN, " ")
|
|
101
|
+
.replace(/\s+/g, " ")
|
|
102
|
+
.trim()
|
|
103
|
+
.toLowerCase();
|
|
104
|
+
// Cap by code points, matching the Go arm's rune slicing.
|
|
105
|
+
const points = Array.from(value);
|
|
106
|
+
if (points.length > MAX_NOTES_LENGTH)
|
|
107
|
+
value = points.slice(0, MAX_NOTES_LENGTH).join("").trim();
|
|
108
|
+
if (value.length === 0)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (EMAIL_PATTERN.test(value) || PHONE_PATTERN.test(value) || TOKEN_SHAPE_PATTERN.test(value)) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Normalizes a raw dim map into canonical dims: unknown keys dropped, enum
|
|
117
|
+
* values validated, tokens pattern-checked, custom dims capped at
|
|
118
|
+
* MAX_CUSTOM_DIMS (bytewise-first wins, deterministically), notes scrubbed.
|
|
119
|
+
*/
|
|
120
|
+
export function normalizeDims(raw) {
|
|
121
|
+
const dims = {};
|
|
122
|
+
const dropped = [];
|
|
123
|
+
const customKeys = [];
|
|
124
|
+
for (const [rawKey, rawValue] of Object.entries(raw)) {
|
|
125
|
+
if (rawValue === undefined || rawValue === null || rawValue === "")
|
|
126
|
+
continue;
|
|
127
|
+
const key = rawKey.toLowerCase();
|
|
128
|
+
const value = String(rawValue).toLowerCase().trim();
|
|
129
|
+
if (value === "")
|
|
130
|
+
continue;
|
|
131
|
+
if (key === "n") {
|
|
132
|
+
const notes = normalizeNotes(String(rawValue));
|
|
133
|
+
if (notes === undefined)
|
|
134
|
+
dropped.push(key);
|
|
135
|
+
else
|
|
136
|
+
dims.n = notes;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (key === "p" || key === "a") {
|
|
140
|
+
if (TOKEN_PATTERN.test(value))
|
|
141
|
+
dims[key] = value;
|
|
142
|
+
else
|
|
143
|
+
dropped.push(`${key}=${value}`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (key === "l") {
|
|
147
|
+
if (LOCALE_PATTERN.test(value)) {
|
|
148
|
+
dims.l = value;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Fall back to the primary language subtag (en-US-x-foo → en).
|
|
152
|
+
const subtag = value.split("-")[0];
|
|
153
|
+
if (/^[a-z]{2,3}$/.test(subtag))
|
|
154
|
+
dims.l = subtag;
|
|
155
|
+
else
|
|
156
|
+
dropped.push(`l=${value}`);
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (key === "c") {
|
|
161
|
+
if (COUNTRY_PATTERN.test(value))
|
|
162
|
+
dims.c = value;
|
|
163
|
+
else
|
|
164
|
+
dropped.push(`c=${value}`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const enumValues = ENUM_DIMS[key];
|
|
168
|
+
if (enumValues) {
|
|
169
|
+
if (enumValues.includes(value))
|
|
170
|
+
dims[key] = value;
|
|
171
|
+
else
|
|
172
|
+
dropped.push(`${key}=${value}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (CUSTOM_KEY_PATTERN.test(key)) {
|
|
176
|
+
if (TOKEN_PATTERN.test(value)) {
|
|
177
|
+
customKeys.push(key);
|
|
178
|
+
dims[key] = value;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
dropped.push(`${key}=${value}`);
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
dropped.push(key);
|
|
186
|
+
}
|
|
187
|
+
if (customKeys.length > MAX_CUSTOM_DIMS) {
|
|
188
|
+
customKeys.sort();
|
|
189
|
+
for (const key of customKeys.slice(MAX_CUSTOM_DIMS)) {
|
|
190
|
+
delete dims[key];
|
|
191
|
+
dropped.push(key);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { dims, dropped };
|
|
195
|
+
}
|
|
196
|
+
/** Maps the public DeclaredContext shape onto short-key dims. */
|
|
197
|
+
export function declaredToDims(declared) {
|
|
198
|
+
const raw = {};
|
|
199
|
+
if (declared.page !== undefined)
|
|
200
|
+
raw.p = declared.page;
|
|
201
|
+
if (declared.intent !== undefined)
|
|
202
|
+
raw.i = declared.intent;
|
|
203
|
+
if (declared.audience !== undefined)
|
|
204
|
+
raw.a = declared.audience;
|
|
205
|
+
if (declared.notes !== undefined)
|
|
206
|
+
raw.n = declared.notes;
|
|
207
|
+
for (const [name, value] of Object.entries(declared.custom ?? {})) {
|
|
208
|
+
raw[`x.${name.toLowerCase()}`] = value;
|
|
209
|
+
}
|
|
210
|
+
return raw;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Canonical form: drop absent/unknown keys → lowercase → sort keys bytewise →
|
|
214
|
+
* join "k:v|k:v". Already-normalized dims only; run normalizeDims first.
|
|
215
|
+
*/
|
|
216
|
+
export function canonicalContextString(dims) {
|
|
217
|
+
return Object.entries(dims)
|
|
218
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== "")
|
|
219
|
+
.map(([key, value]) => [key.toLowerCase(), value.toLowerCase()])
|
|
220
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
221
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
222
|
+
.join("|");
|
|
223
|
+
}
|
|
224
|
+
/** sha256(input) → hex, truncated to 16 chars (64 bits). WebCrypto, so it
|
|
225
|
+
* works unchanged in browsers, workers, and Node 18+. */
|
|
226
|
+
async function sha256Hex16(input) {
|
|
227
|
+
const bytes = new TextEncoder().encode(input);
|
|
228
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
229
|
+
return Array.from(new Uint8Array(digest))
|
|
230
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
231
|
+
.join("")
|
|
232
|
+
.slice(0, 16);
|
|
233
|
+
}
|
|
234
|
+
/** sha256(canonical) → hex, truncated to 16 chars (64 bits). */
|
|
235
|
+
export async function computeContextHash(dims) {
|
|
236
|
+
return sha256Hex16(canonicalContextString(dims));
|
|
237
|
+
}
|
|
238
|
+
// --- Fragment schema hashing (Declarative Context rung 2) -------------------
|
|
239
|
+
/** Length-budget floors per kind — ceilings on the ceiling (spec §2.6). They
|
|
240
|
+
* cap the case where a developer's fallback is a single word. */
|
|
241
|
+
export const FRAGMENT_KIND_FLOORS = {
|
|
242
|
+
cta: 24,
|
|
243
|
+
label: 32,
|
|
244
|
+
headline: 64,
|
|
245
|
+
body: 160,
|
|
246
|
+
"empty-state": 160,
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* ceil(max(len(fallback) × 1.3, floor(kind))). The developer's own fallback
|
|
250
|
+
* defines the layout envelope, which is what makes CLS zero by construction.
|
|
251
|
+
* Server twin: mosaic/services/fragments/fragmentService.ts computeFieldBudget
|
|
252
|
+
* — the server recomputes and never trusts the client's number.
|
|
253
|
+
*/
|
|
254
|
+
export function computeFieldBudget(fallback, kind) {
|
|
255
|
+
const floor = FRAGMENT_KIND_FLOORS[kind] ?? FRAGMENT_KIND_FLOORS.body;
|
|
256
|
+
return Math.ceil(Math.max(fallback.length * 1.3, floor));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Schema hash: keys + kinds + budgets, never the fallback prose (prose in the
|
|
260
|
+
* GET URL would make the CDN object private and delete the cost story). MUST
|
|
261
|
+
* stay byte-identical to mosaic/services/fragments/fragmentService.ts
|
|
262
|
+
* computeSchemaHash; the shared vectors in
|
|
263
|
+
* sdk/parity/fixtures/ctxhash/schema-vectors.json are the contract.
|
|
264
|
+
*/
|
|
265
|
+
export async function computeSchemaHash(fields) {
|
|
266
|
+
const canonical = [...fields]
|
|
267
|
+
.map((field) => `${field.key.toLowerCase()}:${field.kind}:${field.budget}`)
|
|
268
|
+
.sort()
|
|
269
|
+
.join("|");
|
|
270
|
+
return sha256Hex16(canonical);
|
|
271
|
+
}
|
|
272
|
+
/** Convenience: declared shape → normalized dims → hash. */
|
|
273
|
+
export async function hashDeclaredContext(declared, environment = {}) {
|
|
274
|
+
const { dims, dropped } = normalizeDims({ ...environment, ...declaredToDims(declared) });
|
|
275
|
+
return { dims, hash: await computeContextHash(dims), dropped };
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|