js-rpc2 1.2.3 → 2.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-rpc2",
3
- "version": "1.2.3",
3
+ "version": "2.1.0",
4
4
  "description": "js web websocket http rpc",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -30,5 +30,8 @@
30
30
  "@types/node": "^22.13.14",
31
31
  "koa": "^2.15.3",
32
32
  "ws": "^8.18.0"
33
+ },
34
+ "dependencies": {
35
+ "msgpackr": "^1.11.5"
33
36
  }
34
37
  }
package/src/lib.js CHANGED
@@ -1,19 +1,14 @@
1
+ import { Packr } from 'msgpackr'
2
+
1
3
  /**
2
- * @import { ExtensionApi } from "./types.js"
4
+ * @import { CALLBACK_ITEM, ExtensionApi, RPC_DATA, RPC_DATA_ARG_ITEM } from "./types.js"
3
5
  */
4
6
 
5
7
  const JS_RPC_WITH_CRYPTO = true
6
8
 
7
9
  export const sleep = (/** @type {number} */ timeout) => new Promise((resolve) => setTimeout(resolve, timeout))
8
10
 
9
-
10
- /**
11
- * @typedef {{
12
- * promise: Promise<any>;
13
- * resolve: (value: any | null) => void;
14
- * reject: (reason: any | null) => void;
15
- * }} PromiseResolvers
16
- */
11
+ const packr = new Packr({ structuredClone: true, useBigIntExtension: true, moreTypes: true, copyBuffers: true, })
17
12
 
