@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The publishable key: `st_pk_<env>_<base64url(JSON { organizationId, apiBase })>`.
|
|
3
|
+
*
|
|
4
|
+
* It is NOT a credential — it is an ADDRESS, and the docs must say so in the
|
|
5
|
+
* same paragraph that introduces it. It is decoded locally, with zero network,
|
|
6
|
+
* and collapses the two required provider props into one dashboard
|
|
7
|
+
* copy-paste. The read path is unauthenticated BY DESIGN; security lives in
|
|
8
|
+
* the origin registry (minting requires a verified origin registered to the
|
|
9
|
+
* paying org). We ship the key because one string is better onboarding than
|
|
10
|
+
* two, not because it protects anything. `organizationId` and `apiBase`
|
|
11
|
+
* remain as explicit props for self-hosted bases and stay the documented
|
|
12
|
+
* escape hatch — when both are given, the explicit props win.
|
|
13
|
+
*
|
|
14
|
+
* Because the key is unsigned, an apiBase that arrives VIA a key is an
|
|
15
|
+
* unproven address: anyone can hand-encode a key pointing at their own host.
|
|
16
|
+
* Content fetches may follow it (they carry identity hashes, not secrets),
|
|
17
|
+
* but the signed mint token — a real spend credential — may not: the mint
|
|
18
|
+
* POST fires only for an explicit, first-party (*.sparkletree.io), or
|
|
19
|
+
* loopback apiBase (security review S-2; see isFirstPartyApiBase/maybeMint).
|
|
20
|
+
*/
|
|
21
|
+
export interface PublishableKeyPayload {
|
|
22
|
+
organizationId: string;
|
|
23
|
+
apiBase: string;
|
|
24
|
+
}
|
|
25
|
+
export type PublishableKeyEnv = "live" | "test";
|
|
26
|
+
/**
|
|
27
|
+
* Decode a publishable key. Throws TypeError on anything malformed — a bad
|
|
28
|
+
* key is a configuration error at integration time, not a runtime condition
|
|
29
|
+
* to degrade around.
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodePublishableKey(key: string): PublishableKeyPayload;
|
|
32
|
+
/** Encode a publishable key. Dashboard-side counterpart of decode. */
|
|
33
|
+
export declare function encodePublishableKey(options: {
|
|
34
|
+
organizationId: string;
|
|
35
|
+
apiBase: string;
|
|
36
|
+
env: PublishableKeyEnv;
|
|
37
|
+
}): string;
|
|
38
|
+
//# sourceMappingURL=publishableKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publishableKey.d.ts","sourceRoot":"","sources":["../src/publishableKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAkBhD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAqBvE;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,iBAAiB,CAAC;CACxB,GAAG,MAAM,CAMT"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The publishable key: `st_pk_<env>_<base64url(JSON { organizationId, apiBase })>`.
|
|
3
|
+
*
|
|
4
|
+
* It is NOT a credential — it is an ADDRESS, and the docs must say so in the
|
|
5
|
+
* same paragraph that introduces it. It is decoded locally, with zero network,
|
|
6
|
+
* and collapses the two required provider props into one dashboard
|
|
7
|
+
* copy-paste. The read path is unauthenticated BY DESIGN; security lives in
|
|
8
|
+
* the origin registry (minting requires a verified origin registered to the
|
|
9
|
+
* paying org). We ship the key because one string is better onboarding than
|
|
10
|
+
* two, not because it protects anything. `organizationId` and `apiBase`
|
|
11
|
+
* remain as explicit props for self-hosted bases and stay the documented
|
|
12
|
+
* escape hatch — when both are given, the explicit props win.
|
|
13
|
+
*
|
|
14
|
+
* Because the key is unsigned, an apiBase that arrives VIA a key is an
|
|
15
|
+
* unproven address: anyone can hand-encode a key pointing at their own host.
|
|
16
|
+
* Content fetches may follow it (they carry identity hashes, not secrets),
|
|
17
|
+
* but the signed mint token — a real spend credential — may not: the mint
|
|
18
|
+
* POST fires only for an explicit, first-party (*.sparkletree.io), or
|
|
19
|
+
* loopback apiBase (security review S-2; see isFirstPartyApiBase/maybeMint).
|
|
20
|
+
*/
|
|
21
|
+
const KEY_PATTERN = /^st_pk_(live|test)_([A-Za-z0-9_-]+)$/;
|
|
22
|
+
function toBase64Url(text) {
|
|
23
|
+
const bytes = new TextEncoder().encode(text);
|
|
24
|
+
let binary = "";
|
|
25
|
+
for (const byte of bytes)
|
|
26
|
+
binary += String.fromCharCode(byte);
|
|
27
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
28
|
+
}
|
|
29
|
+
function fromBase64Url(encoded) {
|
|
30
|
+
const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
31
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
32
|
+
const binary = atob(padded);
|
|
33
|
+
return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decode a publishable key. Throws TypeError on anything malformed — a bad
|
|
37
|
+
* key is a configuration error at integration time, not a runtime condition
|
|
38
|
+
* to degrade around.
|
|
39
|
+
*/
|
|
40
|
+
export function decodePublishableKey(key) {
|
|
41
|
+
const match = typeof key === "string" ? KEY_PATTERN.exec(key) : null;
|
|
42
|
+
if (!match) {
|
|
43
|
+
throw new TypeError('SparkleTree: malformed publishable key — expected "st_pk_live_…" or "st_pk_test_…" copied from the dashboard.');
|
|
44
|
+
}
|
|
45
|
+
let payload;
|
|
46
|
+
try {
|
|
47
|
+
payload = JSON.parse(fromBase64Url(match[2]));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new TypeError("SparkleTree: publishable key payload is not decodable JSON.");
|
|
51
|
+
}
|
|
52
|
+
const { organizationId, apiBase } = (payload ?? {});
|
|
53
|
+
if (typeof organizationId !== "string" || organizationId === "") {
|
|
54
|
+
throw new TypeError("SparkleTree: publishable key is missing organizationId.");
|
|
55
|
+
}
|
|
56
|
+
if (typeof apiBase !== "string" || apiBase === "") {
|
|
57
|
+
throw new TypeError("SparkleTree: publishable key is missing apiBase.");
|
|
58
|
+
}
|
|
59
|
+
return { organizationId, apiBase };
|
|
60
|
+
}
|
|
61
|
+
/** Encode a publishable key. Dashboard-side counterpart of decode. */
|
|
62
|
+
export function encodePublishableKey(options) {
|
|
63
|
+
const payload = JSON.stringify({
|
|
64
|
+
organizationId: options.organizationId,
|
|
65
|
+
apiBase: options.apiBase,
|
|
66
|
+
});
|
|
67
|
+
return `st_pk_${options.env}_${toBase64Url(payload)}`;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=publishableKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publishableKey.js","sourceRoot":"","sources":["../src/publishableKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,MAAM,WAAW,GAAG,sCAAsC,CAAC;AAE3D,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CACjB,+GAA+G,CAChH,CAAC;IACJ,CAAC;IACD,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,6DAA6D,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAmC,CAAC;IACtF,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,yDAAyD,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AACrC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,OAIpC;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;IACH,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL hygiene for values that cross the wire into a customer's page.
|
|
3
|
+
*
|
|
4
|
+
* Both helpers exist because the SDK runs INSIDE the customer's origin:
|
|
5
|
+
* anything the server sends is rendered with the customer's privileges, so a
|
|
6
|
+
* value we would never type ourselves must be refused here rather than
|
|
7
|
+
* escaped downstream. Refusal is silent — never a throw, never a console
|
|
8
|
+
* line — because the failure mode this package optimises for is "the
|
|
9
|
+
* creative quietly stops adapting", not "the customer's console fills up".
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Vet a server-supplied action URL before it reaches an `<a href>`.
|
|
13
|
+
*
|
|
14
|
+
* Allowed through unchanged:
|
|
15
|
+
* - absolute `https:`, `http:`, `mailto:`, `tel:` URLs;
|
|
16
|
+
* - relative references — `/…`, `./…`, `#…`, `?…` — which resolve against
|
|
17
|
+
* the customer's own origin; plus protocol-relative `//…`, which keeps
|
|
18
|
+
* the page's scheme and is no worse than the absolute `https:` URLs we
|
|
19
|
+
* already allow (both can point at any host).
|
|
20
|
+
*
|
|
21
|
+
* Anything else returns `undefined`: `javascript:`/`data:`/`vbscript:`
|
|
22
|
+
* outright, but also bare strings with no scheme and no relative prefix —
|
|
23
|
+
* we cannot prove where they point, and a CTA with no destination degrades
|
|
24
|
+
* to a plain button, which is the honest outcome.
|
|
25
|
+
*
|
|
26
|
+
* The input is trimmed first because browsers strip leading ASCII whitespace
|
|
27
|
+
* from `href` before parsing — without that, `" javascript:…"` would sneak a
|
|
28
|
+
* scheme past a prefix check that the browser would then honour.
|
|
29
|
+
*/
|
|
30
|
+
export declare function safeActionUrl(url: string | undefined): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Is this apiBase allowed to carry SDK traffic?
|
|
33
|
+
*
|
|
34
|
+
* Production traffic is `https:` only — an `http:` apiBase sends campaign
|
|
35
|
+
* configuration and context over a channel any middlebox can rewrite, which
|
|
36
|
+
* is S-4 with extra steps. The one exception is loopback
|
|
37
|
+
* (`http://localhost`, `http://127.0.0.1`, any port), because local
|
|
38
|
+
* development against a dev edge is a real and legitimate setup.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSecureApiBase(apiBase: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Is this apiBase first-party — the hosted SparkleTree edge, or loopback?
|
|
43
|
+
*
|
|
44
|
+
* The publishable key is an unsigned address ANYONE can hand-encode (see
|
|
45
|
+
* publishableKey.ts), so an apiBase that arrived via a key proves nothing
|
|
46
|
+
* about who chose it. The one value that must never follow an unproven
|
|
47
|
+
* address is the signed mint token: it is a real spend credential, and a
|
|
48
|
+
* doctored key pointing at an attacker host would receive it on the mint
|
|
49
|
+
* POST and replay it against the real API for its TTL (security review
|
|
50
|
+
* S-2). The mint therefore fires only when the apiBase is explicit (the
|
|
51
|
+
* developer's own prop — their choice to make), first-party, or loopback.
|
|
52
|
+
* Self-hosted bases are exactly what the explicit `apiBase` escape hatch is
|
|
53
|
+
* for; a self-made key carrying a third-party host gets content but no mint.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isFirstPartyApiBase(apiBase: string): boolean;
|
|
56
|
+
//# sourceMappingURL=safeUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeUrl.d.ts","sourceRoot":"","sources":["../src/safeUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmBH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAyBzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CASxD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAY5D"}
|
package/dist/safeUrl.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL hygiene for values that cross the wire into a customer's page.
|
|
3
|
+
*
|
|
4
|
+
* Both helpers exist because the SDK runs INSIDE the customer's origin:
|
|
5
|
+
* anything the server sends is rendered with the customer's privileges, so a
|
|
6
|
+
* value we would never type ourselves must be refused here rather than
|
|
7
|
+
* escaped downstream. Refusal is silent — never a throw, never a console
|
|
8
|
+
* line — because the failure mode this package optimises for is "the
|
|
9
|
+
* creative quietly stops adapting", not "the customer's console fills up".
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Schemes a CTA href may carry. `https`/`http` are links; `mailto`/`tel` are
|
|
13
|
+
* the other two things a button legitimately does. Everything else —
|
|
14
|
+
* `javascript:`, `data:`, `vbscript:` — is click-XSS in the customer's origin
|
|
15
|
+
* the moment a consumer renders `ctaAction` as `<a href>`.
|
|
16
|
+
*/
|
|
17
|
+
const ALLOWED_ACTION_SCHEMES = new Set(["https:", "http:", "mailto:", "tel:"]);
|
|
18
|
+
/** A scheme per RFC 3986: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":". */
|
|
19
|
+
const SCHEME_PREFIX = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
20
|
+
/**
|
|
21
|
+
* Sniffing base for `new URL`. Only the SCHEME is read off the result — never
|
|
22
|
+
* the resolved href — so the dummy origin can never leak into returned values.
|
|
23
|
+
*/
|
|
24
|
+
const SNIFF_BASE = "https://url.sparkletree.invalid";
|
|
25
|
+
/**
|
|
26
|
+
* Vet a server-supplied action URL before it reaches an `<a href>`.
|
|
27
|
+
*
|
|
28
|
+
* Allowed through unchanged:
|
|
29
|
+
* - absolute `https:`, `http:`, `mailto:`, `tel:` URLs;
|
|
30
|
+
* - relative references — `/…`, `./…`, `#…`, `?…` — which resolve against
|
|
31
|
+
* the customer's own origin; plus protocol-relative `//…`, which keeps
|
|
32
|
+
* the page's scheme and is no worse than the absolute `https:` URLs we
|
|
33
|
+
* already allow (both can point at any host).
|
|
34
|
+
*
|
|
35
|
+
* Anything else returns `undefined`: `javascript:`/`data:`/`vbscript:`
|
|
36
|
+
* outright, but also bare strings with no scheme and no relative prefix —
|
|
37
|
+
* we cannot prove where they point, and a CTA with no destination degrades
|
|
38
|
+
* to a plain button, which is the honest outcome.
|
|
39
|
+
*
|
|
40
|
+
* The input is trimmed first because browsers strip leading ASCII whitespace
|
|
41
|
+
* from `href` before parsing — without that, `" javascript:…"` would sneak a
|
|
42
|
+
* scheme past a prefix check that the browser would then honour.
|
|
43
|
+
*/
|
|
44
|
+
export function safeActionUrl(url) {
|
|
45
|
+
if (!url)
|
|
46
|
+
return undefined;
|
|
47
|
+
const trimmed = url.trim();
|
|
48
|
+
if (!trimmed)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (trimmed.startsWith("/") ||
|
|
51
|
+
trimmed.startsWith("./") ||
|
|
52
|
+
trimmed.startsWith("#") ||
|
|
53
|
+
trimmed.startsWith("?")) {
|
|
54
|
+
return trimmed;
|
|
55
|
+
}
|
|
56
|
+
// No scheme and no relative prefix: unprovable, so refused. (This also
|
|
57
|
+
// catches whitespace-obfuscated schemes like "java\tscript:" — the tab is
|
|
58
|
+
// not a scheme character, so the string simply has no scheme here.)
|
|
59
|
+
if (!SCHEME_PREFIX.test(trimmed))
|
|
60
|
+
return undefined;
|
|
61
|
+
try {
|
|
62
|
+
const scheme = new URL(trimmed, SNIFF_BASE).protocol;
|
|
63
|
+
return ALLOWED_ACTION_SCHEMES.has(scheme) ? trimmed : undefined;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Is this apiBase allowed to carry SDK traffic?
|
|
71
|
+
*
|
|
72
|
+
* Production traffic is `https:` only — an `http:` apiBase sends campaign
|
|
73
|
+
* configuration and context over a channel any middlebox can rewrite, which
|
|
74
|
+
* is S-4 with extra steps. The one exception is loopback
|
|
75
|
+
* (`http://localhost`, `http://127.0.0.1`, any port), because local
|
|
76
|
+
* development against a dev edge is a real and legitimate setup.
|
|
77
|
+
*/
|
|
78
|
+
export function isSecureApiBase(apiBase) {
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(apiBase);
|
|
81
|
+
if (url.protocol === "https:")
|
|
82
|
+
return true;
|
|
83
|
+
if (url.protocol !== "http:")
|
|
84
|
+
return false;
|
|
85
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Is this apiBase first-party — the hosted SparkleTree edge, or loopback?
|
|
93
|
+
*
|
|
94
|
+
* The publishable key is an unsigned address ANYONE can hand-encode (see
|
|
95
|
+
* publishableKey.ts), so an apiBase that arrived via a key proves nothing
|
|
96
|
+
* about who chose it. The one value that must never follow an unproven
|
|
97
|
+
* address is the signed mint token: it is a real spend credential, and a
|
|
98
|
+
* doctored key pointing at an attacker host would receive it on the mint
|
|
99
|
+
* POST and replay it against the real API for its TTL (security review
|
|
100
|
+
* S-2). The mint therefore fires only when the apiBase is explicit (the
|
|
101
|
+
* developer's own prop — their choice to make), first-party, or loopback.
|
|
102
|
+
* Self-hosted bases are exactly what the explicit `apiBase` escape hatch is
|
|
103
|
+
* for; a self-made key carrying a third-party host gets content but no mint.
|
|
104
|
+
*/
|
|
105
|
+
export function isFirstPartyApiBase(apiBase) {
|
|
106
|
+
try {
|
|
107
|
+
const { hostname } = new URL(apiBase);
|
|
108
|
+
return (hostname === "sparkletree.io" ||
|
|
109
|
+
hostname.endsWith(".sparkletree.io") ||
|
|
110
|
+
hostname === "localhost" ||
|
|
111
|
+
hostname === "127.0.0.1");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=safeUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeUrl.js","sourceRoot":"","sources":["../src/safeUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAE/E,6EAA6E;AAC7E,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,GAAG,iCAAiC,CAAC;AAErD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,GAAuB;IACnD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,IACE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QACvB,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QACxB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QACvB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EACvB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAC1E,oEAAoE;IACpE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAEnD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC;QACrD,OAAO,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3C,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3C,OAAO,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACtC,OAAO,CACL,QAAQ,KAAK,gBAAgB;YAC7B,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YACpC,QAAQ,KAAK,WAAW;YACxB,QAAQ,KAAK,WAAW,CACzB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE parsing.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately NOT `EventSource`, for three reasons that each cost real
|
|
5
|
+
* behaviour:
|
|
6
|
+
*
|
|
7
|
+
* 1. EventSource cannot set request headers, so it cannot send
|
|
8
|
+
* `st-protocol` — negotiation would be query-param-only forever.
|
|
9
|
+
* 2. EventSource reconnects automatically and silently. On a stream that
|
|
10
|
+
* ends in `error` that means retrying a request the server already
|
|
11
|
+
* answered definitively, repeatedly, from inside a customer's page.
|
|
12
|
+
* 3. EventSource cannot read response headers, so the client could never
|
|
13
|
+
* see which protocol version it actually got.
|
|
14
|
+
*
|
|
15
|
+
* fetch + a manual parser gives all three back. The cost is ~80 lines, which
|
|
16
|
+
* is a good trade for not having a background reconnect loop we do not
|
|
17
|
+
* control.
|
|
18
|
+
*/
|
|
19
|
+
export interface SseMessage {
|
|
20
|
+
/** `event:` name. Unnamed messages arrive as "message", per the spec. */
|
|
21
|
+
event: string;
|
|
22
|
+
/** `data:` payload, lines joined with "\n". */
|
|
23
|
+
data: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Incremental SSE frame parser. Feed it decoded text; it yields complete
|
|
27
|
+
* messages. Holds partial frames across chunk boundaries — a `data:` line
|
|
28
|
+
* split mid-UTF-8-sequence or mid-JSON is the normal case on a real network,
|
|
29
|
+
* not an edge case.
|
|
30
|
+
*/
|
|
31
|
+
export declare class SseParser {
|
|
32
|
+
private buffer;
|
|
33
|
+
/** Feed a chunk of decoded text; returns every message it completed. */
|
|
34
|
+
push(chunk: string): SseMessage[];
|
|
35
|
+
/** Flush a trailing frame that never got its blank line (server closed). */
|
|
36
|
+
flush(): SseMessage[];
|
|
37
|
+
}
|
|
38
|
+
export interface ReadSseOptions {
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
/** Called for every complete message, in order. */
|
|
41
|
+
onMessage: (message: SseMessage) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Drain a fetch Response body as SSE.
|
|
45
|
+
*
|
|
46
|
+
* Returns when the server closes the stream. Aborting via `signal` resolves
|
|
47
|
+
* rather than throwing: a component unmounting mid-stream is the normal way
|
|
48
|
+
* these end, not an error worth surfacing in a customer's console.
|
|
49
|
+
*/
|
|
50
|
+
export declare function readSseStream(response: Response, options: ReadSseOptions): Promise<void>;
|
|
51
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAM;IAEpB,wEAAwE;IACxE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAmBjC,4EAA4E;IAC5E,KAAK,IAAI,UAAU,EAAE;CAOtB;AAwBD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAgDf"}
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE parsing.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately NOT `EventSource`, for three reasons that each cost real
|
|
5
|
+
* behaviour:
|
|
6
|
+
*
|
|
7
|
+
* 1. EventSource cannot set request headers, so it cannot send
|
|
8
|
+
* `st-protocol` — negotiation would be query-param-only forever.
|
|
9
|
+
* 2. EventSource reconnects automatically and silently. On a stream that
|
|
10
|
+
* ends in `error` that means retrying a request the server already
|
|
11
|
+
* answered definitively, repeatedly, from inside a customer's page.
|
|
12
|
+
* 3. EventSource cannot read response headers, so the client could never
|
|
13
|
+
* see which protocol version it actually got.
|
|
14
|
+
*
|
|
15
|
+
* fetch + a manual parser gives all three back. The cost is ~80 lines, which
|
|
16
|
+
* is a good trade for not having a background reconnect loop we do not
|
|
17
|
+
* control.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Incremental SSE frame parser. Feed it decoded text; it yields complete
|
|
21
|
+
* messages. Holds partial frames across chunk boundaries — a `data:` line
|
|
22
|
+
* split mid-UTF-8-sequence or mid-JSON is the normal case on a real network,
|
|
23
|
+
* not an edge case.
|
|
24
|
+
*/
|
|
25
|
+
export class SseParser {
|
|
26
|
+
buffer = "";
|
|
27
|
+
/** Feed a chunk of decoded text; returns every message it completed. */
|
|
28
|
+
push(chunk) {
|
|
29
|
+
this.buffer += chunk;
|
|
30
|
+
const messages = [];
|
|
31
|
+
// Frames are separated by a blank line. Normalise CRLF first so a proxy
|
|
32
|
+
// that rewrites line endings does not silently stop the stream.
|
|
33
|
+
const normalised = this.buffer.replace(/\r\n/g, "\n");
|
|
34
|
+
const frames = normalised.split("\n\n");
|
|
35
|
+
// The last element is either "" (buffer ended on a separator) or a
|
|
36
|
+
// partial frame. Either way it stays buffered.
|
|
37
|
+
this.buffer = frames.pop() ?? "";
|
|
38
|
+
for (const frame of frames) {
|
|
39
|
+
const message = parseFrame(frame);
|
|
40
|
+
if (message)
|
|
41
|
+
messages.push(message);
|
|
42
|
+
}
|
|
43
|
+
return messages;
|
|
44
|
+
}
|
|
45
|
+
/** Flush a trailing frame that never got its blank line (server closed). */
|
|
46
|
+
flush() {
|
|
47
|
+
const remaining = this.buffer.trim();
|
|
48
|
+
this.buffer = "";
|
|
49
|
+
if (!remaining)
|
|
50
|
+
return [];
|
|
51
|
+
const message = parseFrame(remaining);
|
|
52
|
+
return message ? [message] : [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function parseFrame(frame) {
|
|
56
|
+
let event = "message";
|
|
57
|
+
const data = [];
|
|
58
|
+
for (const line of frame.split("\n")) {
|
|
59
|
+
if (!line || line.startsWith(":"))
|
|
60
|
+
continue; // comment / keep-alive
|
|
61
|
+
const colon = line.indexOf(":");
|
|
62
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
63
|
+
// Exactly one optional leading space is stripped, per the SSE spec.
|
|
64
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
65
|
+
if (value.startsWith(" "))
|
|
66
|
+
value = value.slice(1);
|
|
67
|
+
if (field === "event")
|
|
68
|
+
event = value;
|
|
69
|
+
else if (field === "data")
|
|
70
|
+
data.push(value);
|
|
71
|
+
// `id` and `retry` are meaningful only to EventSource's reconnect logic,
|
|
72
|
+
// which we deliberately do not have.
|
|
73
|
+
}
|
|
74
|
+
if (!data.length && event === "message")
|
|
75
|
+
return null;
|
|
76
|
+
return { event, data: data.join("\n") };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Drain a fetch Response body as SSE.
|
|
80
|
+
*
|
|
81
|
+
* Returns when the server closes the stream. Aborting via `signal` resolves
|
|
82
|
+
* rather than throwing: a component unmounting mid-stream is the normal way
|
|
83
|
+
* these end, not an error worth surfacing in a customer's console.
|
|
84
|
+
*/
|
|
85
|
+
export async function readSseStream(response, options) {
|
|
86
|
+
if (!response.body) {
|
|
87
|
+
throw new Error("SparkleTree: response has no body — SSE requires a streaming response");
|
|
88
|
+
}
|
|
89
|
+
const reader = response.body.getReader();
|
|
90
|
+
const decoder = new TextDecoder();
|
|
91
|
+
const parser = new SseParser();
|
|
92
|
+
const onAbort = () => {
|
|
93
|
+
void reader.cancel().catch(() => { });
|
|
94
|
+
};
|
|
95
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
96
|
+
try {
|
|
97
|
+
// A signal aborted BEFORE we started reading never fires "abort" again —
|
|
98
|
+
// without this check the drain below would run a stream its owner already
|
|
99
|
+
// cancelled.
|
|
100
|
+
if (options.signal?.aborted) {
|
|
101
|
+
await reader.cancel().catch(() => { });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
for (;;) {
|
|
105
|
+
const { done, value } = await reader.read();
|
|
106
|
+
if (done)
|
|
107
|
+
break;
|
|
108
|
+
// `stream: true` keeps multi-byte characters intact across chunk
|
|
109
|
+
// boundaries — without it a headline can acquire a permanent U+FFFD.
|
|
110
|
+
for (const message of parser.push(decoder.decode(value, { stream: true }))) {
|
|
111
|
+
options.onMessage(message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const message of parser.flush())
|
|
115
|
+
options.onMessage(message);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (options.signal?.aborted)
|
|
119
|
+
return;
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
124
|
+
// Hand the body back: a locked reader pins the response's resources, and
|
|
125
|
+
// cancel is idempotent so an already-finished stream costs nothing.
|
|
126
|
+
// Both are guarded — a stream torn down mid-read can reject either call,
|
|
127
|
+
// and this is cleanup, never a reason to throw into a customer's page.
|
|
128
|
+
await reader.cancel().catch(() => { });
|
|
129
|
+
try {
|
|
130
|
+
reader.releaseLock();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* reads still pending — the lock releases with them */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
|