Use host preparation for TURN connection settings

This commit is contained in:
vorotamoroz
2026-09-16 03:43:09 +00:00
parent a565070809
commit 11a07b26af
33 changed files with 654 additions and 961 deletions
@@ -1,6 +1,6 @@
<script lang="ts">
import TurnConfiguration from "@/features/P2PSync/TurnConfiguration.svelte";
import { validateIceServerSourceConfiguration } from "@/integrations/iceServerSources";
import { validateManagedTurnSettings } from "@/integrations/turnSettings";
// import { delay } from "octagonal-wheels/promises";
import DialogHeader from "@/modules/services/LiveSyncUI/components/DialogHeader.svelte";
import Guidance from "@/modules/services/LiveSyncUI/components/Guidance.svelte";
@@ -101,14 +101,14 @@
async function checkConnection() {
try {
processing = true;
const sourceError = validateIceServerSourceConfiguration(syncSetting.P2P_iceServerSource);
const sourceError = validateManagedTurnSettings(syncSetting);
if (sourceError) return sourceError;
const trialRemoteSetting = generateSetting();
const admission = connectionProbe;
if (!admission) {
throw new Error("The P2P Setup connection probe is not available.");
}
const result = await coordinateP2PSetupConnectionProbe(admission, trialRemoteSetting, async () => {
const result = await coordinateP2PSetupConnectionProbe(admission, trialRemoteSetting, async (signallingSettings) => {
const map = new Map<string, unknown>();
const store = {
get: (key: string) => {
@@ -136,7 +136,7 @@
const env: ReplicatorHostEnv = {
events: context.context.events,
translate: context.context.translate,
settings: trialRemoteSetting,
settings: signallingSettings,
processReplicatedDocs: async (_docs: PouchDB.Core.ExistingDocument<EntryDoc>[]) => {
return;
},
@@ -207,7 +207,7 @@
}
}
function commit() {
error = validateIceServerSourceConfiguration(syncSetting.P2P_iceServerSource) ?? "";
error = validateManagedTurnSettings(syncSetting) ?? "";
if (error) return;
const setting = pickP2PSyncSettings(generateSetting());
setResult(setting);
@@ -221,7 +221,7 @@
syncSetting.P2P_roomID.trim() !== "" &&
syncSetting.P2P_passphrase.trim() !== "" &&
(syncSetting.P2P_DevicePeerName ?? "").trim() !== "" &&
validateIceServerSourceConfiguration(syncSetting.P2P_iceServerSource) === undefined
validateManagedTurnSettings(syncSetting) === undefined
);
});
</script>
@@ -3,6 +3,7 @@ import {
type P2PConnectionProbeAdmission,
type P2PConnectionProbeSettings,
} from "@vrtmrz/livesync-commonlib/p2p";
import { P2PConnectionPaths, type P2PSyncSetting } from "@vrtmrz/livesync-commonlib/compat/common/types";
export type P2PSetupConnectionProbeResult =
| { readonly ok: true }
@@ -20,12 +21,25 @@ export interface P2PSetupConnectionProbe {
}
/** Interpret the stable P2P owner's admission without constructing transport eagerly. */
export async function coordinateP2PSetupConnectionProbe(
export async function coordinateP2PSetupConnectionProbe<T extends P2PConnectionProbeSettings>(
admission: P2PConnectionProbeAdmission,
trialSettings: P2PConnectionProbeSettings,
runOwnedTrial: () => Promise<P2PSetupConnectionProbeResult>
trialSettings: T,
runOwnedTrial: (settings: T) => Promise<P2PSetupConnectionProbeResult>
): Promise<P2PSetupConnectionProbeResult> {
const settlement = await admission.run(trialSettings, runOwnedTrial);
const settlement = await admission.run(trialSettings, () => {
// This trial checks signalling only; TURN allocation belongs to an actual connection.
const settings: T & Partial<P2PSyncSetting> = { ...trialSettings };
delete settings.P2P_managedType;
delete settings.P2P_managedId;
delete settings.P2P_managedToken;
delete settings.P2P_iceServers;
delete settings.P2P_iceServersExpiresAt;
settings.P2P_turnServers = "";
settings.P2P_turnUsername = "";
settings.P2P_turnCredential = "";
settings.P2P_connectionPath = P2PConnectionPaths.Automatic;
return runOwnedTrial(settings);
});
if (settlement.status === "observed-active") return { ok: true };
if (settlement.status === "blocked") {
return {
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { ACTIVE_P2P_RELAY_BINDING_CONFLICT, type P2PConnectionProbeAdmission } from "@vrtmrz/livesync-commonlib/p2p";
import { DEFAULT_SETTINGS, P2PConnectionPaths } from "@vrtmrz/livesync-commonlib/compat/common/types";
import {
coordinateP2PSetupConnectionProbe,
probeP2PSetupConnection,
@@ -7,6 +8,40 @@ import {
} from "./p2pSetupConnectionProbe";
describe("P2P setup connection probe", () => {
it("constructs a signalling-only trial when the draft selects managed TURN", async () => {
const settings = {
...DEFAULT_SETTINGS,
P2P_managedType: "CF",
P2P_managedId: "test-key",
P2P_managedToken: "test-token",
P2P_iceServers: [
{ urls: "turn:temporary.example.test", username: "issued-user", credential: "issued-password" },
],
P2P_iceServersExpiresAt: 123456789,
P2P_turnServers: "turn:unused.example.test:3478",
P2P_turnUsername: "unused-user",
P2P_turnCredential: "unused-password",
P2P_connectionPath: P2PConnectionPaths.Relay,
};
const admission: P2PConnectionProbeAdmission = {
run: async (_settings, trial) => ({ status: "trial", result: await trial() }),
};
const result = await coordinateP2PSetupConnectionProbe(admission, settings, async (trial = settings) => {
expect(trial.P2P_managedType).toBeUndefined();
expect(trial.P2P_managedToken).toBeUndefined();
expect(trial.P2P_iceServers).toBeUndefined();
expect(trial.P2P_iceServersExpiresAt).toBeUndefined();
expect(trial.P2P_turnServers).toBe("");
expect(trial.P2P_turnUsername).toBe("");
expect(trial.P2P_turnCredential).toBe("");
expect(trial.P2P_connectionPath).toBe(P2PConnectionPaths.Automatic);
return { ok: true };
});
expect(result).toEqual({ ok: true });
expect(settings.P2P_managedToken).toBe("test-token");
expect(settings.P2P_connectionPath).toBe(P2PConnectionPaths.Relay);
});
it("uses a compatible active signalling connection without constructing a trial", async () => {
const runOwnedTrial = vi.fn(async (): Promise<P2PSetupConnectionProbeResult> => ({ ok: true }));
const admission: P2PConnectionProbeAdmission = {