A tab names the account it was claimed for on every request, beacon and upload (X-Siftlode-Expect-Account); current_user and /auth/logout refuse a mismatch with 401 account_changed and keep the session, so a tab left showing the account signed in before reloads instead of writing to the new one. /auth/login clears a connect marker left by an abandoned connect, and a link completes only on a session that still holds. The prefs merge reads the row under a lock. A tab's sessionStorage records its own owner. A page the account loses is given up, address bar and remembered page included. The panel layouts are preferences; the storage contract also refuses a key handed to the Storage API or to usePref outside its kind. A refused sign-in as someone else and a failed sign-out say so. The chromium-only e2e specs are in the gate register, the member walk waits for its requests, and devices.spec checks the save itself.
33 lines
1.3 KiB
TypeScript
33 lines
1.3 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// The request layer, the notification store and the error boundary all log through lib/log.ts, so
|
|
// log.ts must not reach any of them at runtime: the import would be a cycle, and the module that
|
|
// logs a failure would load the one that failed. A type-only import is erased, so it does not count.
|
|
// The comment in log.ts said this for a long time while nothing checked it.
|
|
|
|
const LIB = new URL("./", import.meta.url);
|
|
const ALLOWED = ["log.ts", "storage.ts", "version.ts"];
|
|
|
|
/** The app modules `file` imports at runtime (relative, not `import type`), as lib/ file names. */
|
|
function runtimeImports(file: string): string[] {
|
|
const text = readFileSync(new URL(file, LIB), "utf-8");
|
|
return [...text.matchAll(/^import\s+(?!type\b)[^;]*?from\s+"(\.[^"]+)";/gms)].map(
|
|
(m) => `${m[1]!.replace(/^\.\//, "")}.ts`,
|
|
);
|
|
}
|
|
|
|
describe("lib/log.ts", () => {
|
|
it("reaches only the build stamp and the key registry at runtime", () => {
|
|
const seen = new Set<string>();
|
|
const queue = ["log.ts"];
|
|
while (queue.length) {
|
|
const file = queue.pop()!;
|
|
if (seen.has(file)) continue;
|
|
seen.add(file);
|
|
if (ALLOWED.includes(file)) queue.push(...runtimeImports(file));
|
|
}
|
|
expect([...seen].sort()).toEqual([...ALLOWED].sort());
|
|
});
|
|
});
|