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
2026-09-28 10:22:28 +00:00
parent 2c2b9c90e4
commit 3a478cce3f
23 changed files with 1361 additions and 119 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
// Keep CouchDB database-version negotiation isolated from Setup URI generation.
// The exact release must match utils/livesync-commonlib-version.ts; the setup
// tool suite checks every static specifier before release.
export { checkRemoteVersion } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/pouchdb/negotiation";
export { PouchDB } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/pouchdb/pouchdb-browser";
export { checkRemoteVersion } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/compat/pouchdb/negotiation";
export { PouchDB } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/compat/pouchdb/pouchdb-browser";
+1 -1
View File
@@ -2,4 +2,4 @@
// Commonlib registry release. Static npm specifiers cannot interpolate this
// value, so livesync-commonlib-version.test.ts verifies the domain-specific
// facades against it.
export const LIVESYNC_COMMONLIB_VERSION = "0.1.0-rc.4";
export const LIVESYNC_COMMONLIB_VERSION = "0.1.32-next.0";
+2
View File
@@ -45,6 +45,8 @@ deno run --minimum-dependency-age=0 --config=./flyio/deno.jsonc --frozen --lock=
If `uri_passphrase` is omitted, the tool generates and prints a cryptographically random one. Store the Setup URI and its passphrase separately. The `passphrase` value protects synchronised Vault data and must also be stored safely.
The generator defaults to `uri_mode=ephemeral`. The URI opens only during the current fixed seven-day UTC window, and the tool prints its exact end time. It may have less than seven days remaining when generated. For an indefinitely reusable URI, set `uri_mode=persistent` before running the command. Persistent uses the existing encrypted format, which older clients that already support that format can read. The time condition controls opening the URI; it does not revoke settings or credentials after import.
### Object Storage
```sh
+38
View File
@@ -20,6 +20,14 @@ Deno.test("generates an Object Storage Setup URI with a selected S3 profile", as
passphrase: "vault-secret",
uri_passphrase: "setup-secret",
});
assert(
generated.mode === "ephemeral",
"the default Setup URI mode was not Ephemeral",
);
assert(
generated.usableUntil !== null && generated.usableUntil > Date.now(),
"the Ephemeral Setup URI did not report its usable end time",
);
const decoded = await decodeSettingsFromSetupURI(
generated.setupURI,
generated.setupPassphrase,
@@ -121,3 +129,33 @@ Deno.test("generates a random-room P2P Setup URI without copying a device identi
"the selected profile was not a P2P connection URI",
);
});
Deno.test("generates a Persistent Setup URI on explicit request", async () => {
const generated = await generateSetupURI({
remote_type: "p2p",
passphrase: "vault-secret",
uri_passphrase: "setup-secret",
uri_mode: "persistent",
});
assert(generated.mode === "persistent", "the explicit mode was not retained");
assert(
generated.usableUntil === null,
"Persistent unexpectedly has a time condition",
);
const decoded = await decodeSettingsFromSetupURI(
generated.setupURI,
generated.setupPassphrase,
);
assert(decoded, "the Persistent Setup URI could not be opened");
});
Deno.test("rejects an unknown Setup URI mode", async () => {
let rejected = false;
try {
await generateSetupURI({ uri_mode: "later" });
} catch (error) {
rejected = error instanceof Error &&
error.message === "uri_mode must be ephemeral or persistent";
}
assert(rejected, "the generator accepted an unknown Setup URI mode");
});
+54 -7
View File
@@ -1,12 +1,14 @@
import {
createNewVaultSettings,
encodeSettingsToSetupURI,
encodeTimeBoundSetupURI,
generateP2PRoomId,
isTimeBoundSetupURIUsableNow,
type ObsidianLiveSyncSettings,
P2P_DEFAULT_SETTINGS,
PREFERRED_BASE,
PREFERRED_JOURNAL_SYNC,
PREFERRED_SETTING_SELF_HOSTED,
type TimeBoundSetupURIMode,
upsertRemoteConfigurationInPlace,
} from "./livesync-commonlib.ts";
@@ -19,6 +21,8 @@ export interface GeneratedSetupURI {
remoteType: SetupRemoteType;
setupURI: string;
setupPassphrase: string;
mode: TimeBoundSetupURIMode;
usableUntil: number | null;
}
function requireValue(
@@ -147,6 +151,14 @@ function parseRemoteType(
throw new Error("remote_type must be couchdb, s3, or p2p");
}
function parseSetupURIMode(
environment: SetupGeneratorEnvironment,
): TimeBoundSetupURIMode {
const mode = environment.uri_mode?.trim().toLowerCase() || "ephemeral";
if (mode === "ephemeral" || mode === "persistent") return mode;
throw new Error("uri_mode must be ephemeral or persistent");
}
export function createSetupSettings(
environment: SetupGeneratorEnvironment,
): { remoteType: SetupRemoteType; settings: ObsidianLiveSyncSettings } {
@@ -165,19 +177,54 @@ export async function generateSetupURI(
): Promise<GeneratedSetupURI> {
const setupPassphrase = environment.uri_passphrase?.trim() ||
generateSecret();
const mode = parseSetupURIMode(environment);
const { remoteType, settings } = createSetupSettings(environment);
const setupURI = await encodeSettingsToSetupURI(settings, setupPassphrase, [
"pluginSyncExtendedSetting",
"doNotUseFixedRevisionForChunks",
], true);
return { remoteType, setupURI: setupURI.trim(), setupPassphrase };
const { uri, usableUntil } = await encodeTimeBoundSetupURI(
settings,
setupPassphrase,
{
mode,
removeProperties: [
"pluginSyncExtendedSetting",
"doNotUseFixedRevisionForChunks",
],
skipDefaultValue: true,
},
);
if (!isTimeBoundSetupURIUsableNow(usableUntil)) {
throw new Error("Setup URI time window changed during generation");
}
return {
remoteType,
setupURI: uri.trim(),
setupPassphrase,
mode,
usableUntil,
};
}
export async function runSetupURIGenerator(
environment: SetupGeneratorEnvironment = Deno.env.toObject(),
): Promise<void> {
const generated = await generateSetupURI(environment);
let generated = await generateSetupURI(environment);
if (!isTimeBoundSetupURIUsableNow(generated.usableUntil)) {
generated = await generateSetupURI(environment);
}
if (!isTimeBoundSetupURIUsableNow(generated.usableUntil)) {
throw new Error("Setup URI time window changed before it could be shown");
}
console.log(`\nGenerated ${generated.remoteType} Setup URI.`);
if (generated.usableUntil === null) {
console.log(
"Persistent: no time condition. Older clients can open this format.",
);
} else {
console.log(
`Ephemeral: usable until ${
new Date(generated.usableUntil).toISOString()
} (UTC).`,
);
}
console.log(
"Your passphrase for the Setup URI is:",
generated.setupPassphrase,
+8 -6
View File
@@ -3,10 +3,12 @@
// does not load the PouchDB browser adapter.
export {
decodeSettingsFromSetupURI,
encodeSettingsToSetupURI,
} from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/API/processSetting";
export { generateP2PRoomId } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/common/utils";
export { upsertRemoteConfigurationInPlace } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/remote-configurations";
encodeTimeBoundSetupURI,
isTimeBoundSetupURIUsableNow,
} from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/setup-uri";
export type { TimeBoundSetupURIMode } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/setup-uri";
export { generateP2PRoomId } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/compat/common/utils";
export { upsertRemoteConfigurationInPlace } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/remote-configurations";
export {
createNewVaultSettings,
DEFAULT_SETTINGS,
@@ -14,5 +16,5 @@ export {
PREFERRED_BASE,
PREFERRED_JOURNAL_SYNC,
PREFERRED_SETTING_SELF_HOSTED,
} from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/settings";
export type { ObsidianLiveSyncSettings } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/settings";
} from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/settings";
export type { ObsidianLiveSyncSettings } from "npm:@vrtmrz/livesync-commonlib@0.1.32-next.0/settings";