fix: support Repeater workspace over HTTP LAN origins

This commit is contained in:
RuoJi6 committed 2026-10-25 22:22:24 -07:00
1 parent eb5d826f79
commit 2f42baf8a1
2 files changed
+43 -1

No files matched your search

@@ -41,8 +41,14 @@ export type TabSeed = Partial<
>;
export type TabOptions = { groupId?: string; title?: string; activate?: boolean };
let fallbackIdSequence = 0;
export function newId(): string {
return crypto.randomUUID();
// Local workspace IDs must also work on HTTP LAN origins without randomUUID.
return (
globalThis.crypto?.randomUUID?.() ??
`repeater-${Date.now().toString(36)}-${(++fallbackIdSequence).toString(36)}-${Math.random().toString(36).slice(2, 10)}`
);
}
export function emptyTab(groupId = DEFAULT_GROUP_ID, title = "Tab 1"): RepeaterTab {
@@ -15,6 +15,7 @@ const {
MAX_HISTORY,
MAX_RESPONSE_CHARS,
MAX_STORAGE_BYTES,
newId,
seedWorkspace,
serializeWorkspace,
truncateResponse,
@@ -357,3 +358,38 @@ test("migrated HTTP helpers retain raw request and response formatting", () => {
assert.equal(headersFromReqHead("\r\nGET / HTTP/1.1\r\nHost: example.test\r\n"), "Host: example.test");
assert.equal(buildRawResponse(response()), "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nok");
});
test("workspace initializes and adds distinct tabs on HTTP origins without randomUUID", (t) => {
const originalCrypto = Object.getOwnPropertyDescriptor(globalThis, "crypto");
Object.defineProperty(globalThis, "crypto", { configurable: true, value: {} });
t.after(() => {
if (originalCrypto) Object.defineProperty(globalThis, "crypto", originalCrypto);
else delete globalThis.crypto;
});
const workspace = defaultWorkspace();
const updated = addTab(workspace, { url: "https://example.test/" });
assert.equal(updated.tabs.length, 2);
assert.notEqual(updated.tabs[0].id, updated.tabs[1].id);
assert.equal(deserializeWorkspace(null).tabs.length, 1);
assert.equal(new Set(Array.from({ length: 100 }, () => newId())).size, 100);
});
test("fallback IDs stay distinct without crypto even when clock and randomness repeat", (t) => {
const originalCrypto = Object.getOwnPropertyDescriptor(globalThis, "crypto");
Object.defineProperty(globalThis, "crypto", { configurable: true, value: undefined });
t.after(() => {
if (originalCrypto) Object.defineProperty(globalThis, "crypto", originalCrypto);
else delete globalThis.crypto;
});
t.mock.method(Date, "now", () => 1700000000000);
t.mock.method(Math, "random", () => 0);
const ids = Array.from({ length: 100 }, () => newId());
assert.ok(ids.every((id) => typeof id === "string" && id.length > 0));
assert.equal(new Set(ids).size, ids.length);
});
test("secure origins retain native UUID generation", (t) => {
const native = t.mock.method(globalThis.crypto, "randomUUID", () => "native-uuid");
assert.equal(newId(), "native-uuid");
assert.equal(native.mock.callCount(), 1);
});