js-rpc2 1.2.3 → 2.0.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.0.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,127 +350,29 @@ 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
- }
431
- }
432
- return Uint8Array_concat(buffers)
367
+ return Uint8Array.from(packr.pack(box))
433
368
  }
434
369
 
435
370
  /**
436
371
  * @param {Uint8Array} buffer
437
372
  */
438
373
  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
- }
476
- }
477
374
  /** @type{RPC_DATA} */
478
- let box = { id, type, data: args }
375
+ let box = packr.unpack(buffer)
479
376
  return box
480
377
  }
481
378
 
@@ -489,55 +386,18 @@ export function buildRpcItemData(items) {
489
386
  /** @type{RPC_DATA_ARG_TYPE} */
490
387
  let type = null
491
388
  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') {
389
+ if (typeof item == 'function') {
502
390
  type = RPC_DATA_ARG_TYPE_FUNCTION
503
391
  data = item()
504
392
  } else {
505
- type = RPC_DATA_ARG_TYPE_OBJECT
506
- data = JSON.stringify(item)
393
+ type = RPC_DATA_ARG_TYPE_OTHERS
394
+ data = item
507
395
  }
508
396
  arr.push({ type, data })
509
397
  }
510
398
  return arr
511
399
  }
512
400
 
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
401
  /**
542
402
  * @template T
543
403
  * @param {ExtensionApi<T>} extension
@@ -555,7 +415,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
555
415
  try {
556
416
  let o = parseRpcData(buffer)
557
417
  dataId = o.id
558
- let items = parseRpcItemData(o.data)
418
+ let items = o.data
559
419
  fnName = items.at(0).data
560
420
  let args = items.slice(1)
561
421
  for (let i = 0; i < args.length; i++) {
@@ -576,11 +436,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
576
436
  }
577
437
  }
578
438
  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
- }
439
+ box = { id: o.id, type: RPC_TYPE_RETURN, data: ret, }
584
440
  } catch (error) {
585
441
  console.error('rpcRunServerDecodeBuffer', fnName, params.map(o => {
586
442
  if (typeof o == 'function') { return 'function' }
@@ -589,7 +445,7 @@ export async function rpcRunServerDecodeBuffer(extension, writer, buffer, logger
589
445
  box = {
590
446
  id: dataId,
591
447
  type: RPC_TYPE_ERROR,
592
- data: buildRpcItemData([error.message, error.stack]),
448
+ data: { message: error.message, stack: error.stack },
593
449
  }
594
450
  }
595
451
  if (logger) {
@@ -618,7 +474,7 @@ export function createRPCProxy(apiInvoke) {
618
474
  try {
619
475
  return await apiInvoke(String(p), argArray)
620
476
  } catch (error) {
621
- throw new Error(error.message, { cause: error })
477
+ throw new RPCError(error.message, null, { cause: error })
622
478
  }
623
479
  }
624
480
  })
@@ -712,22 +568,20 @@ export function createRpcClientHelper(param) {
712
568
  async write(buffer) {
713
569
  try {
714
570
  let data = parseRpcData(buffer)
715
- let items = parseRpcItemData(data.data)
571
+ let items = data.data
716
572
  if (callbackFunctionMap.has(data.id)) {
717
573
  let o = callbackFunctionMap.get(data.id)
718
574
  if (data.type == RPC_TYPE_ERROR) {
719
- o.promise.reject(new RPCError(items.at(0).data, items.at(1).data))
575
+ let error = data.data
576
+ o.promise.reject(new RPCError(error.message, error.stack))
720
577
  }
721
578
  if (data.type == RPC_TYPE_RETURN) {
722
579
  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))
580
+ o.promise.resolve(data.data)
728
581
  }
729
582
  if (data.type == RPC_TYPE_CALLBACK) {
730
- o.callback(...items.map(o => o.data))
583
+ let args = items.map((/** @type {{ data: any; }} */ o) => o.data)
584
+ o.callback.apply(o, args)
731
585
  }
732
586
  }
733
587
  } 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
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
 
3
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;