relay: rename encrypted handshake message types

This commit is contained in:
Mohamed Boudra
2026-02-21 11:47:26 +07:00
parent e6a7c4de42
commit c123c98f5f
+27 -23
View File
@@ -37,23 +37,23 @@ type ChannelState = "connecting" | "handshaking" | "open" | "closed";
type EncryptedChannelOptions = { type EncryptedChannelOptions = {
/** /**
* If set, the channel can validate repeated plaintext `{type:"hello"}` * If set, the channel can validate repeated plaintext `{type:"e2ee_hello"}`
* messages even after it is open. * messages even after it is open.
* *
* This is useful for robustness when the client retries the handshake * This is useful for robustness when the client retries the handshake
* (e.g., it didn't observe the daemon's `{type:"ready"}` yet). In that case, * (e.g., it didn't observe the daemon's `{type:"e2ee_ready"}` yet). In that case,
* the daemon should re-send `{type:"ready"}` without changing keys. * the daemon should re-send `{type:"e2ee_ready"}` without changing keys.
*/ */
daemonKeyPair?: KeyPair; daemonKeyPair?: KeyPair;
}; };
interface HelloMessage { interface E2EEHelloMessage {
type: "hello"; type: "e2ee_hello";
key: string; key: string;
} }
interface ReadyMessage { interface E2EEReadyMessage {
type: "ready"; type: "e2ee_ready";
} }
const HANDSHAKE_RETRY_MS = 1000; const HANDSHAKE_RETRY_MS = 1000;
@@ -65,7 +65,7 @@ const MAX_PENDING_SENDS = 200;
* The client: * The client:
* 1. Receives daemon's public key via QR code * 1. Receives daemon's public key via QR code
* 2. Generates own keypair * 2. Generates own keypair
* 3. Sends hello with own public key * 3. Sends e2ee_hello with own public key
* 4. Derives shared key and starts encrypted communication * 4. Derives shared key and starts encrypted communication
*/ */
export async function createClientChannel( export async function createClientChannel(
@@ -79,9 +79,9 @@ export async function createClientChannel(
const channel = new EncryptedChannel(transport, sharedKey, events); const channel = new EncryptedChannel(transport, sharedKey, events);
// Send hello with our public key // Send e2ee_hello with our public key
const ourPublicKeyB64 = exportPublicKey(keyPair.publicKey); const ourPublicKeyB64 = exportPublicKey(keyPair.publicKey);
const hello: HelloMessage = { type: "hello", key: ourPublicKeyB64 }; const hello: E2EEHelloMessage = { type: "e2ee_hello", key: ourPublicKeyB64 };
const helloText = JSON.stringify(hello); const helloText = JSON.stringify(hello);
let retry: ReturnType<typeof setInterval> | null = null; let retry: ReturnType<typeof setInterval> | null = null;
@@ -129,7 +129,7 @@ export async function createClientChannel(
* *
* The daemon: * The daemon:
* 1. Has pre-generated keypair (public key was in QR) * 1. Has pre-generated keypair (public key was in QR)
* 2. Waits for client's hello with their public key * 2. Waits for client's e2ee_hello with their public key
* 3. Derives shared key and starts encrypted communication * 3. Derives shared key and starts encrypted communication
*/ */
export async function createDaemonChannel( export async function createDaemonChannel(
@@ -142,8 +142,8 @@ export async function createDaemonChannel(
const shouldIgnorePostHelloPlaintext = (data: string | ArrayBuffer): boolean => { const shouldIgnorePostHelloPlaintext = (data: string | ArrayBuffer): boolean => {
try { try {
const text = typeof data === "string" ? data : new TextDecoder().decode(data); const text = typeof data === "string" ? data : new TextDecoder().decode(data);
const parsed = JSON.parse(text) as Partial<HelloMessage | ReadyMessage>; const parsed = JSON.parse(text) as Partial<E2EEHelloMessage | E2EEReadyMessage>;
return parsed.type === "hello" || parsed.type === "ready"; return parsed.type === "e2ee_hello" || parsed.type === "e2ee_ready";
} catch { } catch {
return false; return false;
} }
@@ -154,8 +154,8 @@ export async function createDaemonChannel(
const helloText = const helloText =
typeof data === "string" ? data : new TextDecoder().decode(data); typeof data === "string" ? data : new TextDecoder().decode(data);
const msg = JSON.parse(helloText) as HelloMessage; const msg = JSON.parse(helloText) as E2EEHelloMessage;
if (msg.type !== "hello" || !msg.key) { if (msg.type !== "e2ee_hello" || !msg.key) {
throw new Error("Invalid hello message"); throw new Error("Invalid hello message");
} }
@@ -171,7 +171,9 @@ export async function createDaemonChannel(
const sharedKey = deriveSharedKey(daemonKeyPair.secretKey, clientPublicKey); const sharedKey = deriveSharedKey(daemonKeyPair.secretKey, clientPublicKey);
const channel = new EncryptedChannel(transport, sharedKey, events, { daemonKeyPair }); const channel = new EncryptedChannel(transport, sharedKey, events, { daemonKeyPair });
transport.send(JSON.stringify({ type: "ready" } satisfies ReadyMessage)); transport.send(
JSON.stringify({ type: "e2ee_ready" } satisfies E2EEReadyMessage)
);
channel.setState("open"); channel.setState("open");
events.onopen?.(); events.onopen?.();
@@ -240,8 +242,8 @@ export class EncryptedChannel {
if (this.state === "handshaking") { if (this.state === "handshaking") {
try { try {
const text = typeof data === "string" ? data : new TextDecoder().decode(data); const text = typeof data === "string" ? data : new TextDecoder().decode(data);
const msg = JSON.parse(text) as Partial<ReadyMessage>; const msg = JSON.parse(text) as Partial<E2EEReadyMessage>;
if (msg.type === "ready") { if (msg.type === "e2ee_ready") {
this.state = "open"; this.state = "open";
this.events.onopen?.(); this.events.onopen?.();
for (const cb of this.onOpenCallbacks) cb(); for (const cb of this.onOpenCallbacks) cb();
@@ -261,9 +263,11 @@ export class EncryptedChannel {
try { try {
const text = typeof data === "string" ? data : new TextDecoder().decode(data); const text = typeof data === "string" ? data : new TextDecoder().decode(data);
if (text.trim().startsWith("{")) { if (text.trim().startsWith("{")) {
const parsed = JSON.parse(text) as Partial<HelloMessage | ReadyMessage>; const parsed = JSON.parse(text) as Partial<
E2EEHelloMessage | E2EEReadyMessage
>;
if (parsed.type === "hello" && typeof parsed.key === "string") { if (parsed.type === "e2ee_hello" && typeof parsed.key === "string") {
if (this.options.daemonKeyPair) { if (this.options.daemonKeyPair) {
try { try {
const clientPublicKey = importPublicKey(parsed.key); const clientPublicKey = importPublicKey(parsed.key);
@@ -277,7 +281,7 @@ export class EncryptedChannel {
// the channel and cause decrypt failures. // the channel and cause decrypt failures.
if (keysEqual(nextSharedKey, this.sharedKey)) { if (keysEqual(nextSharedKey, this.sharedKey)) {
this.transport.send( this.transport.send(
JSON.stringify({ type: "ready" } satisfies ReadyMessage) JSON.stringify({ type: "e2ee_ready" } satisfies E2EEReadyMessage)
); );
return null; return null;
} }
@@ -290,7 +294,7 @@ export class EncryptedChannel {
this.sharedKey = nextSharedKey; this.sharedKey = nextSharedKey;
this.pendingSends = []; this.pendingSends = [];
this.transport.send( this.transport.send(
JSON.stringify({ type: "ready" } satisfies ReadyMessage) JSON.stringify({ type: "e2ee_ready" } satisfies E2EEReadyMessage)
); );
this.state = "open"; this.state = "open";
await this.flushPendingSends(); await this.flushPendingSends();
@@ -302,7 +306,7 @@ export class EncryptedChannel {
return null; return null;
} }
if (parsed.type === "ready") { if (parsed.type === "e2ee_ready") {
return null; return null;
} }