Use existing Setup URI and QR sharing for TURN settings

This commit is contained in:
vorotamoroz
2026-09-15 17:18:40 +00:00
parent 93bc161f20
commit ab55eb5aff
20 changed files with 89 additions and 116 deletions
@@ -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