18
13
  export function Promise_withResolvers() {
19
14
  /** @type{(value?:object)=>void} */
@@ -355,128 +350,38 @@ export async function timeWaitRetryLoop(signal, callback) {
355
350
 
356
351
  export const RPC_TYPE_CALL = 0xdf68f4cb
357
352
  export const RPC_TYPE_RETURN = 0x68b17581
358
- export const RPC_TYPE_RETURN_ARRAY = 0xceddbf64
359
353
  export const RPC_TYPE_CALLBACK = 0x8d65e5cc
360
354
  export const RPC_TYPE_ERROR = 0xa07c0f84
361
355
 
362
- /**
363
- * @typedef {RPC_TYPE_CALL
364
- * |RPC_TYPE_RETURN
365
- * |RPC_TYPE_RETURN_ARRAY
366
- * |RPC_TYPE_CALLBACK
367
- * |RPC_TYPE_ERROR} RPC_TYPES
368
- */
369
-
370
- /**
371
- * @typedef {{
372
- * id:number;
373
- * type:RPC_TYPES;
374
- * promise?:PromiseResolvers;
375
- * callback?:(...data:object)=>void;
376
- * }} CALLBACK_ITEM
377
- */
378
-
379
- /**
380
- * @typedef {{type:RPC_DATA_ARG_TYPE;data:object}} RPC_DATA_ARG_ITEM
381
- * @typedef {{
382
- * id:number;
383
- * type:RPC_TYPES;
384
- * data:{type:RPC_DATA_ARG_TYPE;data:object}[];
385
- * }} RPC_DATA
386
- */
387
-
388
- export const RPC_DATA_ARG_TYPE_OBJECT = 0xa7f68c
356
+ export const RPC_DATA_ARG_TYPE_OTHERS = 0xa7f68c
389
357
  export const RPC_DATA_ARG_TYPE_FUNCTION = 0x7ff45f
390
- export const RPC_DATA_ARG_TYPE_UINT8ARRAY = 0xedb218
391
- export const RPC_DATA_ARG_TYPE_UNDEFINED = 0x7f77fe
392
- export const RPC_DATA_ARG_TYPE_NULL = 0x5794f9
393
358
 
394
359
  /**
395
- * @typedef {RPC_DATA_ARG_TYPE_OBJECT
396
- * |RPC_DATA_ARG_TYPE_FUNCTION
397
- * |RPC_DATA_ARG_TYPE_UINT8ARRAY
398
- * |RPC_DATA_ARG_TYPE_UNDEFINED
399
- * |RPC_DATA_ARG_TYPE_NULL} RPC_DATA_ARG_TYPE
360
+ * @typedef {RPC_DATA_ARG_TYPE_OTHERS|RPC_DATA_ARG_TYPE_FUNCTION} RPC_DATA_ARG_TYPE
400
361
  */
401
362
 
402
363
  /**
403
364
  * @param {RPC_DATA} box
404
365
  */
405
366
  export function buildRpcData(box) {
406
- let buffers = [
407
- buildBufferNumberUInt32LE(box.id),
408
- buildBufferNumberUInt32LE(box.type),
409
- buildBufferNumberUInt32LE(box.data.length),
410
- ]
411
- for (const o of box.data) {
412
- if (o.type == RPC_DATA_ARG_TYPE_UINT8ARRAY) {
413
- buffers.push(buildBufferNumberUInt32LE(o.type))
414
- buffers.push(buildBufferNumberUInt32LE(o.data.length))
415
- buffers.push(o.data)
416
- }
417
- if (o.type == RPC_DATA_ARG_TYPE_FUNCTION) {
418
- buffers.push(buildBufferNumberUInt32LE(o.type))
419
- buffers.push(buildBufferSizeString(o.data))
420
- }
421
- if (o.type == RPC_DATA_ARG_TYPE_OBJECT) {
422
- buffers.push(buildBufferNumberUInt32LE(o.type))
423
- buffers.push(buildBufferSizeString(JSON.stringify(o.data)))
424
- }
425
- if (o.type == RPC_DATA_ARG_TYPE_UNDEFINED) {
426
- buffers.push(buildBufferNumberUInt32LE(o.type))
427
- }
428
- if (o.type == RPC_DATA_ARG_TYPE_NULL) {
429
- buffers.push(buildBufferNumberUInt32LE(o.type))
430
- }
367
+ let type = box.type
368
+ let data = box.data
369
+ if (type == RPC_TYPE_CALL || type == RPC_TYPE_CALLBACK) {
370
+ data = data.map(({ type, data }) => [type, data])
431
371
  }
432
- return Uint8Array_concat(buffers)
372
+ return Uint8Array.from(packr.pack([box.id, type, data]))
433
373
  }
434
374
 
435
375
  /**
436
376
  * @param {Uint8Array} buffer
437
377
  */
438
378
  export function parseRpcData(buffer) {
439
- let offset = 0
440
- let id = readUInt32LE(buffer, offset)
441
- offset += 4
442
- /** @type{*} */
443
- let type = readUInt32LE(buffer, offset)
444
- offset += 4
445
- let dataLength = readUInt32LE(buffer, offset)
446
- offset += 4
447
- /** @type{RPC_DATA_ARG_ITEM[]} */
448
- let args = []
449
- for (let i = 0; i < dataLength; i++) {
450
- let type = readUInt32LE(buffer, offset)
451
- offset += 4
452
- if (type == RPC_DATA_ARG_TYPE_UINT8ARRAY) {
453
- let size = readUInt32LE(buffer, offset)
454
- offset += 4
455
- let data = buffer.slice(offset, offset + size)
456
- offset += size
457
- args.push({ type: type, data })
458
- }
459
- if (type == RPC_DATA_ARG_TYPE_FUNCTION) {
460
- let o = readBufferSizeString(buffer, offset)
461
- offset += o.size
462
- args.push({ type: type, data: o.string })
463
- }
464
- if (type == RPC_DATA_ARG_TYPE_OBJECT) {
465
- let o = readBufferSizeString(buffer, offset)
466
- offset += o.size
467
- let data = o.string
468
- args.push({ type: type, data: JSON.parse(data) })
469
- }
470
- if (type == RPC_DATA_ARG_TYPE_UNDEFINED) {
471
- args.push({ type: type, data: undefined })
472
- }
473
- if (type == RPC_DATA_ARG_TYPE_NULL) {
474
- args.push({ type: type, data: null })
475
- }
379
+ let [id, type, data] = packr.unpack(buffer)
380
+ if (type == RPC_TYPE_CALL || type == RPC_TYPE_CALLBACK) {
381
+ data = data.map(([type, data]) => ({ type, data }))
476
382
  }
477
383
  /** @type{RPC_DATA} */
478
- let box = { id, type, data: args }
479
- return box
384
+ return { id, type, data }
480
385
  }
481
386
 
482
387
  /**
@@ -489,55 +394,18 @@ export function buildRpcItemData(items) {
489
394
  /** @type{RPC_DATA_ARG_TYPE} */
490
395
  let type = null
491
396
  let data = null
492
- if (item === undefined) {
493
- type = RPC_DATA_ARG_TYPE_UNDEFINED
494
- data = item
495
- } else if (item === null) {
496
- type = RPC_DATA_ARG_TYPE_NULL
497
- data = item
498
- } else if (item instanceof Uint8Array) {
499
- type = RPC_DATA_ARG_TYPE_UINT8ARRAY
500
- data = item
501
- } else if (typeof item == 'function') {
397
+ if (typeof item == 'function') {
502
398
  type = RPC_DATA_ARG_TYPE_FUNCTION
503
399
  data = item()
504
400
  } else {
505
- type = RPC_DATA_ARG_TYPE_OBJECT
506
- data = JSON.stringify(item)
401
+ type = RPC_DATA_ARG_TYPE_OTHERS
402
+ data = item
507
403
  }
508
404
  arr.push({ type, data })
509
405
  }
510
406
  return arr
511
407
  }
512
408
 
513
- /**
514
- * @param {RPC_DATA_ARG_ITEM[]} array
515
- */
516
- export function parseRpcItemData(array) {
517
- /** @type{RPC_DATA_ARG_ITEM[]} */
518
- let items = []
519
- for (let i = 0; i < array.length; i++) {
520
- const o = array[i]
521
- if (o.type == RPC_DATA_ARG_TYPE_FUNCTION) {
522
- o.data = o.data
523
- }
524
- if (o.type == RPC_DATA_ARG_TYPE_NULL) {
525
- o.data = null
526
- }
527
- if (o.type == RPC_DATA_ARG_TYPE_UNDEFINED) {
528
- o.data = undefined
529
- }
530
- if (o.type == RPC_DATA_ARG_TYPE_UINT8ARRAY) {
531
- o.data = o.data
532
- }
533
- if (o.type == RPC_DATA_ARG_TYPE_OBJECT) {
534
- o.data = JSON.parse(o.data)
535
- }
536
- items.push(o)
537
- }
538
- return items
539
- }
540
-
541
409
  /**
542
410
  * @template T
543
411
  * @param {ExtensionApi<T>} extension
@@ -555,7 +423,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
555
423
  try {
556
424
  let o = parseRpcData(buffer)
557
425
  dataId = o.id
558
- let items = parseRpcItemData(o.data)
426
+ let items = o.data
559
427
  fnName = items.at(0).data
560
428
  let args = items.slice(1)
561
429
  for (let i = 0; i < args.length; i++) {
@@ -576,11 +444,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
576
444
  }
577
445
  }
578
446
  let ret = await extension[fnName](...params)
579
- if (Array.isArray(ret)) {
580
- box = { id: o.id, type: RPC_TYPE_RETURN_ARRAY, data: buildRpcItemData(ret), }
581
- } else {
582
- box = { id: o.id, type: RPC_TYPE_RETURN, data: buildRpcItemData([ret]), }
583
- }
447
+ box = { id: o.id, type: RPC_TYPE_RETURN, data: ret, }
584
448
  } catch (error) {
585
449
  console.error('rpcRunServerDecodeBuffer', fnName, params.map(o => {
586
450
  if (typeof o == 'function') { return 'function' }
@@ -589,7 +453,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
589
453
  box = {
590
454
  id: dataId,
591
455
  type: RPC_TYPE_ERROR,
592
- data: buildRpcItemData([error.message, error.stack]),
456
+ data: { message: error.message, stack: error.stack },
593
457
  }
594
458
  }
595
459
  if (logger) {
@@ -618,7 +482,7 @@ export function createRPCProxy(apiInvoke) {
618
482
  try {
619
483
  return await apiInvoke(String(p), argArray)
620
484
  } catch (error) {
621
- throw new Error(error.message, { cause: error })
485
+ throw new RPCError(error.message, null, { cause: error })
622
486
  }
623
487
  }
624
488
  })
@@ -712,22 +576,20 @@ export function createRpcClientHelper(param) {
712
576
  async write(buffer) {
713
577
  try {
714
578
  let data = parseRpcData(buffer)
715
- let items = parseRpcItemData(data.data)
579
+ let items = data.data
716
580
  if (callbackFunctionMap.has(data.id)) {
717
581
  let o = callbackFunctionMap.get(data.id)
718
582
  if (data.type == RPC_TYPE_ERROR) {
719
- o.promise.reject(new RPCError(items.at(0).data, items.at(1).data))
583
+ let error = data.data
584
+ o.promise.reject(new RPCError(error.message, error.stack))
720
585
  }
721
586
  if (data.type == RPC_TYPE_RETURN) {
722
587
  callbackFunctionMap.delete(data.id)
723
- o.promise.resolve(items.at(0).data)
724
- }
725
- if (data.type == RPC_TYPE_RETURN_ARRAY) {
726
- callbackFunctionMap.delete(data.id)
727
- o.promise.resolve(items.map(o => o.data))
588
+ o.promise.resolve(data.data)
728
589
  }
729
590
  if (data.type == RPC_TYPE_CALLBACK) {
730
- o.callback(...items.map(o => o.data))
591
+ let args = items.map((/** @type {{ data: any; }} */ o) => o.data)
592
+ o.callback.apply(o, args)
731
593
  }
