@celestia-island/hikari 0.55.179 → 0.55.180
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/package.json
CHANGED
|
@@ -14,7 +14,10 @@ export async function probeOrigin(
|
|
|
14
14
|
url: string,
|
|
15
15
|
opts?: { fetchFn?: typeof fetch },
|
|
16
16
|
): Promise<OriginProbe> {
|
|
17
|
-
|
|
17
|
+
// Bare `?? fetch` detaches Window's fetch receiver — the default path
|
|
18
|
+
// throws "Illegal invocation" in a browser (2026-10-17 chest live
|
|
19
|
+
// report family). bind(globalThis) re-attaches it, re-resolved per call.
|
|
20
|
+
const fetchFn = opts?.fetchFn ?? fetch.bind(globalThis);
|
|
18
21
|
try {
|
|
19
22
|
// A successful credentialed fetch means reachable + CORS-whitelisted,
|
|
20
23
|
// regardless of the HTTP status code (4xx/5xx still prove the origin).
|
|
@@ -41,7 +44,8 @@ export async function probeOriginWithBody(
|
|
|
41
44
|
url: string,
|
|
42
45
|
opts?: { fetchFn?: typeof fetch },
|
|
43
46
|
): Promise<{ ok: boolean; body?: string }> {
|
|
44
|
-
|
|
47
|
+
// Same receiver rule as probeOrigin: never capture Window's fetch bare.
|
|
48
|
+
const fetchFn = opts?.fetchFn ?? fetch.bind(globalThis);
|
|
45
49
|
try {
|
|
46
50
|
const resp = await fetchFn(url, { credentials: "include", mode: "cors" });
|
|
47
51
|
return { ok: true, body: await resp.text().catch(() => undefined) };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { probeOrigin, probeOriginWithBody } from "./connectivity";
|
|
4
|
+
import { probeHealthEndpoint } from "./healthProbe";
|
|
5
|
+
import { fetchChallenge, negotiateNonce } from "./powNonce";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The browser's `fetch` is a Window method: capturing it as a bare value
|
|
9
|
+
* (`opts?.fetchFn ?? fetch`) detaches the receiver, and every default call
|
|
10
|
+
* throws "Failed to execute 'fetch' on 'Window': Illegal invocation"
|
|
11
|
+
* (2026-10-17 chest live report: branding silently fell back and health
|
|
12
|
+
* probes reported every panel unreachable — the TypeError was swallowed
|
|
13
|
+
* into `{ok:false}` results). Node's fetch does not check its receiver,
|
|
14
|
+
* so these tests stub the browser's exact contract — `this !== globalThis`
|
|
15
|
+
* rejects with the same TypeError — making a detached capture red here.
|
|
16
|
+
*/
|
|
17
|
+
function stubWindowFetch(): void {
|
|
18
|
+
vi.stubGlobal(
|
|
19
|
+
"fetch",
|
|
20
|
+
vi.fn(function (this: unknown, input: RequestInfo | URL): Promise<Response> {
|
|
21
|
+
if (this !== globalThis) {
|
|
22
|
+
return Promise.reject(
|
|
23
|
+
new TypeError("Failed to execute 'fetch' on 'Window': Illegal invocation"),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const url = String(input);
|
|
27
|
+
if (url.endsWith("/health")) {
|
|
28
|
+
return Promise.resolve(new Response(JSON.stringify({}), { status: 200 }));
|
|
29
|
+
}
|
|
30
|
+
if (url.endsWith("/auth/nonce")) {
|
|
31
|
+
return Promise.resolve(
|
|
32
|
+
new Response(JSON.stringify({ nonce: "nonce-1" }), { status: 200 }),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
// probeOrigin / probeOriginWithBody / health probe targets.
|
|
36
|
+
return Promise.resolve(new Response("pong", { status: 200 }));
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("connectivity / healthProbe / powNonce — default fetches keep the Window receiver", () => {
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
vi.unstubAllGlobals();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("probeOrigin's default fetchFn probes instead of throwing illegal invocation", async () => {
|
|
47
|
+
stubWindowFetch();
|
|
48
|
+
|
|
49
|
+
await expect(probeOrigin("https://panel.example/health")).resolves.toEqual({ ok: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("probeOriginWithBody's default fetchFn captures the body", async () => {
|
|
53
|
+
stubWindowFetch();
|
|
54
|
+
|
|
55
|
+
// Non-/health URL: the stub's default branch answers the body "pong".
|
|
56
|
+
await expect(probeOriginWithBody("https://panel.example/ping")).resolves.toEqual({
|
|
57
|
+
ok: true,
|
|
58
|
+
body: "pong",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("probeHealthEndpoint's default fetchFn reads a healthy body", async () => {
|
|
63
|
+
stubWindowFetch();
|
|
64
|
+
|
|
65
|
+
const result = await probeHealthEndpoint("https://panel.example/health");
|
|
66
|
+
expect(result.ok).toBe(true);
|
|
67
|
+
expect(result.body).toEqual({});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("powNonce defaults complete the challenge/nonce exchange", async () => {
|
|
71
|
+
stubWindowFetch();
|
|
72
|
+
|
|
73
|
+
// `undefined` (fetch failed) would mean the default threw — the exact
|
|
74
|
+
// silent regression that dropped the X-Nonce on chest's live login.
|
|
75
|
+
await expect(fetchChallenge("/api")).resolves.toBe(null);
|
|
76
|
+
await expect(negotiateNonce("/api")).resolves.toBe("nonce-1");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("an explicit fetchFn still wins over every default", async () => {
|
|
80
|
+
const guard = vi.fn(() => Promise.reject(new Error("global fetch must not be called")));
|
|
81
|
+
vi.stubGlobal("fetch", guard);
|
|
82
|
+
const explicit = vi.fn(async () => new Response("{}", { status: 200 }));
|
|
83
|
+
|
|
84
|
+
await probeOrigin("https://panel.example/", { fetchFn: explicit });
|
|
85
|
+
await probeOriginWithBody("https://panel.example/", { fetchFn: explicit });
|
|
86
|
+
await probeHealthEndpoint("https://panel.example/", { fetchFn: explicit });
|
|
87
|
+
await fetchChallenge("/api", explicit);
|
|
88
|
+
await negotiateNonce("/api", { fetchFn: explicit });
|
|
89
|
+
|
|
90
|
+
// negotiateNonce rides the explicit fn twice (challenge + exchange).
|
|
91
|
+
expect(explicit).toHaveBeenCalledTimes(6);
|
|
92
|
+
expect(guard).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
});
|
package/src/utils/healthProbe.ts
CHANGED
|
@@ -40,7 +40,11 @@ export async function probeHealthEndpoint(
|
|
|
40
40
|
url: string,
|
|
41
41
|
opts: { timeoutMs?: number; fetchFn?: typeof fetch } = {},
|
|
42
42
|
): Promise<HealthProbeResult> {
|
|
43
|
-
|
|
43
|
+
// Bare `?? fetch` captures Window's fetch detached — every default
|
|
44
|
+
// call throws "Illegal invocation" in a browser (2026-10-17 chest live
|
|
45
|
+
// report; the probe then silently reported every panel unreachable).
|
|
46
|
+
// bind(globalThis) re-attaches the receiver and re-resolves per call.
|
|
47
|
+
const fetchFn = opts.fetchFn ?? fetch.bind(globalThis);
|
|
44
48
|
const started = performance.now();
|
|
45
49
|
try {
|
|
46
50
|
const resp = await fetchFn(url, {
|
package/src/utils/powNonce.ts
CHANGED
|
@@ -29,7 +29,11 @@ function normalizeBase(baseUrl: string): string {
|
|
|
29
29
|
*/
|
|
30
30
|
export async function fetchChallenge(
|
|
31
31
|
baseUrl: string,
|
|
32
|
-
|
|
32
|
+
// Bare `= fetch` captures Window's fetch detached — the default throws
|
|
33
|
+
// "Illegal invocation" in a browser (2026-10-17 chest live report
|
|
34
|
+
// family). bind(globalThis) re-attaches the receiver, re-resolved per
|
|
35
|
+
// call so vi.stubGlobal stubs stay visible.
|
|
36
|
+
fetchFn: typeof fetch = fetch.bind(globalThis),
|
|
33
37
|
): Promise<ChallengeDescriptor | undefined> {
|
|
34
38
|
let resp: Response;
|
|
35
39
|
try {
|
|
@@ -59,7 +63,8 @@ export async function negotiateNonce(
|
|
|
59
63
|
},
|
|
60
64
|
): Promise<string | undefined> {
|
|
61
65
|
try {
|
|
62
|
-
|
|
66
|
+
// Same receiver rule as fetchChallenge's default: never bare `?? fetch`.
|
|
67
|
+
const fetchFn = opts?.fetchFn ?? fetch.bind(globalThis);
|
|
63
68
|
let body: Record<string, unknown> | undefined;
|
|
64
69
|
if (opts?.captchaToken) {
|
|
65
70
|
body = { type: "captcha", token: opts.captchaToken };
|