@peers-app/peers-sdk 0.8.8 → 0.8.10

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.
@@ -132,36 +132,80 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
132
132
  const transportType = protocol === 'wrtc' ? 'wrtc' :
133
133
  protocol === 'libp2p' ? 'libp2p' :
134
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);
135
+ // WebRTC SCTP has ~256KB max message size, use 64KB to be safe
136
+ const WRTC_MAX_CHUNK = 64 * 1024;
137
+ // Backpressure thresholds
138
+ const HIGH_WATER_MARK = transportType === 'wrtc' ? 64 * 1024 : 4 * 1024 * 1024;
139
+ const LOW_WATER_MARK = transportType === 'wrtc' ? 16 * 1024 : 2 * 1024 * 1024;
140
+ // Helper to wait for buffer to drain
141
+ const waitForDrain = async () => {
142
+ if (!peer.getBufferedAmount)
143
+ return;
144
+ const buffered = peer.getBufferedAmount();
145
+ if (buffered <= HIGH_WATER_MARK)
146
+ return;
147
+ await new Promise(resolve => {
148
+ const checkDrain = () => {
149
+ const currentBuffered = peer.getBufferedAmount ? peer.getBufferedAmount() : 0;
150
+ if (!peer.getBufferedAmount || currentBuffered < LOW_WATER_MARK) {
151
+ resolve();
152
+ }
153
+ else {
154
+ setTimeout(checkDrain, 5);
155
+ }
156
+ };
157
+ peer.on('drain', resolve);
158
+ checkDrain();
159
+ });
160
+ };
161
+ // Helper to send a single raw bytes packet
162
+ const sendRawBytesPacket = async (streamIdBytes, payload) => {
163
+ const packet = new Uint8Array(1 + 2 + streamIdBytes.length + payload.length);
139
164
  packet[0] = PROTOCOL_RAW_BYTES;
140
165
  const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
141
166
  view.setUint16(1, streamIdBytes.length, false);
142
167
  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
- }
168
+ packet.set(payload, 3 + streamIdBytes.length);
169
+ await waitForDrain();
161
170
  stats.bytesSent += packet.length;
162
171
  stats.messagesSent++;
163
172
  peer.send(packet);
164
173
  };
174
+ // Core sendRawBytes implementation
175
+ // For WebRTC: chunks large data to fit under SCTP message size limit
176
+ // For RPC_STREAM_ID: also prepends 4-byte length for reassembly
177
+ const sendRawBytesImpl = async (streamId, data) => {
178
+ const streamIdBytes = new TextEncoder().encode(streamId);
179
+ const headerOverhead = 1 + 2 + streamIdBytes.length; // PROTOCOL + streamIdLen + streamId
180
+ const maxPayloadSize = WRTC_MAX_CHUNK - headerOverhead;
181
+ // For WebRTC with large data
182
+ if (transportType === 'wrtc' && data.length > maxPayloadSize) {
183
+ // RPC_STREAM_ID needs length prefix for reassembly (receiver must reconstruct complete message)
184
+ if (streamId === RPC_STREAM_ID) {
185
+ // Prepend 4-byte total length to data
186
+ const withLength = new Uint8Array(4 + data.length);
187
+ new DataView(withLength.buffer).setUint32(0, data.length, false);
188
+ withLength.set(data, 4);
189
+ // Send in chunks
190
+ for (let offset = 0; offset < withLength.length; offset += maxPayloadSize) {
191
+ const chunk = withLength.subarray(offset, Math.min(offset + maxPayloadSize, withLength.length));
192
+ await sendRawBytesPacket(streamIdBytes, chunk);
193
+ }
194
+ }
195
+ else {
196
+ // Other streams: just chunk without length prefix
197
+ // Handlers receive smaller pieces but that's OK (e.g., file transfers stream to disk)
198
+ for (let offset = 0; offset < data.length; offset += maxPayloadSize) {
199
+ const chunk = data.subarray(offset, Math.min(offset + maxPayloadSize, data.length));
200
+ await sendRawBytesPacket(streamIdBytes, chunk);
201
+ }
202
+ }
203
+ }
204
+ else {
205
+ // Small data or non-WebRTC: send directly
206
+ await sendRawBytesPacket(streamIdBytes, data);
207
+ }
208
+ };
165
209
  // Helper to send RPC messages - routes large messages through raw bytes streaming
