mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-22 02:27:07 +00:00
Use host preparation for TURN connection settings
This commit is contained in:
@@ -19,15 +19,9 @@ 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 source = {
|
||||
version: 1,
|
||||
id: "cloudflare",
|
||||
configuration: { turnKeyId: "turn-key", apiToken: "private-token" },
|
||||
};
|
||||
const settings = {
|
||||
P2P_iceServerSource: source,
|
||||
remoteConfigurations: {
|
||||
managed: { uri: `sls+p2p://room?source=${encodeURIComponent(JSON.stringify(source))}` },
|
||||
managed: { uri: "sls+p2p://room?managedType=CF&managedId=turn-key&token=private-token" },
|
||||
},
|
||||
};
|
||||
const confirmWithMessage = vi.fn();
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { CLOUDFLARE_ICE_SERVER_SOURCE_ID } from "@/integrations/cloudflare/settings";
|
||||
import type { IceServerSourceFactoryCatalogue } from "@vrtmrz/livesync-commonlib/p2p";
|
||||
import {
|
||||
createCloudflareIceServerSource,
|
||||
type CloudflareIceServerSourceFetch,
|
||||
} from "@/integrations/cloudflare/iceServerSource";
|
||||
|
||||
/**
|
||||
* Compose the closed LiveSync-owned ICE source catalogue from a host HTTP
|
||||
* adapter. The adapter is intentionally narrow so this feature does not take
|
||||
* a dependency on LiveSync core or on native request APIs.
|
||||
*/
|
||||
export function useIceServerSources(fetch: CloudflareIceServerSourceFetch): IceServerSourceFactoryCatalogue {
|
||||
return {
|
||||
[CLOUDFLARE_ICE_SERVER_SOURCE_ID]: (configuration) => createCloudflareIceServerSource(configuration, { fetch }),
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user