@peers-app/peers-sdk 0.7.40 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/files/file-read-stream.d.ts +2 -1
- package/dist/data/files/file-read-stream.js +9 -5
- package/dist/data/files/files.d.ts +4 -2
- package/dist/data/files/files.js +4 -4
- package/dist/data/groups.d.ts +2 -2
- package/dist/device/binary-peer-connection.d.ts +54 -0
- package/dist/device/binary-peer-connection.js +350 -0
- package/dist/device/binary-peer-connection.test.d.ts +1 -0
- package/dist/device/binary-peer-connection.test.js +204 -0
- package/dist/device/connection.d.ts +29 -0
- package/dist/device/connection.js +85 -2
- package/dist/device/connection.test.js +20 -4
- package/dist/device/socket-io-binary-peer.d.ts +27 -0
- package/dist/device/socket-io-binary-peer.js +97 -0
- package/dist/device/socket.type.d.ts +52 -0
- package/dist/device/streamed-socket.d.ts +4 -1
- package/dist/device/streamed-socket.js +45 -4
- package/dist/device/tx-encoding.js +22 -4
- package/dist/device/tx-encoding.test.js +28 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/keys.js +17 -14
- package/dist/package-loader/package-loader.d.ts +2 -0
- package/dist/package-loader/package-loader.js +4 -2
- package/dist/rpc-types.d.ts +1 -0
- package/dist/rpc-types.js +5 -1
- package/dist/user-connect/connection-code.d.ts +67 -0
- package/dist/user-connect/connection-code.js +176 -0
- package/dist/user-connect/connection-code.test.d.ts +1 -0
- package/dist/user-connect/connection-code.test.js +213 -0
- package/dist/user-connect/index.d.ts +3 -0
- package/dist/user-connect/index.js +19 -0
- package/dist/user-connect/user-connect.pvars.d.ts +3 -0
- package/dist/user-connect/user-connect.pvars.js +33 -0
- package/dist/user-connect/user-connect.types.d.ts +58 -0
- package/dist/user-connect/user-connect.types.js +8 -0
- package/package.json +3 -1
|
@@ -4,6 +4,7 @@ export declare class FileReadStream {
|
|
|
4
4
|
private fileRecord;
|
|
5
5
|
private fileTable;
|
|
6
6
|
private preloadChunksCount;
|
|
7
|
+
private skipVerification;
|
|
7
8
|
private chunkHashes;
|
|
8
9
|
private currentChunkIndex;
|
|
9
10
|
private currentChunkBuffer;
|
|
@@ -11,7 +12,7 @@ export declare class FileReadStream {
|
|
|
11
12
|
private positionInChunk;
|
|
12
13
|
private totalPosition;
|
|
13
14
|
private eof;
|
|
14
|
-
constructor(fileRecord: IFile, fileTable: FilesTable, preloadChunksCount?: number);
|
|
15
|
+
constructor(fileRecord: IFile, fileTable: FilesTable, preloadChunksCount?: number, skipVerification?: boolean);
|
|
15
16
|
private loadChunkHashes;
|
|
16
17
|
private loadChunkByIndex;
|
|
17
18
|
private preloadChunks;
|
|
@@ -7,6 +7,7 @@ class FileReadStream {
|
|
|
7
7
|
fileRecord;
|
|
8
8
|
fileTable;
|
|
9
9
|
preloadChunksCount;
|
|
10
|
+
skipVerification;
|
|
10
11
|
chunkHashes = null;
|
|
11
12
|
currentChunkIndex = 0;
|
|
12
13
|
currentChunkBuffer = null;
|
|
@@ -14,10 +15,11 @@ class FileReadStream {
|
|
|
14
15
|
positionInChunk = 0;
|
|
15
16
|
totalPosition = 0;
|
|
16
17
|
eof = false;
|
|
17
|
-
constructor(fileRecord, fileTable, preloadChunksCount = 2) {
|
|
18
|
+
constructor(fileRecord, fileTable, preloadChunksCount = 2, skipVerification = false) {
|
|
18
19
|
this.fileRecord = fileRecord;
|
|
19
20
|
this.fileTable = fileTable;
|
|
20
21
|
this.preloadChunksCount = preloadChunksCount;
|
|
22
|
+
this.skipVerification = skipVerification;
|
|
21
23
|
}
|
|
22
24
|
async loadChunkHashes() {
|
|
23
25
|
if (this.chunkHashes !== null) {
|
|
@@ -55,10 +57,12 @@ class FileReadStream {
|
|
|
55
57
|
throw new Error(`Chunk ${chunkHash} not found in storage or peers`);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
// Verify chunk integrity
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Verify chunk integrity (skip for trusted local reads)
|
|
61
|
+
if (!this.skipVerification) {
|
|
62
|
+
const actualChunkHash = (0, keys_1.hashBytes)(chunk);
|
|
63
|
+
if (actualChunkHash !== chunkHash) {
|
|
64
|
+
throw new Error(`Chunk integrity check failed: expected ${chunkHash}, got ${actualChunkHash}`);
|
|
65
|
+
}
|
|
62
66
|
}
|
|
63
67
|
return chunk;
|
|
64
68
|
}
|
|
@@ -20,9 +20,11 @@ export declare class FilesTable extends Table<IFile> {
|
|
|
20
20
|
* @returns The inserted file record
|
|
21
21
|
*/
|
|
22
22
|
saveFileRecord(fileRecord: IFile): Promise<IFile>;
|
|
23
|
-
openReadStream(fileId: string, preloadChunksCount?: number): Promise<FileReadStream | null>;
|
|
23
|
+
openReadStream(fileId: string, preloadChunksCount?: number, skipVerification?: boolean): Promise<FileReadStream | null>;
|
|
24
24
|
saveFile(metaData: IFileInput, data: Uint8Array | string): Promise<IFile>;
|
|
25
|
-
getFileContents(fileId: string
|
|
25
|
+
getFileContents(fileId: string, opts?: {
|
|
26
|
+
skipVerification?: boolean;
|
|
27
|
+
}): Promise<Uint8Array | null>;
|
|
26
28
|
deleteFile(fileId: string): Promise<void>;
|
|
27
29
|
createIndexFileRecursively(chunkHashes: string[]): Promise<string>;
|
|
28
30
|
loadChunkHashesRecursively(indexFileId: string): Promise<string[]>;
|
package/dist/data/files/files.js
CHANGED
|
@@ -39,12 +39,12 @@ class FilesTable extends orm_1.Table {
|
|
|
39
39
|
async saveFileRecord(fileRecord) {
|
|
40
40
|
return await super.save(fileRecord);
|
|
41
41
|
}
|
|
42
|
-
async openReadStream(fileId, preloadChunksCount) {
|
|
42
|
+
async openReadStream(fileId, preloadChunksCount, skipVerification) {
|
|
43
43
|
const fileRecord = await this.get(fileId);
|
|
44
44
|
if (!fileRecord) {
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
|
-
return new file_read_stream_1.FileReadStream(fileRecord, this, preloadChunksCount);
|
|
47
|
+
return new file_read_stream_1.FileReadStream(fileRecord, this, preloadChunksCount, skipVerification);
|
|
48
48
|
}
|
|
49
49
|
async saveFile(metaData, data) {
|
|
50
50
|
// Use FileWriteStream internally to ensure consistent chunking logic
|
|
@@ -60,9 +60,9 @@ class FilesTable extends orm_1.Table {
|
|
|
60
60
|
// Finalize and return the result
|
|
61
61
|
return await writeStream.finalize();
|
|
62
62
|
}
|
|
63
|
-
async getFileContents(fileId) {
|
|
63
|
+
async getFileContents(fileId, opts) {
|
|
64
64
|
// Use FileReadStream internally to ensure consistent chunk reading logic
|
|
65
|
-
const readStream = await this.openReadStream(fileId);
|
|
65
|
+
const readStream = await this.openReadStream(fileId, undefined, opts?.skipVerification);
|
|
66
66
|
if (!readStream) {
|
|
67
67
|
return null;
|
|
68
68
|
}
|
package/dist/data/groups.d.ts
CHANGED
|
@@ -17,8 +17,8 @@ export declare const groupSchema: z.ZodObject<{
|
|
|
17
17
|
}, "strip", z.ZodTypeAny, {
|
|
18
18
|
name: string;
|
|
19
19
|
description: string;
|
|
20
|
-
signature: string;
|
|
21
20
|
publicKey: string;
|
|
21
|
+
signature: string;
|
|
22
22
|
publicBoxKey: string;
|
|
23
23
|
groupId: string;
|
|
24
24
|
founderUserId: string;
|
|
@@ -28,8 +28,8 @@ export declare const groupSchema: z.ZodObject<{
|
|
|
28
28
|
}, {
|
|
29
29
|
name: string;
|
|
30
30
|
description: string;
|
|
31
|
-
signature: string;
|
|
32
31
|
publicKey: string;
|
|
32
|
+
signature: string;
|
|
33
33
|
publicBoxKey: string;
|
|
34
34
|
groupId: string;
|
|
35
35
|
founderUserId: string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Connection } from "./connection";
|
|
2
|
+
import { Device } from "./device";
|
|
3
|
+
import { ISocket } from "./socket.type";
|
|
4
|
+
type ITrustLevelFn = Connection['getTrustLevel'];
|
|
5
|
+
/**
|
|
6
|
+
* Generic binary peer interface supporting both WebRTC and libp2p 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 type ILibp2pPeer = IBinaryPeer;
|
|
23
|
+
export interface WrapBinaryPeerOptions {
|
|
24
|
+
/** Protocol identifier used for server address (e.g., 'wrtc', 'libp2p', or 'ws') */
|
|
25
|
+
protocol: 'wrtc' | 'libp2p' | 'ws';
|
|
26
|
+
/**
|
|
27
|
+
* Mark the transport as already secure (encrypted at transport layer).
|
|
28
|
+
* When true, sets secureLocal and secureRemote to skip application-level encryption.
|
|
29
|
+
*/
|
|
30
|
+
markTransportSecure?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates an ISocket from a binary peer without creating a full Connection.
|
|
34
|
+
* Useful when you need to pass custom localServerAddresses to the Connection.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string): ISocket;
|
|
37
|
+
/**
|
|
38
|
+
* Wraps a binary peer (WebRTC, libp2p, or WebSocket) into a Connection.
|
|
39
|
+
* This is the main implementation that handles all binary protocols.
|
|
40
|
+
*/
|
|
41
|
+
export declare function wrapBinaryPeer(connectionId: string, peer: IBinaryPeer, localDevice: Device, initiator: boolean, options: WrapBinaryPeerOptions, getTrustLevel?: ITrustLevelFn): Connection;
|
|
42
|
+
/**
|
|
43
|
+
* Wraps a WebRTC peer into a Connection.
|
|
44
|
+
* Backwards-compatible wrapper around wrapBinaryPeer.
|
|
45
|
+
* WebRTC is encrypted at the transport layer (DTLS-SRTP), so we skip application-level encryption.
|
|
46
|
+
*/
|
|
47
|
+
export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn): Connection;
|
|
48
|
+
/**
|
|
49
|
+
* Wraps a libp2p peer into a Connection.
|
|
50
|
+
* Backwards-compatible wrapper around wrapBinaryPeer.
|
|
51
|
+
* libp2p is encrypted at the transport layer (noise protocol), so we skip application-level encryption.
|
|
52
|
+
*/
|
|
53
|
+
export declare function wrapLibp2p(connectionId: string, peer: ILibp2pPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn): Connection;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBinaryPeerSocket = createBinaryPeerSocket;
|
|
4
|
+
exports.wrapBinaryPeer = wrapBinaryPeer;
|
|
5
|
+
exports.wrapWrtc = wrapWrtc;
|
|
6
|
+
exports.wrapLibp2p = wrapLibp2p;
|
|
7
|
+
const msgpack_1 = require("@msgpack/msgpack");
|
|
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 utils_1 = require("../utils");
|
|
12
|
+
const tx_encoding_1 = require("./tx-encoding");
|
|
13
|
+
// Protocol discriminator bytes for multiplexing RPC and raw byte streams
|
|
14
|
+
const PROTOCOL_RPC = 0x00;
|
|
15
|
+
const PROTOCOL_RAW_BYTES = 0x01;
|
|
16
|
+
// Threshold for routing large RPC messages through raw bytes streaming
|
|
17
|
+
// Messages larger than this will be automatically chunked with backpressure handling
|
|
18
|
+
const LARGE_MESSAGE_THRESHOLD = 64 * 1024; // 64KB
|
|
19
|
+
// Reserved stream ID for RPC messages sent via raw bytes
|
|
20
|
+
const RPC_STREAM_ID = '__rpc';
|
|
21
|
+
// Encode message metadata + data into a single Uint8Array
|
|
22
|
+
function encodeMessage(message) {
|
|
23
|
+
// Check if args/result is a Uint8Array - if so, flag it to keep it as raw bytes
|
|
24
|
+
let isUint8ArrayData = false;
|
|
25
|
+
if (message.type === 'call' && message.args instanceof Uint8Array) {
|
|
26
|
+
isUint8ArrayData = true;
|
|
27
|
+
}
|
|
28
|
+
else if (message.type === 'callback' && message.result instanceof Uint8Array) {
|
|
29
|
+
isUint8ArrayData = true;
|
|
30
|
+
}
|
|
31
|
+
// Encode metadata using msgpack (without args/result/error - those go in data)
|
|
32
|
+
const metadata = {
|
|
33
|
+
type: message.type,
|
|
34
|
+
callbackId: message.callbackId,
|
|
35
|
+
...(message.eventName !== undefined && { eventName: message.eventName }),
|
|
36
|
+
...(isUint8ArrayData && { isUint8ArrayData: true }),
|
|
37
|
+
};
|
|
38
|
+
const metadataBytes = (0, msgpack_1.encode)(metadata);
|
|
39
|
+
const metadataLength = metadataBytes.length;
|
|
40
|
+
// Encode the data payload (args, result, or error)
|
|
41
|
+
let dataBytes;
|
|
42
|
+
if (message.type === 'call' && message.args !== undefined) {
|
|
43
|
+
// If args is already a Uint8Array, use it directly without txEncode
|
|
44
|
+
if (isUint8ArrayData) {
|
|
45
|
+
dataBytes = message.args;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
dataBytes = (0, tx_encoding_1.txEncode)(message.args);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (message.type === 'callback') {
|
|
52
|
+
if (message.error !== undefined) {
|
|
53
|
+
dataBytes = (0, tx_encoding_1.txEncode)({ error: message.error });
|
|
54
|
+
}
|
|
55
|
+
else if (isUint8ArrayData) {
|
|
56
|
+
// If result is a Uint8Array, use it directly
|
|
57
|
+
dataBytes = message.result;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
dataBytes = (0, tx_encoding_1.txEncode)({ result: message.result });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
dataBytes = new Uint8Array(0);
|
|
65
|
+
}
|
|
66
|
+
// Create combined buffer: [4 bytes length][metadata][data]
|
|
67
|
+
const combined = new Uint8Array(4 + metadataLength + dataBytes.length);
|
|
68
|
+
// Write metadata length as 32-bit unsigned integer (big-endian)
|
|
69
|
+
const view = new DataView(combined.buffer, combined.byteOffset, combined.byteLength);
|
|
70
|
+
view.setUint32(0, metadataLength, false); // big-endian
|
|
71
|
+
// Write metadata
|
|
72
|
+
combined.set(metadataBytes, 4);
|
|
73
|
+
// Write data
|
|
74
|
+
combined.set(dataBytes, 4 + metadataLength);
|
|
75
|
+
return combined;
|
|
76
|
+
}
|
|
77
|
+
// Decode binary message back into IMessage
|
|
78
|
+
function decodeMessage(bytes) {
|
|
79
|
+
// Read metadata length
|
|
80
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
81
|
+
const metadataLength = view.getUint32(0, false); // big-endian
|
|
82
|
+
// Extract metadata
|
|
83
|
+
const metadataBytes = bytes.subarray(4, 4 + metadataLength);
|
|
84
|
+
const metadata = (0, msgpack_1.decode)(metadataBytes);
|
|
85
|
+
// Extract data
|
|
86
|
+
const dataBytes = bytes.subarray(4 + metadataLength);
|
|
87
|
+
const message = {
|
|
88
|
+
type: metadata.type,
|
|
89
|
+
callbackId: metadata.callbackId,
|
|
90
|
+
eventName: metadata.eventName,
|
|
91
|
+
};
|
|
92
|
+
// Decode the data payload
|
|
93
|
+
if (dataBytes.length > 0) {
|
|
94
|
+
if (message.type === 'call') {
|
|
95
|
+
// If it was a Uint8Array, use it directly without txDecode
|
|
96
|
+
if (metadata.isUint8ArrayData) {
|
|
97
|
+
message.args = dataBytes;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
message.args = (0, tx_encoding_1.txDecode)(dataBytes);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (message.type === 'callback') {
|
|
104
|
+
if (metadata.isUint8ArrayData) {
|
|
105
|
+
// If it was a Uint8Array result, use it directly
|
|
106
|
+
message.result = dataBytes;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const decoded = (0, tx_encoding_1.txDecode)(dataBytes);
|
|
110
|
+
if (decoded.error !== undefined) {
|
|
111
|
+
message.error = decoded.error;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
message.result = decoded.result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return message;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Creates an ISocket from a binary peer without creating a full Connection.
|
|
123
|
+
* Useful when you need to pass custom localServerAddresses to the Connection.
|
|
124
|
+
*/
|
|
125
|
+
function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
|
|
126
|
+
const handlers = {};
|
|
127
|
+
const callbacks = {};
|
|
128
|
+
const rawBytesHandlers = {};
|
|
129
|
+
let isClosed = false;
|
|
130
|
+
const stats = (0, streamed_socket_1.createSocketStats)();
|
|
131
|
+
// Map protocol string to TransportType (needed early for threshold calculations)
|
|
132
|
+
const transportType = protocol === 'wrtc' ? 'wrtc' :
|
|
133
|
+
protocol === 'libp2p' ? 'libp2p' :
|
|
134
|
+
protocol === 'ws' ? 'ws' : 'unknown';
|
|
135
|
+
// Core sendRawBytes implementation - defined early so sendRPCData can use it
|
|
136
|
+
const sendRawBytesImpl = async (streamId, data) => {
|
|
137
|
+
const streamIdBytes = new TextEncoder().encode(streamId);
|
|
138
|
+
const packet = new Uint8Array(1 + 2 + streamIdBytes.length + data.length);
|
|
139
|
+
packet[0] = PROTOCOL_RAW_BYTES;
|
|
140
|
+
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
|
|
141
|
+
view.setUint16(1, streamIdBytes.length, false);
|
|
142
|
+
packet.set(streamIdBytes, 3);
|
|
143
|
+
packet.set(data, 3 + streamIdBytes.length);
|
|
144
|
+
// Use transport-specific thresholds for backpressure
|
|
145
|
+
const HIGH_WATER_MARK = transportType === 'wrtc' ? 128 * 1024 : 4 * 1024 * 1024;
|
|
146
|
+
const LOW_WATER_MARK = transportType === 'wrtc' ? 32 * 1024 : 2 * 1024 * 1024;
|
|
147
|
+
if (peer.getBufferedAmount && peer.getBufferedAmount() > HIGH_WATER_MARK) {
|
|
148
|
+
await new Promise(resolve => {
|
|
149
|
+
const checkDrain = () => {
|
|
150
|
+
if (!peer.getBufferedAmount || peer.getBufferedAmount() < LOW_WATER_MARK) {
|
|
151
|
+
resolve();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
setTimeout(checkDrain, 5);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
peer.on('drain', resolve);
|
|
158
|
+
checkDrain();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
stats.bytesSent += packet.length;
|
|
162
|
+
stats.messagesSent++;
|
|
163
|
+
peer.send(packet);
|
|
164
|
+
};
|
|
165
|
+
// Helper to send RPC messages - routes large messages through raw bytes streaming
|
|
166
|
+
const sendRPCData = async (encoded) => {
|
|
167
|
+
if (encoded.length > LARGE_MESSAGE_THRESHOLD) {
|
|
168
|
+
// Large message: use raw bytes streaming with backpressure
|
|
169
|
+
await sendRawBytesImpl(RPC_STREAM_ID, encoded);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Small message: send directly
|
|
173
|
+
const withProtocol = new Uint8Array(1 + encoded.length);
|
|
174
|
+
withProtocol[0] = PROTOCOL_RPC;
|
|
175
|
+
withProtocol.set(encoded, 1);
|
|
176
|
+
stats.bytesSent += withProtocol.length;
|
|
177
|
+
stats.messagesSent++;
|
|
178
|
+
peer.send(withProtocol);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
// Handle incoming data
|
|
182
|
+
peer.on('data', async (data) => {
|
|
183
|
+
try {
|
|
184
|
+
const bytes = new Uint8Array(data);
|
|
185
|
+
stats.bytesReceived += bytes.length;
|
|
186
|
+
stats.messagesReceived++;
|
|
187
|
+
if (bytes.length === 0)
|
|
188
|
+
return;
|
|
189
|
+
const protocolType = bytes[0];
|
|
190
|
+
// Handle raw bytes stream
|
|
191
|
+
if (protocolType === PROTOCOL_RAW_BYTES) {
|
|
192
|
+
if (bytes.length < 3)
|
|
193
|
+
return;
|
|
194
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
195
|
+
const streamIdLength = view.getUint16(1, false);
|
|
196
|
+
if (bytes.length < 3 + streamIdLength)
|
|
197
|
+
return;
|
|
198
|
+
const streamIdBytes = bytes.subarray(3, 3 + streamIdLength);
|
|
199
|
+
const streamId = new TextDecoder().decode(streamIdBytes);
|
|
200
|
+
const payload = bytes.subarray(3 + streamIdLength);
|
|
201
|
+
// Check if this is an RPC message sent via raw bytes (for large payloads)
|
|
202
|
+
if (streamId === RPC_STREAM_ID) {
|
|
203
|
+
handleRPCMessage(payload, handlers, callbacks, sendRPCData);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const handler = rawBytesHandlers[streamId];
|
|
207
|
+
if (handler)
|
|
208
|
+
handler(payload);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Handle RPC messages
|
|
212
|
+
if (protocolType === PROTOCOL_RPC) {
|
|
213
|
+
const rpcBytes = bytes.subarray(1);
|
|
214
|
+
handleRPCMessage(rpcBytes, handlers, callbacks, sendRPCData);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Legacy format (no discriminator) for backwards compatibility
|
|
218
|
+
handleRPCMessage(bytes, handlers, callbacks, sendRPCData);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
console.error(`Error parsing ${protocol} message:`, err);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
// Handle closure
|
|
225
|
+
peer.on('close', () => {
|
|
226
|
+
if (isClosed)
|
|
227
|
+
return;
|
|
228
|
+
isClosed = true;
|
|
229
|
+
socket.connected = false;
|
|
230
|
+
});
|
|
231
|
+
const socket = {
|
|
232
|
+
id: connectionId,
|
|
233
|
+
connected: true,
|
|
234
|
+
handlesOwnEncoding: true,
|
|
235
|
+
transportType,
|
|
236
|
+
stats,
|
|
237
|
+
emit(eventName, args, callback) {
|
|
238
|
+
const callbackId = (0, utils_1.newid)();
|
|
239
|
+
callbacks[callbackId] = callback;
|
|
240
|
+
const message = { type: 'call', eventName, args, callbackId };
|
|
241
|
+
const encoded = encodeMessage(message);
|
|
242
|
+
sendRPCData(encoded);
|
|
243
|
+
},
|
|
244
|
+
on(eventName, handler) {
|
|
245
|
+
handlers[eventName] = handler;
|
|
246
|
+
},
|
|
247
|
+
removeAllListeners(eventName) {
|
|
248
|
+
if (eventName) {
|
|
249
|
+
delete handlers[eventName];
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
Object.keys(handlers).forEach(key => delete handlers[key]);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
disconnect() {
|
|
256
|
+
peer.destroy();
|
|
257
|
+
},
|
|
258
|
+
sendRawBytes: sendRawBytesImpl,
|
|
259
|
+
onRawBytes(streamId, handler) {
|
|
260
|
+
rawBytesHandlers[streamId] = handler;
|
|
261
|
+
},
|
|
262
|
+
removeRawBytesHandler(streamId) {
|
|
263
|
+
delete rawBytesHandlers[streamId];
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
return socket;
|
|
267
|
+
}
|
|
268
|
+
// Helper to handle RPC message parsing
|
|
269
|
+
// sendRPCData automatically routes large messages through raw bytes streaming
|
|
270
|
+
function handleRPCMessage(bytes, handlers, callbacks, sendRPCData) {
|
|
271
|
+
const message = decodeMessage(bytes);
|
|
272
|
+
if (message.type === 'callback') {
|
|
273
|
+
const callback = callbacks[message.callbackId];
|
|
274
|
+
if (callback) {
|
|
275
|
+
delete callbacks[message.callbackId];
|
|
276
|
+
callback(message.error, message.result);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (message.type === 'call') {
|
|
281
|
+
const handler = handlers[message.eventName];
|
|
282
|
+
if (!handler) {
|
|
283
|
+
console.error(`No handler for event: ${message.eventName}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const callback = (err, result) => {
|
|
288
|
+
const responseMessage = {
|
|
289
|
+
type: 'callback',
|
|
290
|
+
callbackId: message.callbackId,
|
|
291
|
+
};
|
|
292
|
+
if (err)
|
|
293
|
+
responseMessage.error = err;
|
|
294
|
+
else
|
|
295
|
+
responseMessage.result = result;
|
|
296
|
+
const encoded = encodeMessage(responseMessage);
|
|
297
|
+
// Use sendRPCData which auto-routes large responses through raw bytes
|
|
298
|
+
sendRPCData(encoded);
|
|
299
|
+
};
|
|
300
|
+
handler(message.args, callback);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error("Error in handler", { eventName: message.eventName, error });
|
|
304
|
+
const errorMessage = {
|
|
305
|
+
type: 'callback',
|
|
306
|
+
callbackId: message.callbackId,
|
|
307
|
+
error: error.message
|
|
308
|
+
};
|
|
309
|
+
const encoded = encodeMessage(errorMessage);
|
|
310
|
+
sendRPCData(encoded);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Wraps a binary peer (WebRTC, libp2p, or WebSocket) into a Connection.
|
|
316
|
+
* This is the main implementation that handles all binary protocols.
|
|
317
|
+
*/
|
|
318
|
+
function wrapBinaryPeer(connectionId, peer, localDevice, initiator, options, getTrustLevel) {
|
|
319
|
+
const { protocol, markTransportSecure } = options;
|
|
320
|
+
// Create the socket wrapper using shared implementation
|
|
321
|
+
const socket = createBinaryPeerSocket(connectionId, peer, protocol);
|
|
322
|
+
const serverAddress = `${protocol}://${connectionId}`;
|
|
323
|
+
const localServerAddresses = initiator ? undefined : [serverAddress];
|
|
324
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
325
|
+
getTrustLevel ??= (0, get_trust_level_fn_1.getTrustLevelFn)(localDevice, serverAddress);
|
|
326
|
+
}
|
|
327
|
+
const connection = new connection_1.Connection(socket, localDevice, localServerAddresses, getTrustLevel);
|
|
328
|
+
// Mark transport as secure if applicable (WebRTC DTLS-SRTP, libp2p noise, wss)
|
|
329
|
+
if (markTransportSecure) {
|
|
330
|
+
connection.secureLocal = true;
|
|
331
|
+
connection.secureRemote = true;
|
|
332
|
+
}
|
|
333
|
+
return connection;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Wraps a WebRTC peer into a Connection.
|
|
337
|
+
* Backwards-compatible wrapper around wrapBinaryPeer.
|
|
338
|
+
* WebRTC is encrypted at the transport layer (DTLS-SRTP), so we skip application-level encryption.
|
|
339
|
+
*/
|
|
340
|
+
function wrapWrtc(connectionId, peer, localDevice, initiator, getTrustLevel) {
|
|
341
|
+
return wrapBinaryPeer(connectionId, peer, localDevice, initiator, { protocol: 'wrtc', markTransportSecure: true }, getTrustLevel);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Wraps a libp2p peer into a Connection.
|
|
345
|
+
* Backwards-compatible wrapper around wrapBinaryPeer.
|
|
346
|
+
* libp2p is encrypted at the transport layer (noise protocol), so we skip application-level encryption.
|
|
347
|
+
*/
|
|
348
|
+
function wrapLibp2p(connectionId, peer, localDevice, initiator, getTrustLevel) {
|
|
349
|
+
return wrapBinaryPeer(connectionId, peer, localDevice, initiator, { protocol: 'libp2p', markTransportSecure: true }, getTrustLevel);
|
|
350
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|