Add optional Cloudflare TURN credentials and secure profile sharing

This commit is contained in:
vorotamoroz
2026-09-15 16:20:03 +00:00
parent ba297d1233
commit 93bc161f20
40 changed files with 1855 additions and 182 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import { decodeSettingsFromSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
import { configURIBase } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
import { configURIBase, configURIBaseV2 } 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)) {
if (!setupURI.startsWith(configURIBase) && !setupURI.startsWith(configURIBaseV2)) {
throw new Error(`setup URI must start with ${configURIBase}`);
}
const passphrase = await standardIo.prompt("Enter setup URI passphrase: ");
+26 -1
View File
@@ -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 } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
import { configURIBase, configURIBaseV2 } from "@vrtmrz/livesync-commonlib/compat/common/models/shared.const";
import {
DEFAULT_SETTINGS,
REMOTE_COUCHDB,
@@ -419,6 +419,31 @@ describe("runCommand abnormal cases", () => {
expect(appliedSettings.useIndexedDBAdapter).toBe(false);
});
it("setup imports managed TURN through the versioned encrypted URI", async () => {
const core = createCoreMock();
const source = {
version: 1,
id: "cloudflare",
configuration: { turnKeyId: "turn-key", apiToken: "private-token" },
};
const passphrase = "setup-passphrase";
const setupURI = await processSetting.encodeSettingsToSetupURI(
{
...DEFAULT_SETTINGS,
P2P_iceServerSource: source,
},
passphrase
);
expect(setupURI.startsWith(configURIBaseV2)).toBe(true);
expect(setupURI).not.toContain("private-token");
core.services.context.standardIo.prompt.mockResolvedValue(passphrase);
await runCommand(makeOptions("setup", [setupURI]), { ...context, core });
expect(core.services.setting.applyExternalSettings).toHaveBeenCalledWith(
expect.objectContaining({ P2P_iceServerSource: source }),
true
);
});
it("setup rejects encoded URI when passphrase is wrong", async () => {
const core = createCoreMock();
const setupURI = await createSetupURI("correct-passphrase");
+4 -1
View File
@@ -1,3 +1,4 @@
import { useIceServerSources } from "@/serviceFeatures/useIceServerSources";
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
import { LiveSyncBaseCore, type StartupDatabaseOptions } from "@/LiveSyncBaseCore";
@@ -524,7 +525,9 @@ export async function main(
useOfflineScanner(core);
}
// Register P2P replicator feature.
p2pReplicator = useP2PReplicatorFeature(core);
p2pReplicator = useP2PReplicatorFeature(core, undefined, undefined, {
iceServerSources: useIceServerSources(core.services.API.webCompatFetch.bind(core.services.API)),
});
// Add target filter to prevent internal files are handled
core.services.vault.isTargetFile.addHandler(async (target) => {
const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target));