cursedbelt-server 4.5.0 → 4.6.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/dist/server/analytics/index.d.ts +1 -1
- package/dist/server/analytics/index.js +1 -1
- package/dist/server/analytics/webAnalytics.d.ts +37 -0
- package/dist/server/analytics/webAnalytics.js +50 -0
- package/package.json +1 -1
- package/src/server/analytics/index.ts +2 -0
- package/src/server/analytics/webAnalytics.spec.ts +63 -0
- package/src/server/analytics/webAnalytics.ts +52 -0
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* and drags no peer.
|
|
16
16
|
*/
|
|
17
17
|
export { type InjectedScript, foreignScriptSources, scriptsRefusedByCsp, } from "./injectedScripts";
|
|
18
|
-
export { WEB_ANALYTICS_BEACON_ORIGIN, WEB_ANALYTICS_BEACON_SRC, WEB_ANALYTICS_SITE_TAG, WEB_ANALYTICS_SITE_TOKEN, WEB_ANALYTICS_TAG, htmlCarriesWebAnalytics, webAnalyticsTag, } from "./webAnalytics";
|
|
18
|
+
export { WEB_ANALYTICS_BEACON_ORIGIN, WEB_ANALYTICS_BEACON_SRC, WEB_ANALYTICS_PUBLIC_HOST, WEB_ANALYTICS_SITE_TAG, WEB_ANALYTICS_SITE_TOKEN, WEB_ANALYTICS_TAG, htmlCarriesWebAnalytics, webAnalyticsBootstrap, webAnalyticsTag, } from "./webAnalytics";
|
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* and drags no peer.
|
|
16
16
|
*/
|
|
17
17
|
export { foreignScriptSources, scriptsRefusedByCsp, } from "./injectedScripts";
|
|
18
|
-
export { WEB_ANALYTICS_BEACON_ORIGIN, WEB_ANALYTICS_BEACON_SRC, WEB_ANALYTICS_SITE_TAG, WEB_ANALYTICS_SITE_TOKEN, WEB_ANALYTICS_TAG, htmlCarriesWebAnalytics, webAnalyticsTag, } from "./webAnalytics";
|
|
18
|
+
export { WEB_ANALYTICS_BEACON_ORIGIN, WEB_ANALYTICS_BEACON_SRC, WEB_ANALYTICS_PUBLIC_HOST, WEB_ANALYTICS_SITE_TAG, WEB_ANALYTICS_SITE_TOKEN, WEB_ANALYTICS_TAG, htmlCarriesWebAnalytics, webAnalyticsBootstrap, webAnalyticsTag, } from "./webAnalytics";
|
|
@@ -62,6 +62,43 @@ export declare const WEB_ANALYTICS_SITE_TOKEN = "bd88d63678b64bc89f9702f48846bc8
|
|
|
62
62
|
export declare function webAnalyticsTag(token?: string): string;
|
|
63
63
|
/** The fleet's tag, for the one site this generation serves. */
|
|
64
64
|
export declare const WEB_ANALYTICS_TAG: string;
|
|
65
|
+
/** The zone. A page served from anywhere else must not load the beacon — see below. */
|
|
66
|
+
export declare const WEB_ANALYTICS_PUBLIC_HOST = "cursedalchemy.com";
|
|
67
|
+
/**
|
|
68
|
+
* What a static shell actually pastes: the tag, behind a HOSTNAME GUARD.
|
|
69
|
+
*
|
|
70
|
+
* ## 🔴 Why the bare snippet cannot go in an `index.html`
|
|
71
|
+
*
|
|
72
|
+
* Measured 2026-09-18, the first time the eight shells carried it. The beacon posts its
|
|
73
|
+
* pageview to `https://cloudflareinsights.com/cdn-cgi/rum`, and Cloudflare answers that
|
|
74
|
+
* preflight with `Access-Control-Allow-Origin: http://127.0.0.1` — no port. So on every LOCAL
|
|
75
|
+
* page load the browser prints:
|
|
76
|
+
*
|
|
77
|
+
* > Access to XMLHttpRequest at 'https://cloudflareinsights.com/cdn-cgi/rum' from origin
|
|
78
|
+
* > 'http://127.0.0.1:57845' has been blocked by CORS policy … Failed to load resource
|
|
79
|
+
*
|
|
80
|
+
* Under Automatic Setup this never happened: the injection is done by the EDGE, so it existed
|
|
81
|
+
* only on the zone and a developer never saw it. Pasting the raw snippet into the source moves
|
|
82
|
+
* it to every `bun run dev` and every browser test — `apps/music` lost 5 route-render checks and
|
|
83
|
+
* `apps/desk` lost 24, both asserting the console is empty, and the owner's own local console
|
|
84
|
+
* would have gained a red line on every page.
|
|
85
|
+
*
|
|
86
|
+
* That is the exact cost he asked to be rid of, one environment over. So the guard: the beacon
|
|
87
|
+
* is appended only when the page is being served from {@link WEB_ANALYTICS_PUBLIC_HOST}, which
|
|
88
|
+
* is the only place it can report anyway.
|
|
89
|
+
*
|
|
90
|
+
* ## Why an inline script rather than a build-time injection
|
|
91
|
+
*
|
|
92
|
+
* A Vite plugin would mean ten `vite.config.ts` edits and a frontend build depending on a
|
|
93
|
+
* server package — and it would not even work: `apps/desk`'s browser suite runs against a
|
|
94
|
+
* production `dist` served from `127.0.0.1`, so a production-only injection is still a console
|
|
95
|
+
* error there. The hostname is the honest discriminator, not the build mode.
|
|
96
|
+
*
|
|
97
|
+
* 🔴 It needs inline script to be permitted on the page. Every shell that carries it already
|
|
98
|
+
* ships one; `apps/vault` does not, serves `script-src 'self'`, and is exempt from the beacon
|
|
99
|
+
* for that reason — see `tools/check-web-analytics.ts`.
|
|
100
|
+
*/
|
|
101
|
+
export declare function webAnalyticsBootstrap(token?: string): string;
|
|
65
102
|
/**
|
|
66
103
|
* Does `html` carry the beacon for this site?
|
|
67
104
|
*
|
|
@@ -67,6 +67,56 @@ export function webAnalyticsTag(token = WEB_ANALYTICS_SITE_TOKEN) {
|
|
|
67
67
|
}
|
|
68
68
|
/** The fleet's tag, for the one site this generation serves. */
|
|
69
69
|
export const WEB_ANALYTICS_TAG = webAnalyticsTag();
|
|
70
|
+
/** The zone. A page served from anywhere else must not load the beacon — see below. */
|
|
71
|
+
export const WEB_ANALYTICS_PUBLIC_HOST = "cursedalchemy.com";
|
|
72
|
+
/**
|
|
73
|
+
* What a static shell actually pastes: the tag, behind a HOSTNAME GUARD.
|
|
74
|
+
*
|
|
75
|
+
* ## 🔴 Why the bare snippet cannot go in an `index.html`
|
|
76
|
+
*
|
|
77
|
+
* Measured 2026-09-18, the first time the eight shells carried it. The beacon posts its
|
|
78
|
+
* pageview to `https://cloudflareinsights.com/cdn-cgi/rum`, and Cloudflare answers that
|
|
79
|
+
* preflight with `Access-Control-Allow-Origin: http://127.0.0.1` — no port. So on every LOCAL
|
|
80
|
+
* page load the browser prints:
|
|
81
|
+
*
|
|
82
|
+
* > Access to XMLHttpRequest at 'https://cloudflareinsights.com/cdn-cgi/rum' from origin
|
|
83
|
+
* > 'http://127.0.0.1:57845' has been blocked by CORS policy … Failed to load resource
|
|
84
|
+
*
|
|
85
|
+
* Under Automatic Setup this never happened: the injection is done by the EDGE, so it existed
|
|
86
|
+
* only on the zone and a developer never saw it. Pasting the raw snippet into the source moves
|
|
87
|
+
* it to every `bun run dev` and every browser test — `apps/music` lost 5 route-render checks and
|
|
88
|
+
* `apps/desk` lost 24, both asserting the console is empty, and the owner's own local console
|
|
89
|
+
* would have gained a red line on every page.
|
|
90
|
+
*
|
|
91
|
+
* That is the exact cost he asked to be rid of, one environment over. So the guard: the beacon
|
|
92
|
+
* is appended only when the page is being served from {@link WEB_ANALYTICS_PUBLIC_HOST}, which
|
|
93
|
+
* is the only place it can report anyway.
|
|
94
|
+
*
|
|
95
|
+
* ## Why an inline script rather than a build-time injection
|
|
96
|
+
*
|
|
97
|
+
* A Vite plugin would mean ten `vite.config.ts` edits and a frontend build depending on a
|
|
98
|
+
* server package — and it would not even work: `apps/desk`'s browser suite runs against a
|
|
99
|
+
* production `dist` served from `127.0.0.1`, so a production-only injection is still a console
|
|
100
|
+
* error there. The hostname is the honest discriminator, not the build mode.
|
|
101
|
+
*
|
|
102
|
+
* 🔴 It needs inline script to be permitted on the page. Every shell that carries it already
|
|
103
|
+
* ships one; `apps/vault` does not, serves `script-src 'self'`, and is exempt from the beacon
|
|
104
|
+
* for that reason — see `tools/check-web-analytics.ts`.
|
|
105
|
+
*/
|
|
106
|
+
export function webAnalyticsBootstrap(token = WEB_ANALYTICS_SITE_TOKEN) {
|
|
107
|
+
return [
|
|
108
|
+
"<script>",
|
|
109
|
+
`\tvar host = location.hostname;`,
|
|
110
|
+
`\tif (host === "${WEB_ANALYTICS_PUBLIC_HOST}" || host.endsWith(".${WEB_ANALYTICS_PUBLIC_HOST}")) {`,
|
|
111
|
+
`\t\tvar beacon = document.createElement("script");`,
|
|
112
|
+
`\t\tbeacon.type = "module";`,
|
|
113
|
+
`\t\tbeacon.src = "${WEB_ANALYTICS_BEACON_SRC}";`,
|
|
114
|
+
`\t\tbeacon.setAttribute("data-cf-beacon", '{"token": "${token}"}');`,
|
|
115
|
+
`\t\tdocument.head.appendChild(beacon);`,
|
|
116
|
+
"\t}",
|
|
117
|
+
"</script>",
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
70
120
|
/**
|
|
71
121
|
* Does `html` carry the beacon for this site?
|
|
72
122
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The app-facing Bun/Hono server tier of the cursedbelt split \u2014 storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
|
|
@@ -22,9 +22,11 @@ export {
|
|
|
22
22
|
export {
|
|
23
23
|
WEB_ANALYTICS_BEACON_ORIGIN,
|
|
24
24
|
WEB_ANALYTICS_BEACON_SRC,
|
|
25
|
+
WEB_ANALYTICS_PUBLIC_HOST,
|
|
25
26
|
WEB_ANALYTICS_SITE_TAG,
|
|
26
27
|
WEB_ANALYTICS_SITE_TOKEN,
|
|
27
28
|
WEB_ANALYTICS_TAG,
|
|
28
29
|
htmlCarriesWebAnalytics,
|
|
30
|
+
webAnalyticsBootstrap,
|
|
29
31
|
webAnalyticsTag,
|
|
30
32
|
} from "./webAnalytics";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
WEB_ANALYTICS_SITE_TOKEN,
|
|
7
7
|
WEB_ANALYTICS_TAG,
|
|
8
8
|
htmlCarriesWebAnalytics,
|
|
9
|
+
webAnalyticsBootstrap,
|
|
9
10
|
webAnalyticsTag,
|
|
10
11
|
} from "./webAnalytics";
|
|
11
12
|
|
|
@@ -44,7 +45,69 @@ describe("the fleet's beacon", () => {
|
|
|
44
45
|
});
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* 🔴 The guard is EXECUTED, not read. It is eight lines of hand-written browser JavaScript
|
|
50
|
+
* that nothing else runs before production, and the whole reason it exists is a failure that
|
|
51
|
+
* only shows up at a particular hostname — so a test that merely asserted the string contains
|
|
52
|
+
* "cursedalchemy.com" would pass on a guard with the condition inverted.
|
|
53
|
+
*/
|
|
54
|
+
function runBootstrap(hostname: string): { appended: string[]; attrs: Record<string, string> } {
|
|
55
|
+
const body = /<script>([\s\S]*)<\/script>/.exec(webAnalyticsBootstrap())?.[1] ?? "";
|
|
56
|
+
expect(body.trim().length).toBeGreaterThan(0);
|
|
57
|
+
const appended: string[] = [];
|
|
58
|
+
const attrs: Record<string, string> = {};
|
|
59
|
+
const fakeDocument = {
|
|
60
|
+
createElement: () => ({
|
|
61
|
+
type: "",
|
|
62
|
+
src: "",
|
|
63
|
+
setAttribute(name: string, value: string) {
|
|
64
|
+
attrs[name] = value;
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
head: {
|
|
68
|
+
appendChild(el: { src: string }) {
|
|
69
|
+
appended.push(el.src);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
new Function("location", "document", body)({ hostname }, fakeDocument);
|
|
74
|
+
return { appended, attrs };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe("the hostname guard", () => {
|
|
78
|
+
test("appends the beacon on the zone, and on every satellite of it", () => {
|
|
79
|
+
for (const host of ["cursedalchemy.com", "collections.cursedalchemy.com", "vault.cursedalchemy.com"]) {
|
|
80
|
+
const { appended, attrs } = runBootstrap(host);
|
|
81
|
+
expect(appended, `${host} should load the beacon`).toEqual([WEB_ANALYTICS_BEACON_SRC]);
|
|
82
|
+
expect(attrs["data-cf-beacon"]).toBe(`{"token": "${WEB_ANALYTICS_SITE_TOKEN}"}`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 🔴 The failure this guard was added for, 2026-09-18. The beacon POSTs to
|
|
88
|
+
* `cloudflareinsights.com/cdn-cgi/rum`, which answers the preflight with
|
|
89
|
+
* `Access-Control-Allow-Origin: http://127.0.0.1` — no port — so a local page load prints a
|
|
90
|
+
* CORS error. `apps/music` lost 5 route-render checks to it and `apps/desk` lost 24, both
|
|
91
|
+
* asserting an empty console, and the owner's own `bun run dev` would have shown it too.
|
|
92
|
+
*/
|
|
93
|
+
test("loads NOTHING on localhost, 127.0.0.1 or a bare host", () => {
|
|
94
|
+
for (const host of ["localhost", "127.0.0.1", "::1", "app.test", ""]) {
|
|
95
|
+
expect(runBootstrap(host).appended, `${host} must not load the beacon`).toEqual([]);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a lookalike domain does not get the beacon either", () => {
|
|
100
|
+
// `endsWith` alone would admit this, which is why the guard tests for the dot.
|
|
101
|
+
expect(runBootstrap("evilcursedalchemy.com").appended).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
47
105
|
describe("htmlCarriesWebAnalytics", () => {
|
|
106
|
+
test("true for the bootstrap block a shell actually pastes", () => {
|
|
107
|
+
expect(htmlCarriesWebAnalytics(webAnalyticsBootstrap())).toBe(true);
|
|
108
|
+
expect(htmlCarriesWebAnalytics(webAnalyticsBootstrap("0".repeat(32)))).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
48
111
|
test("true for the tag as authored, and for a re-wrapped copy of it", () => {
|
|
49
112
|
expect(htmlCarriesWebAnalytics(`<head>${WEB_ANALYTICS_TAG}</head>`)).toBe(true);
|
|
50
113
|
// A formatter is allowed to break the attributes across lines; the check must survive it.
|
|
@@ -76,6 +76,58 @@ export function webAnalyticsTag(token: string = WEB_ANALYTICS_SITE_TOKEN): strin
|
|
|
76
76
|
/** The fleet's tag, for the one site this generation serves. */
|
|
77
77
|
export const WEB_ANALYTICS_TAG = webAnalyticsTag();
|
|
78
78
|
|
|
79
|
+
/** The zone. A page served from anywhere else must not load the beacon — see below. */
|
|
80
|
+
export const WEB_ANALYTICS_PUBLIC_HOST = "cursedalchemy.com";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* What a static shell actually pastes: the tag, behind a HOSTNAME GUARD.
|
|
84
|
+
*
|
|
85
|
+
* ## 🔴 Why the bare snippet cannot go in an `index.html`
|
|
86
|
+
*
|
|
87
|
+
* Measured 2026-09-18, the first time the eight shells carried it. The beacon posts its
|
|
88
|
+
* pageview to `https://cloudflareinsights.com/cdn-cgi/rum`, and Cloudflare answers that
|
|
89
|
+
* preflight with `Access-Control-Allow-Origin: http://127.0.0.1` — no port. So on every LOCAL
|
|
90
|
+
* page load the browser prints:
|
|
91
|
+
*
|
|
92
|
+
* > Access to XMLHttpRequest at 'https://cloudflareinsights.com/cdn-cgi/rum' from origin
|
|
93
|
+
* > 'http://127.0.0.1:57845' has been blocked by CORS policy … Failed to load resource
|
|
94
|
+
*
|
|
95
|
+
* Under Automatic Setup this never happened: the injection is done by the EDGE, so it existed
|
|
96
|
+
* only on the zone and a developer never saw it. Pasting the raw snippet into the source moves
|
|
97
|
+
* it to every `bun run dev` and every browser test — `apps/music` lost 5 route-render checks and
|
|
98
|
+
* `apps/desk` lost 24, both asserting the console is empty, and the owner's own local console
|
|
99
|
+
* would have gained a red line on every page.
|
|
100
|
+
*
|
|
101
|
+
* That is the exact cost he asked to be rid of, one environment over. So the guard: the beacon
|
|
102
|
+
* is appended only when the page is being served from {@link WEB_ANALYTICS_PUBLIC_HOST}, which
|
|
103
|
+
* is the only place it can report anyway.
|
|
104
|
+
*
|
|
105
|
+
* ## Why an inline script rather than a build-time injection
|
|
106
|
+
*
|
|
107
|
+
* A Vite plugin would mean ten `vite.config.ts` edits and a frontend build depending on a
|
|
108
|
+
* server package — and it would not even work: `apps/desk`'s browser suite runs against a
|
|
109
|
+
* production `dist` served from `127.0.0.1`, so a production-only injection is still a console
|
|
110
|
+
* error there. The hostname is the honest discriminator, not the build mode.
|
|
111
|
+
*
|
|
112
|
+
* 🔴 It needs inline script to be permitted on the page. Every shell that carries it already
|
|
113
|
+
* ships one; `apps/vault` does not, serves `script-src 'self'`, and is exempt from the beacon
|
|
114
|
+
* for that reason — see `tools/check-web-analytics.ts`.
|
|
115
|
+
*/
|
|
116
|
+
export function webAnalyticsBootstrap(token: string = WEB_ANALYTICS_SITE_TOKEN): string {
|
|
117
|
+
return [
|
|
118
|
+
"<script>",
|
|
119
|
+
`\tvar host = location.hostname;`,
|
|
120
|
+
`\tif (host === "${WEB_ANALYTICS_PUBLIC_HOST}" || host.endsWith(".${WEB_ANALYTICS_PUBLIC_HOST}")) {`,
|
|
121
|
+
`\t\tvar beacon = document.createElement("script");`,
|
|
122
|
+
`\t\tbeacon.type = "module";`,
|
|
123
|
+
`\t\tbeacon.src = "${WEB_ANALYTICS_BEACON_SRC}";`,
|
|
124
|
+
`\t\tbeacon.setAttribute("data-cf-beacon", '{"token": "${token}"}');`,
|
|
125
|
+
`\t\tdocument.head.appendChild(beacon);`,
|
|
126
|
+
"\t}",
|
|
127
|
+
"</script>",
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
79
131
|
/**
|
|
80
132
|
* Does `html` carry the beacon for this site?
|
|
81
133
|
*
|