@peers-app/peers-sdk 0.8.0 → 0.8.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.
- 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/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 +2 -0
- package/dist/index.js +2 -0
- 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/package.json +1 -1
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StreamedSocket = void 0;
|
|
4
|
+
exports.createSocketStats = createSocketStats;
|
|
4
5
|
const lodash_1 = require("lodash");
|
|
5
6
|
const utils_1 = require("../utils");
|
|
6
7
|
const socket_type_1 = require("./socket.type");
|
|
7
8
|
const tx_encoding_1 = require("./tx-encoding");
|
|
8
9
|
const msgpack_1 = require("@msgpack/msgpack");
|
|
10
|
+
/** Creates a new stats object for tracking socket throughput */
|
|
11
|
+
function createSocketStats() {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
return {
|
|
14
|
+
bytesSent: 0,
|
|
15
|
+
bytesReceived: 0,
|
|
16
|
+
messagesSent: 0,
|
|
17
|
+
messagesReceived: 0,
|
|
18
|
+
startTime: now,
|
|
19
|
+
// For calculating recent rates
|
|
20
|
+
lastSampleTime: now,
|
|
21
|
+
lastSampleBytesSent: 0,
|
|
22
|
+
lastSampleBytesReceived: 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
9
25
|
// Encode chunk metadata + data into a single Uint8Array
|
|
10
26
|
function encodeChunk(chunk) {
|
|
11
27
|
// Encode metadata using msgpack
|
|
@@ -30,14 +46,24 @@ function encodeChunk(chunk) {
|
|
|
30
46
|
}
|
|
31
47
|
// Decode binary chunk back into IMessageChunk
|
|
32
48
|
function decodeChunk(bytes) {
|
|
49
|
+
// Ensure we have a proper Uint8Array with a valid ArrayBuffer
|
|
50
|
+
// Socket.io might send data that doesn't have a proper buffer property
|
|
51
|
+
let normalizedBytes;
|
|
52
|
+
if (bytes.buffer instanceof ArrayBuffer) {
|
|
53
|
+
normalizedBytes = bytes;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Create a new Uint8Array copy which will have a proper buffer
|
|
57
|
+
normalizedBytes = new Uint8Array(bytes);
|
|
58
|
+
}
|
|
33
59
|
// Read metadata length
|
|
34
|
-
const view = new DataView(
|
|
60
|
+
const view = new DataView(normalizedBytes.buffer, normalizedBytes.byteOffset, normalizedBytes.byteLength);
|
|
35
61
|
const metadataLength = view.getUint32(0, false); // big-endian
|
|
36
62
|
// Extract metadata
|
|
37
|
-
const metadataBytes =
|
|
63
|
+
const metadataBytes = normalizedBytes.subarray(4, 4 + metadataLength);
|
|
38
64
|
const metadata = (0, msgpack_1.decode)(metadataBytes);
|
|
39
65
|
// Extract data
|
|
40
|
-
const data =
|
|
66
|
+
const data = normalizedBytes.subarray(4 + metadataLength);
|
|
41
67
|
return {
|
|
42
68
|
messageId: metadata.messageId,
|
|
43
69
|
chunkIndex: metadata.chunkIndex,
|
|
@@ -51,10 +77,13 @@ class StreamedSocket {
|
|
|
51
77
|
maxChunkSize;
|
|
52
78
|
safeSocketChunkEventName = '__streamed-socket-chunk';
|
|
53
79
|
safeSocketResponseEventName = '__streamed-socket-response';
|
|
80
|
+
stats = createSocketStats();
|
|
54
81
|
constructor(socket, maxChunkSize = socket_type_1.DEFAULT_MAX_CHUNK_SIZE) {
|
|
55
82
|
this.socket = socket;
|
|
56
83
|
this.maxChunkSize = maxChunkSize;
|
|
57
84
|
socket.on(this.safeSocketChunkEventName, (encodedChunk) => {
|
|
85
|
+
// Track received bytes
|
|
86
|
+
this.stats.bytesReceived += encodedChunk.length;
|
|
58
87
|
const chunk = decodeChunk(encodedChunk);
|
|
59
88
|
this.handleChunk(chunk);
|
|
60
89
|
});
|
|
@@ -103,13 +132,18 @@ class StreamedSocket {
|
|
|
103
132
|
if (chunks.length > 1) {
|
|
104
133
|
console.debug(`Sending ${chunks.length} chunks for event ${eventName} with messageId ${messageId}`);
|
|
105
134
|
}
|
|
135
|
+
let totalBytesSent = 0;
|
|
106
136
|
for (const chunk of chunks) {
|
|
107
137
|
// Encode chunk with metadata into single Uint8Array
|
|
108
138
|
const encodedChunk = encodeChunk(chunk);
|
|
139
|
+
totalBytesSent += encodedChunk.length;
|
|
109
140
|
// TODO on error or timeout retry
|
|
110
141
|
// TODO respond to backpressure from socket
|
|
111
142
|
this.socket.emit(this.safeSocketChunkEventName, encodedChunk, lodash_1.noop);
|
|
112
143
|
}
|
|
144
|
+
// Track stats
|
|
145
|
+
this.stats.bytesSent += totalBytesSent;
|
|
146
|
+
this.stats.messagesSent++;
|
|
113
147
|
}
|
|
114
148
|
handlers = {};
|
|
115
149
|
on(eventName, handler) {
|
|
@@ -159,6 +193,8 @@ class StreamedSocket {
|
|
|
159
193
|
reassembled.set(chunk.data, offset);
|
|
160
194
|
offset += chunk.data.length;
|
|
161
195
|
}
|
|
196
|
+
// Track message received
|
|
197
|
+
this.stats.messagesReceived++;
|
|
162
198
|
delete this.chunkBuffers[chunkZero.messageId];
|
|
163
199
|
const eventName = chunkZero.eventName;
|
|
164
200
|
const args = (0, tx_encoding_1.txDecode)(reassembled);
|
|
@@ -190,7 +226,12 @@ class StreamedSocket {
|
|
|
190
226
|
}, lodash_1.noop);
|
|
191
227
|
}
|
|
192
228
|
catch (error) {
|
|
193
|
-
|
|
229
|
+
if (String(error).includes('No handler registered for event')) {
|
|
230
|
+
console.warn(String(error));
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
console.error(`Error handling chunked request:`, error);
|
|
234
|
+
}
|
|
194
235
|
return;
|
|
195
236
|
}
|
|
196
237
|
}
|
|
@@ -5,16 +5,34 @@ exports.txDecode = txDecode;
|
|
|
5
5
|
const msgpack_1 = require("@msgpack/msgpack");
|
|
6
6
|
const fflate_1 = require("fflate");
|
|
7
7
|
const serial_json_1 = require("../serial-json");
|
|
8
|
+
// Minimum size before attempting compression (bytes)
|
|
9
|
+
// Small payloads don't benefit much from compression
|
|
10
|
+
const COMPRESSION_THRESHOLD = 8 * 1024; // 8KB
|
|
11
|
+
// Minimum compression ratio to justify the CPU cost
|
|
12
|
+
// If compressed isn't at least 20% smaller, skip it
|
|
13
|
+
const MIN_COMPRESSION_RATIO = 0.8;
|
|
14
|
+
// Compression level: 1 (fastest) to 9 (best compression)
|
|
15
|
+
// Level 1 is ~5-10x faster than level 6 with reasonable compression
|
|
16
|
+
const COMPRESSION_LEVEL = 1;
|
|
8
17
|
function txEncode(data) {
|
|
9
18
|
const noCycles = (0, serial_json_1.toJSON)(data); // remove cycles and encode dates, etc.
|
|
10
19
|
// First encode to msgpack
|
|
11
20
|
const encoded = (0, msgpack_1.encode)(noCycles);
|
|
12
21
|
let body;
|
|
13
22
|
let flag;
|
|
14
|
-
if (encoded.length >
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
if (encoded.length > COMPRESSION_THRESHOLD) {
|
|
24
|
+
// Try to compress - use fast compression level
|
|
25
|
+
const compressed = (0, fflate_1.compressSync)(encoded, { level: COMPRESSION_LEVEL });
|
|
26
|
+
// Only use compression if it actually helps significantly
|
|
27
|
+
if (compressed.length < encoded.length * MIN_COMPRESSION_RATIO) {
|
|
28
|
+
body = compressed;
|
|
29
|
+
flag = 1; // compressed
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Compression didn't help much, skip it
|
|
33
|
+
body = encoded;
|
|
34
|
+
flag = 0; // not compressed
|
|
35
|
+
}
|
|
18
36
|
}
|
|
19
37
|
else {
|
|
20
38
|
body = encoded;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tx_encoding_1 = require("./tx-encoding");
|
|
4
|
+
const msgpack_1 = require("@msgpack/msgpack");
|
|
5
|
+
const serial_json_1 = require("../serial-json");
|
|
4
6
|
describe('tx-encode-decode', () => {
|
|
5
7
|
describe('txEncode and txDecode', () => {
|
|
6
8
|
it('should encode and decode simple objects', () => {
|
|
@@ -88,14 +90,22 @@ describe('tx-encode-decode', () => {
|
|
|
88
90
|
expect(encoded[0]).toBe(0); // flag = 0 means not compressed
|
|
89
91
|
});
|
|
90
92
|
it('should compress large payloads (> 1024 bytes)', () => {
|
|
91
|
-
// Create an object that will exceed
|
|
93
|
+
// Create an object that will exceed 8KB when msgpack-encoded
|
|
94
|
+
// msgpack is more compact than JSON, so we need significantly more data
|
|
95
|
+
// A string of 20,000 chars is roughly 20KB in JSON, but msgpack uses length prefix + bytes
|
|
96
|
+
// So we need even more to ensure msgpack encoding exceeds 8KB
|
|
92
97
|
const largeObj = {
|
|
93
|
-
data: 'x'.repeat(
|
|
94
|
-
array: Array.from({ length:
|
|
98
|
+
data: 'x'.repeat(30000),
|
|
99
|
+
array: Array.from({ length: 1000 }, (_, i) => ({
|
|
100
|
+
id: i,
|
|
101
|
+
value: `item ${i} with some extra text to make it larger and ensure we exceed the threshold`,
|
|
102
|
+
nested: { prop: `nested value ${i}` }
|
|
103
|
+
}))
|
|
95
104
|
};
|
|
96
|
-
const jsonStr = JSON.stringify(largeObj);
|
|
97
|
-
expect(jsonStr.length).toBeGreaterThan(1024);
|
|
98
105
|
const encoded = (0, tx_encoding_1.txEncode)(largeObj);
|
|
106
|
+
// Verify the encoded size (without flag byte) exceeds 8KB threshold
|
|
107
|
+
const encodedSize = encoded.length - 1; // subtract flag byte
|
|
108
|
+
expect(encodedSize).toBeGreaterThan(8 * 1024);
|
|
99
109
|
// First byte is the flag
|
|
100
110
|
expect(encoded[0]).toBe(1); // flag = 1 means compressed
|
|
101
111
|
// Verify it can still be decoded correctly
|
|
@@ -103,14 +113,22 @@ describe('tx-encode-decode', () => {
|
|
|
103
113
|
expect(decoded).toEqual(largeObj);
|
|
104
114
|
});
|
|
105
115
|
it('should have smaller encoded size for large compressible data', () => {
|
|
116
|
+
// Create data that exceeds 8KB when msgpack-encoded and compresses well
|
|
117
|
+
// Use data with some repetition but also enough variety to ensure msgpack size is large
|
|
106
118
|
const largeRepetitiveObj = {
|
|
107
|
-
data: 'repeat '.repeat(
|
|
108
|
-
values: Array.from({ length:
|
|
119
|
+
data: 'repeat '.repeat(10000), // Highly repetitive string - will compress well
|
|
120
|
+
values: Array.from({ length: 5000 }, (_, i) => `item ${i} with some text that repeats to create compressible data pattern`)
|
|
109
121
|
};
|
|
122
|
+
// First, verify the msgpack-encoded size exceeds 8KB threshold
|
|
123
|
+
const noCycles = (0, serial_json_1.toJSON)(largeRepetitiveObj);
|
|
124
|
+
const msgpackEncoded = (0, msgpack_1.encode)(noCycles);
|
|
125
|
+
expect(msgpackEncoded.length).toBeGreaterThan(8 * 1024);
|
|
110
126
|
const encoded = (0, tx_encoding_1.txEncode)(largeRepetitiveObj);
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
// Verify compression flag is set (means compression was applied)
|
|
128
|
+
expect(encoded[0]).toBe(1); // flag = 1 means compressed
|
|
129
|
+
// Compressed size should be significantly smaller than the raw msgpack data
|
|
130
|
+
const compressedSize = encoded.length - 1; // subtract flag byte
|
|
131
|
+
expect(compressedSize).toBeLessThan(msgpackEncoded.length * 0.8); // Should be at least 20% smaller
|
|
114
132
|
// Verify correctness
|
|
115
133
|
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
116
134
|
expect(decoded).toEqual(largeRepetitiveObj);
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export * from "./types/workflow";
|
|
|
12
12
|
export * from "./types/workflow-logger";
|
|
13
13
|
export * from "./types/workflow-run-context";
|
|
14
14
|
export * from "./types/zod-types";
|
|
15
|
+
export * from "./device/binary-peer-connection";
|
|
16
|
+
export * from "./device/socket-io-binary-peer";
|
|
15
17
|
export * from "./device/connection";
|
|
16
18
|
export * from "./device/device";
|
|
17
19
|
export * from "./device/device-election";
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,8 @@ __exportStar(require("./types/workflow"), exports);
|
|
|
29
29
|
__exportStar(require("./types/workflow-logger"), exports);
|
|
30
30
|
__exportStar(require("./types/workflow-run-context"), exports);
|
|
31
31
|
__exportStar(require("./types/zod-types"), exports);
|
|
32
|
+
__exportStar(require("./device/binary-peer-connection"), exports);
|
|
33
|
+
__exportStar(require("./device/socket-io-binary-peer"), exports);
|
|
32
34
|
__exportStar(require("./device/connection"), exports);
|
|
33
35
|
__exportStar(require("./device/device"), exports);
|
|
34
36
|
__exportStar(require("./device/device-election"), exports);
|
|
@@ -3,6 +3,8 @@ import type { IPeersPackage } from "../types/peers-package";
|
|
|
3
3
|
import { IPackage } from "../data/packages";
|
|
4
4
|
export declare class PackageLoader {
|
|
5
5
|
readonly dataContext: DataContext;
|
|
6
|
+
static PeersSDK: any;
|
|
7
|
+
static Zod: any;
|
|
6
8
|
private packageInstances;
|
|
7
9
|
require: (<T>(module: string) => T) | undefined;
|
|
8
10
|
constructor(dataContext: DataContext);
|
|
@@ -8,6 +8,8 @@ const packages_1 = require("../data/packages");
|
|
|
8
8
|
const tools_2 = require("../data/tools");
|
|
9
9
|
class PackageLoader {
|
|
10
10
|
dataContext;
|
|
11
|
+
static PeersSDK;
|
|
12
|
+
static Zod;
|
|
11
13
|
packageInstances = {};
|
|
12
14
|
require = undefined;
|
|
13
15
|
constructor(dataContext) {
|
|
@@ -59,9 +61,9 @@ class PackageLoader {
|
|
|
59
61
|
case 'PeersSDK':
|
|
60
62
|
// Import all peers-sdk exports
|
|
61
63
|
// return _require('peers-sdk');
|
|
62
|
-
return _require('../index');
|
|
64
|
+
return PackageLoader.PeersSDK || _require('../index');
|
|
63
65
|
case 'zod':
|
|
64
|
-
return _require('zod');
|
|
66
|
+
return PackageLoader.Zod || _require('zod');
|
|
65
67
|
default:
|
|
66
68
|
// For other modules, use the standard require
|
|
67
69
|
console.warn(`Package ${pkg.name} is requiring a module ${moduleId}, which is not provided by the package loader.`);
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare const rpcServerCalls: {
|
|
|
18
18
|
encryptData: ((value: string, groupId?: string) => Promise<string>);
|
|
19
19
|
tableMethodCall: ((dataContextId: string, tableName: string, methodName: string, ...args: any[]) => Promise<any>);
|
|
20
20
|
getFileContents: ((fileId: string, encoding?: BufferEncoding) => Promise<string>);
|
|
21
|
+
injectUIBundle: ((uiBundleFileId: string) => Promise<void>);
|
|
21
22
|
resetAllDeviceSyncInfo: (() => Promise<void>);
|
|
22
23
|
importGroupShare: ((groupShareJson: string) => Promise<string>);
|
|
23
24
|
};
|
package/dist/rpc-types.js
CHANGED
|
@@ -19,6 +19,8 @@ exports.rpcServerCalls = {
|
|
|
19
19
|
tableMethodCall: rpcStub('tableMethodCall'),
|
|
20
20
|
// TODO lock this down so not all code can get any file contents
|
|
21
21
|
getFileContents: rpcStub('getFileContents'),
|
|
22
|
+
// Inject UI bundle directly into WebView (bypasses slow postMessage for large strings)
|
|
23
|
+
injectUIBundle: rpcStub('injectUIBundle'),
|
|
22
24
|
resetAllDeviceSyncInfo: rpcStub('resetAllDeviceSyncInfo'),
|
|
23
25
|
importGroupShare: rpcStub('importGroupShare'),
|
|
24
26
|
// TODO try to get rid of this and rely on the client-side table and server-side table individually emitting events
|
|
@@ -33,7 +35,9 @@ exports.rpcClientCalls = {
|
|
|
33
35
|
setClientPath: rpcStub('setClientPath'),
|
|
34
36
|
openThread: rpcStub('openThread'),
|
|
35
37
|
};
|
|
36
|
-
|
|
38
|
+
// Check if we're in a web browser or webview (not React Native or Node.js)
|
|
39
|
+
// React Native has window but not document, so we check for both
|
|
40
|
+
exports.isClient = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
37
41
|
if (exports.isClient) {
|
|
38
42
|
// @ts-ignore
|
|
39
43
|
const _window = window;
|