mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-18 08:37:06 +00:00
Use existing Setup URI and QR sharing for TURN settings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
date: 2026-09-15
|
||||
commonlib-version: "0.1.25-dev.turn-credentials.3"
|
||||
commonlib-version: "0.1.25-dev.turn-credentials.4"
|
||||
self-hosted-livesync-version: "1.0.28"
|
||||
status: unreleased
|
||||
---
|
||||
@@ -151,21 +151,21 @@ and report a safe error; it must not silently save a plaintext replacement.
|
||||
| Destination | Provider API token | Issued TURN username and credential |
|
||||
| --- | --- | --- |
|
||||
| Saved P2P profile | Included | Omitted |
|
||||
| Encrypted Setup URI | Included with the source and Key ID | Omitted |
|
||||
| Setup URI and QR code | Included with the source and Key ID | Omitted |
|
||||
| Runtime room configuration | Available only to the source | Cached in memory and passed to WebRTC |
|
||||
| General report or diagnostic log | Redacted | Redacted |
|
||||
|
||||
Encrypted Setup URI sharing is the complete sharing route for managed
|
||||
profiles. Preserve the independent main-remote and P2P selections and the
|
||||
receiving device's own peer name. Raw profile and unencrypted QR copy actions
|
||||
should offer encrypted Setup URI sharing when their output includes a managed
|
||||
source, including one in an inactive profile. Never substitute a temporary
|
||||
TURN password or silently export a profile missing its API token.
|
||||
Setup URI, QR code, and profile sharing include the source configuration as
|
||||
ordinary connection settings. Preserve the independent main-remote and P2P
|
||||
selections and the receiving device's own peer name. Setup URIs keep their
|
||||
existing passphrase encryption; QR codes keep their existing unencrypted
|
||||
format and 'FOR YOUR EYES ONLY' display. Include managed sources in inactive
|
||||
profiles as well. Issued temporary credentials remain runtime state.
|
||||
|
||||
Markdown settings export must not leak tokens through either the top-level
|
||||
source or a profile URI. For this first delivery, omit the profile collection,
|
||||
its selections, and the source projection together when managed profiles are
|
||||
present, and explain that connection sharing uses the encrypted Setup URI.
|
||||
present, and explain that connection sharing uses Setup URIs or QR codes.
|
||||
Importing Markdown without that group preserves the local profiles and
|
||||
selections rather than replacing them with a filtered collection.
|
||||
|
||||
@@ -404,31 +404,24 @@ LiveSync Setup dialogue, import handler, profile export, Markdown settings,
|
||||
and report paths. Existing fixed-field serialisers would otherwise discard
|
||||
the source. Generated credentials never populate the manual fields.
|
||||
|
||||
Managed profile strings need a distinguishable format,
|
||||
`sls+p2p-v2://`. Commonlib `0.1.24` rejects that scheme, whereas it silently
|
||||
drops unknown fields in ordinary `sls+p2p://` strings. Manual profiles retain
|
||||
their current format. Validate the source before activation; unknown sources
|
||||
must not become manual connections.
|
||||
P2P profiles keep `sls+p2p://` and carry the optional source descriptor in an
|
||||
additional `source` query parameter. Full Setup URIs keep
|
||||
`obsidian://setuplivesync?settings=` and encrypt the existing settings object
|
||||
directly. QR codes carry the same source through an appended setting index.
|
||||
Existing setting indices and the established formats remain unchanged.
|
||||
|
||||
Full encrypted Setup URIs also need a distinguishable outer format,
|
||||
`obsidian://setuplivesync-v2?settings=`, and a versioned encrypted
|
||||
envelope when managed profiles are included. The old import path decrypts and
|
||||
merges arbitrary JSON, so a nested profile version alone is insufficient.
|
||||
Validate the new envelope before applying settings in every maintained host.
|
||||
Apply stored settings schema checks on load and import, including downgrades;
|
||||
older clients must not activate a managed profile after dropping its source.
|
||||
Document any minimum-client and downgrade requirements with the implementation.
|
||||
Missing source settings use the ordinary manual defaults. Older clients follow
|
||||
their existing handling of additional fields; this feature adds no URI-version
|
||||
gate or stored-settings restriction to prevent them from loading settings.
|
||||
Clients which support source descriptors preserve unknown identifiers and
|
||||
versions and validate them before activation, rather than silently selecting
|
||||
manual TURN when an explicitly configured source is unsupported.
|
||||
|
||||
For a selected managed source, save the complete P2P connection in its
|
||||
versioned profile and disable the persisted legacy P2P projection: clear its
|
||||
Group ID and passphrase, and save `P2P_Enabled` and `P2P_AutoStart` as false.
|
||||
A compatible client restores those runtime values from the selected profile.
|
||||
This prevents an older client which rejects the profile URI from connecting
|
||||
through leftover manual fields. Source-only settings without a configured
|
||||
room can remain disabled until setup is complete. The live settings and
|
||||
setting-saved notifications retain their usable runtime values. A selected
|
||||
manual profile retains its established persisted representation, even when
|
||||
another saved profile has a managed source.
|
||||
Persist the ordinary P2P settings projection alongside its profile. Keep the
|
||||
Group ID, enabled state, and autostart preference consistent with the current
|
||||
settings. Optional configuration encryption continues to protect both the
|
||||
profile and its source configuration. Issued credentials never populate the
|
||||
persisted manual TURN fields.
|
||||
|
||||
The P2P data protocol and Group ID remain unchanged. A peer using manually
|
||||
configured TURN can communicate with one using issued credentials; validate
|
||||
@@ -465,7 +458,7 @@ results contain no provider token, TURN username, or TURN credential.
|
||||
- Manual configuration, default STUN, and existing Setup URIs retain their
|
||||
behaviour. Unsupported managed sources fail explicitly.
|
||||
- Provider tokens survive restart, profile selection, optional configuration
|
||||
encryption, and encrypted Setup URI sharing. Reports and logs reveal no
|
||||
encryption, and ordinary Setup URI and QR code sharing. Reports and logs reveal no
|
||||
tokens or issued credentials, including inactive and encoded copies.
|
||||
- Issued credentials never enter persisted settings, exports, or reports.
|
||||
- Equivalent settings and valid credentials reuse the room. Expired
|
||||
|
||||
+6
-7
@@ -53,13 +53,12 @@ particular TURN provider or operate a credential broker. See Cloudflare's
|
||||
[credential instructions](https://developers.cloudflare.com/realtime/turn/generate-credentials/)
|
||||
for creating a TURN key and its API token.
|
||||
|
||||
The API token is saved with the P2P profile. Use an encrypted Setup URI to share
|
||||
it with your other devices. Managed profiles use the versioned Setup URI format
|
||||
and require a client which supports that format; update receiving devices
|
||||
before importing it. Older clients leave the saved managed P2P connection
|
||||
disabled. Select and save a manual TURN configuration in a compatible client
|
||||
before downgrading if P2P must remain usable. Plain QR export redirects to
|
||||
encrypted Setup URI sharing.
|
||||
The API token is saved with the P2P profile and included when sharing settings
|
||||
through an existing Setup URI or QR code. Setup URIs retain their existing
|
||||
passphrase encryption. QR codes retain their existing unencrypted format and
|
||||
'FOR YOUR EYES ONLY' display. Missing source settings use the ordinary manual
|
||||
configuration defaults. Receiving clients need support for the selected source
|
||||
to acquire its temporary TURN credentials.
|
||||
Markdown settings omit the connection profile group when it contains a managed
|
||||
TURN source, including inactive profiles, and importing those omitted settings
|
||||
preserves this device's existing profiles. Diagnostic reports redact the source
|
||||
|
||||
+1
-1
@@ -491,7 +491,7 @@ Setting key: P2P_iceServerSource
|
||||
|
||||
Select **Manual** for the existing TURN server fields, or **Cloudflare** for a
|
||||
TURN Key ID and TURN Key API Token. The API token is persisted with the profile
|
||||
and included in encrypted Setup URI sharing. Issued temporary credentials are
|
||||
and included in Setup URI and QR code sharing. Issued temporary credentials are
|
||||
kept in memory only. Reports redact the source configuration. See
|
||||
[TURN credentials](p2p.md#turn-credentials) for sharing, expiry, and reconnect
|
||||
behaviour.
|
||||
|
||||
Generated
+4
-4
@@ -23,7 +23,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.3.tgz",
|
||||
"@vrtmrz/livesync-commonlib": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.4.tgz",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
@@ -4567,9 +4567,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vrtmrz/livesync-commonlib": {
|
||||
"version": "0.1.25-dev.turn-credentials.3",
|
||||
"resolved": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.3.tgz",
|
||||
"integrity": "sha512-hngE1zlNocD8IeMgRvssX4WN7SpINOyWOp9Mzs0PGM10yzVvYtR5WcVdxWtEIbaA71DLk9SPmgmQkgu+o7Oy8g==",
|
||||
"version": "0.1.25-dev.turn-credentials.4",
|
||||
"resolved": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.4.tgz",
|
||||
"integrity": "sha512-q8jFJYzBzp7fsE+fU48A8gTvQ5DnJexkhReV8hUu2vhZvlV0e2ywUpPxkzx5KNNwSVpLSX96JCHRBLUvDMXKUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.3.tgz",
|
||||
"@vrtmrz/livesync-commonlib": "file:.devmemo.local/packages/vrtmrz-livesync-commonlib-0.1.25-dev.turn-credentials.4.tgz",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { decodeSettingsFromSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { configURIBase, configURIBaseV2 } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
|
||||
import { configURIBase } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
type FilePathWithPrefix,
|
||||
@@ -298,7 +298,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
throw new Error("setup requires one argument: <setupURI>");
|
||||
}
|
||||
const setupURI = options.commandArgs[0].trim();
|
||||
if (!setupURI.startsWith(configURIBase) && !setupURI.startsWith(configURIBaseV2)) {
|
||||
if (!setupURI.startsWith(configURIBase)) {
|
||||
throw new Error(`setup URI must start with ${configURIBase}`);
|
||||
}
|
||||
const passphrase = await standardIo.prompt("Enter setup URI passphrase: ");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fsPromises as fs, os, path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import * as processSetting from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
|
||||
import { configURIBase, configURIBaseV2 } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
|
||||
import { configURIBase } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
REMOTE_COUCHDB,
|
||||
@@ -419,7 +419,7 @@ describe("runCommand abnormal cases", () => {
|
||||
expect(appliedSettings.useIndexedDBAdapter).toBe(false);
|
||||
});
|
||||
|
||||
it("setup imports managed TURN through the versioned encrypted URI", async () => {
|
||||
it("setup imports managed TURN through the existing encrypted URI", async () => {
|
||||
const core = createCoreMock();
|
||||
const source = {
|
||||
version: 1,
|
||||
@@ -434,7 +434,7 @@ describe("runCommand abnormal cases", () => {
|
||||
},
|
||||
passphrase
|
||||
);
|
||||
expect(setupURI.startsWith(configURIBaseV2)).toBe(true);
|
||||
expect(setupURI.startsWith(configURIBase)).toBe(true);
|
||||
expect(setupURI).not.toContain("private-token");
|
||||
core.services.context.standardIo.prompt.mockResolvedValue(passphrase);
|
||||
await runCommand(makeOptions("setup", [setupURI]), { ...context, core });
|
||||
|
||||
@@ -16,8 +16,8 @@ export const liveSyncProvisionalEnglishMessages = {
|
||||
"TURN Key ID": "TURN Key ID",
|
||||
"TURN Key API Token": "TURN Key API Token",
|
||||
"Unsupported TURN configuration": "Unsupported TURN configuration",
|
||||
"The API token is saved with this profile and included in encrypted Setup URI sharing. Temporary TURN credentials are kept in memory only.":
|
||||
"The API token is saved with this profile and included in encrypted Setup URI sharing. Temporary TURN credentials are kept in memory only.",
|
||||
"The API token is saved with this profile and included in Setup URI and QR code sharing. Temporary TURN credentials are kept in memory only.":
|
||||
"The API token is saved with this profile and included in Setup URI and QR code sharing. Temporary TURN credentials are kept in memory only.",
|
||||
"TURN relay only requires a TURN server or a configured credential source under Advanced Settings.":
|
||||
"TURN relay only requires a TURN server or a configured credential source under Advanced Settings.",
|
||||
"TURN relay only requires TURN configuration. Connection path has been restored to Automatic.":
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("TURN credentials in diagnostic reports", () => {
|
||||
id: "inactive",
|
||||
name: "Inactive TURN",
|
||||
isEncrypted: false,
|
||||
uri: `sls+p2p-v2://room?source=${encodeURIComponent(JSON.stringify(source))}`,
|
||||
uri: `sls+p2p://room?source=${encodeURIComponent(JSON.stringify(source))}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -34,7 +34,7 @@ describe("TURN credentials in diagnostic reports", () => {
|
||||
expect(text).not.toContain(encodeURIComponent(token));
|
||||
expect(text).not.toContain("private-key");
|
||||
expect(text).not.toContain("encrypted-private-copy");
|
||||
expect(report.pluginConfig.remoteConfigurations.inactive.uri).toBe("sls+p2p-v2://");
|
||||
expect(report.pluginConfig.remoteConfigurations.inactive.uri).toBe("sls+p2p://");
|
||||
expect(settings.P2P_iceServerSource).toEqual(source);
|
||||
expect(settings.encryptedP2PIceServerSource).toBe("encrypted-private-copy");
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export function redactTurnSourceForReport(settings: Partial<ObsidianLiveSyncSett
|
||||
}
|
||||
}
|
||||
|
||||
/** Managed connection profiles are shared through encrypted Setup URIs. */
|
||||
/** Managed connection profiles are shared through Setup URIs and QR codes. */
|
||||
export function omitManagedTurnProfilesFromMarkdown(settings: Partial<ObsidianLiveSyncSettings>): void {
|
||||
if (!hasManagedTurnSettings(settings)) return;
|
||||
delete settings.P2P_iceServerSource;
|
||||
|
||||
@@ -20,7 +20,7 @@ function configuredSettings() {
|
||||
id: "managed",
|
||||
name: "Managed TURN",
|
||||
isEncrypted: false,
|
||||
uri: "sls+p2p-v2://room?source=private-token",
|
||||
uri: "sls+p2p://room?source=private-token",
|
||||
},
|
||||
},
|
||||
activeConfigurationId: "central",
|
||||
@@ -42,7 +42,7 @@ describe("managed TURN settings privacy", () => {
|
||||
settings.P2P_iceServerSource.id = "manual";
|
||||
expect(hasManagedTurnSettings(settings)).toBe(true);
|
||||
omitManagedTurnProfilesFromMarkdown(settings);
|
||||
expect(JSON.stringify(settings)).not.toMatch(/private-token|private-key-id|sls\+p2p-v2/);
|
||||
expect(JSON.stringify(settings)).not.toMatch(/private-token|private-key-id|sls\+p2p/);
|
||||
expect(settings).not.toHaveProperty("remoteConfigurations");
|
||||
expect(settings).not.toHaveProperty("activeConfigurationId");
|
||||
expect(settings).not.toHaveProperty("P2P_ActiveRemoteConfigurationId");
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ export type queueItem = {
|
||||
|
||||
export const FileWatchEventQueueMax = 10;
|
||||
|
||||
export { configURIBase, configURIBaseV2, configURIBaseQR } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
export { configURIBase, configURIBaseQR } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
export {
|
||||
CHeader,
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
autocomplete={field.secret ? "new-password" : "off"} autocapitalize="off" spellcheck="false" />
|
||||
</label>
|
||||
{/each}
|
||||
<p>{translate("The API token is saved with this profile and included in encrypted Setup URI sharing. Temporary TURN credentials are kept in memory only.")}</p>
|
||||
<p>{translate("The API token is saved with this profile and included in Setup URI and QR code sharing. Temporary TURN credentials are kept in memory only.")}</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p role="status" class="turn-error">{translateIfAvailable(error)}</p>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { copySetupURI } from "@/serviceFeatures/setupObsidian/setupUri";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import {
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
@@ -418,15 +416,6 @@ export function paneRemoteConfig(
|
||||
})
|
||||
.addItem((item) => {
|
||||
item.setTitle("📤 Export").onClick(async () => {
|
||||
if (config.uri.startsWith("sls+p2p-v2://")) {
|
||||
await copySetupURI(
|
||||
this.core,
|
||||
createInstanceLogFunction("TURN setup sharing", this.services.API),
|
||||
true,
|
||||
getSettingsFromEditingSettings(this.editingSettings)
|
||||
);
|
||||
return;
|
||||
}
|
||||
await this.services.UI.promptCopyToClipboard(
|
||||
`Remote configuration: ${config.name}`,
|
||||
config.uri
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { configURIBase, configURIBaseV2 } from "@/common/types";
|
||||
import { configURIBase } from "@/common/types";
|
||||
import DialogHeader from "@/modules/services/LiveSyncUI/components/DialogHeader.svelte";
|
||||
import Guidance from "@/modules/services/LiveSyncUI/components/Guidance.svelte";
|
||||
import Decision from "@/modules/services/LiveSyncUI/components/Decision.svelte";
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const seemsValid = $derived(setupURI.startsWith(configURIBase) || setupURI.startsWith(configURIBaseV2));
|
||||
const seemsValid = $derived(setupURI.startsWith(configURIBase));
|
||||
async function processSetupURI() {
|
||||
error = "";
|
||||
if (!seemsValid) return;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { hasManagedTurnSettings } from "@/common/turnSettingsPrivacy";
|
||||
import { copySetupURI } from "./setupUri";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import {
|
||||
encodeQR,
|
||||
@@ -13,10 +10,6 @@ import type { SetupFeatureHost } from "./types";
|
||||
|
||||
export async function encodeSetupSettingsAsQR(host: SetupFeatureHost) {
|
||||
const settings = host.services.setting.currentSettings();
|
||||
if (hasManagedTurnSettings(settings)) {
|
||||
await copySetupURI(host, createInstanceLogFunction("SF:SetupQRCode", host.services.API));
|
||||
return "";
|
||||
}
|
||||
const settingString = encodeSettingsToQRCodeData(settings);
|
||||
const result = encodeQR(settingString, OutputFormat.SVG);
|
||||
if (result === "") {
|
||||
|
||||
@@ -18,15 +18,36 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
|
||||
});
|
||||
|
||||
describe("setupObsidian/qrCode", () => {
|
||||
it("routes inactive managed profiles through encrypted Setup URI sharing", async () => {
|
||||
const settings = {
|
||||
remoteConfigurations: { managed: { uri: "sls+p2p-v2://room?source=private-token" } },
|
||||
it("shows managed TURN settings and inactive profiles through the ordinary QR dialogue", async () => {
|
||||
const source = {
|
||||
version: 1,
|
||||
id: "cloudflare",
|
||||
configuration: { turnKeyId: "turn-key", apiToken: "private-token" },
|
||||
};
|
||||
const host = { services: { API: { addLog: vi.fn() }, setting: { currentSettings: () => settings } } } as any;
|
||||
await encodeSetupSettingsAsQR(host);
|
||||
expect(copySetupURI).toHaveBeenCalledWith(host, expect.any(Function));
|
||||
expect(encodeSettingsToQRCodeData).not.toHaveBeenCalled();
|
||||
expect(encodeQR).not.toHaveBeenCalled();
|
||||
const settings = {
|
||||
P2P_iceServerSource: source,
|
||||
remoteConfigurations: {
|
||||
managed: { uri: `sls+p2p://room?source=${encodeURIComponent(JSON.stringify(source))}` },
|
||||
},
|
||||
};
|
||||
const confirmWithMessage = vi.fn();
|
||||
const translate = vi.fn(() => "qr-message");
|
||||
const host = {
|
||||
services: {
|
||||
API: { addLog: vi.fn() },
|
||||
context: createServiceContext({ translate }),
|
||||
setting: { currentSettings: () => settings },
|
||||
UI: { confirm: { confirmWithMessage } },
|
||||
},
|
||||
} as any;
|
||||
vi.mocked(encodeSettingsToQRCodeData).mockReturnValue("encoded-settings");
|
||||
vi.mocked(encodeQR).mockReturnValue("<svg/>");
|
||||
|
||||
expect(await encodeSetupSettingsAsQR(host)).toBe("<svg/>");
|
||||
expect(encodeSettingsToQRCodeData).toHaveBeenCalledWith(settings);
|
||||
expect(translate).toHaveBeenCalledWith("Setup.QRCode", { qr_image: "<svg/>" });
|
||||
expect(confirmWithMessage).toHaveBeenCalledWith("Settings QR Code", "qr-message", ["OK"], "OK");
|
||||
expect(copySetupURI).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -2,23 +2,20 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@vrtmrz/livesync-commonlib/
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import type { SetupFeatureHost } from "@/serviceFeatures/setupObsidian/types";
|
||||
import { configURIBase, configURIBaseV2 } from "@/common/types";
|
||||
import { configURIBase } from "@/common/types";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { type SetupManager, UserMode } from "@/modules/features/SetupManager";
|
||||
|
||||
async function handleSetupProtocol(setupManager: SetupManager, conf: Record<string, string>, uriBase = configURIBase) {
|
||||
async function handleSetupProtocol(setupManager: SetupManager, conf: Record<string, string>) {
|
||||
if (conf.settings) {
|
||||
await setupManager.onUseSetupURI(UserMode.Unknown, `${uriBase}${encodeURIComponent(conf.settings)}`);
|
||||
} else if (conf.settingsQR && uriBase === configURIBase) {
|
||||
await setupManager.onUseSetupURI(UserMode.Unknown, `${configURIBase}${encodeURIComponent(conf.settings)}`);
|
||||
} else if (conf.settingsQR) {
|
||||
await setupManager.decodeQR(conf.settingsQR);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSetupProtocolHandler(host: SetupFeatureHost, log: LogFunction, setupManager: SetupManager) {
|
||||
try {
|
||||
host.services.API.registerProtocolHandler("setuplivesync-v2", async (conf) => {
|
||||
await handleSetupProtocol(setupManager, conf, configURIBaseV2);
|
||||
});
|
||||
host.services.API.registerProtocolHandler("setuplivesync", async (conf) => {
|
||||
await handleSetupProtocol(setupManager, conf);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { registerSetupProtocolHandler, useSetupProtocolFeature } from "./setupPr
|
||||
vi.mock("@/common/types", () => {
|
||||
return {
|
||||
configURIBase: "mock-config://",
|
||||
configURIBaseV2: "mock-config-v2://",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -18,19 +17,6 @@ vi.mock("@/modules/features/SetupManager", () => {
|
||||
});
|
||||
|
||||
describe("setupObsidian/setupProtocol", () => {
|
||||
it("routes the versioned encrypted payload through the matching URI format", async () => {
|
||||
const handlers = new Map<string, (params: Record<string, string>) => Promise<void>>();
|
||||
const host = {
|
||||
services: { API: { registerProtocolHandler: vi.fn((action, handler) => handlers.set(action, handler)) } },
|
||||
} as any;
|
||||
const setupManager = { onUseSetupURI: vi.fn(), decodeQR: vi.fn() } as any;
|
||||
registerSetupProtocolHandler(host, vi.fn(), setupManager);
|
||||
await handlers.get("setuplivesync-v2")!({ settings: "encrypted settings" });
|
||||
expect(setupManager.onUseSetupURI).toHaveBeenCalledWith("unknown", "mock-config-v2://encrypted%20settings");
|
||||
await handlers.get("setuplivesync-v2")!({ settingsQR: "plain settings" });
|
||||
expect(setupManager.decodeQR).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -16,16 +16,11 @@ export async function askEncryptingPassphrase(host: SetupFeatureHost): Promise<s
|
||||
);
|
||||
}
|
||||
|
||||
export async function copySetupURI(
|
||||
host: SetupFeatureHost,
|
||||
log: LogFunction,
|
||||
stripExtra = true,
|
||||
settings = host.services.setting.currentSettings()
|
||||
) {
|
||||
export async function copySetupURI(host: SetupFeatureHost, log: LogFunction, stripExtra = true) {
|
||||
const encryptingPassphrase = await askEncryptingPassphrase(host);
|
||||
if (encryptingPassphrase === false) return;
|
||||
const encryptedURI = await encodeSettingsToSetupURI(
|
||||
settings,
|
||||
host.services.setting.currentSettings(),
|
||||
encryptingPassphrase,
|
||||
[...((stripExtra ? ["pluginSyncExtendedSetting"] : []) as (keyof ObsidianLiveSyncSettings)[])],
|
||||
true
|
||||
|
||||
Reference in New Issue
Block a user