@yefengr/pi-reach 0.0.0-stage → 0.0.2

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.
Files changed (129) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +110 -2
  3. package/dist/actions/handlers.d.ts +143 -0
  4. package/dist/actions/handlers.js +200 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +58 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/actions/session_new_bridge.d.ts +35 -0
  10. package/dist/actions/session_new_bridge.js +88 -0
  11. package/dist/actions/session_new_bridge.js.map +1 -0
  12. package/dist/commands.d.ts +29 -0
  13. package/dist/commands.js +169 -0
  14. package/dist/commands.js.map +1 -0
  15. package/dist/config.d.ts +61 -0
  16. package/dist/config.js +115 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +27 -0
  19. package/dist/index.js +618 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/pairing/crypto.d.ts +15 -0
  22. package/dist/pairing/crypto.js +72 -0
  23. package/dist/pairing/crypto.js.map +1 -0
  24. package/dist/pairing/identity-keyring.d.ts +22 -0
  25. package/dist/pairing/identity-keyring.js +94 -0
  26. package/dist/pairing/identity-keyring.js.map +1 -0
  27. package/dist/pairing/identity-lock.d.ts +14 -0
  28. package/dist/pairing/identity-lock.js +116 -0
  29. package/dist/pairing/identity-lock.js.map +1 -0
  30. package/dist/pairing/owner_storage.d.ts +61 -0
  31. package/dist/pairing/owner_storage.js +263 -0
  32. package/dist/pairing/owner_storage.js.map +1 -0
  33. package/dist/pairing/qr.d.ts +39 -0
  34. package/dist/pairing/qr.js +113 -0
  35. package/dist/pairing/qr.js.map +1 -0
  36. package/dist/pairing/storage.d.ts +36 -0
  37. package/dist/pairing/storage.js +263 -0
  38. package/dist/pairing/storage.js.map +1 -0
  39. package/dist/protocol/types.d.ts +225 -0
  40. package/dist/protocol/types.js +2 -0
  41. package/dist/protocol/types.js.map +1 -0
  42. package/dist/protocol/v2/codec.d.ts +6 -0
  43. package/dist/protocol/v2/codec.js +4 -0
  44. package/dist/protocol/v2/codec.js.map +1 -0
  45. package/dist/protocol/v2/index.d.ts +4 -0
  46. package/dist/protocol/v2/index.js +5 -0
  47. package/dist/protocol/v2/index.js.map +1 -0
  48. package/dist/protocol/v2/marker.d.ts +45 -0
  49. package/dist/protocol/v2/marker.js +47 -0
  50. package/dist/protocol/v2/marker.js.map +1 -0
  51. package/dist/protocol/v2/schemas.d.ts +3 -0
  52. package/dist/protocol/v2/schemas.js +4 -0
  53. package/dist/protocol/v2/schemas.js.map +1 -0
  54. package/dist/protocol/v2/session_state.d.ts +79 -0
  55. package/dist/protocol/v2/session_state.js +166 -0
  56. package/dist/protocol/v2/session_state.js.map +1 -0
  57. package/dist/runtime/endpoint_metadata.d.ts +14 -0
  58. package/dist/runtime/endpoint_metadata.js +18 -0
  59. package/dist/runtime/endpoint_metadata.js.map +1 -0
  60. package/dist/runtime/extension_owner.d.ts +12 -0
  61. package/dist/runtime/extension_owner.js +80 -0
  62. package/dist/runtime/extension_owner.js.map +1 -0
  63. package/dist/runtime/footer.d.ts +6 -0
  64. package/dist/runtime/footer.js +21 -0
  65. package/dist/runtime/footer.js.map +1 -0
  66. package/dist/runtime/owner_router.d.ts +16 -0
  67. package/dist/runtime/owner_router.js +41 -0
  68. package/dist/runtime/owner_router.js.map +1 -0
  69. package/dist/runtime/pairing_coordinator.d.ts +42 -0
  70. package/dist/runtime/pairing_coordinator.js +136 -0
  71. package/dist/runtime/pairing_coordinator.js.map +1 -0
  72. package/dist/runtime/relay_lifecycle.d.ts +64 -0
  73. package/dist/runtime/relay_lifecycle.js +275 -0
  74. package/dist/runtime/relay_lifecycle.js.map +1 -0
  75. package/dist/session/local_config.d.ts +7 -0
  76. package/dist/session/local_config.js +52 -0
  77. package/dist/session/local_config.js.map +1 -0
  78. package/dist/timeline/history.d.ts +30 -0
  79. package/dist/timeline/history.js +170 -0
  80. package/dist/timeline/history.js.map +1 -0
  81. package/dist/timeline/partial_delta.d.ts +2 -0
  82. package/dist/timeline/partial_delta.js +16 -0
  83. package/dist/timeline/partial_delta.js.map +1 -0
  84. package/dist/timeline/run_end.d.ts +16 -0
  85. package/dist/timeline/run_end.js +23 -0
  86. package/dist/timeline/run_end.js.map +1 -0
  87. package/dist/timeline/runtime.d.ts +89 -0
  88. package/dist/timeline/runtime.js +546 -0
  89. package/dist/timeline/runtime.js.map +1 -0
  90. package/dist/timeline/sequence.d.ts +7 -0
  91. package/dist/timeline/sequence.js +16 -0
  92. package/dist/timeline/sequence.js.map +1 -0
  93. package/dist/timeline/tool_lifecycle.d.ts +36 -0
  94. package/dist/timeline/tool_lifecycle.js +171 -0
  95. package/dist/timeline/tool_lifecycle.js.map +1 -0
  96. package/dist/timeline/user_delivery.d.ts +96 -0
  97. package/dist/timeline/user_delivery.js +360 -0
  98. package/dist/timeline/user_delivery.js.map +1 -0
  99. package/dist/timeline/user_delivery_binding.d.ts +43 -0
  100. package/dist/timeline/user_delivery_binding.js +107 -0
  101. package/dist/timeline/user_delivery_binding.js.map +1 -0
  102. package/dist/timeline/v2_service.d.ts +82 -0
  103. package/dist/timeline/v2_service.js +512 -0
  104. package/dist/timeline/v2_service.js.map +1 -0
  105. package/dist/transport/peer_channel.d.ts +34 -0
  106. package/dist/transport/peer_channel.js +116 -0
  107. package/dist/transport/peer_channel.js.map +1 -0
  108. package/dist/transport/relay_client.d.ts +54 -0
  109. package/dist/transport/relay_client.js +270 -0
  110. package/dist/transport/relay_client.js.map +1 -0
  111. package/dist/vendor/protocol/constants.d.ts +4 -0
  112. package/dist/vendor/protocol/constants.js +4 -0
  113. package/dist/vendor/protocol/encoding.d.ts +9 -0
  114. package/dist/vendor/protocol/encoding.js +66 -0
  115. package/dist/vendor/protocol/outer/codec.d.ts +13 -0
  116. package/dist/vendor/protocol/outer/codec.js +105 -0
  117. package/dist/vendor/protocol/outer/index.d.ts +4 -0
  118. package/dist/vendor/protocol/outer/index.js +4 -0
  119. package/dist/vendor/protocol/outer/types.d.ts +120 -0
  120. package/dist/vendor/protocol/outer/types.js +1 -0
  121. package/dist/vendor/protocol/session/codec.d.ts +41 -0
  122. package/dist/vendor/protocol/session/codec.js +236 -0
  123. package/dist/vendor/protocol/session/frames.d.ts +5220 -0
  124. package/dist/vendor/protocol/session/frames.js +83 -0
  125. package/dist/vendor/protocol/session/index.d.ts +3 -0
  126. package/dist/vendor/protocol/session/index.js +3 -0
  127. package/dist/vendor/protocol/session/schema.d.ts +858 -0
  128. package/dist/vendor/protocol/session/schema.js +256 -0
  129. package/package.json +74 -3