166
210
  const sendRPCData = async (encoded) => {
167
211
  if (encoded.length > LARGE_MESSAGE_THRESHOLD) {
@@ -178,6 +222,9 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
178
222
  peer.send(withProtocol);
179
223
  }
180
224
  };
225
+ // Buffer for RPC stream reassembly (large RPC messages sent via raw bytes are length-prefixed)
226
+ let rpcStreamBuffer = null;
227
+ let rpcStreamExpectedLength = null;
181
228
  // Handle incoming data
182
229
  peer.on('data', async (data) => {
183
230
  try {
@@ -198,17 +245,52 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
198
245
  const streamIdBytes = bytes.subarray(3, 3 + streamIdLength);
199
246
  const streamId = new TextDecoder().decode(streamIdBytes);
200
247
  const payload = bytes.subarray(3 + streamIdLength);
201
- // Check if this is an RPC message sent via raw bytes (for large payloads)
248
+ // Handle RPC stream with reassembly (large RPC messages are length-prefixed)
202
249
  if (streamId === RPC_STREAM_ID) {
203
- handleRPCMessage(payload, handlers, callbacks, sendRPCData);
250
+ // Append to buffer
251
+ if (rpcStreamBuffer === null) {
252
+ rpcStreamBuffer = payload;
253
+ }
254
+ else {
255
+ const combined = new Uint8Array(rpcStreamBuffer.length + payload.length);
256
+ combined.set(rpcStreamBuffer, 0);
257
+ combined.set(payload, rpcStreamBuffer.length);
258
+ rpcStreamBuffer = combined;
259
+ }
260
+ // Process complete messages from buffer
261
+ while (rpcStreamBuffer && rpcStreamBuffer.length >= 4) {
262
+ // Read expected length if not yet known
263
+ if (rpcStreamExpectedLength === null) {
264
+ const lenView = new DataView(rpcStreamBuffer.buffer, rpcStreamBuffer.byteOffset, rpcStreamBuffer.byteLength);
265
+ rpcStreamExpectedLength = lenView.getUint32(0, false);
266
+ }
267
+ // Check if we have the complete message
268
+ if (rpcStreamBuffer.length >= 4 + rpcStreamExpectedLength) {
269
+ const rpcData = rpcStreamBuffer.subarray(4, 4 + rpcStreamExpectedLength);
270
+ handleRPCMessage(rpcData, handlers, callbacks, sendRPCData);
271
+ // Remove processed data from buffer
272
+ if (rpcStreamBuffer.length === 4 + rpcStreamExpectedLength) {
273
+ rpcStreamBuffer = null;
274
+ }
275
+ else {
276
+ rpcStreamBuffer = rpcStreamBuffer.subarray(4 + rpcStreamExpectedLength);
277
+ }
278
+ rpcStreamExpectedLength = null;
279
+ }
280
+ else {
281
+ // Need more data
282
+ break;
283
+ }
284
+ }
204
285
  return;
205
286
  }
287
+ // Other streams: pass through to handler
206
288
  const handler = rawBytesHandlers[streamId];
207
289
  if (handler)
208
290
  handler(payload);
209
291
  return;
210
292
  }
211
- // Handle RPC messages
293
+ // Handle RPC messages (small, sent directly)
212
294
  if (protocolType === PROTOCOL_RPC) {
213
295
  const rpcBytes = bytes.subarray(1);
214
296
  handleRPCMessage(rpcBytes, handlers, callbacks, sendRPCData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"