@peers-app/peers-sdk 0.27.4 → 0.27.5

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.
@@ -14,8 +14,8 @@ export interface RPCSendOptions {
14
14
  /** Arm the outbound frame cipher with these keys right after this reply frame has been sent. */
15
15
  armFrameCipherAfter?: FrameKeys;
16
16
  }
17
- /** Reply callback handed to RPC handlers. */
18
- export type RPCCallback = (error?: any, result?: any, sendOptions?: RPCSendOptions) => void;
17
+ /** Reply callback handed to RPC handlers; async transports resolve after sending the reply. */
18
+ export type RPCCallback = (error?: any, result?: any, sendOptions?: RPCSendOptions) => void | Promise<void>;
19
19
  /** Which direction(s) of the frame cipher to arm. */
20
20
  export type FrameCipherDirection = "both" | "send" | "receive";
21
21
  /**
@@ -40,11 +40,6 @@ export type TransportType = "ws" | "wrtc" | "unknown";
40
40
  export interface ISocket {
41
41
  id?: string;
42
42
  connected?: boolean;
43
- /**
44
- * @deprecated No longer needed - all sockets handle their own encoding.
45
- * Kept temporarily for backwards compatibility during v1->v2 transition.
46
- */
47
- handlesOwnEncoding?: boolean;
48
43
  /**
49
44
  * Transport type for this socket. Used to optimize chunk sizes for raw byte streaming.
50
45
  * - 'wrtc': WebRTC (64KB max due to SCTP limits)
@@ -82,8 +77,8 @@ export interface ISocket {
82
77
  /**
83
78
  * Arms the per-connection frame cipher (see `frame-cipher.ts`). Sockets that
84
79
  * implement this seal every outbound frame and require every inbound frame to
85
- * be sealed once the corresponding direction is armed. `Connection` only
86
- * advertises `frameCrypto` support in its handshake when this is present.
80
+ * be sealed once the corresponding direction is armed. `Connection` refuses
81
+ * to run a handshake on a socket without it.
87
82
  * @param keys - This peer's send and receive keys
88
83
  * @param direction - Defaults to `"both"`. The handshake server arms
89
84
  * `"receive"` first and lets the reply's {@link RPCSendOptions} arm send.
@@ -94,9 +89,8 @@ export interface ISocket {
94
89
  /**
95
90
  * Controls delivery of inbound plaintext `STREAM_*` frames. Sockets start
96
91
  * permissive so bare transports keep working; `Connection` turns this off on
97
- * construction and only turns it back on for a verified legacy peer. Sealed
98
- * stream frames are always delivered because arming implies a completed
99
- * handshake.
92
+ * construction. Sealed stream frames are always delivered because arming
93
+ * implies a completed handshake.
100
94
  */
101
95
  setPlaintextStreamsAllowed?(allowed: boolean): void;
