refactor: centralize relay vs direct E2EE

This commit is contained in:
Mohamed Boudra
2026-02-03 14:13:03 +07:00
parent 4fb93d92ed
commit d68596bcbe
12 changed files with 83 additions and 68 deletions
+24 -10
View File
@@ -57,7 +57,7 @@ import {
createClientChannel,
type EncryptedChannel,
type Transport as RelayTransport,
} from "@paseo/relay";
} from "@paseo/relay/e2ee";
export interface Logger {
debug(obj: object, msg?: string): void;
@@ -319,14 +319,16 @@ export class DaemonClientV2 {
createWebSocketTransportFactory(
this.config.webSocketFactory ?? defaultWebSocketFactory
);
const transportFactory =
this.config.e2ee?.enabled === true
? createEncryptedTransportFactory(
baseTransportFactory,
this.config.e2ee?.daemonPublicKeyB64,
this.logger
)
: baseTransportFactory;
const shouldUseRelayE2ee =
this.config.e2ee?.enabled === true &&
isRelayClientWebSocketUrl(this.config.url);
const transportFactory = shouldUseRelayE2ee
? createRelayE2eeTransportFactory(
baseTransportFactory,
this.config.e2ee?.daemonPublicKeyB64,
this.logger
)
: baseTransportFactory;
const transport = transportFactory({ url: this.config.url, headers });
this.transport = transport;
@@ -2342,7 +2344,7 @@ function createWebSocketTransportFactory(
};
}
function createEncryptedTransportFactory(
function createRelayE2eeTransportFactory(
baseFactory: DaemonTransportFactory,
daemonPublicKeyB64: string | undefined,
logger: Logger
@@ -2357,6 +2359,18 @@ function createEncryptedTransportFactory(
};
}
function isRelayClientWebSocketUrl(url: string): boolean {
try {
const parsed = new URL(url);
return (
parsed.searchParams.get("role") === "client" &&
parsed.searchParams.has("session")
);
} catch {
return false;
}
}
function createEncryptedTransport(
base: DaemonTransport,
daemonPublicKeyB64: string,
+1 -2
View File
@@ -437,14 +437,13 @@ export async function createPaseoDaemon(
const endpoints = buildOfferEndpoints({
listenHost: listenTarget.host,
port: listenTarget.port,
relayEnabled,
relayEndpoint,
});
const offer = await createConnectionOfferV1({
sessionId: connectionSessionId,
endpoints,
daemonPublicKeyB64: daemonKeyPair.publicKeyB64,
relay: relayEnabled ? { endpoint: relayEndpoint } : null,
});
const url = encodeOfferToFragmentUrl({ offer, appBaseUrl });
+6 -17
View File
@@ -1,27 +1,18 @@
import os from "node:os";
import { z } from "zod";
export const ConnectionOfferV1Schema = z.object({
v: z.literal(1),
sessionId: z.string().min(1),
endpoints: z.array(z.string().min(1)).min(1),
daemonPublicKeyB64: z.string().min(1),
});
export type ConnectionOfferV1 = z.infer<typeof ConnectionOfferV1Schema>;
import {
ConnectionOfferV1Schema,
type ConnectionOfferV1,
} from "../shared/connection-offer.js";
type BuildOfferEndpointsArgs = {
listenHost: string;
port: number;
relayEnabled: boolean;
relayEndpoint: string;
};
export function buildOfferEndpoints({
listenHost,
port,
relayEnabled,
relayEndpoint,
}: BuildOfferEndpointsArgs): string[] {
const endpoints: string[] = [];
@@ -41,10 +32,6 @@ export function buildOfferEndpoints({
endpoints.push(`localhost:${port}`);
endpoints.push(`127.0.0.1:${port}`);
if (relayEnabled) {
endpoints.push(relayEndpoint);
}
return dedupePreserveOrder(endpoints);
}
@@ -52,12 +39,14 @@ export async function createConnectionOfferV1(args: {
sessionId: string;
endpoints: string[];
daemonPublicKeyB64: string;
relay?: { endpoint: string } | null;
}): Promise<ConnectionOfferV1> {
return ConnectionOfferV1Schema.parse({
v: 1,
sessionId: args.sessionId,
endpoints: args.endpoints,
daemonPublicKeyB64: args.daemonPublicKeyB64,
relay: args.relay ?? null,
});
}
@@ -89,6 +89,7 @@ describe("ConnectionOfferV1 (daemon E2E)", () => {
sessionId: string;
endpoints: string[];
daemonPublicKeyB64: string;
relay?: { endpoint: string } | null;
};
expect(offer.v).toBe(1);
@@ -97,7 +98,8 @@ describe("ConnectionOfferV1 (daemon E2E)", () => {
expect(Array.isArray(offer.endpoints)).toBe(true);
expect(offer.endpoints).toContain(`192.168.1.12:${daemon.port}`);
expect(offer.endpoints).toContain(`localhost:${daemon.port}`);
expect(offer.endpoints).toContain("relay.paseo.sh:443");
expect(offer.endpoints).not.toContain("relay.paseo.sh:443");
expect(offer.relay?.endpoint).toBe("relay.paseo.sh:443");
expect(typeof offer.daemonPublicKeyB64).toBe("string");
expect(offer.daemonPublicKeyB64.length).toBeGreaterThan(0);
expect(() => Buffer.from(offer.daemonPublicKeyB64, "base64")).not.toThrow();
@@ -167,9 +169,11 @@ describe("ConnectionOfferV1 (daemon E2E)", () => {
const offer = decodeOfferFromFragmentUrl(offerUrl) as {
endpoints: string[];
relay?: { endpoint: string } | null;
};
expect(offer.endpoints).not.toContain("relay.paseo.sh:443");
expect(offer.relay).toBe(null);
expect(offer.endpoints).toContain(`localhost:${port}`);
expect(offer.endpoints).toContain(`192.168.1.12:${port}`);
} catch (err) {
@@ -5,7 +5,7 @@ import { Writable } from "node:stream";
import net from "node:net";
import { createTestPaseoDaemon } from "../test-utils/paseo-daemon.js";
import { createClientChannel, type Transport } from "@paseo/relay";
import { createClientChannel, type Transport } from "@paseo/relay/e2ee";
import { createRelayServer } from "@paseo/relay/node";
function createCapturingLogger() {
@@ -6,7 +6,7 @@ import {
createDaemonChannel,
type EncryptedChannel,
type Transport as RelayTransport,
} from "@paseo/relay";
} from "@paseo/relay/e2ee";
type RelayTransportOptions = {
logger: pino.Logger;
@@ -0,0 +1,17 @@
import { z } from "zod";
export const ConnectionOfferV1Schema = z.object({
v: z.literal(1),
sessionId: z.string().min(1),
endpoints: z.array(z.string().min(1)).min(1),
daemonPublicKeyB64: z.string().min(1),
relay: z
.object({
endpoint: z.string().min(1),
})
.nullable()
.optional(),
});
export type ConnectionOfferV1 = z.infer<typeof ConnectionOfferV1Schema>;