js-rpc2 2.6.2 → 2.7.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 +5 -2
- package/src/ipc.js +117 -0
- package/src/lib.test.js +52 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js-rpc2",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "js web websocket http rpc",
|
|
5
5
|
"main": "src/lib.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"rpc",
|
|
25
25
|
"electron",
|
|
26
26
|
"chrome",
|
|
27
|
-
"extensions"
|
|
27
|
+
"extensions",
|
|
28
|
+
"ipc",
|
|
29
|
+
"socket",
|
|
30
|
+
"named-pipe"
|
|
28
31
|
],
|
|
29
32
|
"author": "yuanliwei",
|
|
30
33
|
"license": "MIT",
|
package/src/ipc.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import net from 'node:net'
|
|
2
|
+
import { existsSync, unlinkSync } from 'node:fs'
|
|
3
|
+
import { Readable } from 'node:stream'
|
|
4
|
+
import { platform } from 'node:process'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 跨平台生成 IPC 路径
|
|
8
|
+
* @param {string} name
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function getPipePath(name) {
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
return `\\\\.\\pipe\\${name}`
|
|
14
|
+
}
|
|
15
|
+
return `/tmp/${name}.sock`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 创建 IPC 服务端
|
|
20
|
+
* @param {string} name
|
|
21
|
+
* @returns {Promise<net.Server>}
|
|
22
|
+
*/
|
|
23
|
+
export async function createIPCServer(name) {
|
|
24
|
+
const socketPath = getPipePath(name)
|
|
25
|
+
if (platform !== 'win32' && existsSync(socketPath)) {
|
|
26
|
+
unlinkSync(socketPath)
|
|
27
|
+
}
|
|
28
|
+
const server = net.createServer()
|
|
29
|
+
await new Promise((resolve) => server.listen(socketPath, () => resolve()))
|
|
30
|
+
return server
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 创建 IPC 客户端
|
|
35
|
+
* @param {string} name
|
|
36
|
+
* @returns {Promise<net.Socket>}
|
|
37
|
+
*/
|
|
38
|
+
export function createIPCClient(name) {
|
|
39
|
+
const socketPath = getPipePath(name)
|
|
40
|
+
const client = net.createConnection(socketPath)
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
client.once('connect', () => resolve(client))
|
|
43
|
+
client.once('error', reject)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 将 Node.js Socket 桥接到 js-rpc2 helper 的 readable/writable
|
|
49
|
+
* @param {net.Socket} socket
|
|
50
|
+
* @param {{ writable: WritableStream, readable: ReadableStream }} helper
|
|
51
|
+
*/
|
|
52
|
+
export function bridgeSocketToHelper(socket, helper) {
|
|
53
|
+
Readable.fromWeb(helper.readable).pipe(socket)
|
|
54
|
+
const writer = helper.writable.getWriter()
|
|
55
|
+
socket.on('data', async (chunk) => {
|
|
56
|
+
try { await writer.write(chunk) } catch (_) { /* closed */ }
|
|
57
|
+
})
|
|
58
|
+
socket.on('end', () => writer.close().catch(() => { }))
|
|
59
|
+
socket.on('error', () => writer.close().catch(() => { }))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
import { createRpcServerHelper } from './lib.js'
|
|
63
|
+
import { createRpcClientHelper, createRPCProxy } from './lib.js'
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 创建 IPC RPC 服务端
|
|
67
|
+
*
|
|
68
|
+
* @param {{
|
|
69
|
+
* name: string;
|
|
70
|
+
* rpcKey: string;
|
|
71
|
+
* extension: object;
|
|
72
|
+
* logger?: (msg: string) => void;
|
|
73
|
+
* }} param
|
|
74
|
+
* @returns {Promise<net.Server>}
|
|
75
|
+
*/
|
|
76
|
+
export async function createRpcServerIPC(param) {
|
|
77
|
+
const server = await createIPCServer(param.name)
|
|
78
|
+
|
|
79
|
+
server.on('connection', (socket) => {
|
|
80
|
+
const helper = createRpcServerHelper({
|
|
81
|
+
rpcKey: param.rpcKey,
|
|
82
|
+
extension: param.extension,
|
|
83
|
+
async: true,
|
|
84
|
+
logger: param.logger,
|
|
85
|
+
})
|
|
86
|
+
bridgeSocketToHelper(socket, helper)
|
|
87
|
+
socket.on('error', (err) => {
|
|
88
|
+
console.error('[RpcServerIPC] socket 错误:', err.message)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
server.on('error', (err) => {
|
|
93
|
+
console.error('[RpcServerIPC] 服务器错误:', err.message)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return server
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 创建 IPC RPC 客户端,返回 {rpc, [Symbol.dispose]}
|
|
101
|
+
* 支持 using 语法自动清理:
|
|
102
|
+
* using client = await createRpcClientIPC('myapp', 'mykey')
|
|
103
|
+
* const result = await client.rpc.hello('world')
|
|
104
|
+
*
|
|
105
|
+
* @template T
|
|
106
|
+
* @param {string} name - IPC 管道名称
|
|
107
|
+
* @param {string} rpcKey - 加密密钥
|
|
108
|
+
* @returns {Promise<{rpc: T, [Symbol.dispose]: () => void}>}
|
|
109
|
+
*/
|
|
110
|
+
export async function createRpcClientIPC(name, rpcKey) {
|
|
111
|
+
const socket = await createIPCClient(name)
|
|
112
|
+
const helper = createRpcClientHelper({ rpcKey })
|
|
113
|
+
bridgeSocketToHelper(socket, helper)
|
|
114
|
+
const rpc = createRPCProxy(helper.apiInvoke)
|
|
115
|
+
const close = () => socket.end()
|
|
116
|
+
return { rpc, [Symbol.dispose]: close }
|
|
117
|
+
}
|
package/src/lib.test.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Readable, Transform } from 'node:stream'
|
|
|
11
11
|
import { pipeline } from 'node:stream/promises'
|
|
12
12
|
import { Packr } from 'msgpackr'
|
|
13
13
|
import { Worker } from 'node:worker_threads'
|
|
14
|
+
import { createRpcServerIPC, createRpcClientIPC } from './ipc.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @import {ExtensionApi} from './test-worker.js'
|
|
@@ -601,4 +602,54 @@ test('test-rpc-NodeJSWorker', async () => {
|
|
|
601
602
|
console.info(await rpc.hello('123'))
|
|
602
603
|
deepStrictEqual(await rpc.hello('123'), 'hello 123')
|
|
603
604
|
await worker.terminate()
|
|
604
|
-
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// ==================== IPC 测试 ====================
|
|
608
|
+
|
|
609
|
+
test('test-rpc-IPC-basic', async () => {
|
|
610
|
+
// node --test --test-name-pattern="^test-rpc-IPC-basic$" src/lib.test.js
|
|
611
|
+
|
|
612
|
+
const extension = {
|
|
613
|
+
hello: async function (/** @type {string} */ name) {
|
|
614
|
+
return `hello ${name}`
|
|
615
|
+
},
|
|
616
|
+
add: async function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
617
|
+
return a + b
|
|
618
|
+
},
|
|
619
|
+
echo: async function (/** @type {any} */ value) {
|
|
620
|
+
return value
|
|
621
|
+
},
|
|
622
|
+
ping: async function () {
|
|
623
|
+
return { ok: true }
|
|
624
|
+
},
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const server = await createRpcServerIPC({
|
|
628
|
+
name: 'test-ipc-basic',
|
|
629
|
+
rpcKey: 'test-key',
|
|
630
|
+
extension,
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
using client = await createRpcClientIPC('test-ipc-basic', 'test-key')
|
|
634
|
+
/** @type{typeof extension} */
|
|
635
|
+
const rpc = client.rpc
|
|
636
|
+
|
|
637
|
+
// 基本调用
|
|
638
|
+
const hello = await rpc.hello('world')
|
|
639
|
+
strictEqual(hello, 'hello world')
|
|
640
|
+
|
|
641
|
+
// 数字参数
|
|
642
|
+
const sum = await rpc.add(3, 7)
|
|
643
|
+
strictEqual(sum, 10)
|
|
644
|
+
|
|
645
|
+
// 对象参数
|
|
646
|
+
const echoed = await rpc.echo({ a: 1, b: [2, 3] })
|
|
647
|
+
deepStrictEqual(echoed, { a: 1, b: [2, 3] })
|
|
648
|
+
|
|
649
|
+
// 无参数
|
|
650
|
+
const pong = await rpc.ping()
|
|
651
|
+
strictEqual(pong.ok, true)
|
|
652
|
+
|
|
653
|
+
server.close()
|
|
654
|
+
console.info('test-rpc-IPC-basic passed')
|
|
655
|
+
})
|