Ghostlight/web/test/heimdall-adapter.test.mjs
Meta Vens e42e1c1b31 Finish browser sign-in by attempt id, and on returning to the tab
Heimdall's Discord callback issues a random completion code distinct from
the attempt id, but the vendored adapter required the two to be equal and
rejected every real return as a malformed witness, so no player could sign
in. Re-vendored from Heimdall e8e2832, which completes by attempt id and
never reads the completion code; the test fixture issues a distinct code as
the server does.

A player who finishes Discord elsewhere and switches back to this tab never
reloads it, so the load-time resume never ran. The page now reruns the same
resume when it becomes visible with an attempt in flight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:51:20 +02:00

106 lines
4.3 KiB
JavaScript

import assert from "node:assert/strict";
import { afterEach, test } from "node:test";
import {
heimdallAccessBrowserAdapter,
readHeimdallBrowserReturn,
resumeHeimdallAccess,
} from "../src/heimdall-access-adapter.ts";
const attemptKey = "gamecult.heimdall.access.attempt";
const originalGlobals = {
history: globalThis.history,
sessionStorage: globalThis.sessionStorage,
window: globalThis.window,
};
afterEach(() => {
restoreGlobal("history", originalGlobals.history);
restoreGlobal("sessionStorage", originalGlobals.sessionStorage);
restoreGlobal("window", originalGlobals.window);
});
test("browser return parser completes by attempt id and never reads the completion code", () => {
const handle = "d0a9c1e7-89ac-46f9-8c85-c2484ed18f76";
assert.deepEqual(readHeimdallBrowserReturn(returnUrl(handle), "ghostlight"), { status: "success", handle });
assert.equal(readHeimdallBrowserReturn("https://yggdrasil.gamecult.org/ghostlight/", "ghostlight"), undefined);
assert.equal(readHeimdallBrowserReturn(returnUrl(handle), "another-app")?.status, "error");
assert.deepEqual(readHeimdallBrowserReturn(returnUrl(handle, "secret-redeem-code"), "ghostlight"), { status: "success", handle });
assert.ok(!JSON.stringify(readHeimdallBrowserReturn(returnUrl(handle, "secret-redeem-code"), "ghostlight")).includes("secret-redeem-code"));
assert.equal(readHeimdallBrowserReturn(returnUrl("", "secret-redeem-code"), "ghostlight")?.status, "error");
});
test("fresh browser context redeems the URL witness and scrubs it", async () => {
const handle = "0be30b8c-b599-4920-b8b8-733f5e7bc275";
const storage = memoryStorage();
const replaced = [];
installBrowserGlobals(returnUrl(handle), storage, replaced);
const completed = [];
assert.equal(await resumeHeimdallAccess({ complete: async value => completed.push(value) }, { appSlug: "ghostlight" }), true);
assert.deepEqual(completed, [handle]);
assert.equal(storage.getItem(attemptKey), handle);
assert.deepEqual(replaced, ["/ghostlight/"]);
await heimdallAccessBrowserAdapter.consumeCommandResult({
schemaId: "heimdall.auth_completion_status.v1",
payload: { status: "authenticated" },
});
assert.equal(storage.getItem(attemptKey), null);
});
test("conflicting stored and returned attempts never reach completion", async () => {
const storage = memoryStorage([[attemptKey, "stored-handle"]]);
installBrowserGlobals(returnUrl("returned-handle"), storage, []);
let completed = false;
await assert.rejects(
resumeHeimdallAccess({ complete: async () => { completed = true; } }, { appSlug: "ghostlight" }),
/other than the one this browser started/,
);
assert.equal(completed, false);
assert.equal(storage.getItem(attemptKey), null);
});
function installBrowserGlobals(href, storage, replaced) {
Object.defineProperty(globalThis, "sessionStorage", { configurable: true, value: storage });
Object.defineProperty(globalThis, "history", {
configurable: true,
value: { replaceState: (_state, _title, url) => replaced.push(url) },
});
Object.defineProperty(globalThis, "window", {
configurable: true,
value: { location: { href, pathname: "/ghostlight/", search: "" } },
});
}
function restoreGlobal(name, value) {
if (value === undefined) delete globalThis[name];
else Object.defineProperty(globalThis, name, { configurable: true, value });
}
// Heimdall issues a random completion code distinct from the attempt id, as
// the real callback does; the fixture must not restate the old equality.
function returnUrl(attemptId, completionCode = `code-for-${attemptId}`) {
const parameters = new URLSearchParams({
heimdall_status: "success",
heimdall_provider: "discord",
heimdall_handoff_kind: "browser_completion",
heimdall_app_slug: "ghostlight",
heimdall_mode: "sign_in",
heimdall_attempt_id: attemptId,
heimdall_completion_code: completionCode,
});
return `https://yggdrasil.gamecult.org/ghostlight/#${parameters}`;
}
function memoryStorage(initial = []) {
const values = new Map(initial);
return {
get length() { return values.size; },
clear: () => values.clear(),
getItem: key => values.get(key) ?? null,
key: index => [...values.keys()][index] ?? null,
removeItem: key => { values.delete(key); },
setItem: (key, value) => { values.set(key, value); },
};
}