uxnan-bridge 0.0.1-alpha.20260621
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/README.md +150 -0
- package/dist/src/account-status.d.ts +13 -0
- package/dist/src/account-status.js +78 -0
- package/dist/src/account-status.js.map +1 -0
- package/dist/src/adapters/base-adapter.d.ts +18 -0
- package/dist/src/adapters/base-adapter.js +15 -0
- package/dist/src/adapters/base-adapter.js.map +1 -0
- package/dist/src/adapters/claude-adapter.d.ts +102 -0
- package/dist/src/adapters/claude-adapter.js +486 -0
- package/dist/src/adapters/claude-adapter.js.map +1 -0
- package/dist/src/adapters/claude-tools.d.ts +25 -0
- package/dist/src/adapters/claude-tools.js +146 -0
- package/dist/src/adapters/claude-tools.js.map +1 -0
- package/dist/src/adapters/codex-adapter.d.ts +116 -0
- package/dist/src/adapters/codex-adapter.js +912 -0
- package/dist/src/adapters/codex-adapter.js.map +1 -0
- package/dist/src/adapters/codex-app-server.d.ts +74 -0
- package/dist/src/adapters/codex-app-server.js +225 -0
- package/dist/src/adapters/codex-app-server.js.map +1 -0
- package/dist/src/adapters/codex-approval.d.ts +88 -0
- package/dist/src/adapters/codex-approval.js +160 -0
- package/dist/src/adapters/codex-approval.js.map +1 -0
- package/dist/src/adapters/codex-tools.d.ts +18 -0
- package/dist/src/adapters/codex-tools.js +106 -0
- package/dist/src/adapters/codex-tools.js.map +1 -0
- package/dist/src/adapters/content-blocks.d.ts +68 -0
- package/dist/src/adapters/content-blocks.js +205 -0
- package/dist/src/adapters/content-blocks.js.map +1 -0
- package/dist/src/adapters/echo-agent-adapter.d.ts +23 -0
- package/dist/src/adapters/echo-agent-adapter.js +72 -0
- package/dist/src/adapters/echo-agent-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-adapter.d.ts +87 -0
- package/dist/src/adapters/gemini-adapter.js +594 -0
- package/dist/src/adapters/gemini-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-tools.d.ts +4 -0
- package/dist/src/adapters/gemini-tools.js +48 -0
- package/dist/src/adapters/gemini-tools.js.map +1 -0
- package/dist/src/adapters/opencode-adapter.d.ts +74 -0
- package/dist/src/adapters/opencode-adapter.js +418 -0
- package/dist/src/adapters/opencode-adapter.js.map +1 -0
- package/dist/src/adapters/opencode-tools.d.ts +2 -0
- package/dist/src/adapters/opencode-tools.js +41 -0
- package/dist/src/adapters/opencode-tools.js.map +1 -0
- package/dist/src/adapters/pi-adapter.d.ts +92 -0
- package/dist/src/adapters/pi-adapter.js +467 -0
- package/dist/src/adapters/pi-adapter.js.map +1 -0
- package/dist/src/adapters/pi-tools.d.ts +10 -0
- package/dist/src/adapters/pi-tools.js +72 -0
- package/dist/src/adapters/pi-tools.js.map +1 -0
- package/dist/src/adapters/process-agent-adapter.d.ts +24 -0
- package/dist/src/adapters/process-agent-adapter.js +111 -0
- package/dist/src/adapters/process-agent-adapter.js.map +1 -0
- package/dist/src/adapters/resolve-claude.d.ts +13 -0
- package/dist/src/adapters/resolve-claude.js +57 -0
- package/dist/src/adapters/resolve-claude.js.map +1 -0
- package/dist/src/adapters/resolve-codex.d.ts +13 -0
- package/dist/src/adapters/resolve-codex.js +48 -0
- package/dist/src/adapters/resolve-codex.js.map +1 -0
- package/dist/src/adapters/resolve-gemini.d.ts +13 -0
- package/dist/src/adapters/resolve-gemini.js +47 -0
- package/dist/src/adapters/resolve-gemini.js.map +1 -0
- package/dist/src/adapters/resolve-opencode.d.ts +11 -0
- package/dist/src/adapters/resolve-opencode.js +49 -0
- package/dist/src/adapters/resolve-opencode.js.map +1 -0
- package/dist/src/adapters/resolve-pi.d.ts +13 -0
- package/dist/src/adapters/resolve-pi.js +46 -0
- package/dist/src/adapters/resolve-pi.js.map +1 -0
- package/dist/src/adapters/run-options.d.ts +22 -0
- package/dist/src/adapters/run-options.js +48 -0
- package/dist/src/adapters/run-options.js.map +1 -0
- package/dist/src/adapters/spawn.d.ts +20 -0
- package/dist/src/adapters/spawn.js +16 -0
- package/dist/src/adapters/spawn.js.map +1 -0
- package/dist/src/agents/agent-manager.d.ts +98 -0
- package/dist/src/agents/agent-manager.js +433 -0
- package/dist/src/agents/agent-manager.js.map +1 -0
- package/dist/src/agents/attachments.d.ts +28 -0
- package/dist/src/agents/attachments.js +121 -0
- package/dist/src/agents/attachments.js.map +1 -0
- package/dist/src/bridge-context.d.ts +45 -0
- package/dist/src/bridge-context.js +2 -0
- package/dist/src/bridge-context.js.map +1 -0
- package/dist/src/bridge-status.d.ts +12 -0
- package/dist/src/bridge-status.js +17 -0
- package/dist/src/bridge-status.js.map +1 -0
- package/dist/src/bridge.d.ts +37 -0
- package/dist/src/bridge.js +446 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +194 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/conversation/session-history.d.ts +27 -0
- package/dist/src/conversation/session-history.js +1082 -0
- package/dist/src/conversation/session-history.js.map +1 -0
- package/dist/src/conversation/thread-store.d.ts +74 -0
- package/dist/src/conversation/thread-store.js +366 -0
- package/dist/src/conversation/thread-store.js.map +1 -0
- package/dist/src/daemon-config.d.ts +123 -0
- package/dist/src/daemon-config.js +64 -0
- package/dist/src/daemon-config.js.map +1 -0
- package/dist/src/daemon-state.d.ts +27 -0
- package/dist/src/daemon-state.js +76 -0
- package/dist/src/daemon-state.js.map +1 -0
- package/dist/src/git/git-runner.d.ts +24 -0
- package/dist/src/git/git-runner.js +63 -0
- package/dist/src/git/git-runner.js.map +1 -0
- package/dist/src/git/git-service.d.ts +76 -0
- package/dist/src/git/git-service.js +435 -0
- package/dist/src/git/git-service.js.map +1 -0
- package/dist/src/handler-router.d.ts +34 -0
- package/dist/src/handler-router.js +67 -0
- package/dist/src/handler-router.js.map +1 -0
- package/dist/src/handlers/account-handler.d.ts +4 -0
- package/dist/src/handlers/account-handler.js +27 -0
- package/dist/src/handlers/account-handler.js.map +1 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +8 -0
- package/dist/src/handlers/agent-handler.js.map +1 -0
- package/dist/src/handlers/bridge-control-handler.d.ts +2 -0
- package/dist/src/handlers/bridge-control-handler.js +64 -0
- package/dist/src/handlers/bridge-control-handler.js.map +1 -0
- package/dist/src/handlers/desktop-handler.d.ts +12 -0
- package/dist/src/handlers/desktop-handler.js +5 -0
- package/dist/src/handlers/desktop-handler.js.map +1 -0
- package/dist/src/handlers/git-handler.d.ts +2 -0
- package/dist/src/handlers/git-handler.js +82 -0
- package/dist/src/handlers/git-handler.js.map +1 -0
- package/dist/src/handlers/index.d.ts +8 -0
- package/dist/src/handlers/index.js +22 -0
- package/dist/src/handlers/index.js.map +1 -0
- package/dist/src/handlers/not-implemented.d.ts +10 -0
- package/dist/src/handlers/not-implemented.js +21 -0
- package/dist/src/handlers/not-implemented.js.map +1 -0
- package/dist/src/handlers/notifications-handler.d.ts +2 -0
- package/dist/src/handlers/notifications-handler.js +62 -0
- package/dist/src/handlers/notifications-handler.js.map +1 -0
- package/dist/src/handlers/params.d.ts +11 -0
- package/dist/src/handlers/params.js +72 -0
- package/dist/src/handlers/params.js.map +1 -0
- package/dist/src/handlers/project-handler.d.ts +2 -0
- package/dist/src/handlers/project-handler.js +6 -0
- package/dist/src/handlers/project-handler.js.map +1 -0
- package/dist/src/handlers/thread-context-handler.d.ts +2 -0
- package/dist/src/handlers/thread-context-handler.js +211 -0
- package/dist/src/handlers/thread-context-handler.js.map +1 -0
- package/dist/src/handlers/workspace-handler.d.ts +2 -0
- package/dist/src/handlers/workspace-handler.js +101 -0
- package/dist/src/handlers/workspace-handler.js.map +1 -0
- package/dist/src/hooks/claude-approval-hook.d.ts +7 -0
- package/dist/src/hooks/claude-approval-hook.js +95 -0
- package/dist/src/hooks/claude-approval-hook.js.map +1 -0
- package/dist/src/hooks/gemini-approval-hook.d.ts +7 -0
- package/dist/src/hooks/gemini-approval-hook.js +113 -0
- package/dist/src/hooks/gemini-approval-hook.js.map +1 -0
- package/dist/src/index.d.ts +62 -0
- package/dist/src/index.js +65 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keyring-secret-store.d.ts +36 -0
- package/dist/src/keyring-secret-store.js +70 -0
- package/dist/src/keyring-secret-store.js.map +1 -0
- package/dist/src/lock-file.d.ts +18 -0
- package/dist/src/lock-file.js +60 -0
- package/dist/src/lock-file.js.map +1 -0
- package/dist/src/logger.d.ts +28 -0
- package/dist/src/logger.js +99 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/pairing/pairing-code-service.d.ts +45 -0
- package/dist/src/pairing/pairing-code-service.js +183 -0
- package/dist/src/pairing/pairing-code-service.js.map +1 -0
- package/dist/src/projects/project-registry.d.ts +14 -0
- package/dist/src/projects/project-registry.js +60 -0
- package/dist/src/projects/project-registry.js.map +1 -0
- package/dist/src/push/push-sender.d.ts +21 -0
- package/dist/src/push/push-sender.js +96 -0
- package/dist/src/push/push-sender.js.map +1 -0
- package/dist/src/push/push-service.d.ts +122 -0
- package/dist/src/push/push-service.js +260 -0
- package/dist/src/push/push-service.js.map +1 -0
- package/dist/src/qr.d.ts +17 -0
- package/dist/src/qr.js +31 -0
- package/dist/src/qr.js.map +1 -0
- package/dist/src/secret-store.d.ts +23 -0
- package/dist/src/secret-store.js +27 -0
- package/dist/src/secret-store.js.map +1 -0
- package/dist/src/secure-device-state.d.ts +16 -0
- package/dist/src/secure-device-state.js +63 -0
- package/dist/src/secure-device-state.js.map +1 -0
- package/dist/src/service-installer.d.ts +57 -0
- package/dist/src/service-installer.js +254 -0
- package/dist/src/service-installer.js.map +1 -0
- package/dist/src/session-state.d.ts +14 -0
- package/dist/src/session-state.js +19 -0
- package/dist/src/session-state.js.map +1 -0
- package/dist/src/transport/crypto.d.ts +43 -0
- package/dist/src/transport/crypto.js +78 -0
- package/dist/src/transport/crypto.js.map +1 -0
- package/dist/src/transport/lan-server.d.ts +33 -0
- package/dist/src/transport/lan-server.js +105 -0
- package/dist/src/transport/lan-server.js.map +1 -0
- package/dist/src/transport/local-hosts.d.ts +17 -0
- package/dist/src/transport/local-hosts.js +30 -0
- package/dist/src/transport/local-hosts.js.map +1 -0
- package/dist/src/transport/mdns-advertiser.d.ts +83 -0
- package/dist/src/transport/mdns-advertiser.js +282 -0
- package/dist/src/transport/mdns-advertiser.js.map +1 -0
- package/dist/src/transport/message-io.d.ts +33 -0
- package/dist/src/transport/message-io.js +87 -0
- package/dist/src/transport/message-io.js.map +1 -0
- package/dist/src/transport/outbound-log.d.ts +24 -0
- package/dist/src/transport/outbound-log.js +78 -0
- package/dist/src/transport/outbound-log.js.map +1 -0
- package/dist/src/transport/relay-client.d.ts +19 -0
- package/dist/src/transport/relay-client.js +27 -0
- package/dist/src/transport/relay-client.js.map +1 -0
- package/dist/src/transport/secure-channel.d.ts +33 -0
- package/dist/src/transport/secure-channel.js +81 -0
- package/dist/src/transport/secure-channel.js.map +1 -0
- package/dist/src/transport/server-handshake.d.ts +49 -0
- package/dist/src/transport/server-handshake.js +137 -0
- package/dist/src/transport/server-handshake.js.map +1 -0
- package/dist/src/transport/session-handler.d.ts +19 -0
- package/dist/src/transport/session-handler.js +134 -0
- package/dist/src/transport/session-handler.js.map +1 -0
- package/dist/src/transport/session-registry.d.ts +58 -0
- package/dist/src/transport/session-registry.js +91 -0
- package/dist/src/transport/session-registry.js.map +1 -0
- package/dist/src/transport/trust-store.d.ts +23 -0
- package/dist/src/transport/trust-store.js +33 -0
- package/dist/src/transport/trust-store.js.map +1 -0
- package/dist/src/transport/ws-adapter.d.ts +7 -0
- package/dist/src/transport/ws-adapter.js +16 -0
- package/dist/src/transport/ws-adapter.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +13 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/workspace/browse-service.d.ts +10 -0
- package/dist/src/workspace/browse-service.js +97 -0
- package/dist/src/workspace/browse-service.js.map +1 -0
- package/dist/src/workspace/checkpoint-service.d.ts +21 -0
- package/dist/src/workspace/checkpoint-service.js +219 -0
- package/dist/src/workspace/checkpoint-service.js.map +1 -0
- package/dist/src/workspace/path-guard.d.ts +7 -0
- package/dist/src/workspace/path-guard.js +51 -0
- package/dist/src/workspace/path-guard.js.map +1 -0
- package/dist/src/workspace/workspace-service.d.ts +8 -0
- package/dist/src/workspace/workspace-service.js +111 -0
- package/dist/src/workspace/workspace-service.js.map +1 -0
- package/package.json +46 -0
- package/scripts/extract-gemini-hook.mjs +16 -0
- package/scripts/fake-approval-bridge.mjs +23 -0
- package/scripts/install-service-linux.sh +38 -0
- package/scripts/install-service-macos.sh +38 -0
- package/scripts/install-service-windows.ps1 +26 -0
- package/scripts/test-gemini-hook-e2e.mjs +168 -0
- package/scripts/write-gemini-settings.mjs +31 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic binary message channel. Both the relay client and the LAN
|
|
3
|
+
* server adapt their WebSocket to this interface so the secure session logic is
|
|
4
|
+
* written once.
|
|
5
|
+
*
|
|
6
|
+
* Frames are raw bytes: UTF-8 JSON for handshake control messages and encrypted
|
|
7
|
+
* envelopes (matching the mobile app, which sends binary frames).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* An async FIFO over inbound frames. `next()` resolves with the next frame or
|
|
11
|
+
* rejects once the channel is closed and drained. Used to drive the request/
|
|
12
|
+
* response handshake sequentially.
|
|
13
|
+
*/
|
|
14
|
+
export class MessageQueue {
|
|
15
|
+
#buffer = [];
|
|
16
|
+
#waiters = [];
|
|
17
|
+
#closed = false;
|
|
18
|
+
push(bytes) {
|
|
19
|
+
const waiter = this.#waiters.shift();
|
|
20
|
+
if (waiter) {
|
|
21
|
+
waiter.resolve(bytes);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.#buffer.push(bytes);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
close() {
|
|
28
|
+
this.#closed = true;
|
|
29
|
+
while (this.#waiters.length > 0) {
|
|
30
|
+
this.#waiters.shift().reject(new Error('message channel closed'));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
next() {
|
|
34
|
+
const buffered = this.#buffer.shift();
|
|
35
|
+
if (buffered !== undefined)
|
|
36
|
+
return Promise.resolve(buffered);
|
|
37
|
+
if (this.#closed)
|
|
38
|
+
return Promise.reject(new Error('message channel closed'));
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.#waiters.push({ resolve, reject });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Attach a {@link MessageQueue} to a {@link MessageIO}. */
|
|
45
|
+
export function queueFor(io) {
|
|
46
|
+
const queue = new MessageQueue();
|
|
47
|
+
io.onMessage((bytes) => queue.push(bytes));
|
|
48
|
+
io.onClose(() => queue.close());
|
|
49
|
+
return queue;
|
|
50
|
+
}
|
|
51
|
+
/** Create a connected in-memory pair of {@link MessageIO}s (for tests). */
|
|
52
|
+
export function createInMemoryIoPair() {
|
|
53
|
+
const listeners = { a: [], b: [] };
|
|
54
|
+
const closers = { a: [], b: [] };
|
|
55
|
+
let open = true;
|
|
56
|
+
const closeBoth = () => {
|
|
57
|
+
if (!open)
|
|
58
|
+
return;
|
|
59
|
+
open = false;
|
|
60
|
+
for (const c of closers.a)
|
|
61
|
+
c();
|
|
62
|
+
for (const c of closers.b)
|
|
63
|
+
c();
|
|
64
|
+
};
|
|
65
|
+
const a = {
|
|
66
|
+
send: (bytes) => {
|
|
67
|
+
if (open)
|
|
68
|
+
for (const l of listeners.b)
|
|
69
|
+
l(bytes);
|
|
70
|
+
},
|
|
71
|
+
onMessage: (l) => listeners.a.push(l),
|
|
72
|
+
onClose: (l) => closers.a.push(l),
|
|
73
|
+
close: closeBoth,
|
|
74
|
+
};
|
|
75
|
+
const b = {
|
|
76
|
+
send: (bytes) => {
|
|
77
|
+
if (open)
|
|
78
|
+
for (const l of listeners.a)
|
|
79
|
+
l(bytes);
|
|
80
|
+
},
|
|
81
|
+
onMessage: (l) => listeners.b.push(l),
|
|
82
|
+
onClose: (l) => closers.b.push(l),
|
|
83
|
+
close: closeBoth,
|
|
84
|
+
};
|
|
85
|
+
return [a, b];
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=message-io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-io.js","sourceRoot":"","sources":["../../../src/transport/message-io.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A retained outbound message: its sequence number and the plaintext bytes. */
|
|
2
|
+
export interface OutboundLogEntry {
|
|
3
|
+
seq: number;
|
|
4
|
+
plaintext: Buffer;
|
|
5
|
+
}
|
|
6
|
+
export declare class OutboundLog {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(maxMessages?: number, maxBytes?: number);
|
|
9
|
+
/**
|
|
10
|
+
* Assign the next `seq` to a plaintext payload, retain it in the window, and
|
|
11
|
+
* return the assigned seq. Called for EVERY bridge→phone message (replies and
|
|
12
|
+
* notifications), whether sent live or buffered while offline.
|
|
13
|
+
*/
|
|
14
|
+
record(plaintext: Buffer): number;
|
|
15
|
+
/**
|
|
16
|
+
* Retained entries with `seq` strictly greater than `lastAppliedSeq`, oldest
|
|
17
|
+
* first — i.e. exactly what the phone is missing and must be replayed.
|
|
18
|
+
*/
|
|
19
|
+
entriesAfter(lastAppliedSeq: number): OutboundLogEntry[];
|
|
20
|
+
/** The seq that the next `record` will assign. */
|
|
21
|
+
get nextSeq(): number;
|
|
22
|
+
get length(): number;
|
|
23
|
+
get byteLength(): number;
|
|
24
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-device outbound (bridge → phone) log: assigns every bridge→phone payload a
|
|
3
|
+
* continuous, monotonic `seq` and retains the recent plaintext in a sliding
|
|
4
|
+
* window so it can be REPLAYED after a reconnect (seq-based catch-up).
|
|
5
|
+
*
|
|
6
|
+
* Why plaintext (not encrypted envelopes): every reconnect derives a FRESH
|
|
7
|
+
* session key (X25519 ephemeral handshake), so retained envelopes encrypted
|
|
8
|
+
* under the previous key are useless to the new channel. The log keeps the
|
|
9
|
+
* plaintext + its seq; on reconnect the new channel re-encrypts the entries the
|
|
10
|
+
* phone is missing (`encryptReplay`).
|
|
11
|
+
*
|
|
12
|
+
* The `seq` counter is owned HERE (not by the per-connection channel) precisely
|
|
13
|
+
* so it survives a reconnect: a new `BridgeSecureChannel` built over the same
|
|
14
|
+
* log continues the sequence instead of restarting at 1.
|
|
15
|
+
*
|
|
16
|
+
* Caps follow the spec (architecture/02a §5.9.2): at most
|
|
17
|
+
* MAX_BRIDGE_OUTBOUND_MESSAGES messages and MAX_BRIDGE_OUTBOUND_BYTES total; the
|
|
18
|
+
* oldest entries are evicted first. At least one message is always kept. When
|
|
19
|
+
* the phone's `lastAppliedBridgeOutboundSeq` predates the oldest retained entry
|
|
20
|
+
* (the bridge already evicted it, or restarted and lost the log), those messages
|
|
21
|
+
* are unrecoverable here — the phone falls back to `turn/list` re-sync.
|
|
22
|
+
*/
|
|
23
|
+
import { MAX_BRIDGE_OUTBOUND_BYTES, MAX_BRIDGE_OUTBOUND_MESSAGES } from '@uxnan/shared';
|
|
24
|
+
export class OutboundLog {
|
|
25
|
+
#entries = [];
|
|
26
|
+
#maxMessages;
|
|
27
|
+
#maxBytes;
|
|
28
|
+
#totalBytes = 0;
|
|
29
|
+
/** Next sequence number to hand out (1-based, never reset across reconnects). */
|
|
30
|
+
#nextSeq = 1;
|
|
31
|
+
constructor(maxMessages = MAX_BRIDGE_OUTBOUND_MESSAGES, maxBytes = MAX_BRIDGE_OUTBOUND_BYTES) {
|
|
32
|
+
this.#maxMessages = maxMessages;
|
|
33
|
+
this.#maxBytes = maxBytes;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Assign the next `seq` to a plaintext payload, retain it in the window, and
|
|
37
|
+
* return the assigned seq. Called for EVERY bridge→phone message (replies and
|
|
38
|
+
* notifications), whether sent live or buffered while offline.
|
|
39
|
+
*/
|
|
40
|
+
record(plaintext) {
|
|
41
|
+
const seq = this.#nextSeq;
|
|
42
|
+
this.#nextSeq += 1;
|
|
43
|
+
const bytes = plaintext.byteLength;
|
|
44
|
+
this.#entries.push({ seq, plaintext, bytes });
|
|
45
|
+
this.#totalBytes += bytes;
|
|
46
|
+
while (this.#entries.length > this.#maxMessages ||
|
|
47
|
+
(this.#totalBytes > this.#maxBytes && this.#entries.length > 1)) {
|
|
48
|
+
const evicted = this.#entries.shift();
|
|
49
|
+
if (!evicted)
|
|
50
|
+
break;
|
|
51
|
+
this.#totalBytes -= evicted.bytes;
|
|
52
|
+
}
|
|
53
|
+
return seq;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Retained entries with `seq` strictly greater than `lastAppliedSeq`, oldest
|
|
57
|
+
* first — i.e. exactly what the phone is missing and must be replayed.
|
|
58
|
+
*/
|
|
59
|
+
entriesAfter(lastAppliedSeq) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const entry of this.#entries) {
|
|
62
|
+
if (entry.seq > lastAppliedSeq)
|
|
63
|
+
out.push({ seq: entry.seq, plaintext: entry.plaintext });
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** The seq that the next `record` will assign. */
|
|
68
|
+
get nextSeq() {
|
|
69
|
+
return this.#nextSeq;
|
|
70
|
+
}
|
|
71
|
+
get length() {
|
|
72
|
+
return this.#entries.length;
|
|
73
|
+
}
|
|
74
|
+
get byteLength() {
|
|
75
|
+
return this.#totalBytes;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=outbound-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-log.js","sourceRoot":"","sources":["../../../src/transport/outbound-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAYxF,MAAM,OAAO,WAAW;IACb,QAAQ,GAAoB,EAAE,CAAC;IAC/B,YAAY,CAAS;IACrB,SAAS,CAAS;IAC3B,WAAW,GAAG,CAAC,CAAC;IAChB,iFAAiF;IACjF,QAAQ,GAAG,CAAC,CAAC;IAEb,YACE,cAAsB,4BAA4B,EAClD,WAAmB,yBAAyB;QAE5C,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,SAAiB;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC1B,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC;QAC1B,OACE,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY;YACxC,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,EAC/D,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO;gBAAE,MAAM;YACpB,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QACpC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,cAAsB;QACjC,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,GAAG,GAAG,cAAc;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,kDAAkD;IAClD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connects the bridge to the relay as the `mac` role for a given session, so a
|
|
3
|
+
* phone connecting with the same `x-session-id` is paired and its E2EE frames
|
|
4
|
+
* are forwarded here. Relay headers per architecture/02a §5.10.1.
|
|
5
|
+
*/
|
|
6
|
+
import { WebSocket } from 'ws';
|
|
7
|
+
import type { MessageIO } from './message-io.js';
|
|
8
|
+
export interface ConnectRelayOptions {
|
|
9
|
+
relayUrl: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
macDeviceId: string;
|
|
12
|
+
macIdentityPublicKey: string;
|
|
13
|
+
machineName: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RelayConnection {
|
|
16
|
+
ws: WebSocket;
|
|
17
|
+
io: MessageIO;
|
|
18
|
+
}
|
|
19
|
+
export declare function connectRelayAsMac(options: ConnectRelayOptions): Promise<RelayConnection>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connects the bridge to the relay as the `mac` role for a given session, so a
|
|
3
|
+
* phone connecting with the same `x-session-id` is paired and its E2EE frames
|
|
4
|
+
* are forwarded here. Relay headers per architecture/02a §5.10.1.
|
|
5
|
+
*/
|
|
6
|
+
import { WebSocket } from 'ws';
|
|
7
|
+
import { wsToMessageIO } from './ws-adapter.js';
|
|
8
|
+
export function connectRelayAsMac(options) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const ws = new WebSocket(options.relayUrl, {
|
|
11
|
+
headers: {
|
|
12
|
+
'x-role': 'mac',
|
|
13
|
+
'x-session-id': options.sessionId,
|
|
14
|
+
'x-mac-device-id': options.macDeviceId,
|
|
15
|
+
'x-mac-identity-public-key': options.macIdentityPublicKey,
|
|
16
|
+
'x-machine-name': options.machineName,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const onError = (err) => reject(err);
|
|
20
|
+
ws.once('error', onError);
|
|
21
|
+
ws.once('open', () => {
|
|
22
|
+
ws.removeListener('error', onError);
|
|
23
|
+
resolve({ ws, io: wsToMessageIO(ws) });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=relay-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-client.js","sourceRoot":"","sources":["../../../src/transport/relay-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAehD,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;YACzC,OAAO,EAAE;gBACP,QAAQ,EAAE,KAAK;gBACf,cAAc,EAAE,OAAO,CAAC,SAAS;gBACjC,iBAAiB,EAAE,OAAO,CAAC,WAAW;gBACtC,2BAA2B,EAAE,OAAO,CAAC,oBAAoB;gBACzD,gBAAgB,EAAE,OAAO,CAAC,WAAW;aACtC;SACF,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,GAAU,EAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1B,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;YACnB,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted channel over an established session (bridge side).
|
|
3
|
+
*
|
|
4
|
+
* Outbound (bridge → phone) sequence numbers are 1-based; inbound (phone →
|
|
5
|
+
* bridge) envelopes must have a strictly increasing `seq` (replay protection),
|
|
6
|
+
* matching the mobile `SecureChannel` (architecture/02a §5.9.1, 02b §5.3).
|
|
7
|
+
*/
|
|
8
|
+
import type { SecureEnvelope } from '@uxnan/shared';
|
|
9
|
+
import type { OutboundLog } from './outbound-log.js';
|
|
10
|
+
export declare class ReplayError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class BridgeSecureChannel {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(key: Buffer, sessionId: string, log?: OutboundLog);
|
|
16
|
+
get sessionId(): string;
|
|
17
|
+
get nextOutboundSeq(): number;
|
|
18
|
+
get lastInboundSeq(): number;
|
|
19
|
+
/**
|
|
20
|
+
* Encrypt a plaintext payload into the next outbound envelope. The seq is
|
|
21
|
+
* drawn from (and the plaintext retained in) the outbound log when one is
|
|
22
|
+
* attached, so the message can be replayed after a reconnect.
|
|
23
|
+
*/
|
|
24
|
+
encrypt(plaintext: Buffer): SecureEnvelope;
|
|
25
|
+
/**
|
|
26
|
+
* Re-encrypt a retained plaintext under THIS channel's (new) key with its
|
|
27
|
+
* ORIGINAL seq, for seq-based catch-up after a reconnect. Does NOT advance the
|
|
28
|
+
* counter or record into the log — the entry already lives there.
|
|
29
|
+
*/
|
|
30
|
+
encryptReplay(seq: number, plaintext: Buffer): SecureEnvelope;
|
|
31
|
+
/** Decrypt an inbound envelope, enforcing session and replay checks. */
|
|
32
|
+
decrypt(envelope: SecureEnvelope): Buffer;
|
|
33
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { aesGcmDecrypt, aesGcmEncrypt } from './crypto.js';
|
|
2
|
+
export class ReplayError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'ReplayError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class BridgeSecureChannel {
|
|
9
|
+
#key;
|
|
10
|
+
#sessionId;
|
|
11
|
+
/**
|
|
12
|
+
* Per-device outbound log that owns the seq counter + retains plaintext for
|
|
13
|
+
* catch-up. When present, `encrypt` records here and the seq continues across
|
|
14
|
+
* reconnects. When absent (tests, the phone-side peer), an internal 1-based
|
|
15
|
+
* counter is used and nothing is retained.
|
|
16
|
+
*/
|
|
17
|
+
#log;
|
|
18
|
+
#fallbackSeq;
|
|
19
|
+
#lastInboundSeq;
|
|
20
|
+
constructor(key, sessionId, log) {
|
|
21
|
+
this.#key = key;
|
|
22
|
+
this.#sessionId = sessionId;
|
|
23
|
+
this.#log = log;
|
|
24
|
+
this.#fallbackSeq = 1;
|
|
25
|
+
this.#lastInboundSeq = 0;
|
|
26
|
+
}
|
|
27
|
+
get sessionId() {
|
|
28
|
+
return this.#sessionId;
|
|
29
|
+
}
|
|
30
|
+
get nextOutboundSeq() {
|
|
31
|
+
return this.#log ? this.#log.nextSeq : this.#fallbackSeq;
|
|
32
|
+
}
|
|
33
|
+
get lastInboundSeq() {
|
|
34
|
+
return this.#lastInboundSeq;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Encrypt a plaintext payload into the next outbound envelope. The seq is
|
|
38
|
+
* drawn from (and the plaintext retained in) the outbound log when one is
|
|
39
|
+
* attached, so the message can be replayed after a reconnect.
|
|
40
|
+
*/
|
|
41
|
+
encrypt(plaintext) {
|
|
42
|
+
const seq = this.#log ? this.#log.record(plaintext) : this.#fallbackSeq++;
|
|
43
|
+
return this.#seal(seq, plaintext);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Re-encrypt a retained plaintext under THIS channel's (new) key with its
|
|
47
|
+
* ORIGINAL seq, for seq-based catch-up after a reconnect. Does NOT advance the
|
|
48
|
+
* counter or record into the log — the entry already lives there.
|
|
49
|
+
*/
|
|
50
|
+
encryptReplay(seq, plaintext) {
|
|
51
|
+
return this.#seal(seq, plaintext);
|
|
52
|
+
}
|
|
53
|
+
#seal(seq, plaintext) {
|
|
54
|
+
const parts = aesGcmEncrypt(this.#key, plaintext);
|
|
55
|
+
return {
|
|
56
|
+
kind: 'encryptedEnvelope',
|
|
57
|
+
sessionId: this.#sessionId,
|
|
58
|
+
seq,
|
|
59
|
+
nonce: parts.nonceHex,
|
|
60
|
+
ciphertext: parts.ciphertextB64,
|
|
61
|
+
tag: parts.tagB64,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Decrypt an inbound envelope, enforcing session and replay checks. */
|
|
65
|
+
decrypt(envelope) {
|
|
66
|
+
if (envelope.sessionId !== this.#sessionId) {
|
|
67
|
+
throw new Error('envelope sessionId mismatch');
|
|
68
|
+
}
|
|
69
|
+
if (envelope.seq <= this.#lastInboundSeq) {
|
|
70
|
+
throw new ReplayError(`envelope seq ${envelope.seq} <= last applied ${this.#lastInboundSeq}`);
|
|
71
|
+
}
|
|
72
|
+
const plaintext = aesGcmDecrypt(this.#key, {
|
|
73
|
+
nonceHex: envelope.nonce,
|
|
74
|
+
ciphertextB64: envelope.ciphertext,
|
|
75
|
+
tagB64: envelope.tag,
|
|
76
|
+
});
|
|
77
|
+
this.#lastInboundSeq = envelope.seq;
|
|
78
|
+
return plaintext;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=secure-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-channel.js","sourceRoot":"","sources":["../../../src/transport/secure-channel.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG3D,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,mBAAmB;IACrB,IAAI,CAAS;IACb,UAAU,CAAS;IAC5B;;;;;OAKG;IACM,IAAI,CAA0B;IACvC,YAAY,CAAS;IACrB,eAAe,CAAS;IAExB,YAAY,GAAW,EAAE,SAAiB,EAAE,GAAiB;QAC3D,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;IAC3D,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,SAAiB;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1E,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,GAAW,EAAE,SAAiB;QAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,SAAiB;QAClC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAClD,OAAO;YACL,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,GAAG;YACH,KAAK,EAAE,KAAK,CAAC,QAAQ;YACrB,UAAU,EAAE,KAAK,CAAC,aAAa;YAC/B,GAAG,EAAE,KAAK,CAAC,MAAM;SAClB,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,OAAO,CAAC,QAAwB;QAC9B,IAAI,QAAQ,CAAC,SAAS,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,QAAQ,CAAC,GAAG,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzC,MAAM,IAAI,WAAW,CAAC,gBAAgB,QAAQ,CAAC,GAAG,oBAAoB,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE;YACzC,QAAQ,EAAE,QAAQ,CAAC,KAAK;YACxB,aAAa,EAAE,QAAQ,CAAC,UAAU;YAClC,MAAM,EAAE,QAAQ,CAAC,GAAG;SACrB,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server (bridge) side of the E2EE handshake
|
|
3
|
+
* (architecture/02a §5.9.1, Phase 2): clientHello → serverHello → clientAuth →
|
|
4
|
+
* ready. Interoperates byte-for-byte with the mobile `SecureTransportLayer`.
|
|
5
|
+
*/
|
|
6
|
+
import { type HandshakeMode } from '@uxnan/shared';
|
|
7
|
+
import type { SecureDeviceState } from '../secure-device-state.js';
|
|
8
|
+
import type { MessageQueue } from './message-io.js';
|
|
9
|
+
import type { TrustStore } from './trust-store.js';
|
|
10
|
+
import { BridgeSecureChannel } from './secure-channel.js';
|
|
11
|
+
import type { OutboundLog } from './outbound-log.js';
|
|
12
|
+
export declare class HandshakeError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export interface ServerHandshakeOptions {
|
|
16
|
+
queue: MessageQueue;
|
|
17
|
+
send: (message: unknown) => void;
|
|
18
|
+
deviceState: SecureDeviceState;
|
|
19
|
+
trustStore: TrustStore;
|
|
20
|
+
displayName: string;
|
|
21
|
+
now: () => number;
|
|
22
|
+
/** If set, the clientHello sessionId must match (active pairing session). */
|
|
23
|
+
expectedSessionId?: string;
|
|
24
|
+
/** Key epoch to advertise (default 1). */
|
|
25
|
+
keyEpoch?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the per-device outbound log (seq counter + catch-up window) for the
|
|
28
|
+
* phone identified in the clientHello. The channel is built over it so its
|
|
29
|
+
* `seq` continues across reconnects and every outbound message is retained.
|
|
30
|
+
* Omitted in tests that don't exercise catch-up (channel uses its fallback
|
|
31
|
+
* counter, nothing retained).
|
|
32
|
+
*/
|
|
33
|
+
outboundLogFor?: (phoneDeviceId: string) => OutboundLog;
|
|
34
|
+
}
|
|
35
|
+
export interface ServerHandshakeResult {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
phoneDeviceId: string;
|
|
38
|
+
phoneIdentityPublicKey: string;
|
|
39
|
+
mode: HandshakeMode;
|
|
40
|
+
keyEpoch: number;
|
|
41
|
+
channel: BridgeSecureChannel;
|
|
42
|
+
/**
|
|
43
|
+
* Highest bridge→phone `seq` the phone reports having applied, from
|
|
44
|
+
* `clientHello.resumeState` (0 when absent/invalid). The caller replays the
|
|
45
|
+
* outbound log's entries with a greater seq.
|
|
46
|
+
*/
|
|
47
|
+
lastAppliedBridgeOutboundSeq: number;
|
|
48
|
+
}
|
|
49
|
+
export declare function performServerHandshake(options: ServerHandshakeOptions): Promise<ServerHandshakeResult>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server (bridge) side of the E2EE handshake
|
|
3
|
+
* (architecture/02a §5.9.1, Phase 2): clientHello → serverHello → clientAuth →
|
|
4
|
+
* ready. Interoperates byte-for-byte with the mobile `SecureTransportLayer`.
|
|
5
|
+
*/
|
|
6
|
+
import { MAX_PAIRING_AGE_MS, SECURE_PROTOCOL_VERSION, buildHandshakeTranscript, } from '@uxnan/shared';
|
|
7
|
+
import { BridgeSecureChannel } from './secure-channel.js';
|
|
8
|
+
import { deriveSessionKey, generateEphemeralKeyPair, randomHex, verifyEd25519 } from './crypto.js';
|
|
9
|
+
export class HandshakeError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'HandshakeError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function requireString(obj, key) {
|
|
16
|
+
const value = obj[key];
|
|
17
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
18
|
+
throw new HandshakeError(`clientHello missing field: ${key}`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export async function performServerHandshake(options) {
|
|
23
|
+
const { queue, send, deviceState, trustStore, now } = options;
|
|
24
|
+
const helloRaw = await queue.next();
|
|
25
|
+
const hello = parseJson(helloRaw);
|
|
26
|
+
if (hello['kind'] !== 'clientHello') {
|
|
27
|
+
throw new HandshakeError(`expected clientHello, got ${String(hello['kind'])}`);
|
|
28
|
+
}
|
|
29
|
+
const sessionId = requireString(hello, 'sessionId');
|
|
30
|
+
if (options.expectedSessionId && sessionId !== options.expectedSessionId) {
|
|
31
|
+
throw new HandshakeError('sessionId does not match the active pairing session');
|
|
32
|
+
}
|
|
33
|
+
const phoneDeviceId = requireString(hello, 'phoneDeviceId');
|
|
34
|
+
const phoneIdentityPublicKey = requireString(hello, 'phoneIdentityPublicKey');
|
|
35
|
+
const phoneEphemeralPublicKey = requireString(hello, 'phoneEphemeralPublicKey');
|
|
36
|
+
const clientNonce = requireString(hello, 'clientNonce');
|
|
37
|
+
const lastAppliedBridgeOutboundSeq = parseResumeSeq(hello['resumeState']);
|
|
38
|
+
const mode = hello['handshakeMode'] === 'trusted_reconnect' ? 'trusted_reconnect' : 'qr_bootstrap';
|
|
39
|
+
if (mode === 'trusted_reconnect') {
|
|
40
|
+
const trusted = await trustStore.get(phoneDeviceId);
|
|
41
|
+
if (!trusted || trusted.publicKey !== phoneIdentityPublicKey) {
|
|
42
|
+
throw new HandshakeError('trusted reconnect: phone identity is not trusted');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const ephemeral = generateEphemeralKeyPair();
|
|
46
|
+
const serverNonce = randomHex(32);
|
|
47
|
+
const keyEpoch = options.keyEpoch ?? 1;
|
|
48
|
+
const expiresAtForTranscript = now() + MAX_PAIRING_AGE_MS;
|
|
49
|
+
const identity = deviceState.identity;
|
|
50
|
+
const transcript = buildHandshakeTranscript({
|
|
51
|
+
clientNonce,
|
|
52
|
+
phoneEphemeralPublicKey,
|
|
53
|
+
macEphemeralPublicKey: ephemeral.publicKeyHex,
|
|
54
|
+
serverNonce,
|
|
55
|
+
sessionId,
|
|
56
|
+
keyEpoch,
|
|
57
|
+
expiresAtForTranscript,
|
|
58
|
+
});
|
|
59
|
+
const transcriptBytes = Buffer.from(transcript, 'utf-8');
|
|
60
|
+
const macSignature = deviceState.sign(transcriptBytes);
|
|
61
|
+
send({
|
|
62
|
+
kind: 'serverHello',
|
|
63
|
+
protocolVersion: SECURE_PROTOCOL_VERSION,
|
|
64
|
+
sessionId,
|
|
65
|
+
macDeviceId: identity.macDeviceId,
|
|
66
|
+
macIdentityPublicKey: identity.macIdentityPublicKey,
|
|
67
|
+
macEphemeralPublicKey: ephemeral.publicKeyHex,
|
|
68
|
+
serverNonce,
|
|
69
|
+
keyEpoch,
|
|
70
|
+
expiresAtForTranscript,
|
|
71
|
+
macSignature,
|
|
72
|
+
clientNonce,
|
|
73
|
+
displayName: options.displayName,
|
|
74
|
+
});
|
|
75
|
+
const authRaw = await queue.next();
|
|
76
|
+
const auth = parseJson(authRaw);
|
|
77
|
+
if (auth['kind'] !== 'clientAuth') {
|
|
78
|
+
throw new HandshakeError(`expected clientAuth, got ${String(auth['kind'])}`);
|
|
79
|
+
}
|
|
80
|
+
if (auth['sessionId'] !== sessionId) {
|
|
81
|
+
throw new HandshakeError('clientAuth sessionId mismatch');
|
|
82
|
+
}
|
|
83
|
+
const phoneSignature = requireString(auth, 'phoneSignature');
|
|
84
|
+
if (!verifyEd25519(transcriptBytes, phoneSignature, phoneIdentityPublicKey)) {
|
|
85
|
+
throw new HandshakeError('phone signature verification failed');
|
|
86
|
+
}
|
|
87
|
+
const key = deriveSessionKey({
|
|
88
|
+
privateKey: ephemeral.privateKey,
|
|
89
|
+
peerPublicHex: phoneEphemeralPublicKey,
|
|
90
|
+
clientNonceHex: clientNonce,
|
|
91
|
+
serverNonceHex: serverNonce,
|
|
92
|
+
});
|
|
93
|
+
if (mode === 'qr_bootstrap') {
|
|
94
|
+
await trustStore.upsert({
|
|
95
|
+
deviceId: phoneDeviceId,
|
|
96
|
+
displayName: phoneDeviceId,
|
|
97
|
+
publicKey: phoneIdentityPublicKey,
|
|
98
|
+
pairedAt: now(),
|
|
99
|
+
lastSeen: now(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
send({ kind: 'ready', sessionId, keyEpoch, macDeviceId: identity.macDeviceId });
|
|
103
|
+
// Build the channel over this phone's persistent outbound log so its seq
|
|
104
|
+
// continues across reconnects and every outbound message is retained for
|
|
105
|
+
// catch-up.
|
|
106
|
+
const outboundLog = options.outboundLogFor?.(phoneDeviceId);
|
|
107
|
+
return {
|
|
108
|
+
sessionId,
|
|
109
|
+
phoneDeviceId,
|
|
110
|
+
phoneIdentityPublicKey,
|
|
111
|
+
mode,
|
|
112
|
+
keyEpoch,
|
|
113
|
+
channel: new BridgeSecureChannel(key, sessionId, outboundLog),
|
|
114
|
+
lastAppliedBridgeOutboundSeq,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Read `clientHello.resumeState.lastAppliedBridgeOutboundSeq` defensively: a
|
|
119
|
+
* non-negative integer is honored, anything else (absent, NaN, negative, wrong
|
|
120
|
+
* type) means "no catch-up" → 0.
|
|
121
|
+
*/
|
|
122
|
+
function parseResumeSeq(resumeState) {
|
|
123
|
+
if (!resumeState || typeof resumeState !== 'object')
|
|
124
|
+
return 0;
|
|
125
|
+
const value = resumeState['lastAppliedBridgeOutboundSeq'];
|
|
126
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0)
|
|
127
|
+
return 0;
|
|
128
|
+
return Math.floor(value);
|
|
129
|
+
}
|
|
130
|
+
function parseJson(bytes) {
|
|
131
|
+
const decoded = JSON.parse(bytes.toString('utf-8'));
|
|
132
|
+
if (typeof decoded !== 'object' || decoded === null) {
|
|
133
|
+
throw new HandshakeError('handshake frame is not a JSON object');
|
|
134
|
+
}
|
|
135
|
+
return decoded;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=server-handshake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-handshake.js","sourceRoot":"","sources":["../../../src/transport/server-handshake.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BridgeContext } from '../bridge-context.js';
|
|
2
|
+
import type { HandlerRouter } from '../handler-router.js';
|
|
3
|
+
import type { SecureDeviceState } from '../secure-device-state.js';
|
|
4
|
+
import { type MessageIO } from './message-io.js';
|
|
5
|
+
import type { TrustStore } from './trust-store.js';
|
|
6
|
+
export interface SecureConnectionOptions {
|
|
7
|
+
io: MessageIO;
|
|
8
|
+
ctx: BridgeContext;
|
|
9
|
+
router: HandlerRouter;
|
|
10
|
+
deviceState: SecureDeviceState;
|
|
11
|
+
trustStore: TrustStore;
|
|
12
|
+
displayName: string;
|
|
13
|
+
expectedSessionId?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Handle a connection end-to-end. Resolves when the connection closes. Never
|
|
17
|
+
* throws — handshake/transport failures are logged and the channel is closed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleSecureConnection(options: SecureConnectionOptions): Promise<void>;
|