@@ -0,0 +1,116 @@
1
+ import { decodeClientFrameV2, encodeServerFrameV2, } from "../protocol/v2/index.js";
2
+ import { decodeRoute, decodeRoutePayload, parseJson } from "../vendor/protocol/outer/index.js";
3
+ /**
4
+ * A Host-side protocol-v2 channel for one authorized Owner. The relay
5
+ * authenticates and injects `source_owner_id` for Owner-to-Host routes; this
6
+ * channel only accepts routes for its immutable endpoint identity and owner.
7
+ */
8
+ export class V2PeerChannel {
9
+ relay;
10
+ ownerId;
11
+ host;
12
+ onMessage;
13
+ unsubscribe;
14
+ constructor(relay, ownerId, host, onMessage) {
15
+ this.relay = relay;
16
+ this.ownerId = ownerId;
17
+ this.host = host;
18
+ this.onMessage = onMessage;
19
+ const listener = (line) => this.onLine(line);
20
+ relay.on("message", listener);
21
+ this.unsubscribe = () => relay.off("message", listener);
22
+ }
23
+ getPeerId() {
24
+ return this.ownerId;
25
+ }
26
+ sendV2(msg) {
27
+ try {
28
+ const ct = Buffer.from(encodeServerFrameV2(msg)).toString("base64");
29
+ this.relay.send(JSON.stringify({
30
+ type: "route",
31
+ purpose: msg.type === "pair_ok" || msg.type === "pair_error" ? "pairing" : "session",
32
+ device_id: this.host.deviceId,
33
+ endpoint_id: this.host.endpointId,
34
+ runtime_instance_id: this.host.runtimeInstanceId,
35
+ target_owner_id: this.ownerId,
36
+ ct,
37
+ }));
38
+ return true;
39
+ }
40
+ catch {
41
+ // Formal history recovers messages lost while the relay reconnects.
42
+ return false;
43
+ }
44
+ }
45
+ detach() {
46
+ this.unsubscribe();
47
+ }
48
+ onLine(line) {
49
+ const outer = parseIncomingRoute(line, this.host, this.ownerId);
50
+ if (!outer)
51
+ return;
52
+ const payload = decodeRoutePayload(outer);
53
+ if (!payload)
54
+ return;
55
+ try {
56
+ const frame = decodeClientFrameV2(payload);
57
+ // Pair requests have one owner in installOwnerRouter. Letting an active
58
+ // session binding process retries would duplicate or misroute replies.
59
+ if (frame.type === "pair_request")
60
+ return;
61
+ const expectedPurpose = "session";
62
+ if (outer.purpose !== expectedPurpose)
63
+ return;
64
+ this.onMessage(frame);
65
+ }
66
+ catch {
67
+ this.sendUpgradeRequired(outer.ct);
68
+ }
69
+ }
70
+ sendUpgradeRequired(ct) {
71
+ try {
72
+ const raw = JSON.parse(Buffer.from(ct, "base64").toString("utf8"));
73
+ const id = typeof raw.id === "string" ? raw.id : undefined;
74
+ const channelId = typeof raw.channel_id === "string" ? raw.channel_id : undefined;
75
+ if (!id)
76
+ return;
77
+ this.sendV2({
78
+ protocol_version: 2,
79
+ type: "protocol_error",
80
+ ...(channelId ? { target_channel_id: channelId } : {}),
81
+ in_reply_to: id,
82
+ code: "protocol_upgrade_required",
83
+ message: "Protocol v2 is required",
84
+ });
85
+ }
86
+ catch {
87
+ // Invalid bytes without a request id are not targetable.
88
+ }
89
+ }
90
+ }
91
+ export function parseIncomingRoute(line, host, ownerId) {
92
+ const route = decodeRoute(parseJson(line));
93
+ if (!route ||
94
+ route.device_id !== host.deviceId ||
95
+ route.endpoint_id !== host.endpointId ||
96
+ route.runtime_instance_id !== host.runtimeInstanceId ||
97
+ typeof route.source_owner_id !== "string" ||
98
+ route.target_owner_id !== undefined) {
99
+ return null;
100
+ }
101
+ if (ownerId !== undefined && route.source_owner_id !== ownerId)
102
+ return null;
103
+ return route;
104
+ }
105
+ export function sendRoute(relay, host, ownerId, purpose, ct) {
106
+ relay.send(JSON.stringify({
107
+ type: "route",
108
+ purpose,
109
+ device_id: host.deviceId,
110
+ endpoint_id: host.endpointId,
111
+ runtime_instance_id: host.runtimeInstanceId,
112
+ target_owner_id: ownerId,
113
+ ct,
114
+ }));
115
+ }
116
+ //# sourceMappingURL=peer_channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_channel.js","sourceRoot":"","sources":["../../src/transport/peer_channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { type EndpointMetadata as WireEndpointMetadata } from "../vendor/protocol/outer/index.js";
3
+ import type { Ed25519Keypair } from "../pairing/crypto.js";
4
+ export type EndpointMetadata = {
5
+ [Key in keyof WireEndpointMetadata]: Exclude<WireEndpointMetadata[Key], null>;
6
+ };
7
+ export type HostConnectOptions = {
8
+ role: "host";
9
+ endpointId: string;
10
+ runtimeInstanceId: string;
11
+ metadata: EndpointMetadata;
12
+ authorizedOwnerIds: readonly string[];
13
+ };
14
+ export type OwnerConnectOptions = {
15
+ role: "owner";
16
+ };
17
+ export type ConnectOptions = HostConnectOptions | OwnerConnectOptions;
18
+ export interface RelayClientEvents {
19
+ message: [line: string];
20
+ close: [];
21
+ error: [err: Error];
22
+ }
23
+ /** Authenticated WebSocket client for the endpoint relay. */
24
+ export declare class RelayClient extends EventEmitter {
25
+ private readonly url;
26
+ private readonly keypair;
27
+ private ws;
28
+ private lastActivityAt;
29
+ private livenessTimer;
30
+ private livenessSocket;
31
+ private pendingConnection;
32
+ private pendingMessage;
33
+ constructor(url: string, keypair: Ed25519Keypair);
34
+ connect(options?: ConnectOptions): Promise<void>;
35
+ isOpen(): boolean;
36
+ send(line: string): void;
37
+ sendControl(frame: object): boolean;
38
+ close(): void;
39
+ private finishConnection;
40
+ private startLiveness;
41
+ private stopLiveness;
42
+ private authenticate;
43
+ private nextMessage;
44
+ private isPendingConnection;
45
+ private assertPendingConnection;
46
+ private resolvePendingConnection;
47
+ private abortPendingConnection;
48
+ private clearConnectTimer;
49
+ private clearPendingConnectionListeners;
50
+ private cancelPendingMessage;
51
+ private reportError;
52
+ private asError;
53
+ private rawSend;
54
+ }
@@ -0,0 +1,270 @@
1
+ import { EventEmitter } from "node:events";
2
+ import WebSocket from "ws";
3
+ import { PROTOCOL_VERSION } from "../vendor/protocol/outer/index.js";
4
+ import { ed25519Sign } from "../pairing/crypto.js";
5
+ const CONNECT_TIMEOUT_MS = 10_000;
6
+ const AUTH_TIMEOUT_MS = 5_000;
7
+ const LIVENESS_TIMEOUT_MS = 70_000;
8
+ const LIVENESS_CHECK_MS = 20_000;
9
+ /** Authenticated WebSocket client for the endpoint relay. */
10
+ export class RelayClient extends EventEmitter {
11
+ url;
12
+ keypair;
13
+ ws = null;
14
+ lastActivityAt = 0;
15
+ livenessTimer = null;
16
+ livenessSocket = null;
17
+ pendingConnection = null;
18
+ pendingMessage = null;
19
+ constructor(url, keypair) {
20
+ super();
21
+ this.url = url;
22
+ this.keypair = keypair;
23
+ }
24
+ connect(options = { role: "owner" }) {
25
+ this.close();
26
+ return new Promise((resolve, reject) => {
27
+ const ws = new WebSocket(this.url);
28
+ const onEarlyClose = () => this.abortPendingConnection(ws, new Error("relay connection closed"), false);
29
+ const onOpen = () => {
30
+ this.clearConnectTimer(ws);
31
+ void this.finishConnection(ws, options);
32
+ };
33
+ this.ws = ws;
34
+ const pending = { ws, resolve, reject, onEarlyClose, onOpen, connectTimer: null };
35
+ this.pendingConnection = pending;
36
+ pending.connectTimer = setTimeout(() => this.abortPendingConnection(ws, new Error("relay connection timeout"), true), CONNECT_TIMEOUT_MS);
37
+ ws.on("error", (error) => {
38
+ if (this.ws !== ws)
39
+ return;
40
+ this.reportError(error);
41
+ if (this.isPendingConnection(ws))
42
+ this.abortPendingConnection(ws, error, true);
43
+ });
44
+ ws.once("close", onEarlyClose);
45
+ ws.once("open", onOpen);
46
+ });
47
+ }
48
+ isOpen() {
49
+ return this.ws?.readyState === WebSocket.OPEN;
50
+ }
51
+ send(line) {
52
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
53
+ throw new Error("relay: not connected");
54
+ }
55
+ this.ws.send(line);
56
+ }
57
+ sendControl(frame) {
58
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
59
+ return false;
60
+ try {
61
+ this.ws.send(JSON.stringify(frame));
62
+ return true;
63
+ }
64
+ catch (error) {
65
+ this.reportError(this.asError(error));
66
+ return false;
67
+ }
68
+ }
69
+ close() {
70
+ const ws = this.ws;
71
+ if (!ws)
72
+ return;
73
+ this.stopLiveness(ws);
74
+ if (this.isPendingConnection(ws)) {
75
+ this.abortPendingConnection(ws, new Error("relay connection closed"), true);
76
+ return;
77
+ }
78
+ this.ws = null;
79
+ ws.close();
80
+ }
81
+ async finishConnection(ws, options) {
82
+ if (!this.isPendingConnection(ws))
83
+ return;
84
+ try {
85
+ await this.authenticate(ws, options);
86
+ if (!this.isPendingConnection(ws))
87
+ return;
88
+ this.lastActivityAt = Date.now();
89
+ const pending = this.pendingConnection;
90
+ if (!pending || pending.ws !== ws)
91
+ return;
92
+ ws.removeListener("close", pending.onEarlyClose);
93
+ ws.on("message", (raw) => {
94
+ if (this.ws !== ws)
95
+ return;
96
+ this.lastActivityAt = Date.now();
97
+ const text = Buffer.isBuffer(raw) ? raw.toString() : String(raw);
98
+ for (const line of text.split("\n")) {
99
+ const trimmed = line.trim();
100
+ if (trimmed)
101
+ this.emit("message", trimmed);
102
+ }
103
+ });
104
+ ws.on("ping", () => { if (this.ws === ws)
105
+ this.lastActivityAt = Date.now(); });
106
+ ws.on("pong", () => { if (this.ws === ws)
107
+ this.lastActivityAt = Date.now(); });
108
+ ws.on("close", () => {
109
+ if (this.ws !== ws)
110
+ return;
111
+ this.stopLiveness(ws);
112
+ this.ws = null;
113
+ this.emit("close");
114
+ });
115
+ this.startLiveness(ws);
116
+ this.resolvePendingConnection(ws);
117
+ }
118
+ catch (error) {
119
+ this.abortPendingConnection(ws, this.asError(error), true);
120
+ }
121
+ }
122
+ startLiveness(ws) {
123
+ this.stopLiveness();
124
+ this.livenessSocket = ws;
125
+ this.livenessTimer = setInterval(() => {
126
+ if (Date.now() - this.lastActivityAt > LIVENESS_TIMEOUT_MS) {
127
+ this.stopLiveness(ws);
128
+ ws.terminate();
129
+ }
130
+ }, LIVENESS_CHECK_MS);
131
+ }
132
+ stopLiveness(ws) {
133
+ if (!this.livenessTimer || (ws && this.livenessSocket !== ws))
134
+ return;
135
+ clearInterval(this.livenessTimer);
136
+ this.livenessTimer = null;
137
+ this.livenessSocket = null;
138
+ }
139
+ async authenticate(ws, options) {
140
+ this.assertPendingConnection(ws);
141
+ const pubkey = Buffer.from(this.keypair.publicKey).toString("base64");
142
+ const hello = options.role === "host"
143
+ ? {
144
+ type: "hello",
145
+ protocol_version: PROTOCOL_VERSION,
146
+ role: "host",
147
+ pubkey,
148
+ endpoint_id: options.endpointId,
149
+ runtime_instance_id: options.runtimeInstanceId,
150
+ metadata: options.metadata,
151
+ authorized_owner_ids: options.authorizedOwnerIds,
152
+ }
153
+ : { type: "hello", protocol_version: PROTOCOL_VERSION, role: "owner", pubkey };
154
+ this.rawSend(ws, JSON.stringify(hello));
155
+ const challengeRaw = await this.nextMessage(ws);
156
+ this.assertPendingConnection(ws);
157
+ let challenge;
158
+ try {
159
+ challenge = JSON.parse(challengeRaw);
160
+ }
161
+ catch {
162
+ throw new Error(`relay auth_failed: not JSON: ${challengeRaw}`);
163
+ }
164
+ if (challenge.type === "error") {
165
+ const detail = challenge.code || challenge.message || "unknown";
166
+ throw new Error(`relay rejected hello: ${detail}`);
167
+ }
168
+ if (challenge.type !== "challenge" || !challenge.nonce) {
169
+ throw new Error(`relay auth_failed: expected challenge, got ${challengeRaw}`);
170
+ }
171
+ const nonce = Buffer.from(challenge.nonce, "base64");
172
+ const auth = {
173
+ type: "auth",
174
+ sig: Buffer.from(ed25519Sign(this.keypair.secretKey, nonce)).toString("base64"),
175
+ };
176
+ this.assertPendingConnection(ws);
177
+ this.rawSend(ws, JSON.stringify(auth));
178
+ }
179
+ nextMessage(ws) {
180
+ return new Promise((resolve, reject) => {
181
+ let timer = null;
182
+ let settled = false;
183
+ const cleanup = () => {
184
+ if (timer)
185
+ clearTimeout(timer);
186
+ ws.removeListener("message", onMessage);
187
+ ws.removeListener("error", onError);
188
+ ws.removeListener("close", onClose);
189
+ if (this.pendingMessage?.ws === ws)
190
+ this.pendingMessage = null;
191
+ };
192
+ const finish = (settle) => {
193
+ if (settled)
194
+ return;
195
+ settled = true;
196
+ cleanup();
197
+ settle();
198
+ };
199
+ const onMessage = (raw) => finish(() => resolve(Buffer.isBuffer(raw) ? raw.toString() : String(raw)));
200
+ const onError = (error) => finish(() => reject(error));
201
+ const onClose = () => finish(() => reject(new Error("relay connection closed")));
202
+ timer = setTimeout(() => finish(() => reject(new Error("relay auth timeout"))), AUTH_TIMEOUT_MS);
203
+ ws.once("message", onMessage);
204
+ ws.once("error", onError);
205
+ ws.once("close", onClose);
206
+ this.pendingMessage = { ws, cancel: (error) => finish(() => reject(error)) };
207
+ });
208
+ }
209
+ isPendingConnection(ws) {
210
+ return this.ws === ws && this.pendingConnection?.ws === ws;
211
+ }
212
+ assertPendingConnection(ws) {
213
+ if (!this.isPendingConnection(ws))
214
+ throw new Error("relay connection closed");
215
+ }
216
+ resolvePendingConnection(ws) {
217
+ const pending = this.pendingConnection;
218
+ if (!pending || pending.ws !== ws)
219
+ return;
220
+ this.pendingConnection = null;
221
+ this.clearPendingConnectionListeners(pending);
222
+ pending.resolve();
223
+ }
224
+ abortPendingConnection(ws, error, terminate) {
225
+ const pending = this.pendingConnection;
226
+ if (!pending || pending.ws !== ws)
227
+ return;
228
+ this.pendingConnection = null;
229
+ this.clearPendingConnectionListeners(pending);
230
+ this.cancelPendingMessage(ws, error);
231
+ this.stopLiveness(ws);
232
+ if (this.ws === ws)
233
+ this.ws = null;
234
+ pending.reject(error);
235
+ if (terminate)
236
+ ws.terminate();
237
+ }
238
+ clearConnectTimer(ws) {
239
+ const pending = this.pendingConnection;
240
+ if (!pending || pending.ws !== ws || !pending.connectTimer)
241
+ return;
242
+ clearTimeout(pending.connectTimer);
243
+ pending.connectTimer = null;
244
+ }
245
+ clearPendingConnectionListeners(pending) {
246
+ if (pending.connectTimer)
247
+ clearTimeout(pending.connectTimer);
248
+ pending.connectTimer = null;
249
+ pending.ws.removeListener("close", pending.onEarlyClose);
250
+ pending.ws.removeListener("open", pending.onOpen);
251
+ }
252
+ cancelPendingMessage(ws, error) {
253
+ const pending = this.pendingMessage;
254
+ if (!pending || pending.ws !== ws)
255
+ return;
256
+ this.pendingMessage = null;
257
+ pending.cancel(error);
258
+ }
259
+ reportError(error) {
260
+ if (this.listenerCount("error") > 0)
261
+ this.emit("error", error);
262
+ }
263
+ asError(error) {
264
+ return error instanceof Error ? error : new Error(String(error));
265
+ }
266
+ rawSend(ws, data) {
267
+ ws.send(data);
268
+ }
269
+ }
270
+ //# sourceMappingURL=relay_client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay_client.js","sourceRoot":"","sources":["../../src/transport/relay_client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const PROTOCOL_VERSION: 2;
2
+ export declare const PAIRING_INVITE_TTL_MS = 300000;
3
+ export declare const PAIR_TTL_MIN_MS = 10000;
4
+ export declare const PAIR_TTL_MAX_MS = 300000;
@@ -0,0 +1,4 @@
1
+ export const PROTOCOL_VERSION = 2;
2
+ export const PAIRING_INVITE_TTL_MS = 300_000;
3
+ export const PAIR_TTL_MIN_MS = 10_000;
4
+ export const PAIR_TTL_MAX_MS = 300_000;
@@ -0,0 +1,9 @@
1
+ export type Base64Variant = "standard" | "url";
2
+ export declare function encodeBase64(bytes: Uint8Array, variant?: Base64Variant): string;
3
+ export declare function decodeBase64(value: string, variant?: Base64Variant): Uint8Array;
4
+ export declare function encodeUtf8(value: string): Uint8Array;
5
+ export declare function decodeUtf8(bytes: Uint8Array): string;
6
+ export declare function truncateUtf8(value: string, maxBytes: number): string;
7
+ export declare function toWebSocketUrl(value: string | URL): string;
8
+ /** Canonical device and owner identities are standard Ed25519 base64 values. */
9
+ export declare function normalizeDeviceId(value: string): string;
@@ -0,0 +1,66 @@
1
+ function assertString(value) {
2
+ if (typeof value !== "string")
3
+ throw new TypeError("Expected a base64 string");
4
+ }
5
+ function normalizeBase64(value) {
6
+ const compact = value.replace(/\s+/g, "");
7
+ if (!compact || !/^[A-Za-z0-9+/_-]*={0,2}$/.test(compact))
8
+ throw new Error("Invalid base64");
9
+ const unpadded = compact.replace(/=+$/, "");
10
+ if (unpadded.length % 4 === 1)
11
+ throw new Error("Invalid base64 length");
12
+ return unpadded.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (unpadded.length % 4)) % 4);
13
+ }
14
+ export function encodeBase64(bytes, variant = "standard") {
15
+ if (typeof btoa !== "function")
16
+ throw new Error("Browser base64 encoder is unavailable");
17
+ let binary = "";
18
+ for (const byte of bytes)
19
+ binary += String.fromCharCode(byte);
20
+ const encoded = btoa(binary);
21
+ return variant === "url" ? encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") : encoded;
22
+ }
23
+ export function decodeBase64(value, variant) {
24
+ assertString(value);
25
+ const normalized = normalizeBase64(value);
26
+ if (variant === "standard" && /[-_]/.test(value))
27
+ throw new Error("Expected standard base64");
28
+ if (variant === "url" && /[+/]/.test(value))
29
+ throw new Error("Expected URL-safe base64");
30
+ if (typeof atob !== "function")
31
+ throw new Error("Browser base64 decoder is unavailable");
32
+ const binary = atob(normalized);
33
+ const bytes = new Uint8Array(binary.length);
34
+ for (let index = 0; index < binary.length; index += 1)
35
+ bytes[index] = binary.charCodeAt(index);
36
+ return bytes;
37
+ }
38
+ export function encodeUtf8(value) {
39
+ return new TextEncoder().encode(value);
40
+ }
41
+ export function decodeUtf8(bytes) {
42
+ return new TextDecoder().decode(bytes);
43
+ }
44
+ export function truncateUtf8(value, maxBytes) {
45
+ const bytes = encodeUtf8(value);
46
+ if (bytes.length <= maxBytes)
47
+ return value;
48
+ let end = Math.max(0, maxBytes);
49
+ while (end > 0 && (bytes[end] & 0xc0) === 0x80)
50
+ end -= 1;
51
+ return decodeUtf8(bytes.slice(0, end));
52
+ }
53
+ export function toWebSocketUrl(value) {
54
+ const url = new URL(value.toString());
55
+ if (url.protocol === "http:")
56
+ url.protocol = "ws:";
57
+ else if (url.protocol === "https:")
58
+ url.protocol = "wss:";
59
+ else if (url.protocol !== "ws:" && url.protocol !== "wss:")
60
+ throw new Error("Relay URL must use http(s) or ws(s)");
61
+ return url.toString();
62
+ }
63
+ /** Canonical device and owner identities are standard Ed25519 base64 values. */
64
+ export function normalizeDeviceId(value) {
65
+ return encodeBase64(decodeBase64(value), "standard");
66
+ }
@@ -0,0 +1,13 @@
1
+ import type { ControlFrame, ControlOutbound, RelayFrame, RouteFrame } from "./types.js";
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ export declare function parseJson(value: unknown): unknown;
4
+ export declare function decodeRoute(value: unknown): RouteFrame | undefined;
5
+ export declare function decodeControlFrame(value: unknown): ControlFrame | undefined;
6
+ export declare function decodeRelayFrame(value: unknown): RelayFrame | undefined;
7
+ export declare function encodeRoutePayload(payload: Uint8Array | string): string;
8
+ export declare function decodeRoutePayload(route: RouteFrame): Uint8Array | undefined;
9
+ export declare function encodeControlFrame(frame: ControlOutbound): string;
10
+ export declare function decodeChallenge(value: unknown): {
11
+ type: "challenge";
12
+ nonce: string;
13
+ } | undefined;