732
594
  }
733
595
  } catch (error) {
package/src/lib.test.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { test } from 'node:test'
2
- import { deepStrictEqual, ok, strictEqual } from 'node:assert'
2
+ import { deepStrictEqual, fail, ok, strictEqual } from 'node:assert'
3
3
  import { createRpcClientHttp, createRpcClientWebSocket, sleep, Uint8Array_from } from './lib.js'
4
4
  import { createServer } from 'node:http'
5
5
  import { WebSocketServer } from 'ws'
@@ -9,6 +9,7 @@ import { createRpcServerKoaRouter, createRpcServerWebSocket } from './server.js'
9
9
  import { AsyncLocalStorage } from 'node:async_hooks'
10
10
  import { Readable, Transform } from 'node:stream'
11
11
  import { pipeline } from 'node:stream/promises'
12
+ import { Packr } from 'msgpackr'
12
13
 
13
14
  test('basic', async () => {
14
15
  // node --test-name-pattern="^basic$" src/lib.test.js
@@ -354,7 +355,9 @@ test('error-stack', async () => {
354
355
  const router = new Router()
355
356
  app.use(router.routes())
356
357
  app.use(router.allowedMethods())
357
- let server = createServer(app.callback()).listen(9000)
358
+ let server = createServer(app.callback()).listen(9001)
359
+ using s = new DisposableStack()
360
+ s.adopt(0, () => { server.close() })
358
361
 
359
362
  class RpcApi {
360
363
  asyncLocalStorage = new AsyncLocalStorage()
@@ -377,16 +380,17 @@ test('error-stack', async () => {
377
380
  /** @type{RpcApi} */
378
381
  const rpc = createRpcClientHttp({
379
382
  // url: `/rpc/abc`, // in same site html page
380
- url: `http://127.0.0.1:9000/rpc/abc`, // others
383
+ url: `http://127.0.0.1:9001/rpc/abc`, // others
381
384
  })
382
385
 
383
386
  try {
384
387
  let ret = await rpc.hello()
385
388
  console.info(ret)
389
+ fail('boom')
386
390
  } catch (error) {
387
391
  console.error(error)
392
+ ok(error.cause.stack.includes('at RpcApi.hello'))
388
393
  }
389
- server.close()
390
394
 
391
395
  })
392
396
 
@@ -467,4 +471,116 @@ test('async-local-storage-stream', async () => {
467
471
  await sleep(100)
468
472
 
469
473
  await Promise.all([p1.promise])
474
+ })
475
+
476
+ test('msgpackr', async () => {
477
+ // node --test-name-pattern="^msgpackr$" src/lib.test.js
478
+ let value = {
479
+ int: 99,
480
+ float: 0.3,
481
+ bigint: 3000n,
482
+ string: "string",
483
+ bool: true,
484
+ date: new Date('2000-01-02 03:04:05.678'),
485
+ uint8array: Uint8Array.from([12, 34, 56, 78]),
486
+ object: { a: 1, b: 2 },
487
+ array: [1, 2, 3, 'a', 'b', 'c'],
488
+ map: new Map(),
489
+ set: new Set(),
490
+
491
+ // 补充更多类型
492
+ null: null,
493
+ undefined: undefined,
494
+ emptyString: "",
495
+ zero: 0,
496
+ negativeInt: -42,
497
+ negativeFloat: -3.14,
498
+ infinity: Infinity,
499
+ negativeInfinity: -Infinity,
500
+ nan: NaN,
501
+
502
+ // 大数字测试
503
+ largeInt: 9007199254740991, // Number.MAX_SAFE_INTEGER
504
+ veryLargeBigInt: 123456789012345678901234567890n,
505
+
506
+ // 字符串测试
507
+ unicodeString: "Hello 世界 🌍",
508
+ specialChars: "特殊字符: \n\t\r\\\"'",
509
+
510
+ // 数组测试
511
+ emptyArray: [],
512
+ nestedArray: [1, [2, 3], [4, [5, 6]]],
513
+ mixedTypeArray: [1, "string", true, null, { x: 1 }],
514
+
515
+ // 对象测试
516
+ emptyObject: {},
517
+ nestedObject: {
518
+ level1: {
519
+ level2: {
520
+ value: "deep"
521
+ }
522
+ }
523
+ },
524
+ objectWithSpecialKeys: {
525
+ "": "empty key",
526
+ "123": "numeric key",
527
+ "key with spaces": "value"
528
+ },
529
+
530
+ // Map 和 Set 扩展测试
531
+ // @ts-ignore
532
+ mapWithMixedTypes: new Map([
533
+ ['string', 'value'],
534
+ [123, 'number key'],
535
+ [true, 'boolean key'],
536
+ [null, 'null key'],
537
+ [{ nested: 'key' }, 'object key']
538
+ ]),
539
+ setWithMixedValues: new Set([1, 'string', true, null, undefined]),
540
+ emptyMap: new Map(),
541
+ emptySet: new Set(),
542
+
543
+ // TypedArray 测试
544
+ int8Array: new Int8Array([-128, -1, 0, 1, 127]),
545
+ int16Array: new Int16Array([-32768, -1, 0, 1, 32767]),
546
+ int32Array: new Int32Array([-2147483648, -1, 0, 1, 2147483647]),
547
+ uint8ClampedArray: new Uint8ClampedArray([0, 127, 255]),
548
+ float32Array: new Float32Array([1.1, 2.2, 3.3]),
549
+ float64Array: new Float64Array([1.111111111111111, 2.222222222222222]),
550
+
551
+ // ArrayBuffer 测试
552
+ arrayBuffer: new ArrayBuffer(8),
553
+
554
+ // 其他特殊对象
555
+ regexp: /test\/pattern/gi,
556
+ error: new Error("test error"),
557
+
558
+ // 嵌套复杂结构
559
+ complexNested: {
560
+ arrayWithObjects: [
561
+ { id: 1, data: new Map([['a', 1]]) },
562
+ { id: 2, data: new Set([1, 2, 3]) }
563
+ ],
564
+ mapWithComplexValues: new Map([
565
+ ['nested', {
566
+ array: [1, 2, new Set([4, 5])]
567
+ }]
568
+ ])
569
+ }
570
+ }
571
+
572
+ // 初始化一些 ArrayBuffer 数据
573
+ const bufferView = new Uint8Array(value.arrayBuffer)
574
+ for (let i = 0; i < bufferView.length; i++) {
575
+ bufferView[i] = i
576
+ }
577
+
578
+ let packr = new Packr({ structuredClone: true, useBigIntExtension: true, moreTypes: true, copyBuffers: true, })
579
+ let serializedAsBuffer = packr.pack(value)
580
+ let data = packr.unpack(serializedAsBuffer)
581
+
582
+ strictEqual(data.error.message, value.error.message)
583
+ delete data['error']
584
+ delete value['error']
585
+ deepStrictEqual(data, value)
470
586
  })
package/src/types.ts CHANGED
@@ -1,3 +1,49 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
-
3
- export type ExtensionApi<T> = { asyncLocalStorage: AsyncLocalStorage<T> } & object;
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ export type ExtensionApi<T> = { asyncLocalStorage: AsyncLocalStorage<T> } & object;
4
+
5
+
6
+ export type RPC_TYPE_CALL = 0xdf68f4cb
7
+ export type RPC_TYPE_RETURN = 0x68b17581
8
+ export type RPC_TYPE_CALLBACK = 0x8d65e5cc
9
+ export type RPC_TYPE_ERROR = 0xa07c0f84
10
+
11
+ export type RPC_DATA_ARG_TYPE_OTHERS = 0xa7f68c
12
+ export type RPC_DATA_ARG_TYPE_FUNCTION = 0x7ff45f
13
+
14
+
15
+ export type RPC_TYPES = RPC_TYPE_CALL |
16
+ RPC_TYPE_RETURN |
17
+ RPC_TYPE_CALLBACK |
18
+ RPC_TYPE_ERROR;
19
+
20
+
21
+ export type PromiseResolvers = {
22
+ promise: Promise<any>;
23
+ resolve: (value: any | null) => void;
24
+ reject: (reason: any | null) => void;
25
+ };
26
+
27
+ export type CALLBACK_ITEM = {
28
+ id: number;
29
+ type: RPC_TYPES;
30
+ promise?: PromiseResolvers;
31
+ callback?: (...data: object[]) => void;
32
+ };
33
+
34
+ export type RPC_DATA_ARG_ITEM = { type: RPC_DATA_ARG_TYPE; data: object; };
35
+ export type RPC_DATA = {
36
+ id: number;
37
+ type: RPC_TYPE_CALL | RPC_TYPE_CALLBACK;
38
+ data: { type: RPC_DATA_ARG_TYPE; data: any; }[];
39
+ } | {
40
+ id: number;
41
+ type: RPC_TYPE_RETURN;
42
+ data: any;
43
+ } | {
44
+ id: number;
45
+ type: RPC_TYPE_ERROR;
46
+ data: { message: string, stack: string };
47
+ };
48
+
49
+ export type RPC_DATA_ARG_TYPE = RPC_DATA_ARG_TYPE_OTHERS | RPC_DATA_ARG_TYPE_FUNCTION;