@peers-app/peers-sdk 0.7.18 → 0.7.20
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/device/connection.test.js +18 -11
- package/dist/device/get-trust-level.js +15 -3
- package/dist/device/streamed-socket.d.ts +0 -7
- package/dist/device/streamed-socket.js +72 -12
- package/dist/device/streamed-socket.test.js +177 -5
- package/dist/device/tx-encoding.d.ts +2 -0
- package/dist/device/tx-encoding.js +44 -0
- package/dist/device/tx-encoding.test.d.ts +1 -0
- package/dist/device/tx-encoding.test.js +267 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keys.js +10 -11
- package/dist/logging/console-logs.table.d.ts +2 -2
- package/dist/types/peer-device.d.ts +2 -2
- package/package.json +3 -1
|
@@ -230,16 +230,17 @@ describe(connection_1.Connection, () => {
|
|
|
230
230
|
const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
|
|
231
231
|
expect(result).toMatch(/Inconsistent public keys/);
|
|
232
232
|
});
|
|
233
|
-
it("should handle RPC calls with large arguments through chunking", async () => {
|
|
233
|
+
it("should handle RPC calls with large arguments and responses through chunking", async () => {
|
|
234
234
|
const { clientSocket, serverSocket } = createTestSocketPair();
|
|
235
235
|
const clientDevice = new device_1.Device();
|
|
236
236
|
const serverDevice = new device_1.Device();
|
|
237
237
|
const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
|
|
238
238
|
const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['localhost']);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
const chunkSize = 256; // 256 minimum chunk size due to encoding overheads
|
|
240
|
+
clientConnection.maxChunkSize = chunkSize;
|
|
241
|
+
serverConnection.maxChunkSize = chunkSize;
|
|
242
|
+
clientConnection.socket.maxChunkSize = chunkSize;
|
|
243
|
+
serverConnection.socket.maxChunkSize = chunkSize;
|
|
243
244
|
await clientConnection.doHandshake('localhost');
|
|
244
245
|
const clientEmitSpy = jest.spyOn(clientConnection.socket.socket, 'emit');
|
|
245
246
|
const serverEmitSpy = jest.spyOn(serverConnection.socket.socket, 'emit');
|
|
@@ -259,15 +260,21 @@ describe(connection_1.Connection, () => {
|
|
|
259
260
|
expect(result.received.message).toBe("A".repeat(500));
|
|
260
261
|
expect(result.received.metadata).toBe("B".repeat(300));
|
|
261
262
|
expect(result.received.payload).toBe("C".repeat(400));
|
|
262
|
-
expect(
|
|
263
|
+
expect(JSON.stringify(largeData).length).toEqual(1241);
|
|
263
264
|
// Count chunk emissions
|
|
264
265
|
const clientChunkCalls = clientEmitSpy.mock.calls.filter((call) => call[0] === clientConnection.socket.safeSocketChunkEventName);
|
|
265
266
|
const serverChunkCalls = serverEmitSpy.mock.calls.filter((call) => call[0] === serverConnection.socket.safeSocketChunkEventName);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
expect(clientChunkCalls.length).toBe(4);
|
|
268
|
+
expect(serverChunkCalls.length).toBe(5);
|
|
269
|
+
// expect calls to never have chunks greater than chunk size
|
|
270
|
+
for (const call of clientChunkCalls) {
|
|
271
|
+
const chunkData = call[1];
|
|
272
|
+
expect(chunkData.length).toBeLessThanOrEqual(chunkSize);
|
|
273
|
+
}
|
|
274
|
+
for (const call of serverChunkCalls) {
|
|
275
|
+
const chunkData = call[1];
|
|
276
|
+
expect(chunkData.length).toBeLessThanOrEqual(chunkSize);
|
|
277
|
+
}
|
|
271
278
|
});
|
|
272
279
|
it("should reject handshake if device timestamps are too far apart", async () => {
|
|
273
280
|
const { clientSocket, serverSocket } = createTestSocketPair();
|
|
@@ -9,9 +9,21 @@ function getTrustLevelFn(me, serverUrl) {
|
|
|
9
9
|
const userContext = await (0, context_1.getUserContext)();
|
|
10
10
|
const userDataContext = userContext.userDataContext;
|
|
11
11
|
if (deviceInfo.userId === me.userId && deviceInfo.publicKey === me.publicKey && deviceInfo.publicBoxKey === me.publicBoxKey) {
|
|
12
|
-
if (deviceInfo.deviceId ===
|
|
13
|
-
|
|
14
|
-
}
|
|
12
|
+
// if (deviceInfo.deviceId === thisDeviceId()) {
|
|
13
|
+
// return TrustLevel.Untrusted;
|
|
14
|
+
// }
|
|
15
|
+
const device = await (0, data_1.Devices)(userDataContext).get(deviceInfo.deviceId) || {
|
|
16
|
+
deviceId: deviceInfo.deviceId,
|
|
17
|
+
userId: deviceInfo.userId,
|
|
18
|
+
firstSeen: new Date(),
|
|
19
|
+
lastSeen: new Date(),
|
|
20
|
+
trustLevel: socket_type_1.TrustLevel.Trusted,
|
|
21
|
+
serverUrl,
|
|
22
|
+
};
|
|
23
|
+
device.lastSeen = new Date();
|
|
24
|
+
device.trustLevel = socket_type_1.TrustLevel.Trusted;
|
|
25
|
+
console.log(`Updating my own device: ${deviceInfo.deviceId}`);
|
|
26
|
+
await (0, data_1.Devices)(userDataContext).save(device);
|
|
15
27
|
return socket_type_1.TrustLevel.Trusted;
|
|
16
28
|
}
|
|
17
29
|
// await Devices().delete(deviceInfo.deviceId);
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { ISocket, RPCCallback } from "./socket.type";
|
|
2
|
-
export interface IMessageChunk {
|
|
3
|
-
messageId: string;
|
|
4
|
-
chunkIndex: number;
|
|
5
|
-
totalChunks?: number;
|
|
6
|
-
eventName?: string;
|
|
7
|
-
data: string;
|
|
8
|
-
}
|
|
9
2
|
export declare class StreamedSocket implements ISocket {
|
|
10
3
|
readonly socket: ISocket;
|
|
11
4
|
maxChunkSize: number;
|
|
@@ -2,9 +2,50 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StreamedSocket = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
|
-
const serial_json_1 = require("../serial-json");
|
|
6
5
|
const utils_1 = require("../utils");
|
|
7
6
|
const socket_type_1 = require("./socket.type");
|
|
7
|
+
const tx_encoding_1 = require("./tx-encoding");
|
|
8
|
+
const msgpack_1 = require("@msgpack/msgpack");
|
|
9
|
+
// Encode chunk metadata + data into a single Uint8Array
|
|
10
|
+
function encodeChunk(chunk) {
|
|
11
|
+
// Encode metadata using msgpack
|
|
12
|
+
const metadata = {
|
|
13
|
+
messageId: chunk.messageId,
|
|
14
|
+
chunkIndex: chunk.chunkIndex,
|
|
15
|
+
...(chunk.totalChunks !== undefined && { totalChunks: chunk.totalChunks }),
|
|
16
|
+
...(chunk.eventName !== undefined && { eventName: chunk.eventName }),
|
|
17
|
+
};
|
|
18
|
+
const metadataBytes = (0, msgpack_1.encode)(metadata);
|
|
19
|
+
const metadataLength = metadataBytes.length;
|
|
20
|
+
// Create combined buffer: [4 bytes length][metadata][data]
|
|
21
|
+
const combined = new Uint8Array(4 + metadataLength + chunk.data.length);
|
|
22
|
+
// Write metadata length as 32-bit unsigned integer (big-endian)
|
|
23
|
+
const view = new DataView(combined.buffer, combined.byteOffset, combined.byteLength);
|
|
24
|
+
view.setUint32(0, metadataLength, false); // big-endian
|
|
25
|
+
// Write metadata
|
|
26
|
+
combined.set(metadataBytes, 4);
|
|
27
|
+
// Write data
|
|
28
|
+
combined.set(chunk.data, 4 + metadataLength);
|
|
29
|
+
return combined;
|
|
30
|
+
}
|
|
31
|
+
// Decode binary chunk back into IMessageChunk
|
|
32
|
+
function decodeChunk(bytes) {
|
|
33
|
+
// Read metadata length
|
|
34
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
35
|
+
const metadataLength = view.getUint32(0, false); // big-endian
|
|
36
|
+
// Extract metadata
|
|
37
|
+
const metadataBytes = bytes.subarray(4, 4 + metadataLength);
|
|
38
|
+
const metadata = (0, msgpack_1.decode)(metadataBytes);
|
|
39
|
+
// Extract data
|
|
40
|
+
const data = bytes.subarray(4 + metadataLength);
|
|
41
|
+
return {
|
|
42
|
+
messageId: metadata.messageId,
|
|
43
|
+
chunkIndex: metadata.chunkIndex,
|
|
44
|
+
totalChunks: metadata.totalChunks,
|
|
45
|
+
eventName: metadata.eventName,
|
|
46
|
+
data: data,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
8
49
|
class StreamedSocket {
|
|
9
50
|
socket;
|
|
10
51
|
maxChunkSize;
|
|
@@ -13,7 +54,10 @@ class StreamedSocket {
|
|
|
13
54
|
constructor(socket, maxChunkSize = socket_type_1.DEFAULT_MAX_CHUNK_SIZE) {
|
|
14
55
|
this.socket = socket;
|
|
15
56
|
this.maxChunkSize = maxChunkSize;
|
|
16
|
-
socket.on(this.safeSocketChunkEventName, (
|
|
57
|
+
socket.on(this.safeSocketChunkEventName, (encodedChunk) => {
|
|
58
|
+
const chunk = decodeChunk(encodedChunk);
|
|
59
|
+
this.handleChunk(chunk);
|
|
60
|
+
});
|
|
17
61
|
}
|
|
18
62
|
get id() {
|
|
19
63
|
return this.socket.id;
|
|
@@ -26,8 +70,8 @@ class StreamedSocket {
|
|
|
26
70
|
}
|
|
27
71
|
callbacks = {};
|
|
28
72
|
emit(eventName, args, callback) {
|
|
29
|
-
const
|
|
30
|
-
// if (
|
|
73
|
+
const encoded = (0, tx_encoding_1.txEncode)(args);
|
|
74
|
+
// if (encoded.length < this.maxChunkSize) {
|
|
31
75
|
// // If the data is small enough, send it directly
|
|
32
76
|
// this.socket.emit(eventName, args, callback);
|
|
33
77
|
// return;
|
|
@@ -37,25 +81,34 @@ class StreamedSocket {
|
|
|
37
81
|
this.callbacks[messageId] = callback;
|
|
38
82
|
}
|
|
39
83
|
const chunks = [];
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
// Reserve space for metadata overhead (typically ~50-100 bytes)
|
|
85
|
+
const metadataOverhead = 128;
|
|
86
|
+
if (this.maxChunkSize < (metadataOverhead * 2)) {
|
|
87
|
+
throw new Error(`maxChunkSize ${this.maxChunkSize} is too small to accommodate metadata overhead, must be greater than ${metadataOverhead * 2}`);
|
|
88
|
+
}
|
|
89
|
+
const effectiveChunkSize = this.maxChunkSize - metadataOverhead;
|
|
90
|
+
for (let i = 0; i < encoded.length; i += effectiveChunkSize) {
|
|
91
|
+
const chunkData = encoded.subarray(i, i + effectiveChunkSize);
|
|
42
92
|
chunks.push({
|
|
43
93
|
messageId,
|
|
44
|
-
chunkIndex: Math.floor(i /
|
|
45
|
-
data: chunkData
|
|
94
|
+
chunkIndex: Math.floor(i / effectiveChunkSize),
|
|
95
|
+
data: chunkData,
|
|
46
96
|
});
|
|
47
97
|
if (i === 0) {
|
|
48
98
|
// The first chunk contains metadata about the chunks
|
|
49
99
|
chunks[0].eventName = eventName;
|
|
50
|
-
chunks[0].totalChunks = Math.ceil(
|
|
100
|
+
chunks[0].totalChunks = Math.ceil(encoded.length / effectiveChunkSize);
|
|
51
101
|
}
|
|
52
102
|
}
|
|
53
103
|
if (chunks.length > 1) {
|
|
54
104
|
console.debug(`Sending ${chunks.length} chunks for event ${eventName} with messageId ${messageId}`);
|
|
55
105
|
}
|
|
56
106
|
for (const chunk of chunks) {
|
|
107
|
+
// Encode chunk with metadata into single Uint8Array
|
|
108
|
+
const encodedChunk = encodeChunk(chunk);
|
|
57
109
|
// TODO on error or timeout retry
|
|
58
|
-
|
|
110
|
+
// TODO respond to backpressure from socket
|
|
111
|
+
this.socket.emit(this.safeSocketChunkEventName, encodedChunk, lodash_1.noop);
|
|
59
112
|
}
|
|
60
113
|
}
|
|
61
114
|
handlers = {};
|
|
@@ -98,10 +151,17 @@ class StreamedSocket {
|
|
|
98
151
|
async processChunks(chunks) {
|
|
99
152
|
try {
|
|
100
153
|
const chunkZero = chunks[0];
|
|
101
|
-
|
|
154
|
+
// Reassemble Uint8Array chunks (already decoded from binary format)
|
|
155
|
+
const totalSize = chunks.reduce((sum, chunk) => sum + chunk.data.length, 0);
|
|
156
|
+
const reassembled = new Uint8Array(totalSize);
|
|
157
|
+
let offset = 0;
|
|
158
|
+
for (const chunk of chunks) {
|
|
159
|
+
reassembled.set(chunk.data, offset);
|
|
160
|
+
offset += chunk.data.length;
|
|
161
|
+
}
|
|
102
162
|
delete this.chunkBuffers[chunkZero.messageId];
|
|
103
163
|
const eventName = chunkZero.eventName;
|
|
104
|
-
const args = (0,
|
|
164
|
+
const args = (0, tx_encoding_1.txDecode)(reassembled);
|
|
105
165
|
if (eventName === this.safeSocketResponseEventName) {
|
|
106
166
|
this.handleResponse(args.messageId, args.error, args.result);
|
|
107
167
|
return;
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const streamed_socket_1 = require("./streamed-socket");
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
+
const tx_encoding_1 = require("./tx-encoding");
|
|
6
|
+
const msgpack_1 = require("@msgpack/msgpack");
|
|
7
|
+
// Helper to decode the binary chunk format
|
|
8
|
+
function decodeChunk(bytes) {
|
|
9
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset);
|
|
10
|
+
const metadataLength = view.getUint32(0, false);
|
|
11
|
+
const metadataBytes = bytes.subarray(4, 4 + metadataLength);
|
|
12
|
+
const metadata = (0, msgpack_1.decode)(metadataBytes);
|
|
13
|
+
const data = bytes.subarray(4 + metadataLength);
|
|
14
|
+
return { ...metadata, data };
|
|
15
|
+
}
|
|
5
16
|
function createMockSocket() {
|
|
6
17
|
const emittedEvents = [];
|
|
7
18
|
const socket = {
|
|
@@ -35,10 +46,171 @@ describe('StreamedSocket', () => {
|
|
|
35
46
|
expect(emittedEvents).toHaveLength(1);
|
|
36
47
|
const chunkEvent = emittedEvents[0];
|
|
37
48
|
expect(chunkEvent.eventName).toBe('__streamed-socket-chunk');
|
|
38
|
-
|
|
39
|
-
expect(chunkEvent.args
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(
|
|
49
|
+
// Args should be a Uint8Array (binary encoded chunk)
|
|
50
|
+
expect(chunkEvent.args).toBeInstanceOf(Uint8Array);
|
|
51
|
+
// Decode the binary chunk
|
|
52
|
+
const decodedChunk = decodeChunk(chunkEvent.args);
|
|
53
|
+
expect(decodedChunk.messageId).toBeTruthy();
|
|
54
|
+
expect(decodedChunk.chunkIndex).toBe(0);
|
|
55
|
+
expect(decodedChunk.totalChunks).toBe(1);
|
|
56
|
+
expect(decodedChunk.eventName).toBe('test-event');
|
|
57
|
+
// Decode the actual data
|
|
58
|
+
const decoded = (0, tx_encoding_1.txDecode)(decodedChunk.data);
|
|
59
|
+
expect(decoded).toEqual(testData);
|
|
60
|
+
});
|
|
61
|
+
it('should emit multiple chunks for large messages', () => {
|
|
62
|
+
const mock = createMockSocket();
|
|
63
|
+
const streamedSocket = new streamed_socket_1.StreamedSocket(mock.socket, 256); // Small chunk size to force chunking
|
|
64
|
+
// Create a large test object
|
|
65
|
+
const testData = {
|
|
66
|
+
message: 'x'.repeat(200),
|
|
67
|
+
array: [1, 2, 3, 4, 5],
|
|
68
|
+
nested: { deep: 'value' }
|
|
69
|
+
};
|
|
70
|
+
streamedSocket.emit('test-event', testData, () => { });
|
|
71
|
+
const emittedEvents = mock.getEmittedEvents();
|
|
72
|
+
expect(emittedEvents.length).toBeGreaterThan(1);
|
|
73
|
+
// Decode all chunks
|
|
74
|
+
const decodedChunks = emittedEvents.map(event => {
|
|
75
|
+
expect(event.args).toBeInstanceOf(Uint8Array);
|
|
76
|
+
return decodeChunk(event.args);
|
|
77
|
+
});
|
|
78
|
+
// Verify first chunk has metadata
|
|
79
|
+
const firstChunk = decodedChunks[0];
|
|
80
|
+
expect(firstChunk.eventName).toBe('test-event');
|
|
81
|
+
expect(firstChunk.totalChunks).toBe(emittedEvents.length);
|
|
82
|
+
expect(firstChunk.chunkIndex).toBe(0);
|
|
83
|
+
// Verify all chunks have the same messageId
|
|
84
|
+
const messageId = firstChunk.messageId;
|
|
85
|
+
decodedChunks.forEach(chunk => {
|
|
86
|
+
expect(chunk.messageId).toBe(messageId);
|
|
87
|
+
});
|
|
88
|
+
// Verify chunks are in order
|
|
89
|
+
decodedChunks.forEach((chunk, index) => {
|
|
90
|
+
expect(chunk.chunkIndex).toBe(index);
|
|
91
|
+
});
|
|
92
|
+
// Verify all chunk data are Uint8Arrays
|
|
93
|
+
decodedChunks.forEach(chunk => {
|
|
94
|
+
expect(chunk.data).toBeInstanceOf(Uint8Array);
|
|
95
|
+
});
|
|
96
|
+
// Reassemble chunks and verify data integrity
|
|
97
|
+
const reassembled = new Uint8Array(decodedChunks.reduce((sum, chunk) => sum + chunk.data.length, 0));
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const chunk of decodedChunks) {
|
|
100
|
+
reassembled.set(chunk.data, offset);
|
|
101
|
+
offset += chunk.data.length;
|
|
102
|
+
}
|
|
103
|
+
const decoded = (0, tx_encoding_1.txDecode)(reassembled);
|
|
104
|
+
expect(decoded).toEqual(testData);
|
|
105
|
+
});
|
|
106
|
+
it('should handle binary data in messages', () => {
|
|
107
|
+
const mock = createMockSocket();
|
|
108
|
+
const streamedSocket = new streamed_socket_1.StreamedSocket(mock.socket, 1000);
|
|
109
|
+
const testData = {
|
|
110
|
+
binaryData: new Uint8Array([1, 2, 3, 4, 5]),
|
|
111
|
+
metadata: { name: 'test' }
|
|
112
|
+
};
|
|
113
|
+
streamedSocket.emit('test-event', testData, () => { });
|
|
114
|
+
const emittedEvents = mock.getEmittedEvents();
|
|
115
|
+
expect(emittedEvents).toHaveLength(1);
|
|
116
|
+
const decodedChunk = decodeChunk(emittedEvents[0].args);
|
|
117
|
+
const decoded = (0, tx_encoding_1.txDecode)(decodedChunk.data);
|
|
118
|
+
expect(decoded.binaryData).toBeInstanceOf(Uint8Array);
|
|
119
|
+
expect(decoded.binaryData).toEqual(testData.binaryData);
|
|
120
|
+
expect(decoded.metadata).toEqual(testData.metadata);
|
|
121
|
+
});
|
|
122
|
+
it('should handle special types in messages', () => {
|
|
123
|
+
const mock = createMockSocket();
|
|
124
|
+
const streamedSocket = new streamed_socket_1.StreamedSocket(mock.socket, 1000);
|
|
125
|
+
const testData = {
|
|
126
|
+
date: new Date('2023-01-01T00:00:00.000Z'),
|
|
127
|
+
nan: NaN,
|
|
128
|
+
infinity: Infinity,
|
|
129
|
+
undef: undefined,
|
|
130
|
+
nullVal: null
|
|
131
|
+
};
|
|
132
|
+
streamedSocket.emit('test-event', testData, () => { });
|
|
133
|
+
const emittedEvents = mock.getEmittedEvents();
|
|
134
|
+
expect(emittedEvents).toHaveLength(1);
|
|
135
|
+
const decodedChunk = decodeChunk(emittedEvents[0].args);
|
|
136
|
+
const decoded = (0, tx_encoding_1.txDecode)(decodedChunk.data);
|
|
137
|
+
expect(decoded.date).toBeInstanceOf(Date);
|
|
138
|
+
expect(decoded.date.toISOString()).toBe('2023-01-01T00:00:00.000Z');
|
|
139
|
+
expect(Number.isNaN(decoded.nan)).toBe(true);
|
|
140
|
+
expect(decoded.infinity).toBe(Infinity);
|
|
141
|
+
expect(decoded.undef).toBeUndefined();
|
|
142
|
+
expect(decoded.nullVal).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
it('should demonstrate msgpack efficiency: structured data vs JSON string (uncompressed)', () => {
|
|
145
|
+
const mock1 = createMockSocket();
|
|
146
|
+
const mock2 = createMockSocket();
|
|
147
|
+
const streamedSocket1 = new streamed_socket_1.StreamedSocket(mock1.socket, 1000000); // Large chunk to avoid splitting
|
|
148
|
+
const streamedSocket2 = new streamed_socket_1.StreamedSocket(mock2.socket, 1000000);
|
|
149
|
+
// Create a small structured object that won't trigger compression (< 1024 bytes)
|
|
150
|
+
const smallObject = {
|
|
151
|
+
users: Array.from({ length: 5 }, (_, i) => ({
|
|
152
|
+
id: i + 1000000, // Use larger numbers to make JSON less compressible
|
|
153
|
+
name: `User${i}_${Math.random().toString(36).substring(7)}`, // Add randomness
|
|
154
|
+
email: `user${i}@example.com`,
|
|
155
|
+
score: Math.random() * 1000,
|
|
156
|
+
active: i % 2 === 0,
|
|
157
|
+
}))
|
|
158
|
+
};
|
|
159
|
+
// Convert to JSON string
|
|
160
|
+
const jsonString = JSON.stringify(smallObject);
|
|
161
|
+
console.log(`Original JSON string size: ${jsonString.length} bytes`);
|
|
162
|
+
// Send as structured object (msgpack can optimize)
|
|
163
|
+
streamedSocket1.emit('test-object', smallObject, () => { });
|
|
164
|
+
// Send as JSON string (msgpack treats it as opaque string)
|
|
165
|
+
streamedSocket2.emit('test-string', { jsonPayload: jsonString }, () => { });
|
|
166
|
+
const objectEvents = mock1.getEmittedEvents();
|
|
167
|
+
const stringEvents = mock2.getEmittedEvents();
|
|
168
|
+
expect(objectEvents).toHaveLength(1);
|
|
169
|
+
expect(stringEvents).toHaveLength(1);
|
|
170
|
+
// Decode and measure sizes
|
|
171
|
+
const objectChunk = decodeChunk(objectEvents[0].args);
|
|
172
|
+
const stringChunk = decodeChunk(stringEvents[0].args);
|
|
173
|
+
const objectSize = objectChunk.data.length;
|
|
174
|
+
const stringSize = stringChunk.data.length;
|
|
175
|
+
console.log(`Msgpack with structured data: ${objectSize} bytes (${((1 - objectSize / jsonString.length) * 100).toFixed(1)}% reduction from JSON)`);
|
|
176
|
+
console.log(`Msgpack with JSON string: ${stringSize} bytes (${((1 - stringSize / jsonString.length) * 100).toFixed(1)}% reduction from JSON)`);
|
|
177
|
+
console.log(`Efficiency gain: structured data is ${((1 - objectSize / stringSize) * 100).toFixed(1)}% smaller than JSON string`);
|
|
178
|
+
// Msgpack should be more efficient with structured data than with a JSON string
|
|
179
|
+
expect(objectSize).toBeLessThan(stringSize);
|
|
180
|
+
// Structured data should be at least 10% more efficient
|
|
181
|
+
const efficiency = (1 - objectSize / stringSize) * 100;
|
|
182
|
+
expect(efficiency).toBeGreaterThan(10);
|
|
183
|
+
// Verify data integrity
|
|
184
|
+
const decodedObject = (0, tx_encoding_1.txDecode)(objectChunk.data);
|
|
185
|
+
const decodedString = (0, tx_encoding_1.txDecode)(stringChunk.data);
|
|
186
|
+
expect(decodedObject.users).toHaveLength(5);
|
|
187
|
+
expect(decodedString.jsonPayload).toBe(jsonString);
|
|
188
|
+
});
|
|
189
|
+
it('should demonstrate compression effectiveness for large repetitive data', () => {
|
|
190
|
+
const mock = createMockSocket();
|
|
191
|
+
const streamedSocket = new streamed_socket_1.StreamedSocket(mock.socket, 1000000);
|
|
192
|
+
// Create large repetitive object that will compress well
|
|
193
|
+
const largeObject = {
|
|
194
|
+
data: Array.from({ length: 200 }, () => ({
|
|
195
|
+
type: 'event',
|
|
196
|
+
status: 'active',
|
|
197
|
+
category: 'user_action',
|
|
198
|
+
timestamp: new Date('2024-01-01'),
|
|
199
|
+
metadata: { app: 'test', version: '1.0.0' }
|
|
200
|
+
}))
|
|
201
|
+
};
|
|
202
|
+
const jsonString = JSON.stringify(largeObject);
|
|
203
|
+
console.log(`Large repetitive data - JSON size: ${jsonString.length} bytes`);
|
|
204
|
+
streamedSocket.emit('test-compressed', largeObject, () => { });
|
|
205
|
+
const events = mock.getEmittedEvents();
|
|
206
|
+
const chunk = decodeChunk(events[0].args);
|
|
207
|
+
console.log(`After msgpack + compression: ${chunk.data.length} bytes`);
|
|
208
|
+
console.log(`Compression ratio: ${((1 - chunk.data.length / jsonString.length) * 100).toFixed(1)}% reduction`);
|
|
209
|
+
// Should achieve significant compression (>80% reduction)
|
|
210
|
+
expect(chunk.data.length).toBeLessThan(jsonString.length * 0.2);
|
|
211
|
+
// Verify data integrity
|
|
212
|
+
const decoded = (0, tx_encoding_1.txDecode)(chunk.data);
|
|
213
|
+
expect(decoded.data).toHaveLength(200);
|
|
214
|
+
expect(decoded.data[0].type).toBe('event');
|
|
43
215
|
});
|
|
44
216
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.txEncode = txEncode;
|
|
4
|
+
exports.txDecode = txDecode;
|
|
5
|
+
const msgpack_1 = require("@msgpack/msgpack");
|
|
6
|
+
const fflate_1 = require("fflate");
|
|
7
|
+
const serial_json_1 = require("../serial-json");
|
|
8
|
+
function txEncode(data) {
|
|
9
|
+
const noCycles = (0, serial_json_1.toJSON)(data); // remove cycles and encode dates, etc.
|
|
10
|
+
// First encode to msgpack
|
|
11
|
+
const encoded = (0, msgpack_1.encode)(noCycles);
|
|
12
|
+
let body;
|
|
13
|
+
let flag;
|
|
14
|
+
if (encoded.length > 1024) {
|
|
15
|
+
// Compress big payloads
|
|
16
|
+
body = (0, fflate_1.compressSync)(encoded);
|
|
17
|
+
flag = 1; // compressed
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
body = encoded;
|
|
21
|
+
flag = 0; // not compressed
|
|
22
|
+
}
|
|
23
|
+
const out = new Uint8Array(1 + body.length);
|
|
24
|
+
out[0] = flag;
|
|
25
|
+
out.set(body, 1);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function txDecode(data) {
|
|
29
|
+
if (data.length === 0) {
|
|
30
|
+
throw new Error("Empty payload");
|
|
31
|
+
}
|
|
32
|
+
const flag = data[0];
|
|
33
|
+
let body = data.subarray(1);
|
|
34
|
+
if (flag === 1) {
|
|
35
|
+
body = (0, fflate_1.decompressSync)(body);
|
|
36
|
+
}
|
|
37
|
+
else if (flag !== 0) {
|
|
38
|
+
// Optional: future versioning or legacy handling
|
|
39
|
+
throw new Error(`Unknown tx flag: ${flag}`);
|
|
40
|
+
}
|
|
41
|
+
const unpacked = (0, msgpack_1.decode)(body);
|
|
42
|
+
const restored = (0, serial_json_1.fromJSON)(unpacked); // restore cycles, dates, etc.
|
|
43
|
+
return restored;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tx_encoding_1 = require("./tx-encoding");
|
|
4
|
+
describe('tx-encode-decode', () => {
|
|
5
|
+
describe('txEncode and txDecode', () => {
|
|
6
|
+
it('should encode and decode simple objects', () => {
|
|
7
|
+
const testObj = {
|
|
8
|
+
name: 'test',
|
|
9
|
+
number: 42,
|
|
10
|
+
bool: true,
|
|
11
|
+
str: 'hello world'
|
|
12
|
+
};
|
|
13
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
14
|
+
expect(encoded).toBeInstanceOf(Uint8Array);
|
|
15
|
+
expect(encoded.length).toBeGreaterThan(0);
|
|
16
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
17
|
+
expect(decoded).toEqual(testObj);
|
|
18
|
+
});
|
|
19
|
+
it('should encode and decode arrays', () => {
|
|
20
|
+
const testArray = [1, 2, 3, 'four', { five: 5 }];
|
|
21
|
+
const encoded = (0, tx_encoding_1.txEncode)(testArray);
|
|
22
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
23
|
+
expect(decoded).toEqual(testArray);
|
|
24
|
+
});
|
|
25
|
+
it('should encode and decode nested objects', () => {
|
|
26
|
+
const testObj = {
|
|
27
|
+
level1: {
|
|
28
|
+
level2: {
|
|
29
|
+
level3: {
|
|
30
|
+
value: 'deep nested value',
|
|
31
|
+
number: 123
|
|
32
|
+
},
|
|
33
|
+
array: [1, 2, 3]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
38
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
39
|
+
expect(decoded).toEqual(testObj);
|
|
40
|
+
});
|
|
41
|
+
it('should encode and decode special types (dates, etc.)', () => {
|
|
42
|
+
const testObj = {
|
|
43
|
+
date: new Date('2023-01-01T00:00:00.000Z'),
|
|
44
|
+
nan: NaN,
|
|
45
|
+
infinity: Infinity,
|
|
46
|
+
negInfinity: -Infinity,
|
|
47
|
+
undef: undefined,
|
|
48
|
+
nullVal: null
|
|
49
|
+
};
|
|
50
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
51
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
52
|
+
expect(decoded.date).toBeInstanceOf(Date);
|
|
53
|
+
expect(decoded.date.toISOString()).toBe('2023-01-01T00:00:00.000Z');
|
|
54
|
+
expect(Number.isNaN(decoded.nan)).toBe(true);
|
|
55
|
+
expect(decoded.infinity).toBe(Infinity);
|
|
56
|
+
expect(decoded.negInfinity).toBe(-Infinity);
|
|
57
|
+
expect(decoded.undef).toBeUndefined();
|
|
58
|
+
expect(decoded.nullVal).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
it('should encode and decode Uint8Array', () => {
|
|
61
|
+
const testObj = {
|
|
62
|
+
data: new Uint8Array([72, 101, 108, 108, 111]),
|
|
63
|
+
metadata: { size: 5 }
|
|
64
|
+
};
|
|
65
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
66
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
67
|
+
expect(decoded.data).toBeInstanceOf(Uint8Array);
|
|
68
|
+
expect(decoded.data).toEqual(testObj.data);
|
|
69
|
+
expect(decoded.metadata).toEqual(testObj.metadata);
|
|
70
|
+
});
|
|
71
|
+
it('should encode and decode Buffer', () => {
|
|
72
|
+
const testObj = {
|
|
73
|
+
buffer: Buffer.from('Hello Buffer'),
|
|
74
|
+
name: 'test'
|
|
75
|
+
};
|
|
76
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
77
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
78
|
+
expect(Buffer.isBuffer(decoded.buffer)).toBe(true);
|
|
79
|
+
expect(decoded.buffer).toEqual(testObj.buffer);
|
|
80
|
+
expect(decoded.name).toBe('test');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('compression threshold', () => {
|
|
84
|
+
it('should not compress small payloads (< 1024 bytes)', () => {
|
|
85
|
+
const smallObj = { message: 'small' };
|
|
86
|
+
const encoded = (0, tx_encoding_1.txEncode)(smallObj);
|
|
87
|
+
// First byte is the flag
|
|
88
|
+
expect(encoded[0]).toBe(0); // flag = 0 means not compressed
|
|
89
|
+
});
|
|
90
|
+
it('should compress large payloads (> 1024 bytes)', () => {
|
|
91
|
+
// Create an object that will exceed 1024 bytes when encoded
|
|
92
|
+
const largeObj = {
|
|
93
|
+
data: 'x'.repeat(2000),
|
|
94
|
+
array: Array.from({ length: 100 }, (_, i) => ({ id: i, value: `item ${i}` }))
|
|
95
|
+
};
|
|
96
|
+
const jsonStr = JSON.stringify(largeObj);
|
|
97
|
+
expect(jsonStr.length).toBeGreaterThan(1024);
|
|
98
|
+
const encoded = (0, tx_encoding_1.txEncode)(largeObj);
|
|
99
|
+
// First byte is the flag
|
|
100
|
+
expect(encoded[0]).toBe(1); // flag = 1 means compressed
|
|
101
|
+
// Verify it can still be decoded correctly
|
|
102
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
103
|
+
expect(decoded).toEqual(largeObj);
|
|
104
|
+
});
|
|
105
|
+
it('should have smaller encoded size for large compressible data', () => {
|
|
106
|
+
const largeRepetitiveObj = {
|
|
107
|
+
data: 'repeat '.repeat(500),
|
|
108
|
+
values: Array.from({ length: 200 }, () => 'same string')
|
|
109
|
+
};
|
|
110
|
+
const encoded = (0, tx_encoding_1.txEncode)(largeRepetitiveObj);
|
|
111
|
+
// Compressed size should be significantly smaller than the raw data
|
|
112
|
+
const rawDataSize = JSON.stringify(largeRepetitiveObj).length;
|
|
113
|
+
expect(encoded.length).toBeLessThan(rawDataSize / 2);
|
|
114
|
+
// Verify correctness
|
|
115
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
116
|
+
expect(decoded).toEqual(largeRepetitiveObj);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe('error handling', () => {
|
|
120
|
+
it('should throw error on empty payload', () => {
|
|
121
|
+
const emptyPayload = new Uint8Array(0);
|
|
122
|
+
expect(() => (0, tx_encoding_1.txDecode)(emptyPayload)).toThrow('Empty payload');
|
|
123
|
+
});
|
|
124
|
+
it('should throw error on unknown flag', () => {
|
|
125
|
+
// Create a payload with an invalid flag (2)
|
|
126
|
+
const invalidPayload = new Uint8Array([2, 0, 0, 0]);
|
|
127
|
+
expect(() => (0, tx_encoding_1.txDecode)(invalidPayload)).toThrow('Unknown tx flag: 2');
|
|
128
|
+
});
|
|
129
|
+
it('should throw error on unknown flag (255)', () => {
|
|
130
|
+
const invalidPayload = new Uint8Array([255, 0, 0, 0]);
|
|
131
|
+
expect(() => (0, tx_encoding_1.txDecode)(invalidPayload)).toThrow('Unknown tx flag: 255');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('round-trip consistency', () => {
|
|
135
|
+
it('should handle empty objects', () => {
|
|
136
|
+
const emptyObj = {};
|
|
137
|
+
const encoded = (0, tx_encoding_1.txEncode)(emptyObj);
|
|
138
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
139
|
+
expect(decoded).toEqual(emptyObj);
|
|
140
|
+
});
|
|
141
|
+
it('should handle empty arrays', () => {
|
|
142
|
+
const emptyArray = [];
|
|
143
|
+
const encoded = (0, tx_encoding_1.txEncode)(emptyArray);
|
|
144
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
145
|
+
expect(decoded).toEqual(emptyArray);
|
|
146
|
+
});
|
|
147
|
+
it('should handle strings with special characters', () => {
|
|
148
|
+
const testObj = {
|
|
149
|
+
emoji: '🚀🎉',
|
|
150
|
+
unicode: 'Hello 世界',
|
|
151
|
+
special: 'line1\nline2\ttab',
|
|
152
|
+
quotes: 'He said "hello"'
|
|
153
|
+
};
|
|
154
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
155
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
156
|
+
expect(decoded).toEqual(testObj);
|
|
157
|
+
});
|
|
158
|
+
it('should handle very large numbers', () => {
|
|
159
|
+
const testObj = {
|
|
160
|
+
maxSafeInt: Number.MAX_SAFE_INTEGER,
|
|
161
|
+
minSafeInt: Number.MIN_SAFE_INTEGER,
|
|
162
|
+
large: 9007199254740991,
|
|
163
|
+
small: -9007199254740991
|
|
164
|
+
};
|
|
165
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
166
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
167
|
+
expect(decoded).toEqual(testObj);
|
|
168
|
+
});
|
|
169
|
+
it('should handle mixed type arrays', () => {
|
|
170
|
+
const testArray = [
|
|
171
|
+
1,
|
|
172
|
+
'string',
|
|
173
|
+
true,
|
|
174
|
+
null,
|
|
175
|
+
undefined,
|
|
176
|
+
{ obj: 'value' },
|
|
177
|
+
[1, 2, 3],
|
|
178
|
+
new Date('2023-01-01'),
|
|
179
|
+
new Uint8Array([1, 2, 3])
|
|
180
|
+
];
|
|
181
|
+
const encoded = (0, tx_encoding_1.txEncode)(testArray);
|
|
182
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
183
|
+
expect(decoded[0]).toBe(1);
|
|
184
|
+
expect(decoded[1]).toBe('string');
|
|
185
|
+
expect(decoded[2]).toBe(true);
|
|
186
|
+
expect(decoded[3]).toBeNull();
|
|
187
|
+
expect(decoded[4]).toBeUndefined();
|
|
188
|
+
expect(decoded[5]).toEqual({ obj: 'value' });
|
|
189
|
+
expect(decoded[6]).toEqual([1, 2, 3]);
|
|
190
|
+
expect(decoded[7]).toBeInstanceOf(Date);
|
|
191
|
+
expect(decoded[8]).toBeInstanceOf(Uint8Array);
|
|
192
|
+
expect(decoded[8]).toEqual(new Uint8Array([1, 2, 3]));
|
|
193
|
+
});
|
|
194
|
+
it('should handle objects with circular references removed by toJSON', () => {
|
|
195
|
+
// toJSON handles circular references, so this should work
|
|
196
|
+
const obj = { name: 'test' };
|
|
197
|
+
obj.self = obj; // circular reference
|
|
198
|
+
const encoded = (0, tx_encoding_1.txEncode)(obj);
|
|
199
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
200
|
+
// The circular reference should be handled by toJSON/fromJSON
|
|
201
|
+
expect(decoded).toHaveProperty('name', 'test');
|
|
202
|
+
expect(decoded).toHaveProperty('self');
|
|
203
|
+
expect(decoded.self).toBe(decoded); // self-reference restored
|
|
204
|
+
});
|
|
205
|
+
it('should preserve data integrity across multiple encode/decode cycles', () => {
|
|
206
|
+
const original = {
|
|
207
|
+
id: 123,
|
|
208
|
+
name: 'multi-cycle test',
|
|
209
|
+
data: new Uint8Array([1, 2, 3, 4, 5]),
|
|
210
|
+
timestamp: new Date('2023-06-15T12:00:00.000Z')
|
|
211
|
+
};
|
|
212
|
+
// Encode and decode multiple times
|
|
213
|
+
let current = original;
|
|
214
|
+
for (let i = 0; i < 5; i++) {
|
|
215
|
+
const encoded = (0, tx_encoding_1.txEncode)(current);
|
|
216
|
+
current = (0, tx_encoding_1.txDecode)(encoded);
|
|
217
|
+
}
|
|
218
|
+
expect(current.id).toBe(original.id);
|
|
219
|
+
expect(current.name).toBe(original.name);
|
|
220
|
+
expect(current.data).toEqual(original.data);
|
|
221
|
+
expect(current.timestamp).toEqual(original.timestamp);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
describe('payload structure', () => {
|
|
225
|
+
it('should have flag byte as first byte', () => {
|
|
226
|
+
const testObj = { test: 'value' };
|
|
227
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
228
|
+
// First byte should be 0 or 1
|
|
229
|
+
expect(encoded[0]).toBeGreaterThanOrEqual(0);
|
|
230
|
+
expect(encoded[0]).toBeLessThanOrEqual(1);
|
|
231
|
+
});
|
|
232
|
+
it('should have body after flag byte', () => {
|
|
233
|
+
const testObj = { test: 'value' };
|
|
234
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
235
|
+
// Should have at least 2 bytes (flag + body)
|
|
236
|
+
expect(encoded.length).toBeGreaterThan(1);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('edge cases', () => {
|
|
240
|
+
it('should handle objects at exactly 1024 bytes threshold', () => {
|
|
241
|
+
// Create an object that when msgpack-encoded is very close to 1024 bytes
|
|
242
|
+
// This is tricky to get exact, so we just verify it works
|
|
243
|
+
const testObj = {
|
|
244
|
+
data: 'x'.repeat(1000)
|
|
245
|
+
};
|
|
246
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
247
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
248
|
+
expect(decoded).toEqual(testObj);
|
|
249
|
+
});
|
|
250
|
+
it('should handle primitive values', () => {
|
|
251
|
+
expect((0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)(42))).toBe(42);
|
|
252
|
+
expect((0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)('string'))).toBe('string');
|
|
253
|
+
expect((0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)(true))).toBe(true);
|
|
254
|
+
expect((0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)(false))).toBe(false);
|
|
255
|
+
expect((0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)(null))).toBe(null);
|
|
256
|
+
});
|
|
257
|
+
it('should handle objects with only undefined values', () => {
|
|
258
|
+
const testObj = {
|
|
259
|
+
a: undefined,
|
|
260
|
+
b: undefined
|
|
261
|
+
};
|
|
262
|
+
const encoded = (0, tx_encoding_1.txEncode)(testObj);
|
|
263
|
+
const decoded = (0, tx_encoding_1.txDecode)(encoded);
|
|
264
|
+
expect(decoded).toEqual(testObj);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./device/device";
|
|
|
17
17
|
export * from "./device/device-election";
|
|
18
18
|
export * from "./device/get-trust-level";
|
|
19
19
|
export * from "./device/socket.type";
|
|
20
|
+
export * from "./device/tx-encoding";
|
|
20
21
|
export * from "./package-loader";
|
|
21
22
|
export * from "./events";
|
|
22
23
|
export * from "./data";
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __exportStar(require("./device/device"), exports);
|
|
|
34
34
|
__exportStar(require("./device/device-election"), exports);
|
|
35
35
|
__exportStar(require("./device/get-trust-level"), exports);
|
|
36
36
|
__exportStar(require("./device/socket.type"), exports);
|
|
37
|
+
__exportStar(require("./device/tx-encoding"), exports);
|
|
37
38
|
__exportStar(require("./package-loader"), exports);
|
|
38
39
|
__exportStar(require("./events"), exports);
|
|
39
40
|
__exportStar(require("./data"), exports);
|
package/dist/keys.js
CHANGED
|
@@ -22,12 +22,13 @@ exports.addSignatureToObject = addSignatureToObject;
|
|
|
22
22
|
exports.verifyObjectSignature = verifyObjectSignature;
|
|
23
23
|
exports.isObjectSignatureValid = isObjectSignatureValid;
|
|
24
24
|
exports.getPublicKeyFromObjectSignature = getPublicKeyFromObjectSignature;
|
|
25
|
+
const sha2_1 = require("@noble/hashes/sha2");
|
|
26
|
+
const buffer_1 = require("buffer");
|
|
25
27
|
const nacl = require("tweetnacl");
|
|
26
28
|
const utils = require("tweetnacl-util");
|
|
27
29
|
const tweetnacl_util_1 = require("tweetnacl-util");
|
|
30
|
+
const tx_encoding_1 = require("./device/tx-encoding");
|
|
28
31
|
const serial_json_1 = require("./serial-json");
|
|
29
|
-
const sha2_1 = require("@noble/hashes/sha2");
|
|
30
|
-
const buffer_1 = require("buffer");
|
|
31
32
|
globalThis.Buffer = buffer_1.Buffer; // shim for browsers/RN
|
|
32
33
|
const _stableStringify = require('fast-json-stable-stringify');
|
|
33
34
|
function setPRNG(fn) {
|
|
@@ -114,10 +115,10 @@ function signObjectWithSecretKey(obj, secretKey) {
|
|
|
114
115
|
}
|
|
115
116
|
const objStr = (0, serial_json_1.toJSONString)(obj);
|
|
116
117
|
const objDecoded = (0, tweetnacl_util_1.decodeUTF8)(objStr);
|
|
117
|
-
const
|
|
118
|
+
const signatureByteArray = nacl.sign.detached(objDecoded, _secretKey);
|
|
118
119
|
return {
|
|
119
120
|
contents: obj,
|
|
120
|
-
signature: encodeBase64(
|
|
121
|
+
signature: encodeBase64(signatureByteArray),
|
|
121
122
|
publicKey: encodeBase64(_secretKey.slice(32)),
|
|
122
123
|
};
|
|
123
124
|
}
|
|
@@ -146,9 +147,8 @@ function boxDataWithKeys(data, toPublicBoxKey, mySecretKey) {
|
|
|
146
147
|
const boxKeyPair = nacl.box.keyPair.fromSecretKey(_secretKey.slice(0, 32));
|
|
147
148
|
const _toPublicBoxKey = decodeBase64(toPublicBoxKey);
|
|
148
149
|
const nonce = nacl.randomBytes(24);
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const dataBoxed = nacl.box(dataDecoded, nonce, _toPublicBoxKey, boxKeyPair.secretKey);
|
|
150
|
+
const dataByteArray = (0, tx_encoding_1.txEncode)(data);
|
|
151
|
+
const dataBoxed = nacl.box(dataByteArray, nonce, _toPublicBoxKey, boxKeyPair.secretKey);
|
|
152
152
|
return {
|
|
153
153
|
contents: encodeBase64(dataBoxed),
|
|
154
154
|
nonce: encodeBase64(nonce),
|
|
@@ -161,12 +161,11 @@ function openBoxWithSecretKey(box, mySecretKey) {
|
|
|
161
161
|
const boxedData = decodeBase64(box.contents);
|
|
162
162
|
const nonce = decodeBase64(box.nonce);
|
|
163
163
|
const _fromPublicBoxKey = decodeBase64(box.fromPublicKey);
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
164
|
+
const dataByteArray = nacl.box.open(boxedData, nonce, _fromPublicBoxKey, boxKeyPair.secretKey);
|
|
165
|
+
if (dataByteArray === null) {
|
|
166
166
|
throw new Error('Message was null or verification failed');
|
|
167
167
|
}
|
|
168
|
-
|
|
169
|
-
return (0, serial_json_1.fromJSONString)(dataStr);
|
|
168
|
+
return (0, tx_encoding_1.txDecode)(dataByteArray);
|
|
170
169
|
}
|
|
171
170
|
function encryptString(data, secretKey) {
|
|
172
171
|
let _secretKey = decodeBase64(secretKey);
|
|
@@ -13,9 +13,9 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
13
13
|
stackTrace: z.ZodOptional<z.ZodString>;
|
|
14
14
|
}, "strip", z.ZodTypeAny, {
|
|
15
15
|
message: string;
|
|
16
|
+
level: "error" | "debug" | "info" | "log" | "warn";
|
|
16
17
|
timestamp: number;
|
|
17
18
|
logId: string;
|
|
18
|
-
level: "error" | "debug" | "info" | "log" | "warn";
|
|
19
19
|
process: string;
|
|
20
20
|
processInstanceId: string;
|
|
21
21
|
source?: string | undefined;
|
|
@@ -23,8 +23,8 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
23
23
|
stackTrace?: string | undefined;
|
|
24
24
|
}, {
|
|
25
25
|
message: string;
|
|
26
|
-
logId: string;
|
|
27
26
|
level: "error" | "debug" | "info" | "log" | "warn";
|
|
27
|
+
logId: string;
|
|
28
28
|
process: string;
|
|
29
29
|
processInstanceId: string;
|
|
30
30
|
source?: string | undefined;
|
|
@@ -9,13 +9,13 @@ export interface IPeerDevice {
|
|
|
9
9
|
notifyOfChanges(deviceId: string, timestampLastApplied: number): Promise<void>;
|
|
10
10
|
sendDeviceMessage(message: IDeviceMessage): Promise<any>;
|
|
11
11
|
}
|
|
12
|
-
export interface IDeviceMessage {
|
|
12
|
+
export interface IDeviceMessage<T = any> {
|
|
13
13
|
deviceMessageId: string;
|
|
14
14
|
fromDeviceId: string;
|
|
15
15
|
toDeviceId: string;
|
|
16
16
|
dataContextId: string;
|
|
17
17
|
ttl: number;
|
|
18
|
-
payload:
|
|
18
|
+
payload: T;
|
|
19
19
|
hops: string[];
|
|
20
20
|
suggestedPath?: string[];
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.20",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-sdk.git"
|
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
"release": "yarn release:patch"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@msgpack/msgpack": "^3.1.2",
|
|
33
34
|
"@noble/hashes": "^1.8.0",
|
|
34
35
|
"fast-json-stable-stringify": "^2.1.0",
|
|
36
|
+
"fflate": "^0.8.2",
|
|
35
37
|
"lodash": "^4.17.21",
|
|
36
38
|
"moment": "^2.30.1",
|
|
37
39
|
"moment-timezone": "^0.5.46",
|