102
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.27.4",
3
+ "version": "0.27.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"
@@ -1,58 +0,0 @@
1
- import { Connection } from "./connection";
2
- import type { Device } from "./device";
3
- import type { ISocket } from "./socket.type";
4
- type ITrustLevelFn = Connection["getTrustLevel"];
5
- /**
6
- * Generic binary peer interface supporting WebRTC and WebSocket transports.
7
- * Provides a unified abstraction for binary data channel communication.
8
- */
9
- export interface IBinaryPeer {
10
- on(event: "data", handler: (data: Buffer | Uint8Array) => void): void;
11
- on(event: "close", handler: () => void): void;
12
- on(event: "drain", handler: () => void): void;
13
- send(data: string | Uint8Array | Buffer): void;
14
- destroy(): void;
15
- /**
16
- * Returns the number of bytes currently buffered waiting to be sent.
17
- * Used for backpressure detection. Returns 0 if not supported.
18
- */
19
- getBufferedAmount?(): number;
20
- }
21
- export type IWebRTCPeer = IBinaryPeer;
22
- export interface WrapBinaryPeerOptions {
23
- /** Protocol identifier used for server address (e.g., 'wrtc' or 'ws') */
24
- protocol: "wrtc" | "ws";
25
- /**
26
- * Mark the transport as already secure (encrypted at transport layer).
27
- * When true, sets secureLocal and secureRemote to skip application-level encryption.
28
- */
29
- markTransportSecure?: boolean;
30
- /** Optional hard limits for untrusted inbound messages. */
31
- receiveLimits?: Partial<BinaryPeerReceiveLimits>;
32
- }
33
- /** Hard receive limits for the legacy binary peer parser. */
34
- export interface BinaryPeerReceiveLimits {
35
- /** Maximum bytes in one complete or reassembled RPC message. */
36
- maxRpcMessageBytes: number;
37
- /** Maximum bytes in one raw stream frame. */
38
- maxStreamMessageBytes: number;
39
- /** Maximum time an incomplete RPC message may retain memory. */
40
- reassemblyTimeoutMs: number;
41
- }
42
- /**
43
- * Creates an ISocket from a binary peer without creating a full Connection.
44
- * Useful when you need to pass custom localServerAddresses to the Connection.
45
- */
46
- export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string, receiveLimitOverrides?: Partial<BinaryPeerReceiveLimits>): ISocket;
47
- /**
48
- * Wraps a binary peer (WebRTC or WebSocket) into a Connection.
49
- * This is the main implementation that handles all binary protocols.
50
- */
51
- export declare function wrapBinaryPeer(connectionId: string, peer: IBinaryPeer, localDevice: Device, initiator: boolean, options: WrapBinaryPeerOptions, getTrustLevel?: ITrustLevelFn): Connection;
52
- /**
53
- * Wraps a WebRTC peer into a Connection.
54
- * Backwards-compatible wrapper around wrapBinaryPeer.
55
- * WebRTC is encrypted at the transport layer (DTLS-SRTP), so we skip application-level encryption.
56
- */
57
- export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn, receiveLimits?: Partial<BinaryPeerReceiveLimits>): Connection;
58
- export {};
@@ -1,516 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createBinaryPeerSocket = createBinaryPeerSocket;
4
- exports.wrapBinaryPeer = wrapBinaryPeer;
5
- exports.wrapWrtc = wrapWrtc;
6
- const msgpack_1 = require("@msgpack/msgpack");
7
- const utils_1 = require("../utils");
8
- const connection_1 = require("./connection");
9
- const get_trust_level_fn_1 = require("./get-trust-level-fn");
10
- const streamed_socket_1 = require("./streamed-socket");
11
- const tx_encoding_1 = require("./tx-encoding");
12
- // Protocol discriminator bytes for multiplexing RPC and raw byte streams
13
- const PROTOCOL_RPC = 0x00;
14
- const PROTOCOL_RAW_BYTES = 0x01;
15
- // Threshold for routing large RPC messages through raw bytes streaming.
16
- // Messages larger than this are sent via the __rpc raw bytes stream,
17
- // automatically chunked to fit transport limits with backpressure.
18
- const LARGE_MESSAGE_THRESHOLD = 64 * 1024; // 64KB
19
- // Transport-specific chunk sizes for the __rpc raw bytes path.
20
- // Must stay under the SCTP single-message limit (~256KB practical max).
21
- const RPC_CHUNK_SIZE_WRTC = 16 * 1024; // 16KB for WebRTC (fits in Unix socket kernel buffer)
22
- const RPC_CHUNK_SIZE_DEFAULT = 512 * 1024; // 512KB for WebSocket
23
- // Reserved stream ID for RPC messages sent via raw bytes
24
- const RPC_STREAM_ID = "__rpc";
25
- // Flags for __rpc stream chunking protocol:
26
- // Each message on the __rpc stream starts with a 1-byte flag.
27
- const RPC_FLAG_SINGLE = 0x00; // [0x00][encoded message] - complete in one message
28
- const RPC_FLAG_START = 0x01; // [0x01][4 bytes: totalSize][chunk data] - first chunk
29
- const RPC_FLAG_CONT = 0x02; // [0x02][chunk data] - continuation chunk
30
- // Cache for encoded stream IDs to avoid TextEncoder allocation on every send/receive
31
- const streamIdBytesCache = new Map();
32
- function getStreamIdBytes(streamId) {
33
- let cached = streamIdBytesCache.get(streamId);
34
- if (!cached) {
35
- cached = new TextEncoder().encode(streamId);
36
- streamIdBytesCache.set(streamId, cached);
37
- }
38
- return cached;
39
- }
40
- // Reverse cache: decode stream IDs from raw bytes without re-creating TextDecoder
41
- const streamIdStringCache = new Map();
42
- function decodeStreamId(bytes) {
43
- // Build a key that uniquely identifies the byte content to avoid collisions
44
- let key = `${bytes.length}`;
45
- for (let i = 0; i < bytes.length; i++) {
46
- key += `:${bytes[i]}`;
47
- }
48
- let cached = streamIdStringCache.get(key);
49
- if (cached !== undefined)
50
- return cached;
51
- cached = new TextDecoder().decode(bytes);
52
- streamIdStringCache.set(key, cached);
53
- return cached;
54
- }
55
- // Encode message metadata + data into a single Uint8Array
56
- function encodeMessage(message) {
57
- // Check if args/result is a Uint8Array - if so, flag it to keep it as raw bytes
58
- let isUint8ArrayData = false;
59
- if (message.type === "call" && message.args instanceof Uint8Array) {
60
- isUint8ArrayData = true;
61
- }
62
- else if (message.type === "callback" && message.result instanceof Uint8Array) {
63
- isUint8ArrayData = true;
64
- }
65
- // Encode metadata using msgpack (without args/result/error - those go in data)
66
- const metadata = {
67
- type: message.type,
68
- callbackId: message.callbackId,
69
- ...(message.eventName !== undefined && { eventName: message.eventName }),
70
- ...(isUint8ArrayData && { isUint8ArrayData: true }),
71
- };
72
- const metadataBytes = (0, msgpack_1.encode)(metadata);
73
- const metadataLength = metadataBytes.length;
74
- // Encode the data payload (args, result, or error)
75
- let dataBytes;
76
- if (message.type === "call" && message.args !== undefined) {
77
- // If args is already a Uint8Array, use it directly without txEncode
78
- if (isUint8ArrayData) {
79
- dataBytes = message.args;
80
- }
81
- else {
82
- dataBytes = (0, tx_encoding_1.txEncode)(message.args);
83
- }
84
- }
85
- else if (message.type === "callback") {
86
- if (message.error !== undefined) {
87
- dataBytes = (0, tx_encoding_1.txEncode)({ error: message.error });
88
- }
89
- else if (isUint8ArrayData) {
90
- // If result is a Uint8Array, use it directly
91
- dataBytes = message.result;
92
- }
93
- else {
94
- dataBytes = (0, tx_encoding_1.txEncode)({ result: message.result });
95
- }
96
- }
97
- else {
98
- dataBytes = new Uint8Array(0);
99
- }
100
- // Create combined buffer: [4 bytes length][metadata][data]
101
- const combined = new Uint8Array(4 + metadataLength + dataBytes.length);
102
- // Write metadata length as 32-bit unsigned integer (big-endian)
103
- const view = new DataView(combined.buffer, combined.byteOffset, combined.byteLength);
104
- view.setUint32(0, metadataLength, false); // big-endian
105
- // Write metadata
106
- combined.set(metadataBytes, 4);
107
- // Write data
108
- combined.set(dataBytes, 4 + metadataLength);
109
- return combined;
110
- }
111
- // Decode binary message back into IMessage
112
- function decodeMessage(bytes) {
113
- // Read metadata length
114
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
115
- const metadataLength = view.getUint32(0, false); // big-endian
116
- // Extract metadata
117
- const metadataBytes = bytes.subarray(4, 4 + metadataLength);
118
- const metadata = (0, msgpack_1.decode)(metadataBytes);
119
- // Extract data
120
- const dataBytes = bytes.subarray(4 + metadataLength);
121
- const message = {
122
- type: metadata.type,
123
- callbackId: metadata.callbackId,
124
- eventName: metadata.eventName,
125
- };
126
- // Decode the data payload
127
- if (dataBytes.length > 0) {
128
- if (message.type === "call") {
129
- // If it was a Uint8Array, use it directly without txDecode
130
- if (metadata.isUint8ArrayData) {
131
- message.args = dataBytes;
132
- }
133
- else {
134
- message.args = (0, tx_encoding_1.txDecode)(dataBytes);
135
- }
136
- }
137
- else if (message.type === "callback") {
138
- if (metadata.isUint8ArrayData) {
139
- // If it was a Uint8Array result, use it directly
140
- message.result = dataBytes;
141
- }
142
- else {
143
- const decoded = (0, tx_encoding_1.txDecode)(dataBytes);
144
- if (decoded.error !== undefined) {
145
- message.error = decoded.error;
146
- }
147
- else {
148
- message.result = decoded.result;
149
- }
150
- }
151
- }
152
- }
153
- return message;
154
- }
155
- const DEFAULT_RECEIVE_LIMITS = Object.freeze({
156
- maxRpcMessageBytes: 256 * 1024 * 1024,
157
- maxStreamMessageBytes: 256 * 1024 * 1024,
158
- reassemblyTimeoutMs: 30_000,
159
- });
160
- /**
161
- * Creates an ISocket from a binary peer without creating a full Connection.
162
- * Useful when you need to pass custom localServerAddresses to the Connection.
163
- */
164
- function createBinaryPeerSocket(connectionId, peer, protocol = "binary", receiveLimitOverrides = {}) {
165
- const handlers = {};
166
- const callbacks = {};
167
- const rawBytesHandlers = {};
168
- let isClosed = false;
169
- const stats = (0, streamed_socket_1.createSocketStats)();
170
- const receiveLimits = { ...DEFAULT_RECEIVE_LIMITS, ...receiveLimitOverrides };
171
- for (const [name, value] of Object.entries(receiveLimits)) {
172
- if (!Number.isSafeInteger(value) || value <= 0) {
173
- throw new Error(`Invalid binary peer receive limit ${name}: ${value}`);
174
- }
175
- }
176
- // Map protocol string to TransportType (needed early for threshold calculations)
177
- const transportType = protocol === "wrtc" ? "wrtc" : protocol === "ws" ? "ws" : "unknown";
178
- // Transport-specific thresholds (computed once)
179
- const HIGH_WATER_MARK = transportType === "wrtc" ? 128 * 1024 : 4 * 1024 * 1024;
180
- const LOW_WATER_MARK = transportType === "wrtc" ? 32 * 1024 : 2 * 1024 * 1024;
181
- const RPC_CHUNK_SIZE = transportType === "wrtc" ? RPC_CHUNK_SIZE_WRTC : RPC_CHUNK_SIZE_DEFAULT;
182
- // Core sendRawBytes implementation - defined early so sendRPCData can use it
183
- const sendRawBytesImpl = async (streamId, data) => {
184
- const sidBytes = getStreamIdBytes(streamId);
185
- const headerLen = 1 + 2 + sidBytes.length;
186
- const packet = new Uint8Array(headerLen + data.length);
187
- packet[0] = PROTOCOL_RAW_BYTES;
188
- const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
189
- view.setUint16(1, sidBytes.length, false);
190
- packet.set(sidBytes, 3);
191
- packet.set(data, headerLen);
192
- // Backpressure: wait for buffer to drain before sending more
193
- if (peer.getBufferedAmount && peer.getBufferedAmount() > HIGH_WATER_MARK) {
194
- await new Promise((resolve) => {
195
- const checkDrain = () => {
196
- if (!peer.getBufferedAmount || peer.getBufferedAmount() < LOW_WATER_MARK) {
197
- resolve();
198
- }
199
- else {
200
- setTimeout(checkDrain, 5);
201
- }
202
- };
203
- peer.on("drain", resolve);
204
- checkDrain();
205
- });
206
- }
207
- stats.bytesSent += packet.length;
208
- stats.messagesSent++;
209
- peer.send(packet);
210
- };
211
- // Serialize large RPC sends so chunked messages don't interleave
212
- let rpcSendQueue = Promise.resolve();
213
- // Helper to send RPC messages - routes large messages through raw bytes streaming
214
- // with automatic chunking to stay within transport single-message limits.
215
- const sendRPCData = (encoded) => {
216
- if (encoded.length <= LARGE_MESSAGE_THRESHOLD) {
217
- // Small message: send directly via PROTOCOL_RPC (no chunking needed)
218
- const withProtocol = new Uint8Array(1 + encoded.length);
219
- withProtocol[0] = PROTOCOL_RPC;
220
- withProtocol.set(encoded, 1);
221
- stats.bytesSent += withProtocol.length;
222
- stats.messagesSent++;
223
- peer.send(withProtocol);
224
- return Promise.resolve();
225
- }
226
- if (encoded.length <= RPC_CHUNK_SIZE) {
227
- // Fits in one raw bytes message: send with SINGLE flag
228
- const msg = new Uint8Array(1 + encoded.length);
229
- msg[0] = RPC_FLAG_SINGLE;
230
- msg.set(encoded, 1);
231
- return sendRawBytesImpl(RPC_STREAM_ID, msg);
232
- }
233
- // Large message: chunk it. Serialize through queue to prevent interleaving.
234
- const sendChunked = async () => {
235
- // First chunk: [FLAG_START][4 bytes totalSize][chunk data]
236
- const firstChunkDataSize = Math.min(RPC_CHUNK_SIZE - 5, encoded.length); // 5 = 1 flag + 4 size
237
- const firstMsg = new Uint8Array(5 + firstChunkDataSize);
238
- firstMsg[0] = RPC_FLAG_START;
239
- const view = new DataView(firstMsg.buffer, firstMsg.byteOffset, firstMsg.byteLength);
240
- view.setUint32(1, encoded.length, false);
241
- firstMsg.set(encoded.subarray(0, firstChunkDataSize), 5);
242
- await sendRawBytesImpl(RPC_STREAM_ID, firstMsg);
243
- // Continuation chunks: [FLAG_CONT][chunk data]
244
- let offset = firstChunkDataSize;
245
- while (offset < encoded.length) {
246
- const end = Math.min(offset + RPC_CHUNK_SIZE - 1, encoded.length); // 1 = flag byte
247
- const contMsg = new Uint8Array(1 + (end - offset));
248
- contMsg[0] = RPC_FLAG_CONT;
249
- contMsg.set(encoded.subarray(offset, end), 1);
250
- await sendRawBytesImpl(RPC_STREAM_ID, contMsg);
251
- offset = end;
252
- }
253
- };
254
- rpcSendQueue = rpcSendQueue.then(sendChunked, sendChunked);
255
- return rpcSendQueue;
256
- };
257
- // Reassembly state for chunked __rpc messages
258
- let rpcReassemblyBuffer = null;
259
- let rpcReassemblyOffset = 0;
260
- let rpcReassemblyTimer;
261
- function clearRpcReassembly() {
262
- if (rpcReassemblyTimer) {
263
- clearTimeout(rpcReassemblyTimer);
264
- rpcReassemblyTimer = undefined;
265
- }
266
- rpcReassemblyBuffer = null;
267
- rpcReassemblyOffset = 0;
268
- }
269
- function closeForProtocolViolation(reason) {
270
- if (isClosed) {
271
- return;
272
- }
273
- console.warn(`Closing ${protocol} peer after binary protocol violation: ${reason}`);
274
- isClosed = true;
275
- clearRpcReassembly();
276
- socket.connected = false;
277
- peer.destroy();
278
- }
279
- // Handle incoming data
280
- peer.on("data", async (data) => {
281
- try {
282
- const bytes = new Uint8Array(data);
283
- stats.bytesReceived += bytes.length;
284
- stats.messagesReceived++;
285
- if (bytes.length === 0)
286
- return;
287
- const protocolType = bytes[0];
288
- // Handle raw bytes stream
289
- if (protocolType === PROTOCOL_RAW_BYTES) {
290
- if (bytes.length < 3)
291
- return;
292
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
293
- const streamIdLength = view.getUint16(1, false);
294
- if (bytes.length < 3 + streamIdLength)
295
- return;
296
- const streamIdBytes = bytes.subarray(3, 3 + streamIdLength);
297
- const streamId = decodeStreamId(streamIdBytes);
298
- const payload = bytes.subarray(3 + streamIdLength);
299
- if (payload.length > receiveLimits.maxStreamMessageBytes) {
300
- closeForProtocolViolation("raw stream frame exceeds the message-size limit");
301
- return;
302
- }
303
- // Check if this is an RPC message sent via raw bytes (for large payloads)
304
- if (streamId === RPC_STREAM_ID) {
305
- if (payload.length === 0)
306
- return;
307
- const flag = payload[0];
308
- if (flag === RPC_FLAG_SINGLE) {
309
- // Complete message in one raw bytes send
310
- if (payload.length - 1 > receiveLimits.maxRpcMessageBytes) {
311
- closeForProtocolViolation("complete RPC exceeds the message-size limit");
312
- return;
313
- }
314
- handleRPCMessage(payload.subarray(1), handlers, callbacks, sendRPCData);
315
- }
316
- else if (flag === RPC_FLAG_START) {
317
- // First chunk of a multi-part message
318
- if (payload.length < 5)
319
- return; // flag + 4 bytes size minimum
320
- const totalSize = new DataView(payload.buffer, payload.byteOffset + 1, 4).getUint32(0, false);
321
- const chunkData = payload.subarray(5);
322
- if (totalSize === 0 ||
323
- totalSize > receiveLimits.maxRpcMessageBytes ||
324
- chunkData.length > totalSize ||
325
- rpcReassemblyBuffer) {
326
- closeForProtocolViolation("invalid, duplicate, or oversized RPC start frame");
327
- return;
328
- }
329
- rpcReassemblyBuffer = new Uint8Array(totalSize);
330
- rpcReassemblyOffset = 0;
331
- rpcReassemblyBuffer.set(chunkData, 0);
332
- rpcReassemblyOffset = chunkData.length;
333
- rpcReassemblyTimer = setTimeout(() => closeForProtocolViolation("RPC reassembly timed out"), receiveLimits.reassemblyTimeoutMs);
334
- rpcReassemblyTimer.unref?.();
335
- // Check if this single START chunk completes the message (unlikely but safe)
336
- if (rpcReassemblyOffset === totalSize) {
337
- handleRPCMessage(rpcReassemblyBuffer, handlers, callbacks, sendRPCData);
338
- clearRpcReassembly();
339
- }
340
- }
341
- else if (flag === RPC_FLAG_CONT) {
342
- // Continuation chunk
343
- if (!rpcReassemblyBuffer)
344
- return; // no START received, discard
345
- const chunkData = payload.subarray(1);
346
- if (chunkData.length > rpcReassemblyBuffer.length - rpcReassemblyOffset) {
347
- closeForProtocolViolation("RPC continuation exceeds declared size");
348
- return;
349
- }
350
- rpcReassemblyBuffer.set(chunkData, rpcReassemblyOffset);
351
- rpcReassemblyOffset += chunkData.length;
352
- if (rpcReassemblyOffset === rpcReassemblyBuffer.length) {
353
- handleRPCMessage(rpcReassemblyBuffer, handlers, callbacks, sendRPCData);
354
- clearRpcReassembly();
355
- }
356
- }
357
- else {
358
- // Legacy/unknown: treat entire payload as a complete RPC message
359
- // (backwards compat with code before chunking was added)
360
- if (payload.length > receiveLimits.maxRpcMessageBytes) {
361
- closeForProtocolViolation("legacy RPC exceeds the message-size limit");
362
- return;
363
- }
364
- handleRPCMessage(payload, handlers, callbacks, sendRPCData);
365
- }
366
- return;
367
- }
368
- const handler = rawBytesHandlers[streamId];
369
- if (handler)
370
- handler(payload);
371
- return;
372
- }
373
- // Handle RPC messages
374
- if (protocolType === PROTOCOL_RPC) {
375
- const rpcBytes = bytes.subarray(1);
376
- if (rpcBytes.length > receiveLimits.maxRpcMessageBytes) {
377
- closeForProtocolViolation("complete RPC exceeds the message-size limit");
378
- return;
379
- }
380
- handleRPCMessage(rpcBytes, handlers, callbacks, sendRPCData);
381
- return;
382
- }
383
- // Legacy format (no discriminator) for backwards compatibility
384
- if (bytes.length > receiveLimits.maxRpcMessageBytes) {
385
- closeForProtocolViolation("legacy RPC exceeds the message-size limit");
386
- return;
387
- }
388
- handleRPCMessage(bytes, handlers, callbacks, sendRPCData);
389
- }
390
- catch (err) {
391
- console.error(`Error parsing ${protocol} message:`, err);
392
- }
393
- });
394
- // Handle closure
395
- peer.on("close", () => {
396
- if (isClosed)
397
- return;
398
- isClosed = true;
399
- clearRpcReassembly();
400
- socket.connected = false;
401
- });
402
- const socket = {
403
- id: connectionId,
404
- connected: true,
405
- handlesOwnEncoding: true,
406
- transportType,
407
- stats,
408
- emit(eventName, args, callback) {
409
- const callbackId = (0, utils_1.newid)();
410
- callbacks[callbackId] = callback;
411
- const message = { type: "call", eventName, args, callbackId };
412
- const encoded = encodeMessage(message);
413
- sendRPCData(encoded);
414
- },
415
- on(eventName, handler) {
416
- handlers[eventName] = handler;
417
- },
418
- removeAllListeners(eventName) {
419
- if (eventName) {
420
- delete handlers[eventName];
421
- }
422
- else {
423
- for (const key of Object.keys(handlers)) {
424
- delete handlers[key];
425
- }
426
- }
427
- },
428
- disconnect() {
429
- peer.destroy();
430
- },
431
- sendRawBytes: sendRawBytesImpl,
432
- onRawBytes(streamId, handler) {
433
- rawBytesHandlers[streamId] = handler;
434
- },
435
- removeRawBytesHandler(streamId) {
436
- delete rawBytesHandlers[streamId];
437
- },
438
- };
439
- return socket;
440
- }
441
- // Helper to handle RPC message parsing
442
- // sendRPCData automatically routes large messages through raw bytes streaming
443
- function handleRPCMessage(bytes, handlers, callbacks, sendRPCData) {
444
- const message = decodeMessage(bytes);
445
- if (message.type === "callback") {
446
- const callback = callbacks[message.callbackId];
447
- if (callback) {
448
- delete callbacks[message.callbackId];
449
- callback(message.error, message.result);
450
- }
451
- return;
452
- }
453
- if (message.type === "call") {
454
- const eventName = message.eventName;
455
- const handler = eventName ? handlers[eventName] : undefined;
456
- if (!handler) {
457
- console.warn(eventName ? `No handler for event: ${eventName}` : "RPC call did not include an event name");
458
- return;
459
- }
460
- try {
461
- const callback = (err, result) => {
462
- const responseMessage = {
463
- type: "callback",
464
- callbackId: message.callbackId,
465
- };
466
- if (err)
467
- responseMessage.error = err;
468
- else
469
- responseMessage.result = result;
470
- const encoded = encodeMessage(responseMessage);
471
- // Use sendRPCData which auto-routes large responses through raw bytes
472
- sendRPCData(encoded);
473
- };
474
- handler(message.args, callback);
475
- }
476
- catch (error) {
477
- console.error("Error in handler", { eventName: message.eventName, error });
478
- const errorMessage = {
479
- type: "callback",
480
- callbackId: message.callbackId,
481
- error: error.message,
482
- };
483
- const encoded = encodeMessage(errorMessage);
484
- sendRPCData(encoded);
485
- }
486
- }
487
- }
488
- /**
489
- * Wraps a binary peer (WebRTC or WebSocket) into a Connection.
490
- * This is the main implementation that handles all binary protocols.
491
- */
492
- function wrapBinaryPeer(connectionId, peer, localDevice, initiator, options, getTrustLevel) {
493
- const { protocol, markTransportSecure, receiveLimits } = options;
494
- // Create the socket wrapper using shared implementation
495
- const socket = createBinaryPeerSocket(connectionId, peer, protocol, receiveLimits);
496
- const serverAddress = `${protocol}://${connectionId}`;
497
- const localServerAddresses = initiator ? undefined : [serverAddress];
498
- if (process.env.NODE_ENV !== "test") {
499
- getTrustLevel ??= (0, get_trust_level_fn_1.getTrustLevelFn)(localDevice, serverAddress);
500
- }
501
- const connection = new connection_1.Connection(socket, localDevice, localServerAddresses, getTrustLevel);
502
- // Mark transport as secure if applicable (WebRTC DTLS-SRTP, wss)
503
- if (markTransportSecure) {
504
- connection.secureLocal = true;
505
- connection.secureRemote = true;
506
- }
507
- return connection;
508
- }
509
- /**
510
- * Wraps a WebRTC peer into a Connection.
511
- * Backwards-compatible wrapper around wrapBinaryPeer.
512
- * WebRTC is encrypted at the transport layer (DTLS-SRTP), so we skip application-level encryption.
513
- */
514
- function wrapWrtc(connectionId, peer, localDevice, initiator, getTrustLevel, receiveLimits) {
515
- return wrapBinaryPeer(connectionId, peer, localDevice, initiator, { protocol: "wrtc", markTransportSecure: true, receiveLimits }, getTrustLevel);
516
- }
@@ -1,23 +0,0 @@
1
- import { type ISocket, type ISocketStats, type RPCCallback } from "./socket.type";
2
- /** Creates a new stats object for tracking socket throughput */
3
- export declare function createSocketStats(): ISocketStats;
4
- export declare class StreamedSocket implements ISocket {
5
- readonly socket: ISocket;
6
- maxChunkSize: number;
7
- readonly safeSocketChunkEventName = "__streamed-socket-chunk";
8
- readonly safeSocketResponseEventName = "__streamed-socket-response";
9
- readonly stats: ISocketStats;
10
- constructor(socket: ISocket, maxChunkSize?: number);
11
- get id(): string | undefined;
12
- get connected(): boolean | undefined;
13
- disconnect(close?: boolean): void;
14
- private readonly callbacks;
15
- emit(eventName: string, args: any, callback: RPCCallback): void;
16
- private handlers;
17
- on(eventName: string, handler: (...args: any[]) => void): void;
18
- removeAllListeners(eventName: string): void;
19
- private readonly chunkBuffers;
20
- private handleChunk;
21
- private processChunks;
22
- private handleResponse;
23
- }