socket-function 0.10.7 → 0.11.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/CallFactory.ts +142 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.10.7",
3
+ "version": "0.11.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -29,7 +29,6 @@ type InternalReturnType = {
29
29
  result: unknown;
30
30
  error?: string;
31
31
  seqNum: number;
32
- resultSize: number;
33
32
  isResultCompressed?: boolean;
34
33
  };
35
34
 
@@ -228,7 +227,6 @@ export async function createCallFactory(
228
227
  result: undefined,
229
228
  error: error,
230
229
  seqNum: call.call.seqNum,
231
- resultSize: 0,
232
230
  });
233
231
  }
234
232
 
@@ -275,7 +273,16 @@ export async function createCallFactory(
275
273
  }
276
274
 
277
275
  const BASE_LENGTH_OFFSET = 324_432_461_592_612;
278
- async function send(data: Buffer[]) {
276
+ type MessageHeader = {
277
+ type: "serialized";
278
+ bufferCount: number;
279
+ } | {
280
+ type: "Buffer[]" | "Buffer";
281
+ bufferCount: number;
282
+ bufferLengths?: number[];
283
+ metadata: Omit<InternalReturnType, "result">;
284
+ };
285
+ async function sendRaw(data: (string | Buffer)[]) {
279
286
  if (!webSocketPromise) {
280
287
  if (canReconnect) {
281
288
  webSocketPromise = tryToReconnect();
@@ -284,11 +291,44 @@ export async function createCallFactory(
284
291
  }
285
292
  }
286
293
  let webSocket = await webSocketPromise;
287
- webSocket.send((data.length + BASE_LENGTH_OFFSET).toString());
288
294
  for (let d of data) {
289
295
  webSocket.send(d);
290
296
  }
291
297
  }
298
+ async function send(data: Buffer[]) {
299
+ sendRaw([
300
+ (data.length + BASE_LENGTH_OFFSET).toString(),
301
+ ...data,
302
+ ]);
303
+ }
304
+ async function sendWithHeader(data: Buffer[], header: MessageHeader) {
305
+ if (data.some(x => x.length > SocketFunction.MAX_MESSAGE_SIZE * 1.5)) {
306
+ if (header.type === "Buffer" || header.type === "Buffer[]") {
307
+ header.bufferLengths = data.map(x => x.length);
308
+ let fitBuffers: Buffer[] = [];
309
+ for (let buf of data) {
310
+ if (buf.length > SocketFunction.MAX_MESSAGE_SIZE) {
311
+ let offset = 0;
312
+ while (offset < buf.length) {
313
+ fitBuffers.push(buf.slice(offset, offset + SocketFunction.MAX_MESSAGE_SIZE));
314
+ offset += SocketFunction.MAX_MESSAGE_SIZE;
315
+ }
316
+ } else {
317
+ fitBuffers.push(buf);
318
+ }
319
+ }
320
+ data = fitBuffers;
321
+ } else {
322
+ throw new Error(`Cannot send large amounts of data unless we are returning Buffer or Buffer[]`);
323
+ }
324
+ }
325
+ // if (totalResultSize > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
326
+ // Split up Buffer[] if they are too large
327
+ sendRaw([
328
+ JSON.stringify(header),
329
+ ...data,
330
+ ]);
331
+ }
292
332
  async function tryToReconnect(): Promise<SenderInterface> {
293
333
  // Don't try to reconnect too often!
294
334
  let timeSinceLastAttempt = Date.now() - lastConnectionAttempt;
@@ -303,7 +343,9 @@ export async function createCallFactory(
303
343
  return newWebSocket;
304
344
  }
305
345
 
306
- let pendingCall: { buffers: Buffer[]; targetCount: number; } | undefined;
346
+ let pendingCall: MessageHeader & {
347
+ buffers: Buffer[];
348
+ } | undefined;
307
349
  let clientsideSerial = runInSerial(async <T>(val: Promise<T>) => val);
308
350
  async function onMessage(message: ws.RawData | ws.MessageEvent | string) {
309
351
  try {
@@ -320,21 +362,30 @@ export async function createCallFactory(
320
362
  }
321
363
  }
322
364
  if (typeof message === "string") {
323
- let size = parseInt(message);
324
- if (isNaN(size)) {
325
- throw new Error(`Invalid message size ${message}`);
326
- }
327
- if (size < BASE_LENGTH_OFFSET) {
328
- throw new Error(`Invalid message size ${message}`);
329
- }
330
- size -= BASE_LENGTH_OFFSET;
331
- if (size > 1000 * 1000) {
332
- throw new Error(`Invalid message size ${size}`);
365
+ if (message.startsWith("{")) {
366
+ let obj = JSON.parse(message);
367
+ pendingCall = {
368
+ ...obj,
369
+ buffers: [],
370
+ };
371
+ } else {
372
+ let count = parseInt(message);
373
+ if (isNaN(count)) {
374
+ throw new Error(`Invalid message count ${message}`);
375
+ }
376
+ if (count < BASE_LENGTH_OFFSET) {
377
+ throw new Error(`Invalid message count ${message}`);
378
+ }
379
+ count -= BASE_LENGTH_OFFSET;
380
+ if (count > 1000 * 1000) {
381
+ throw new Error(`Invalid message count ${count}`);
382
+ }
383
+ pendingCall = {
384
+ buffers: [],
385
+ bufferCount: count,
386
+ type: "serialized",
387
+ };
333
388
  }
334
- pendingCall = {
335
- buffers: [],
336
- targetCount: size,
337
- };
338
389
  return;
339
390
  }
340
391
  if (message instanceof Buffer) {
@@ -343,17 +394,63 @@ export async function createCallFactory(
343
394
  }
344
395
  pendingCall.buffers.push(message);
345
396
  let currentBuffers: Buffer[];
346
- if (pendingCall.buffers.length !== pendingCall.targetCount) {
397
+ if (pendingCall.buffers.length !== pendingCall.bufferCount) {
347
398
  return;
348
399
  }
349
400
 
350
- currentBuffers = pendingCall.buffers;
401
+ let currentCall = pendingCall;
351
402
  pendingCall = undefined;
352
-
353
- let resultSize = currentBuffers.map(x => x.length).reduce((a, b) => a + b, 0);
354
-
403
+ currentBuffers = currentCall.buffers;
404
+ let call: InternalCallType | InternalReturnType;
405
+ let resultSize: number;
355
406
  let time = Date.now();
356
- let call = await SocketFunction.WIRE_SERIALIZER.deserialize(currentBuffers) as InternalCallType | InternalReturnType;
407
+ if (currentCall.type === "Buffer" || currentCall.type === "Buffer[]") {
408
+ let result: Buffer | Buffer[] = currentBuffers;
409
+ if (currentCall.bufferLengths) {
410
+ let pendingBuffers = currentBuffers;
411
+ function takeBuffer(len: number) {
412
+ let lenLeft = len;
413
+ let buffers: Buffer[] = [];
414
+ while (lenLeft > 0) {
415
+ let buf = currentBuffers.pop();
416
+ if (!buf) {
417
+ throw new Error(`Not enough buffers received.`);
418
+ }
419
+ if (buf.length > lenLeft) {
420
+ buffers.push(buf.slice(0, lenLeft));
421
+ currentBuffers.unshift(buf.slice(lenLeft));
422
+ break;
423
+ } else {
424
+ buffers.push(buf);
425
+ lenLeft -= buf.length;
426
+ }
427
+ }
428
+ if (buffers.length === 1) {
429
+ return buffers[0];
430
+ }
431
+ return Buffer.concat(buffers);
432
+ }
433
+ result = currentCall.bufferLengths.map(takeBuffer);
434
+ if (pendingBuffers.length > 0) {
435
+ throw new Error(`Received too many buffers.`);
436
+ }
437
+ }
438
+ resultSize = result.map(x => x.length).reduce((a, b) => a + b, 0);
439
+ if (currentCall.type === "Buffer") {
440
+ if (result.length === 1) {
441
+ result = result[0];
442
+ } else {
443
+ result = Buffer.concat(result);
444
+ }
445
+ }
446
+ call = {
447
+ ...currentCall.metadata,
448
+ result,
449
+ };
450
+ } else {
451
+ resultSize = currentBuffers.map(x => x.length).reduce((a, b) => a + b, 0);
452
+ call = await SocketFunction.WIRE_SERIALIZER.deserialize(currentBuffers) as InternalCallType | InternalReturnType;
453
+ }
357
454
  time = Date.now() - time;
358
455
  for (let callback of SocketFunction.trackMessageSizes.download) {
359
456
  callback(resultSize);
@@ -365,7 +462,7 @@ export async function createCallFactory(
365
462
  console.log(red(`Slow parse, took ${time}ms to parse ${resultSize} bytes, for receieving result of call to ${callbackObj?.call.classGuid}.${callbackObj?.call.functionName}`));
366
463
  }
367
464
  if (!callbackObj) {
368
- console.log(`Got return for unknown call ${call.seqNum}`);
465
+ console.log(`Got return for unknown call ${call.seqNum} (created at time ${new Date(call.seqNum)})`);
369
466
  return;
370
467
  }
371
468
  if (SocketFunction.logMessages) {
@@ -376,7 +473,6 @@ export async function createCallFactory(
376
473
  call.result = await decompressObj(call.result as Buffer);
377
474
  call.isResultCompressed = false;
378
475
  }
379
- call.resultSize = resultSize;
380
476
  callbackObj.callback(call);
381
477
  } else {
382
478
  if (call.isArgsCompressed) {
@@ -397,7 +493,6 @@ export async function createCallFactory(
397
493
  isReturn: true,
398
494
  result,
399
495
  seqNum: call.seqNum,
400
- resultSize: resultSize,
401
496
  };
402
497
  if (shouldCompressCall(call)) {
403
498
  response.result = await compressObj(response.result) as any;
@@ -409,23 +504,29 @@ export async function createCallFactory(
409
504
  result: undefined,
410
505
  seqNum: call.seqNum,
411
506
  error: e.stack,
412
- resultSize: resultSize,
413
507
  };
414
508
  }
415
509
 
416
- let result: Buffer[] = await SocketFunction.WIRE_SERIALIZER.serialize(response);
417
- let totalResultSize = result.map(x => x.length).reduce((a, b) => a + b, 0);
418
- if (totalResultSize > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
419
- response = {
420
- isReturn: true,
421
- result: undefined,
422
- seqNum: call.seqNum,
423
- error: new Error(`Response too large to send (${call.classGuid}.${call.functionName}, size: ${formatNumber(totalResultSize)} > ${formatNumber(SocketFunction.MAX_MESSAGE_SIZE)}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`).stack,
424
- resultSize: resultSize,
425
- };
426
- result = await SocketFunction.WIRE_SERIALIZER.serialize(response);
510
+ if (response.result instanceof Buffer) {
511
+ let { result, ...remaining } = response;
512
+ await sendWithHeader([result], { type: "Buffer", bufferCount: 1, metadata: remaining });
513
+ } else if (Array.isArray(response.result) && response.result.every(x => x instanceof Buffer)) {
514
+ let { result, ...remaining } = response;
515
+ await sendWithHeader(result, { type: "Buffer[]", bufferCount: result.length, metadata: remaining });
516
+ } else {
517
+ let result: Buffer[] = await SocketFunction.WIRE_SERIALIZER.serialize(response);
518
+ let totalResultSize = result.map(x => x.length).reduce((a, b) => a + b, 0);
519
+ if (totalResultSize > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
520
+ response = {
521
+ isReturn: true,
522
+ result: undefined,
523
+ seqNum: call.seqNum,
524
+ error: new Error(`Response too large to send (${call.classGuid}.${call.functionName}, size: ${formatNumber(totalResultSize)} > ${formatNumber(SocketFunction.MAX_MESSAGE_SIZE)}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`).stack,
525
+ };
526
+ result = await SocketFunction.WIRE_SERIALIZER.serialize(response);
527
+ }
528
+ await send(result);
427
529
  }
428
- await send(result);
429
530
  }
430
531
  return;
431
532
  }