Prepare time-bound Setup URI integration for Commonlib release

Add Time-bound and Compatible generation to the Obsidian dialogue, browser peer check, and setup tools, with boundary and compatibility coverage. Show the fixed window end before sharing a Time-bound URI.

This review branch depends on unpublished @vrtmrz/livesync-commonlib 0.1.32-next.0. The root npm pin and Deno lockfile remain unchanged until that candidate is available; a fresh install of this commit is not yet expected to build.
This commit is contained in:
vorotamoroz committed 2026-09-28 10:22:28 +00:00
1 parent 2c2b9c90e4
commit 3a478cce3f
23 files changed
+1361 -119

No files matched your search

@@ -1,119 +1,203 @@
import { describe, expect, it, vi, afterEach } from "vitest";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
import {
encodeTimeBoundSetupURI,
getTimeBoundSetupURIUsableUntil,
isTimeBoundSetupURIUsableNow,
} from "@vrtmrz/livesync-commonlib/setup-uri";
import { askEncryptingPassphrase, copySetupURI, copySetupURIFull, useSetupURIFeature } from "./setupUri";
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
return {
encodeSettingsToSetupURI: vi.fn(),
};
});
vi.mock("@vrtmrz/livesync-commonlib/setup-uri", () => ({
encodeTimeBoundSetupURI: vi.fn(),
getTimeBoundSetupURIUsableUntil: vi.fn(),
isTimeBoundSetupURIUsableNow: vi.fn(),
}));
describe("setupObsidian/setupUri", () => {
const usableUntil = Date.parse("2026-10-01T00:00:00Z");
beforeEach(() => {
vi.mocked(getTimeBoundSetupURIUsableUntil).mockReturnValue(usableUntil);
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "obsidian://setup-time-bound ", usableUntil });
vi.mocked(isTimeBoundSetupURIUsableNow).mockReturnValue(true);
});
afterEach(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("askEncryptingPassphrase should delegate to confirm.askString", async () => {
const askString = vi.fn(() => "secret");
const host = {
services: {
UI: {
confirm: {
askString,
},
},
},
} as any;
it("uses the existing password prompt", async () => {
const askString = vi.fn(async () => "secret");
const host = { services: { UI: { confirm: { askString } } } } as any;
const result = await askEncryptingPassphrase(host);
expect(result).toBe("secret");
expect(askString).toHaveBeenCalled();
await expect(askEncryptingPassphrase(host)).resolves.toBe("secret");
expect(askString).toHaveBeenCalledWith(
"Encrypt your settings",
"The passphrase to encrypt the setup URI",
"",
true
);
});
it("copySetupURI should return early when user cancels passphrase", async () => {
const promptCopyToClipboard = vi.fn();
const host = {
services: {
setting: {
currentSettings: vi.fn(() => ({ foo: "bar" })),
},
UI: {
confirm: {
askString: vi.fn(() => false),
},
promptCopyToClipboard,
},
},
} as any;
const log = vi.fn();
await copySetupURI(host, log);
expect(encodeSettingsToSetupURI).not.toHaveBeenCalled();
expect(promptCopyToClipboard).not.toHaveBeenCalled();
expect(log).not.toHaveBeenCalled();
});
it("copySetupURI should encode with short mode by default", async () => {
const promptCopyToClipboard = vi.fn(() => true);
it("shows the exact Time-bound end at selection and uses it by default", async () => {
const askSelectStringDialogue = vi.fn(async () => "Time-bound");
const promptCopyToClipboard = vi.fn(async () => true);
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
const host = {
services: {
setting: {
currentSettings: vi.fn(() => currentSettings),
},
setting: { currentSettings: vi.fn(() => currentSettings) },
UI: {
confirm: {
askString: vi.fn(() => "pass"),
},
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
promptCopyToClipboard,
},
},
} as any;
const log = vi.fn();
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://value" as any);
await copySetupURI(host, log);
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(
currentSettings,
"pass",
["pluginSyncExtendedSetting"],
true
expect(askSelectStringDialogue).toHaveBeenCalledWith(
expect.stringContaining("2026"),
["Time-bound", "Compatible (no time limit)", "Cancel"],
{
title: "Setup URI availability",
defaultAction: "Time-bound",
}
);
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://value");
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith(currentSettings, "pass", {
mode: "ephemeral",
removeProperties: ["pluginSyncExtendedSetting"],
skipDefaultValue: true,
});
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "obsidian://setup-time-bound ");
expect(log).toHaveBeenCalled();
});
it("copySetupURIFull should encode with full mode", async () => {
const promptCopyToClipboard = vi.fn(() => true);
it("selects old-reader-compatible output without a time limit and preserves full export settings", async () => {
const promptCopyToClipboard = vi.fn(async () => true);
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
const host = {
services: {
setting: {
currentSettings: vi.fn(() => currentSettings),
},
setting: { currentSettings: vi.fn(() => currentSettings) },
UI: {
confirm: {
askString: vi.fn(() => "pass-full"),
askString: vi.fn(async () => "pass"),
askSelectStringDialogue: vi.fn(async () => "Compatible (no time limit)"),
},
promptCopyToClipboard,
},
},
} as any;
const log = vi.fn();
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://full" as any);
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({
uri: "obsidian://setup-compatible ",
usableUntil: null,
});
await copySetupURIFull(host, log);
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(currentSettings, "pass-full", [], false);
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://full");
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith(currentSettings, "pass", {
mode: "persistent",
removeProperties: [],
skipDefaultValue: false,
});
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "obsidian://setup-compatible ");
expect(log).toHaveBeenCalled();
});
it("keeps Compatible available when the clock is invalid", async () => {
vi.mocked(getTimeBoundSetupURIUsableUntil).mockImplementation(() => {
throw new Error("Invalid Setup URI clock");
});
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "compatible", usableUntil: null });
const askSelectStringDialogue = vi.fn(async () => "Compatible (no time limit)");
const host = {
services: {
setting: { currentSettings: vi.fn(() => ({})) },
UI: {
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
promptCopyToClipboard: vi.fn(async () => false),
},
},
} as any;
await copySetupURI(host, vi.fn());
expect(askSelectStringDialogue).toHaveBeenCalledWith(
expect.stringContaining("valid device clock"),
["Compatible (no time limit)", "Cancel"],
expect.objectContaining({ defaultAction: "Compatible (no time limit)" })
);
});
it("does not generate after password or mode cancellation", async () => {
const askString = vi.fn(async (): Promise<string | false> => false);
const askSelectStringDialogue = vi.fn(async () => "Cancel");
const promptCopyToClipboard = vi.fn();
const host = {
services: {
setting: { currentSettings: vi.fn(() => ({})) },
UI: { confirm: { askString, askSelectStringDialogue }, promptCopyToClipboard },
},
} as any;
await copySetupURI(host, vi.fn());
expect(askSelectStringDialogue).not.toHaveBeenCalled();
askString.mockResolvedValueOnce("pass");
await copySetupURI(host, vi.fn());
expect(encodeTimeBoundSetupURI).not.toHaveBeenCalled();
expect(promptCopyToClipboard).not.toHaveBeenCalled();
});
it("keeps an empty passphrase distinct from cancelling the existing prompt", async () => {
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "compatible", usableUntil: null });
const promptCopyToClipboard = vi.fn(async () => false);
const host = {
services: {
setting: { currentSettings: vi.fn(() => ({})) },
UI: {
confirm: {
askString: vi.fn(async () => ""),
askSelectStringDialogue: vi.fn(async () => "Compatible (no time limit)"),
},
promptCopyToClipboard,
},
},
} as any;
await copySetupURI(host, vi.fn());
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith({}, "", expect.objectContaining({ mode: "persistent" }));
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "compatible");
});
it("asks again if the window changes after selection", async () => {
const nextUntil = usableUntil + 604_800_000;
vi.mocked(getTimeBoundSetupURIUsableUntil).mockReturnValueOnce(usableUntil).mockReturnValueOnce(nextUntil);
vi.mocked(encodeTimeBoundSetupURI)
.mockResolvedValueOnce({ uri: "old-window", usableUntil: nextUntil })
.mockResolvedValueOnce({ uri: "new-window", usableUntil: nextUntil });
const askSelectStringDialogue = vi.fn(async () => "Time-bound");
const promptCopyToClipboard = vi.fn(async () => false);
const host = {
services: {
setting: { currentSettings: vi.fn(() => ({})) },
UI: {
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
promptCopyToClipboard,
},
},
} as any;
await copySetupURI(host, vi.fn());
expect(askSelectStringDialogue).toHaveBeenCalledTimes(2);
expect(promptCopyToClipboard).toHaveBeenCalledTimes(1);
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "new-window");
});
it("useSetupURIFeature should register onLoaded handler that wires commands and event", async () => {
const addHandler = vi.fn();
const addCommand = vi.fn();