Merge main into stale-file protection integration

This commit is contained in:
vorotamoroz
2026-09-17 12:46:32 +00:00
67 changed files with 2299 additions and 403 deletions
+2 -1
View File
@@ -9,7 +9,8 @@ import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import type { SetupFeatureHost } from "./types";
export async function encodeSetupSettingsAsQR(host: SetupFeatureHost) {
const settingString = encodeSettingsToQRCodeData(host.services.setting.currentSettings());
const settings = host.services.setting.currentSettings();
const settingString = encodeSettingsToQRCodeData(settings);
const result = encodeQR(settingString, OutputFormat.SVG);
if (result === "") {
return "";
@@ -3,6 +3,9 @@ import { EVENT_REQUEST_SHOW_SETUP_QR } from "@vrtmrz/livesync-commonlib/compat/e
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
import { encodeSetupSettingsAsQR, useSetupQRCodeFeature } from "./qrCode";
import { encodeQR, encodeSettingsToQRCodeData } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
import { copySetupURI } from "./setupUri";
vi.mock("./setupUri", () => ({ copySetupURI: vi.fn() }));
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
return {
@@ -15,6 +18,32 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
});
describe("setupObsidian/qrCode", () => {
it("shows managed TURN settings and inactive profiles through the ordinary QR dialogue", async () => {
const settings = {
remoteConfigurations: {
managed: { uri: "sls+p2p://room?managedType=CF&managedId=turn-key&token=private-token" },
},
};
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(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
@@ -0,0 +1,18 @@
import type { P2PSyncSetting } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { acquireCloudflareTurnCredentials, type CloudflareTurnFetch } from "@/integrations/cloudflare/turnCredentials";
import { validateManagedTurnSettings } from "@/integrations/turnSettings";
/** Prepare a connection copy using the host's HTTP adapter. */
export function useP2PSettingsPreparation(fetch: CloudflareTurnFetch) {
return async (settings: Readonly<P2PSyncSetting>, signal: AbortSignal): Promise<P2PSyncSetting> => {
const error = validateManagedTurnSettings(settings);
if (error) throw new Error(error);
if (!settings.P2P_managedType) return { ...settings };
const { iceServers, expiresAt } = await acquireCloudflareTurnCredentials(
{ turnKeyId: settings.P2P_managedId ?? "", apiToken: settings.P2P_managedToken ?? "" },
{ fetch },
signal
);
return { ...settings, P2P_iceServers: iceServers, P2P_iceServersExpiresAt: expiresAt };
};
}
@@ -0,0 +1,48 @@
import { describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { useP2PSettingsPreparation } from "./useP2PSettingsPreparation";
const managed = {
...DEFAULT_SETTINGS,
P2P_managedType: "CF",
P2P_managedId: "key-123",
P2P_managedToken: "test-token",
};
describe("host preparation of P2P settings", () => {
it("puts issued ICE credentials on a connection copy without changing saved inputs", async () => {
const iceServers = [
{ urls: ["turn:relay.example.test:3478"], username: "issued-user", credential: "issued-password" },
];
const fetch = vi.fn(async () => new Response(JSON.stringify({ iceServers }), { status: 201 }));
const before = structuredClone(managed);
const settings = await useP2PSettingsPreparation(fetch)(managed, new AbortController().signal);
expect(settings.P2P_iceServers).toEqual(iceServers);
expect(settings.P2P_iceServersExpiresAt).toBeGreaterThan(Date.now());
expect(managed).toEqual(before);
expect(settings).not.toBe(managed);
expect(fetch).toHaveBeenCalledOnce();
});
it("keeps manual settings and rejects an unsupported provider without HTTP requests", async () => {
const fetch = vi.fn();
const prepare = useP2PSettingsPreparation(fetch);
await expect(prepare(DEFAULT_SETTINGS, new AbortController().signal)).resolves.toEqual(DEFAULT_SETTINGS);
await expect(prepare({ ...managed, P2P_managedType: "unknown" }, new AbortController().signal)).rejects.toThrow(
"not supported"
);
await expect(
prepare({ ...managed, P2P_managedToken: "invalid token" }, new AbortController().signal)
).rejects.toThrow("Bearer token syntax");
expect(fetch).not.toHaveBeenCalled();
});
it("propagates a safe acquisition failure without using the manual TURN fields", async () => {
const fetch = vi.fn(async () => new Response(null, { status: 401 }));
const prepare = useP2PSettingsPreparation(fetch);
await expect(
prepare({ ...managed, P2P_turnServers: "turn:manual.example.test" }, new AbortController().signal)
).rejects.toThrow("not authorised");
expect(fetch).toHaveBeenCalledOnce();
});
});