@junctionpanel/relay 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/crypto.js ADDED
@@ -0,0 +1,126 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * E2EE crypto primitives using NaCl (tweetnacl).
4
+ *
5
+ * - Key exchange: Curve25519 (nacl.box.before)
6
+ * - Encryption: XSalsa20-Poly1305 (nacl.box.after / open.after)
7
+ *
8
+ * Bundle format (binary):
9
+ * [nonce (24 bytes)] [ciphertext...]
10
+ *
11
+ * Transport format:
12
+ * The encrypted-channel sends the bundle as base64 text over WebSocket.
13
+ */
14
+ import nacl from "tweetnacl";
15
+ import { fromByteArray, toByteArray } from "base64-js";
16
+ const NONCE_LENGTH = nacl.box.nonceLength; // 24
17
+ let prngReady = false;
18
+ function ensurePrng() {
19
+ if (prngReady)
20
+ return;
21
+ try {
22
+ nacl.randomBytes(1);
23
+ prngReady = true;
24
+ return;
25
+ }
26
+ catch {
27
+ // fallthrough
28
+ }
29
+ const cryptoObj = globalThis.crypto;
30
+ if (cryptoObj?.getRandomValues) {
31
+ nacl.setPRNG((x, n) => {
32
+ cryptoObj.getRandomValues(x.subarray(0, n));
33
+ });
34
+ prngReady = true;
35
+ return;
36
+ }
37
+ throw new Error("No secure PRNG available for tweetnacl (missing crypto.getRandomValues)");
38
+ }
39
+ function encodeBase64(bytes) {
40
+ return fromByteArray(bytes);
41
+ }
42
+ function decodeBase64(base64) {
43
+ return toByteArray(base64);
44
+ }
45
+ function toUint8(data) {
46
+ return typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
47
+ }
48
+ function toArrayBuffer(bytes) {
49
+ const out = new Uint8Array(bytes.byteLength);
50
+ out.set(bytes);
51
+ return out.buffer;
52
+ }
53
+ export function generateKeyPair() {
54
+ ensurePrng();
55
+ const { publicKey, secretKey } = nacl.box.keyPair();
56
+ return { publicKey, secretKey };
57
+ }
58
+ export function exportPublicKey(publicKey) {
59
+ if (!(publicKey instanceof Uint8Array) || publicKey.byteLength !== nacl.box.publicKeyLength) {
60
+ throw new Error(`Invalid public key length (expected ${nacl.box.publicKeyLength})`);
61
+ }
62
+ return encodeBase64(publicKey);
63
+ }
64
+ export function importPublicKey(base64) {
65
+ const bytes = decodeBase64(base64);
66
+ if (bytes.byteLength !== nacl.box.publicKeyLength) {
67
+ throw new Error(`Invalid public key length (expected ${nacl.box.publicKeyLength})`);
68
+ }
69
+ return bytes;
70
+ }
71
+ export function exportSecretKey(secretKey) {
72
+ if (!(secretKey instanceof Uint8Array) || secretKey.byteLength !== nacl.box.secretKeyLength) {
73
+ throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
74
+ }
75
+ return encodeBase64(secretKey);
76
+ }
77
+ export function importSecretKey(base64) {
78
+ const bytes = decodeBase64(base64);
79
+ if (bytes.byteLength !== nacl.box.secretKeyLength) {
80
+ throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
81
+ }
82
+ return bytes;
83
+ }
84
+ export function deriveSharedKey(ourSecretKey, peerPublicKey) {
85
+ if (ourSecretKey.byteLength !== nacl.box.secretKeyLength) {
86
+ throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
87
+ }
88
+ if (peerPublicKey.byteLength !== nacl.box.publicKeyLength) {
89
+ throw new Error(`Invalid peer public key length (expected ${nacl.box.publicKeyLength})`);
90
+ }
91
+ return nacl.box.before(peerPublicKey, ourSecretKey);
92
+ }
93
+ /**
94
+ * Encrypts data and returns the binary bundle:
95
+ * [nonce (24)] [ciphertext...]
96
+ */
97
+ export function encrypt(sharedKey, data) {
98
+ ensurePrng();
99
+ const nonce = nacl.randomBytes(NONCE_LENGTH);
100
+ const plaintext = toUint8(data);
101
+ const ciphertext = nacl.box.after(plaintext, nonce, sharedKey);
102
+ const out = new Uint8Array(nonce.byteLength + ciphertext.byteLength);
103
+ out.set(nonce, 0);
104
+ out.set(ciphertext, nonce.byteLength);
105
+ return toArrayBuffer(out);
106
+ }
107
+ export function decrypt(sharedKey, data) {
108
+ const bytes = new Uint8Array(data);
109
+ if (bytes.byteLength < NONCE_LENGTH) {
110
+ throw new Error("Ciphertext bundle too short");
111
+ }
112
+ const nonce = bytes.slice(0, NONCE_LENGTH);
113
+ const ciphertext = bytes.slice(NONCE_LENGTH);
114
+ const opened = nacl.box.open.after(ciphertext, nonce, sharedKey);
115
+ if (!opened) {
116
+ throw new Error("Decryption failed");
117
+ }
118
+ const plaintext = toArrayBuffer(opened);
119
+ try {
120
+ return new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
121
+ }
122
+ catch {
123
+ return plaintext;
124
+ }
125
+ }
126
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B;;;;;;;;;;;GAWG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AASvD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,KAAK;AAEhD,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB,SAAS,UAAU;IACjB,IAAI,SAAS;QAAE,OAAO;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACpB,SAAS,GAAG,IAAI,CAAC;QACjB,OAAO;IACT,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IAED,MAAM,SAAS,GAAI,UAA6C,CAAC,MAAM,CAAC;IACxE,IAAI,SAAS,EAAE,eAAe,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACpB,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,SAAS,GAAG,IAAI,CAAC;QACjB,OAAO;IACT,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;AAC7F,CAAC;AAED,SAAS,YAAY,CAAC,KAAiB;IACrC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,OAAO,CAAC,IAA0B;IACzC,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,aAAa,CAAC,KAAiB;IACtC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7C,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAqB;IACnD,IAAI,CAAC,CAAC,SAAS,YAAY,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAqB;IACnD,IAAI,CAAC,CAAC,SAAS,YAAY,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,YAAwB,EACxB,aAAyB;IAEzB,IAAI,YAAY,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,aAAa,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,4CAA4C,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,SAAoB,EAAE,IAA0B;IACtE,UAAU,EAAE,CAAC;IACb,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrE,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClB,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,SAAoB,EAAE,IAAiB;IAC7D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,UAAU,GAAG,YAAY,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
package/dist/e2ee.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
2
+ export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
3
+ export { generateKeyPair, exportPublicKey, importPublicKey, exportSecretKey, importSecretKey, } from "./crypto.js";
4
+ export type { KeyPair, SharedKey } from "./crypto.js";
5
+ //# sourceMappingURL=e2ee.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2ee.d.ts","sourceRoot":"","sources":["../src/e2ee.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
package/dist/e2ee.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
2
+ export { generateKeyPair, exportPublicKey, importPublicKey, exportSecretKey, importSecretKey, } from "./crypto.js";
3
+ //# sourceMappingURL=e2ee.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2ee.js","sourceRoot":"","sources":["../src/e2ee.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Encrypted channel that wraps a WebSocket-like transport.
3
+ *
4
+ * Handles ECDH handshake and encrypts/decrypts all messages.
5
+ * Works identically for daemon and client sides.
6
+ */
7
+ import { type KeyPair, type SharedKey } from "./crypto.js";
8
+ export interface Transport {
9
+ send(data: string | ArrayBuffer): void;
10
+ close(code?: number, reason?: string): void;
11
+ onmessage: ((data: string | ArrayBuffer) => void) | null;
12
+ onclose: ((code: number, reason: string) => void) | null;
13
+ onerror: ((error: Error) => void) | null;
14
+ }
15
+ export interface EncryptedChannelEvents {
16
+ onopen?: () => void;
17
+ onmessage?: (data: string | ArrayBuffer) => void;
18
+ onclose?: (code: number, reason: string) => void;
19
+ onerror?: (error: Error) => void;
20
+ }
21
+ type ChannelState = "connecting" | "handshaking" | "open" | "closed";
22
+ type EncryptedChannelOptions = {
23
+ /**
24
+ * If set, the channel can validate repeated plaintext `{type:"e2ee_hello"}`
25
+ * messages even after it is open.
26
+ *
27
+ * This is useful for robustness when the client retries the handshake
28
+ * (e.g., it didn't observe the daemon's `{type:"e2ee_ready"}` yet). In that case,
29
+ * the daemon should re-send `{type:"e2ee_ready"}` without changing keys.
30
+ */
31
+ daemonKeyPair?: KeyPair;
32
+ };
33
+ /**
34
+ * Creates an encrypted channel as the initiator (client).
35
+ *
36
+ * The client:
37
+ * 1. Receives daemon's public key via QR code
38
+ * 2. Generates own keypair
39
+ * 3. Sends e2ee_hello with own public key
40
+ * 4. Derives shared key and starts encrypted communication
41
+ */
42
+ export declare function createClientChannel(transport: Transport, daemonPublicKeyB64: string, events?: EncryptedChannelEvents): Promise<EncryptedChannel>;
43
+ /**
44
+ * Creates an encrypted channel as the responder (daemon).
45
+ *
46
+ * The daemon:
47
+ * 1. Has pre-generated keypair (public key was in QR)
48
+ * 2. Waits for client's e2ee_hello with their public key
49
+ * 3. Derives shared key and starts encrypted communication
50
+ */
51
+ export declare function createDaemonChannel(transport: Transport, daemonKeyPair: KeyPair, events?: EncryptedChannelEvents): Promise<EncryptedChannel>;
52
+ /**
53
+ * Encrypted channel that wraps a transport with E2EE.
54
+ */
55
+ export declare class EncryptedChannel {
56
+ private transport;
57
+ private sharedKey;
58
+ private state;
59
+ private events;
60
+ private options;
61
+ private pendingSends;
62
+ private onOpenCallbacks;
63
+ private onCloseCallbacks;
64
+ constructor(transport: Transport, sharedKey: SharedKey, events?: EncryptedChannelEvents, options?: EncryptedChannelOptions);
65
+ setState(state: ChannelState): void;
66
+ private handleMessage;
67
+ send(data: string | ArrayBuffer): Promise<void>;
68
+ private flushPendingSends;
69
+ close(code?: number, reason?: string): void;
70
+ isOpen(): boolean;
71
+ onTransitionToOpen(cb: () => void): void;
72
+ onClose(cb: () => void): void;
73
+ }
74
+ export {};
75
+ //# sourceMappingURL=encrypted-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted-channel.d.ts","sourceRoot":"","sources":["../src/encrypted-channel.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,KAAK,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErE,KAAK,uBAAuB,GAAG;IAC7B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AA+BF;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,EACpB,kBAAkB,EAAE,MAAM,EAC1B,MAAM,GAAE,sBAA2B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAkD3B;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,OAAO,EACtB,MAAM,GAAE,sBAA2B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAoE3B;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,gBAAgB,CAAyB;gBAG/C,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,sBAA2B,EACnC,OAAO,GAAE,uBAA4B;IAkBvC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;YAIrB,aAAa;IAqHrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAkBvC,iBAAiB;IAS/B,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAmB,GAAG,IAAI;IAKnD,MAAM,IAAI,OAAO;IAIjB,kBAAkB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAIxC,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;CAG9B"}
@@ -0,0 +1,323 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Encrypted channel that wraps a WebSocket-like transport.
4
+ *
5
+ * Handles ECDH handshake and encrypts/decrypts all messages.
6
+ * Works identically for daemon and client sides.
7
+ */
8
+ import { generateKeyPair, exportPublicKey, importPublicKey, deriveSharedKey, encrypt, decrypt, } from "./crypto.js";
9
+ import { arrayBufferToBase64, base64ToArrayBuffer } from "./base64.js";
10
+ function buildInvalidHelloError(rawText, parsed) {
11
+ const parsedRecord = parsed && typeof parsed === "object"
12
+ ? parsed
13
+ : null;
14
+ const rawType = parsedRecord?.type;
15
+ const receivedType = typeof rawType === "string" ? rawType : rawType === undefined ? "undefined" : typeof rawType;
16
+ const hasKey = typeof parsedRecord?.key === "string" && parsedRecord.key.trim().length > 0;
17
+ const compact = rawText.replace(/\s+/g, " ").trim();
18
+ const preview = compact.length > 160 ? `${compact.slice(0, 157)}...` : compact;
19
+ return new Error(`Invalid hello message (receivedType=${receivedType}, hasKey=${hasKey}, preview=${JSON.stringify(preview)})`);
20
+ }
21
+ const HANDSHAKE_RETRY_MS = 1000;
22
+ const MAX_PENDING_SENDS = 200;
23
+ /**
24
+ * Creates an encrypted channel as the initiator (client).
25
+ *
26
+ * The client:
27
+ * 1. Receives daemon's public key via QR code
28
+ * 2. Generates own keypair
29
+ * 3. Sends e2ee_hello with own public key
30
+ * 4. Derives shared key and starts encrypted communication
31
+ */
32
+ export async function createClientChannel(transport, daemonPublicKeyB64, events = {}) {
33
+ const keyPair = generateKeyPair();
34
+ const daemonPublicKey = importPublicKey(daemonPublicKeyB64);
35
+ const sharedKey = deriveSharedKey(keyPair.secretKey, daemonPublicKey);
36
+ const channel = new EncryptedChannel(transport, sharedKey, events);
37
+ // Send e2ee_hello with our public key
38
+ const ourPublicKeyB64 = exportPublicKey(keyPair.publicKey);
39
+ const hello = { type: "e2ee_hello", key: ourPublicKeyB64 };
40
+ const helloText = JSON.stringify(hello);
41
+ let retry = null;
42
+ const emitSendError = (error) => {
43
+ const err = error instanceof Error ? error : new Error(String(error));
44
+ events.onerror?.(err);
45
+ };
46
+ const sendHello = () => {
47
+ try {
48
+ transport.send(helloText);
49
+ return true;
50
+ }
51
+ catch (error) {
52
+ // This can happen during daemon restarts while the socket transitions
53
+ // through CLOSING/CLOSED states. Report it but do not throw from timers.
54
+ emitSendError(error);
55
+ return false;
56
+ }
57
+ };
58
+ const clearRetry = () => {
59
+ if (retry) {
60
+ clearInterval(retry);
61
+ retry = null;
62
+ }
63
+ };
64
+ channel.onTransitionToOpen(() => clearRetry());
65
+ channel.onClose(() => clearRetry());
66
+ sendHello();
67
+ retry = setInterval(() => {
68
+ if (channel.isOpen()) {
69
+ clearRetry();
70
+ return;
71
+ }
72
+ sendHello();
73
+ }, HANDSHAKE_RETRY_MS);
74
+ // Avoid keeping Node processes alive (e.g. tests) if the handshake is stuck.
75
+ retry.unref?.();
76
+ return channel;
77
+ }
78
+ /**
79
+ * Creates an encrypted channel as the responder (daemon).
80
+ *
81
+ * The daemon:
82
+ * 1. Has pre-generated keypair (public key was in QR)
83
+ * 2. Waits for client's e2ee_hello with their public key
84
+ * 3. Derives shared key and starts encrypted communication
85
+ */
86
+ export async function createDaemonChannel(transport, daemonKeyPair, events = {}) {
87
+ return new Promise((resolve, reject) => {
88
+ const bufferedMessages = [];
89
+ const shouldIgnorePostHelloPlaintext = (data) => {
90
+ try {
91
+ const text = typeof data === "string" ? data : new TextDecoder().decode(data);
92
+ const parsed = JSON.parse(text);
93
+ return parsed.type === "e2ee_hello" || parsed.type === "e2ee_ready";
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ };
99
+ transport.onmessage = async (data) => {
100
+ try {
101
+ const helloText = typeof data === "string" ? data : new TextDecoder().decode(data);
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(helloText);
105
+ }
106
+ catch {
107
+ throw buildInvalidHelloError(helloText);
108
+ }
109
+ const msg = parsed;
110
+ if (msg.type !== "e2ee_hello" || typeof msg.key !== "string" || !msg.key.trim()) {
111
+ throw buildInvalidHelloError(helloText, parsed);
112
+ }
113
+ // Buffer any subsequent messages that arrive while we're doing async
114
+ // WebCrypto work to derive the shared key. Without this, it's possible
115
+ // for the next message (already encrypted) to be misinterpreted as a
116
+ // second hello, causing the handshake to fail.
117
+ transport.onmessage = (next) => {
118
+ bufferedMessages.push(next);
119
+ };
120
+ const clientPublicKey = importPublicKey(msg.key);
121
+ const sharedKey = deriveSharedKey(daemonKeyPair.secretKey, clientPublicKey);
122
+ const channel = new EncryptedChannel(transport, sharedKey, events, { daemonKeyPair });
123
+ transport.send(JSON.stringify({ type: "e2ee_ready" }));
124
+ channel.setState("open");
125
+ events.onopen?.();
126
+ for (const buffered of bufferedMessages) {
127
+ if (shouldIgnorePostHelloPlaintext(buffered))
128
+ continue;
129
+ transport.onmessage?.(buffered);
130
+ }
131
+ resolve(channel);
132
+ }
133
+ catch (error) {
134
+ reject(error);
135
+ }
136
+ };
137
+ transport.onerror = (error) => {
138
+ reject(error);
139
+ };
140
+ transport.onclose = (code, reason) => {
141
+ reject(new Error(`Connection closed during handshake: ${code} ${reason}`));
142
+ };
143
+ });
144
+ }
145
+ /**
146
+ * Encrypted channel that wraps a transport with E2EE.
147
+ */
148
+ export class EncryptedChannel {
149
+ constructor(transport, sharedKey, events = {}, options = {}) {
150
+ this.state = "handshaking";
151
+ this.pendingSends = [];
152
+ this.onOpenCallbacks = [];
153
+ this.onCloseCallbacks = [];
154
+ this.transport = transport;
155
+ this.sharedKey = sharedKey;
156
+ this.events = events;
157
+ this.options = options;
158
+ transport.onmessage = (data) => this.handleMessage(data);
159
+ transport.onclose = (code, reason) => {
160
+ this.state = "closed";
161
+ this.events.onclose?.(code, reason);
162
+ for (const cb of this.onCloseCallbacks)
163
+ cb();
164
+ };
165
+ transport.onerror = (error) => {
166
+ this.events.onerror?.(error);
167
+ };
168
+ }
169
+ setState(state) {
170
+ this.state = state;
171
+ }
172
+ async handleMessage(data) {
173
+ if (this.state === "handshaking") {
174
+ try {
175
+ const text = typeof data === "string" ? data : new TextDecoder().decode(data);
176
+ const msg = JSON.parse(text);
177
+ if (msg.type === "e2ee_ready") {
178
+ this.state = "open";
179
+ this.events.onopen?.();
180
+ for (const cb of this.onOpenCallbacks)
181
+ cb();
182
+ await this.flushPendingSends();
183
+ }
184
+ }
185
+ catch {
186
+ // ignore non-ready handshake traffic
187
+ }
188
+ return;
189
+ }
190
+ if (this.state !== "open")
191
+ return;
192
+ try {
193
+ const ciphertext = await (async () => {
194
+ // Handle (or ignore) any stray plaintext handshake traffic.
195
+ try {
196
+ const text = typeof data === "string" ? data : new TextDecoder().decode(data);
197
+ if (text.trim().startsWith("{")) {
198
+ const parsed = JSON.parse(text);
199
+ if (parsed.type === "e2ee_hello" && typeof parsed.key === "string") {
200
+ if (this.options.daemonKeyPair) {
201
+ try {
202
+ const clientPublicKey = importPublicKey(parsed.key);
203
+ const nextSharedKey = deriveSharedKey(this.options.daemonKeyPair.secretKey, clientPublicKey);
204
+ // If it's the same client key (handshake retry), re-send
205
+ // "ready" but do not re-key. Re-keying here would desync
206
+ // the channel and cause decrypt failures.
207
+ if (keysEqual(nextSharedKey, this.sharedKey)) {
208
+ this.transport.send(JSON.stringify({ type: "e2ee_ready" }));
209
+ return null;
210
+ }
211
+ // Different key implies a new client connection (common with relays
212
+ // where the daemon's socket stays open while the client reconnects).
213
+ // Re-key and re-send "ready". Drop any queued sends to avoid leaking
214
+ // messages between logical client sessions.
215
+ this.state = "handshaking";
216
+ this.sharedKey = nextSharedKey;
217
+ this.pendingSends = [];
218
+ this.transport.send(JSON.stringify({ type: "e2ee_ready" }));
219
+ this.state = "open";
220
+ await this.flushPendingSends();
221
+ return null;
222
+ }
223
+ catch (error) {
224
+ throw error;
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ if (parsed.type === "e2ee_ready") {
230
+ return null;
231
+ }
232
+ // Any other JSON-looking payload is plaintext app traffic, which
233
+ // means the peer is not encrypting (or we are out of sync).
234
+ throw new Error("Received plaintext frame on encrypted channel");
235
+ }
236
+ }
237
+ catch (error) {
238
+ // If we detected plaintext protocol mismatch, fail hard.
239
+ if (error instanceof Error && error.message.includes("plaintext frame")) {
240
+ throw error;
241
+ }
242
+ // Otherwise ignore JSON parse/TextDecoder failures and fall back to
243
+ // decoding ciphertext below.
244
+ }
245
+ if (typeof data === "string") {
246
+ return base64ToArrayBuffer(data);
247
+ }
248
+ // Some WebSocket implementations deliver text frames as ArrayBuffer.
249
+ // Our protocol always transmits ciphertext as base64 text.
250
+ try {
251
+ const decoded = new TextDecoder().decode(data);
252
+ return base64ToArrayBuffer(decoded);
253
+ }
254
+ catch {
255
+ return data;
256
+ }
257
+ })();
258
+ if (ciphertext) {
259
+ const plaintext = await decrypt(this.sharedKey, ciphertext);
260
+ this.events.onmessage?.(plaintext);
261
+ }
262
+ }
263
+ catch (error) {
264
+ const err = error instanceof Error ? error : new Error(String(error));
265
+ // Treat decryption/protocol errors as fatal so the peer can reconnect and
266
+ // re-handshake. Emitting an error event here can cause higher-level code
267
+ // to tear down the session without triggering a clean reconnect.
268
+ try {
269
+ this.transport.close(1011, err.message);
270
+ }
271
+ catch {
272
+ // ignore
273
+ }
274
+ }
275
+ }
276
+ async send(data) {
277
+ if (this.state === "handshaking") {
278
+ if (this.pendingSends.length >= MAX_PENDING_SENDS) {
279
+ this.pendingSends.shift();
280
+ }
281
+ this.pendingSends.push(data);
282
+ return;
283
+ }
284
+ if (this.state !== "open") {
285
+ throw new Error("Channel not open");
286
+ }
287
+ const ciphertext = await encrypt(this.sharedKey, data);
288
+ // Send as base64 for WebSocket text compatibility
289
+ this.transport.send(arrayBufferToBase64(ciphertext));
290
+ }
291
+ async flushPendingSends() {
292
+ if (this.state !== "open")
293
+ return;
294
+ const pending = this.pendingSends;
295
+ this.pendingSends = [];
296
+ for (const item of pending) {
297
+ await this.send(item);
298
+ }
299
+ }
300
+ close(code = 1000, reason = "Normal closure") {
301
+ this.state = "closed";
302
+ this.transport.close(code, reason);
303
+ }
304
+ isOpen() {
305
+ return this.state === "open";
306
+ }
307
+ onTransitionToOpen(cb) {
308
+ this.onOpenCallbacks.push(cb);
309
+ }
310
+ onClose(cb) {
311
+ this.onCloseCallbacks.push(cb);
312
+ }
313
+ }
314
+ function keysEqual(a, b) {
315
+ if (a.byteLength !== b.byteLength)
316
+ return false;
317
+ for (let i = 0; i < a.byteLength; i += 1) {
318
+ if (a[i] !== b[i])
319
+ return false;
320
+ }
321
+ return true;
322
+ }
323
+ //# sourceMappingURL=encrypted-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted-channel.js","sourceRoot":"","sources":["../src/encrypted-channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type { ConnectionRole, RelaySessionAttachment, } from "./types.js";
2
+ export { generateKeyPair, exportPublicKey, importPublicKey, deriveSharedKey, encrypt, decrypt, } from "./crypto.js";
3
+ export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
4
+ export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,OAAO,EACP,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { generateKeyPair, exportPublicKey, importPublicKey, deriveSharedKey, encrypt, decrypt, } from "./crypto.js";
2
+ export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,OAAO,EACP,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Relay connection types and interfaces.
3
+ *
4
+ * The relay bridges two WebSocket connections:
5
+ * - Server (daemon): The Junction server connecting to the relay
6
+ * - Client (app): The mobile/web app connecting to the relay
7
+ *
8
+ * Messages are forwarded bidirectionally without modification.
9
+ */
10
+ export type ConnectionRole = "server" | "client";
11
+ export interface RelaySessionAttachment {
12
+ serverId: string;
13
+ role: ConnectionRole;
14
+ /**
15
+ * Relay protocol version carried by this socket.
16
+ * v1: single server/client socket pair
17
+ * v2: control + per-client data sockets
18
+ */
19
+ version?: "1" | "2";
20
+ /**
21
+ * Unique id for the connection. Allows the daemon to create an
22
+ * independent socket + E2EE channel per connected connection.
23
+ */
24
+ connectionId?: string | null;
25
+ createdAt: number;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Relay connection types and interfaces.
3
+ *
4
+ * The relay bridges two WebSocket connections:
5
+ * - Server (daemon): The Junction server connecting to the relay
6
+ * - Client (app): The mobile/web app connecting to the relay
7
+ *
8
+ * Messages are forwarded bidirectionally without modification.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}