@motrix/mdxp 0.1.1 → 0.3.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/README.md +40 -24
- package/README.zh-CN.md +37 -24
- package/dist/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +8 -0
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +5 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +7 -0
- package/dist/node.js.map +1 -1
- package/dist/transports/stdio.d.ts +19 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +17 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/transports/worker.d.ts +27 -0
- package/dist/transports/worker.d.ts.map +1 -0
- package/dist/transports/worker.js +22 -0
- package/dist/transports/worker.js.map +1 -0
- package/dist/transports/ws.d.ts +35 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +144 -0
- package/dist/transports/ws.js.map +1 -0
- package/package.json +1 -1
- package/src/browser.ts +13 -0
- package/src/index.ts +11 -0
- package/src/node.ts +18 -0
- package/src/transports/stdio.ts +27 -0
- package/src/transports/worker.ts +49 -0
- package/src/transports/ws.ts +188 -0
package/README.md
CHANGED
|
@@ -33,7 +33,11 @@ top of it.
|
|
|
33
33
|
- **Transport-agnostic.** Works over anything that implements
|
|
34
34
|
`MessageReader`/`MessageWriter`.
|
|
35
35
|
- **Platform RAL entry points.** `./node` and `./browser` install the matching
|
|
36
|
-
`vscode-jsonrpc` runtime abstraction layer
|
|
36
|
+
`vscode-jsonrpc` runtime abstraction layer and re-export its transport classes,
|
|
37
|
+
so you import everything — connection helper and reader/writer — from one place.
|
|
38
|
+
- **One-call constructors.** `fromWebSocket`, `fromStdio`, and `fromWorker` build
|
|
39
|
+
a connection over a common transport in a single call; `createMdxpConnection`
|
|
40
|
+
stays the generic escape hatch.
|
|
37
41
|
- **Agent-ready.** A built-in tool registry emits a JSON-Schema tool catalog you
|
|
38
42
|
can feed straight into an LLM function-calling API.
|
|
39
43
|
- **Forward-compatible.** Unknown methods and fields are ignored, not rejected.
|
|
@@ -46,10 +50,11 @@ npm install @motrix/mdxp
|
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
Runtime dependency: [`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
|
|
49
|
-
`^9`,
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
+
`^9`, installed alongside this package. Its transport classes (reader/writer)
|
|
54
|
+
and the primitives that surface in this package's API — `MessageReader`,
|
|
55
|
+
`MessageConnection`, `CancellationToken`, `CancellationTokenSource`, … — are
|
|
56
|
+
re-exported from `@motrix/mdxp` (see [Entry points](#entry-points)), so you
|
|
57
|
+
rarely need to import `vscode-jsonrpc` directly. ESM-only; requires
|
|
53
58
|
Node.js ≥ 18 or a modern bundler.
|
|
54
59
|
|
|
55
60
|
### Entry points
|
|
@@ -63,21 +68,20 @@ Node.js ≥ 18 or a modern bundler.
|
|
|
63
68
|
`vscode-jsonrpc` v9 requires a runtime abstraction layer (RAL) to be installed
|
|
64
69
|
before a connection can be created. Importing `@motrix/mdxp/node` or
|
|
65
70
|
`@motrix/mdxp/browser` installs the right one into the **same** `vscode-jsonrpc`
|
|
66
|
-
instance this package uses, and re-exports the entire public API
|
|
67
|
-
|
|
71
|
+
instance this package uses, and re-exports the entire public API **plus that
|
|
72
|
+
platform's transport classes** (`StreamMessageReader`/`Writer` for Node,
|
|
73
|
+
`BrowserMessageReader`/`Writer` for the browser) — so a host imports everything,
|
|
74
|
+
including its reader/writer, from a single place.
|
|
68
75
|
|
|
69
76
|
## Quick start
|
|
70
77
|
|
|
71
78
|
### Node host (over stdio)
|
|
72
79
|
|
|
73
80
|
```ts
|
|
74
|
-
import {
|
|
75
|
-
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
|
|
81
|
+
import { fromStdio } from '@motrix/mdxp/node'
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
new StreamMessageWriter(process.stdout),
|
|
80
|
-
)
|
|
83
|
+
// Over process.stdin / process.stdout (the native-messaging / CLI case).
|
|
84
|
+
const conn = fromStdio()
|
|
81
85
|
|
|
82
86
|
// Register handlers BEFORE listen().
|
|
83
87
|
conn.onNotification('$/task/progress', (p) => {
|
|
@@ -88,21 +92,31 @@ conn.onNotification('$/task/progress', (p) => {
|
|
|
88
92
|
conn.listen()
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
### Browser host
|
|
95
|
+
### Browser host (over WebSocket)
|
|
92
96
|
|
|
93
97
|
```ts
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// e.g. a MessagePort / Worker; adapt your WebSocket to reader/writer as needed.
|
|
99
|
-
const conn = createMdxpConnection(
|
|
100
|
-
new BrowserMessageReader(worker),
|
|
101
|
-
new BrowserMessageWriter(worker),
|
|
102
|
-
)
|
|
98
|
+
import { fromWebSocket } from '@motrix/mdxp/browser'
|
|
99
|
+
|
|
100
|
+
const conn = fromWebSocket(new WebSocket('ws://127.0.0.1:16650/v1'))
|
|
101
|
+
conn.onNotification('$/task/progress', (p) => {})
|
|
103
102
|
conn.listen()
|
|
104
103
|
```
|
|
105
104
|
|
|
105
|
+
### Convenience constructors
|
|
106
|
+
|
|
107
|
+
`createMdxpConnection(reader, writer)` is the generic entry point — bring any
|
|
108
|
+
`vscode-jsonrpc` reader/writer. For the common transports, skip the boilerplate:
|
|
109
|
+
|
|
110
|
+
| Constructor | Entry | Transport |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `fromWebSocket(ws)` | `./node` · `./browser` | A browser `WebSocket` or a Node `ws` socket |
|
|
113
|
+
| `fromStdio(opts?)` | `./node` | `process.stdin` / `process.stdout`, or given streams |
|
|
114
|
+
| `fromWorker(port)` | `./browser` | A `Worker` or `MessagePort` |
|
|
115
|
+
|
|
116
|
+
Each returns a ready `MdxpConnection` — you still register handlers and call
|
|
117
|
+
`listen()`. For any other transport, build the reader/writer and call
|
|
118
|
+
`createMdxpConnection` directly.
|
|
119
|
+
|
|
106
120
|
## Core concepts
|
|
107
121
|
|
|
108
122
|
**Server vs. client.** The Motrix desktop app is the **server** — it owns the
|
|
@@ -245,7 +259,7 @@ conn.onNotification('$/task/error', (p) => {
|
|
|
245
259
|
handler observes `token.isCancellationRequested`.
|
|
246
260
|
|
|
247
261
|
```ts
|
|
248
|
-
import { CancellationTokenSource } from '
|
|
262
|
+
import { CancellationTokenSource } from '@motrix/mdxp'
|
|
249
263
|
|
|
250
264
|
const cts = new CancellationTokenSource()
|
|
251
265
|
const pending = conn.sendRequest('url/resolve', { url }, cts.token)
|
|
@@ -311,6 +325,7 @@ const tools = toAgentToolCatalog()
|
|
|
311
325
|
| Export | Kind | Purpose |
|
|
312
326
|
| --- | --- | --- |
|
|
313
327
|
| `createMdxpConnection(reader, writer)` | function | Wrap a reader/writer pair in a typed `MdxpConnection`. |
|
|
328
|
+
| `fromWebSocket` · `fromStdio` · `fromWorker` | function | One-call constructors over a WebSocket / stdio / Worker (from `./node` · `./browser`). |
|
|
314
329
|
| `MdxpConnection` | type | The connection interface (`sendRequest`, `onRequest`, `sendNotification`, `onNotification`, `dispose`, `raw`). |
|
|
315
330
|
| `MdxpRequestMap` / `MdxpNotificationMap` | type | Method/notification name → params/result type maps. |
|
|
316
331
|
| `Methods` / `Notifications` | const | Wire-name constants (`Methods.DownloadAdd === 'download/add'`). |
|
|
@@ -321,6 +336,7 @@ const tools = toAgentToolCatalog()
|
|
|
321
336
|
| `toAgentToolCatalog()` | function | The `agentFacing` subset as JSON-Schema tools. |
|
|
322
337
|
| `SERVER_INITIATED_METHODS` | const | Methods the server calls on the client (`url/probe`, `url/resolve`). |
|
|
323
338
|
| `*Schema` | Zod schema | Every wire shape, for runtime validation. |
|
|
339
|
+
| `MessageReader` · `MessageWriter` · `MessageConnection` · `CancellationToken` · `CancellationTokenSource` · `Disposable` | re-export | `vscode-jsonrpc` primitives used across the API. Platform transport classes (`StreamMessageReader`/`Writer`, `BrowserMessageReader`/`Writer`) are re-exported from `./node` and `./browser`. |
|
|
324
340
|
|
|
325
341
|
### Methods
|
|
326
342
|
|
package/README.zh-CN.md
CHANGED
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
调用处无需任何 cast。
|
|
29
29
|
- **transport-agnostic**:只要实现了 `MessageReader`/`MessageWriter`,任何双工流都能用。
|
|
30
30
|
- **平台 RAL 入口**:`./node` 与 `./browser` 会替你装好对应的 `vscode-jsonrpc`
|
|
31
|
-
runtime abstraction layer
|
|
31
|
+
runtime abstraction layer,并 re-export 它的 transport class —— 连接封装与
|
|
32
|
+
reader/writer 都从同一处 import。
|
|
33
|
+
- **一步构造**:`fromWebSocket`、`fromStdio`、`fromWorker` 一行即可在常见 transport
|
|
34
|
+
上建好连接;`createMdxpConnection` 仍是通用的底层入口。
|
|
32
35
|
- **面向 agent**:内置的 tool registry 可产出一份 JSON-Schema tool catalog,
|
|
33
36
|
能直接对接 LLM 的 function-calling API。
|
|
34
37
|
- **forward-compatible**:遇到未知的 method 或字段选择忽略,而非 reject。
|
|
@@ -41,10 +44,11 @@ npm install @motrix/mdxp
|
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
运行时依赖:[`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
|
|
44
|
-
`^9
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
`^9`,随本包一并装上。它的 transport class(reader/writer),以及在本包 API 中出现的
|
|
48
|
+
那些 primitive —— `MessageReader`、`MessageConnection`、`CancellationToken`、
|
|
49
|
+
`CancellationTokenSource` 等 —— 都已从 `@motrix/mdxp` re-export(见
|
|
50
|
+
[入口点](#入口点)),因此你几乎无需直接 import `vscode-jsonrpc`。仅 ESM;需要
|
|
51
|
+
Node.js ≥ 18 或现代 bundler。
|
|
48
52
|
|
|
49
53
|
### 入口点
|
|
50
54
|
|
|
@@ -56,21 +60,19 @@ npm install @motrix/mdxp
|
|
|
56
60
|
|
|
57
61
|
`vscode-jsonrpc` v9 要求先装好一层 runtime abstraction layer(RAL),才能创建连接。
|
|
58
62
|
import `@motrix/mdxp/node` 或 `@motrix/mdxp/browser` 会把对应的 RAL 装进本包所用的
|
|
59
|
-
**同一个** `vscode-jsonrpc` 实例,并 re-export 完整的公开 API
|
|
60
|
-
|
|
63
|
+
**同一个** `vscode-jsonrpc` 实例,并 re-export 完整的公开 API,**外加该平台的
|
|
64
|
+
transport class**(Node 的 `StreamMessageReader`/`Writer`、浏览器的
|
|
65
|
+
`BrowserMessageReader`/`Writer`)—— 于是 host 连同 reader/writer 都只需从一个入口 import。
|
|
61
66
|
|
|
62
67
|
## 快速开始
|
|
63
68
|
|
|
64
69
|
### Node host(走 stdio)
|
|
65
70
|
|
|
66
71
|
```ts
|
|
67
|
-
import {
|
|
68
|
-
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
|
|
72
|
+
import { fromStdio } from '@motrix/mdxp/node'
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
new StreamMessageWriter(process.stdout),
|
|
73
|
-
)
|
|
74
|
+
// 走 process.stdin / process.stdout(native-messaging / CLI 场景)。
|
|
75
|
+
const conn = fromStdio()
|
|
74
76
|
|
|
75
77
|
// 务必在 listen() 之前注册 handler。
|
|
76
78
|
conn.onNotification('$/task/progress', (p) => {
|
|
@@ -81,21 +83,30 @@ conn.onNotification('$/task/progress', (p) => {
|
|
|
81
83
|
conn.listen()
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
### 浏览器 host
|
|
86
|
+
### 浏览器 host(走 WebSocket)
|
|
85
87
|
|
|
86
88
|
```ts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// 例如一个 MessagePort / Worker;若用 WebSocket,需自行适配成 reader/writer。
|
|
92
|
-
const conn = createMdxpConnection(
|
|
93
|
-
new BrowserMessageReader(worker),
|
|
94
|
-
new BrowserMessageWriter(worker),
|
|
95
|
-
)
|
|
89
|
+
import { fromWebSocket } from '@motrix/mdxp/browser'
|
|
90
|
+
|
|
91
|
+
const conn = fromWebSocket(new WebSocket('ws://127.0.0.1:16650/v1'))
|
|
92
|
+
conn.onNotification('$/task/progress', (p) => {})
|
|
96
93
|
conn.listen()
|
|
97
94
|
```
|
|
98
95
|
|
|
96
|
+
### 便捷构造器
|
|
97
|
+
|
|
98
|
+
`createMdxpConnection(reader, writer)` 是通用入口 —— 传入任意 `vscode-jsonrpc` 的
|
|
99
|
+
reader/writer。常见 transport 则可直接省去样板:
|
|
100
|
+
|
|
101
|
+
| 构造器 | 入口 | Transport |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `fromWebSocket(ws)` | `./node` · `./browser` | 浏览器 `WebSocket` 或 Node 的 `ws` socket |
|
|
104
|
+
| `fromStdio(opts?)` | `./node` | `process.stdin` / `process.stdout`,或指定的流 |
|
|
105
|
+
| `fromWorker(port)` | `./browser` | 一个 `Worker` 或 `MessagePort` |
|
|
106
|
+
|
|
107
|
+
每个都返回开箱即用的 `MdxpConnection` —— 你仍需注册 handler 并调用 `listen()`。
|
|
108
|
+
其它 transport 则自行构造 reader/writer,再直接调用 `createMdxpConnection`。
|
|
109
|
+
|
|
99
110
|
## 核心概念
|
|
100
111
|
|
|
101
112
|
**server 与 client**:Motrix 桌面端是 **server** —— 下载引擎由它持有。**client**
|
|
@@ -232,7 +243,7 @@ conn.onNotification('$/task/error', (p) => {
|
|
|
232
243
|
`token.isCancellationRequested` 即可响应。
|
|
233
244
|
|
|
234
245
|
```ts
|
|
235
|
-
import { CancellationTokenSource } from '
|
|
246
|
+
import { CancellationTokenSource } from '@motrix/mdxp'
|
|
236
247
|
|
|
237
248
|
const cts = new CancellationTokenSource()
|
|
238
249
|
const pending = conn.sendRequest('url/resolve', { url }, cts.token)
|
|
@@ -296,6 +307,7 @@ const tools = toAgentToolCatalog()
|
|
|
296
307
|
| 导出 | 类型 | 作用 |
|
|
297
308
|
| --- | --- | --- |
|
|
298
309
|
| `createMdxpConnection(reader, writer)` | function | 把一对 reader/writer 封装成类型化的 `MdxpConnection`。 |
|
|
310
|
+
| `fromWebSocket` · `fromStdio` · `fromWorker` | function | 在 WebSocket / stdio / Worker 上的一步构造器(来自 `./node` · `./browser`)。 |
|
|
299
311
|
| `MdxpConnection` | type | 连接接口(`sendRequest`、`onRequest`、`sendNotification`、`onNotification`、`dispose`、`raw`)。 |
|
|
300
312
|
| `MdxpRequestMap` / `MdxpNotificationMap` | type | method / notification 名 → params/result 类型的映射表。 |
|
|
301
313
|
| `Methods` / `Notifications` | const | wire 名常量(`Methods.DownloadAdd === 'download/add'`)。 |
|
|
@@ -306,6 +318,7 @@ const tools = toAgentToolCatalog()
|
|
|
306
318
|
| `toAgentToolCatalog()` | function | 取 `agentFacing` 子集,转成 JSON-Schema tools。 |
|
|
307
319
|
| `SERVER_INITIATED_METHODS` | const | 由 server 向 client 发起的 method(`url/probe`、`url/resolve`)。 |
|
|
308
320
|
| `*Schema` | Zod schema | 全部 wire 形态,供运行时校验。 |
|
|
321
|
+
| `MessageReader` · `MessageWriter` · `MessageConnection` · `CancellationToken` · `CancellationTokenSource` · `Disposable` | re-export | 本包 API 中用到的 `vscode-jsonrpc` primitive。平台 transport class(`StreamMessageReader`/`Writer`、`BrowserMessageReader`/`Writer`)从 `./node` 与 `./browser` re-export。 |
|
|
309
322
|
|
|
310
323
|
### Methods
|
|
311
324
|
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import 'vscode-jsonrpc/browser';
|
|
2
|
+
export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
|
|
2
3
|
export * from './index.js';
|
|
4
|
+
export type { WorkerLike } from './transports/worker.js';
|
|
5
|
+
export { fromWorker } from './transports/worker.js';
|
|
6
|
+
export type { WebSocketLike } from './transports/ws.js';
|
|
7
|
+
export { fromWebSocket } from './transports/ws.js';
|
|
3
8
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAWA,OAAO,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAWA,OAAO,wBAAwB,CAAA;AAM/B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/browser.js
CHANGED
|
@@ -10,5 +10,13 @@
|
|
|
10
10
|
// `@motrix/mdxp/browser` once at startup. The full public API is re-exported, so
|
|
11
11
|
// `import { createMdxpConnection } from '@motrix/mdxp/browser'` also works.
|
|
12
12
|
import 'vscode-jsonrpc/browser';
|
|
13
|
+
// Browser transport classes, re-exported so a browser host imports its
|
|
14
|
+
// reader/writer from the same place as `createMdxpConnection` — no separate
|
|
15
|
+
// `vscode-jsonrpc` import, and guaranteed to be the instance this package
|
|
16
|
+
// installed the RAL into.
|
|
17
|
+
export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
|
|
13
18
|
export * from './index.js';
|
|
19
|
+
export { fromWorker } from './transports/worker.js';
|
|
20
|
+
// High-level convenience constructors for browser transports.
|
|
21
|
+
export { fromWebSocket } from './transports/ws.js';
|
|
14
22
|
//# sourceMappingURL=browser.js.map
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,4EAA4E;AAC5E,OAAO,wBAAwB,CAAA;AAE/B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,4EAA4E;AAC5E,OAAO,wBAAwB,CAAA;AAE/B,uEAAuE;AACvE,4EAA4E;AAC5E,0EAA0E;AAC1E,0BAA0B;AAC1B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAEnD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { Disposable, MessageConnection, MessageReader, MessageWriter, } from 'vscode-jsonrpc';
|
|
2
|
+
export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc';
|
|
1
3
|
export type { MdxpConnection, MdxpNotificationMap, MdxpRequestMap, } from './connection.js';
|
|
2
4
|
export { createMdxpConnection } from './connection.js';
|
|
3
5
|
export type { ErrorCode, MdxpError, MdxpErrorData } from './errors.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAC3E,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGrD,cAAc,oBAAoB,CAAA;AAClC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA;AAEnB,mBAAmB,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// MDXP — Motrix Download eXchange Protocol
|
|
2
2
|
// Public API surface for both Motrix desktop and the browser extension.
|
|
3
|
+
export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc';
|
|
3
4
|
export { createMdxpConnection } from './connection.js';
|
|
4
5
|
export { ErrorCodes, isMotrixError, isProtocolError, makeMdxpError, } from './errors.js';
|
|
5
6
|
export { Methods, Notifications } from './methods.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,wEAAwE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,wEAAwE;AAYxE,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAM3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,qDAAqD;AACrD,cAAc,oBAAoB,CAAA;AAGlC,wCAAwC;AACxC,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import 'vscode-jsonrpc/node';
|
|
2
|
+
export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
|
|
2
3
|
export * from './index.js';
|
|
4
|
+
export type { StdioOptions } from './transports/stdio.js';
|
|
5
|
+
export { fromStdio } from './transports/stdio.js';
|
|
6
|
+
export type { WebSocketLike } from './transports/ws.js';
|
|
7
|
+
export { fromWebSocket } from './transports/ws.js';
|
|
3
8
|
//# sourceMappingURL=node.d.ts.map
|
package/dist/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAYA,OAAO,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAYA,OAAO,qBAAqB,CAAA;AAK5B,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/node.js
CHANGED
|
@@ -11,5 +11,12 @@
|
|
|
11
11
|
// once at bootstrap. The full public API is re-exported for convenience, so
|
|
12
12
|
// `import { createMdxpConnection } from '@motrix/mdxp/node'` also works.
|
|
13
13
|
import 'vscode-jsonrpc/node';
|
|
14
|
+
// Node transport classes, re-exported so a Node host imports its reader/writer
|
|
15
|
+
// from the same place as `createMdxpConnection` — no separate `vscode-jsonrpc`
|
|
16
|
+
// import, and guaranteed to be the instance this package installed the RAL into.
|
|
17
|
+
export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
|
|
14
18
|
export * from './index.js';
|
|
19
|
+
// High-level convenience constructors for Node transports.
|
|
20
|
+
export { fromStdio } from './transports/stdio.js';
|
|
21
|
+
export { fromWebSocket } from './transports/ws.js';
|
|
15
22
|
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,4BAA4B;AAC5B,EAAE;AACF,mFAAmF;AACnF,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,qBAAqB,CAAA;AAE5B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,4BAA4B;AAC5B,EAAE;AACF,mFAAmF;AACnF,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,qBAAqB,CAAA;AAE5B,+EAA+E;AAC/E,+EAA+E;AAC/E,iFAAiF;AACjF,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA;AAE1B,2DAA2D;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MdxpConnection } from '../connection.js';
|
|
2
|
+
/** Streams for {@link fromStdio}; defaults to the process's stdio. */
|
|
3
|
+
export interface StdioOptions {
|
|
4
|
+
input?: NodeJS.ReadableStream;
|
|
5
|
+
output?: NodeJS.WritableStream;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create an {@link MdxpConnection} over Node streams — `process.stdin` /
|
|
9
|
+
* `process.stdout` by default. This is the native-messaging-host / CLI case:
|
|
10
|
+
* the parent process pipes JSON-RPC over the child's stdio.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const conn = fromStdio()
|
|
14
|
+
* conn.onRequest('url/resolve', async (params) => {})
|
|
15
|
+
* conn.listen()
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function fromStdio(options?: StdioOptions): MdxpConnection;
|
|
19
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,YAAiB,GAAG,cAAc,CAKpE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
|
|
2
|
+
import { createMdxpConnection } from '../connection.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create an {@link MdxpConnection} over Node streams — `process.stdin` /
|
|
5
|
+
* `process.stdout` by default. This is the native-messaging-host / CLI case:
|
|
6
|
+
* the parent process pipes JSON-RPC over the child's stdio.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const conn = fromStdio()
|
|
10
|
+
* conn.onRequest('url/resolve', async (params) => {})
|
|
11
|
+
* conn.listen()
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function fromStdio(options = {}) {
|
|
15
|
+
return createMdxpConnection(new StreamMessageReader(options.input ?? process.stdin), new StreamMessageWriter(options.output ?? process.stdout));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,UAAwB,EAAE;IAClD,OAAO,oBAAoB,CACzB,IAAI,mBAAmB,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,EACvD,IAAI,mBAAmB,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAC1D,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MdxpConnection } from '../connection.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural subset of a `MessagePort` / `Worker` /
|
|
4
|
+
* `DedicatedWorkerGlobalScope` — anything the browser RAL's reader/writer
|
|
5
|
+
* accept. Consumers pass their worker or port as-is.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkerLike {
|
|
8
|
+
postMessage(message: unknown): void;
|
|
9
|
+
addEventListener(type: 'message', listener: (event: {
|
|
10
|
+
readonly data: unknown;
|
|
11
|
+
}) => void): void;
|
|
12
|
+
removeEventListener(type: 'message', listener: (event: {
|
|
13
|
+
readonly data: unknown;
|
|
14
|
+
}) => void): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
|
|
18
|
+
* `postMessage` transport).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const conn = fromWorker(self) // inside a Web Worker
|
|
22
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
23
|
+
* conn.listen()
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function fromWorker(port: WorkerLike): MdxpConnection;
|
|
27
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/transports/worker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACnC,gBAAgB,CACd,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACpD,IAAI,CAAA;IACP,mBAAmB,CACjB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACpD,IAAI,CAAA;CACR;AAaD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAE3D"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
|
|
2
|
+
import { createMdxpConnection } from '../connection.js';
|
|
3
|
+
// vscode-jsonrpc types the port as `MessagePort | Worker | DedicatedWorkerGlobalScope`,
|
|
4
|
+
// which are DOM / WebWorker globals. This package targets `lib: ES2022` (no DOM),
|
|
5
|
+
// so re-type the constructors against the structural `WorkerLike` instead of
|
|
6
|
+
// pulling the DOM libs into every consumer.
|
|
7
|
+
const Reader = BrowserMessageReader;
|
|
8
|
+
const Writer = BrowserMessageWriter;
|
|
9
|
+
/**
|
|
10
|
+
* Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
|
|
11
|
+
* `postMessage` transport).
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const conn = fromWorker(self) // inside a Web Worker
|
|
15
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
16
|
+
* conn.listen()
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function fromWorker(port) {
|
|
20
|
+
return createMdxpConnection(new Reader(port), new Writer(port));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/transports/worker.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAmBvD,wFAAwF;AACxF,kFAAkF;AAClF,6EAA6E;AAC7E,4CAA4C;AAC5C,MAAM,MAAM,GAAG,oBAEG,CAAA;AAClB,MAAM,MAAM,GAAG,oBAEG,CAAA;AAElB;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,oBAAoB,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AACjE,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MdxpConnection } from '../connection.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural subset of a WebSocket shared by the browser `WebSocket`
|
|
4
|
+
* and the Node [`ws`](https://www.npmjs.com/package/ws) package. Consumers pass
|
|
5
|
+
* their socket as-is — no wrapping required.
|
|
6
|
+
*/
|
|
7
|
+
export interface WebSocketLike {
|
|
8
|
+
readonly readyState: number;
|
|
9
|
+
send(data: string): void;
|
|
10
|
+
close(code?: number, reason?: string): void;
|
|
11
|
+
addEventListener(type: 'message' | 'close' | 'error', listener: (event: {
|
|
12
|
+
readonly data?: unknown;
|
|
13
|
+
}) => void): void;
|
|
14
|
+
removeEventListener(type: 'message' | 'close' | 'error', listener: (event: {
|
|
15
|
+
readonly data?: unknown;
|
|
16
|
+
}) => void): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create an {@link MdxpConnection} over a WebSocket.
|
|
20
|
+
*
|
|
21
|
+
* Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
|
|
22
|
+
* the WebSocket protocol already frames messages, so there is no Content-Length
|
|
23
|
+
* framing. Works with the browser `WebSocket` and the Node `ws` package alike
|
|
24
|
+
* (both satisfy {@link WebSocketLike}).
|
|
25
|
+
*
|
|
26
|
+
* Register handlers first, then call `listen()`:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* const conn = fromWebSocket(ws)
|
|
30
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
31
|
+
* conn.listen()
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function fromWebSocket(ws: WebSocketLike): MdxpConnection;
|
|
35
|
+
//# sourceMappingURL=ws.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/transports/ws.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAMtD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,gBAAgB,CACd,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EACnC,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACrD,IAAI,CAAA;IACP,mBAAmB,CACjB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EACnC,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACrD,IAAI,CAAA;CACR;AAsID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,aAAa,GAAG,cAAc,CAK/D"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Emitter } from 'vscode-jsonrpc';
|
|
2
|
+
import { createMdxpConnection } from '../connection.js';
|
|
3
|
+
/** `WebSocket.readyState` value for an open socket (browser and `ws` agree). */
|
|
4
|
+
const OPEN = 1;
|
|
5
|
+
function toText(data) {
|
|
6
|
+
if (typeof data === 'string')
|
|
7
|
+
return data;
|
|
8
|
+
if (data instanceof ArrayBuffer)
|
|
9
|
+
return new TextDecoder().decode(data);
|
|
10
|
+
// A typed array / DataView / Node Buffer. TS 6's generic `ArrayBufferView`
|
|
11
|
+
// typing is stricter than `TextDecoder.decode`'s runtime contract (which
|
|
12
|
+
// accepts any view), so widen to satisfy the compiler.
|
|
13
|
+
if (ArrayBuffer.isView(data)) {
|
|
14
|
+
return new TextDecoder().decode(data);
|
|
15
|
+
}
|
|
16
|
+
throw new Error('unsupported WebSocket message data type');
|
|
17
|
+
}
|
|
18
|
+
class WebSocketMessageReader {
|
|
19
|
+
ws;
|
|
20
|
+
errorEmitter = new Emitter();
|
|
21
|
+
closeEmitter = new Emitter();
|
|
22
|
+
partialEmitter = new Emitter();
|
|
23
|
+
callback = null;
|
|
24
|
+
attached = false;
|
|
25
|
+
constructor(ws) {
|
|
26
|
+
this.ws = ws;
|
|
27
|
+
}
|
|
28
|
+
get onError() {
|
|
29
|
+
return this.errorEmitter.event;
|
|
30
|
+
}
|
|
31
|
+
get onClose() {
|
|
32
|
+
return this.closeEmitter.event;
|
|
33
|
+
}
|
|
34
|
+
get onPartialMessage() {
|
|
35
|
+
return this.partialEmitter.event;
|
|
36
|
+
}
|
|
37
|
+
onMessage = (event) => {
|
|
38
|
+
if (!this.callback)
|
|
39
|
+
return;
|
|
40
|
+
let message;
|
|
41
|
+
try {
|
|
42
|
+
message = JSON.parse(toText(event.data));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.errorEmitter.fire(error instanceof Error ? error : new Error(String(error)));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.callback(message);
|
|
49
|
+
};
|
|
50
|
+
onCloseEvent = () => {
|
|
51
|
+
this.closeEmitter.fire(undefined);
|
|
52
|
+
};
|
|
53
|
+
onErrorEvent = () => {
|
|
54
|
+
this.errorEmitter.fire(new Error('WebSocket error'));
|
|
55
|
+
};
|
|
56
|
+
listen(callback) {
|
|
57
|
+
if (this.attached) {
|
|
58
|
+
throw new Error('WebSocketMessageReader is already listening');
|
|
59
|
+
}
|
|
60
|
+
this.callback = callback;
|
|
61
|
+
this.ws.addEventListener('message', this.onMessage);
|
|
62
|
+
this.ws.addEventListener('close', this.onCloseEvent);
|
|
63
|
+
this.ws.addEventListener('error', this.onErrorEvent);
|
|
64
|
+
this.attached = true;
|
|
65
|
+
return { dispose: () => this.dispose() };
|
|
66
|
+
}
|
|
67
|
+
dispose() {
|
|
68
|
+
if (!this.attached)
|
|
69
|
+
return;
|
|
70
|
+
this.ws.removeEventListener('message', this.onMessage);
|
|
71
|
+
this.ws.removeEventListener('close', this.onCloseEvent);
|
|
72
|
+
this.ws.removeEventListener('error', this.onErrorEvent);
|
|
73
|
+
this.attached = false;
|
|
74
|
+
this.callback = null;
|
|
75
|
+
this.errorEmitter.dispose();
|
|
76
|
+
this.closeEmitter.dispose();
|
|
77
|
+
this.partialEmitter.dispose();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
class WebSocketMessageWriter {
|
|
81
|
+
ws;
|
|
82
|
+
errorEmitter = new Emitter();
|
|
83
|
+
closeEmitter = new Emitter();
|
|
84
|
+
attached = false;
|
|
85
|
+
onCloseEvent = () => {
|
|
86
|
+
this.closeEmitter.fire(undefined);
|
|
87
|
+
};
|
|
88
|
+
constructor(ws) {
|
|
89
|
+
this.ws = ws;
|
|
90
|
+
this.ws.addEventListener('close', this.onCloseEvent);
|
|
91
|
+
this.attached = true;
|
|
92
|
+
}
|
|
93
|
+
get onError() {
|
|
94
|
+
return this.errorEmitter.event;
|
|
95
|
+
}
|
|
96
|
+
get onClose() {
|
|
97
|
+
return this.closeEmitter.event;
|
|
98
|
+
}
|
|
99
|
+
async write(message) {
|
|
100
|
+
if (this.ws.readyState !== OPEN) {
|
|
101
|
+
throw new Error(`WebSocket is not open (readyState=${this.ws.readyState})`);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
this.ws.send(JSON.stringify(message));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
108
|
+
this.errorEmitter.fire([err, message, undefined]);
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
end() {
|
|
113
|
+
if (this.ws.readyState === OPEN)
|
|
114
|
+
this.ws.close();
|
|
115
|
+
}
|
|
116
|
+
dispose() {
|
|
117
|
+
if (!this.attached)
|
|
118
|
+
return;
|
|
119
|
+
this.ws.removeEventListener('close', this.onCloseEvent);
|
|
120
|
+
this.attached = false;
|
|
121
|
+
this.errorEmitter.dispose();
|
|
122
|
+
this.closeEmitter.dispose();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create an {@link MdxpConnection} over a WebSocket.
|
|
127
|
+
*
|
|
128
|
+
* Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
|
|
129
|
+
* the WebSocket protocol already frames messages, so there is no Content-Length
|
|
130
|
+
* framing. Works with the browser `WebSocket` and the Node `ws` package alike
|
|
131
|
+
* (both satisfy {@link WebSocketLike}).
|
|
132
|
+
*
|
|
133
|
+
* Register handlers first, then call `listen()`:
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* const conn = fromWebSocket(ws)
|
|
137
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
138
|
+
* conn.listen()
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function fromWebSocket(ws) {
|
|
142
|
+
return createMdxpConnection(new WebSocketMessageReader(ws), new WebSocketMessageWriter(ws));
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../src/transports/ws.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -11,4 +11,17 @@
|
|
|
11
11
|
// `import { createMdxpConnection } from '@motrix/mdxp/browser'` also works.
|
|
12
12
|
import 'vscode-jsonrpc/browser'
|
|
13
13
|
|
|
14
|
+
// Browser transport classes, re-exported so a browser host imports its
|
|
15
|
+
// reader/writer from the same place as `createMdxpConnection` — no separate
|
|
16
|
+
// `vscode-jsonrpc` import, and guaranteed to be the instance this package
|
|
17
|
+
// installed the RAL into.
|
|
18
|
+
export {
|
|
19
|
+
BrowserMessageReader,
|
|
20
|
+
BrowserMessageWriter,
|
|
21
|
+
} from 'vscode-jsonrpc/browser'
|
|
14
22
|
export * from './index.js'
|
|
23
|
+
export type { WorkerLike } from './transports/worker.js'
|
|
24
|
+
export { fromWorker } from './transports/worker.js'
|
|
25
|
+
export type { WebSocketLike } from './transports/ws.js'
|
|
26
|
+
// High-level convenience constructors for browser transports.
|
|
27
|
+
export { fromWebSocket } from './transports/ws.js'
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
// MDXP — Motrix Download eXchange Protocol
|
|
2
2
|
// Public API surface for both Motrix desktop and the browser extension.
|
|
3
3
|
|
|
4
|
+
// vscode-jsonrpc primitives that appear in this package's public API surface,
|
|
5
|
+
// re-exported so consumers import them from `@motrix/mdxp` instead of reaching
|
|
6
|
+
// for a second package. Platform transport classes (StreamMessageReader,
|
|
7
|
+
// BrowserMessageReader, …) are re-exported from `./node` and `./browser`.
|
|
8
|
+
export type {
|
|
9
|
+
Disposable,
|
|
10
|
+
MessageConnection,
|
|
11
|
+
MessageReader,
|
|
12
|
+
MessageWriter,
|
|
13
|
+
} from 'vscode-jsonrpc'
|
|
14
|
+
export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc'
|
|
4
15
|
export type {
|
|
5
16
|
MdxpConnection,
|
|
6
17
|
MdxpNotificationMap,
|
package/src/node.ts
CHANGED
|
@@ -12,4 +12,22 @@
|
|
|
12
12
|
// `import { createMdxpConnection } from '@motrix/mdxp/node'` also works.
|
|
13
13
|
import 'vscode-jsonrpc/node'
|
|
14
14
|
|
|
15
|
+
// Node transport classes, re-exported so a Node host imports its reader/writer
|
|
16
|
+
// from the same place as `createMdxpConnection` — no separate `vscode-jsonrpc`
|
|
17
|
+
// import, and guaranteed to be the instance this package installed the RAL into.
|
|
18
|
+
export {
|
|
19
|
+
IPCMessageReader,
|
|
20
|
+
IPCMessageWriter,
|
|
21
|
+
PortMessageReader,
|
|
22
|
+
PortMessageWriter,
|
|
23
|
+
SocketMessageReader,
|
|
24
|
+
SocketMessageWriter,
|
|
25
|
+
StreamMessageReader,
|
|
26
|
+
StreamMessageWriter,
|
|
27
|
+
} from 'vscode-jsonrpc/node'
|
|
15
28
|
export * from './index.js'
|
|
29
|
+
export type { StdioOptions } from './transports/stdio.js'
|
|
30
|
+
// High-level convenience constructors for Node transports.
|
|
31
|
+
export { fromStdio } from './transports/stdio.js'
|
|
32
|
+
export type { WebSocketLike } from './transports/ws.js'
|
|
33
|
+
export { fromWebSocket } from './transports/ws.js'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
|
|
2
|
+
import type { MdxpConnection } from '../connection.js'
|
|
3
|
+
import { createMdxpConnection } from '../connection.js'
|
|
4
|
+
|
|
5
|
+
/** Streams for {@link fromStdio}; defaults to the process's stdio. */
|
|
6
|
+
export interface StdioOptions {
|
|
7
|
+
input?: NodeJS.ReadableStream
|
|
8
|
+
output?: NodeJS.WritableStream
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create an {@link MdxpConnection} over Node streams — `process.stdin` /
|
|
13
|
+
* `process.stdout` by default. This is the native-messaging-host / CLI case:
|
|
14
|
+
* the parent process pipes JSON-RPC over the child's stdio.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const conn = fromStdio()
|
|
18
|
+
* conn.onRequest('url/resolve', async (params) => {})
|
|
19
|
+
* conn.listen()
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function fromStdio(options: StdioOptions = {}): MdxpConnection {
|
|
23
|
+
return createMdxpConnection(
|
|
24
|
+
new StreamMessageReader(options.input ?? process.stdin),
|
|
25
|
+
new StreamMessageWriter(options.output ?? process.stdout)
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MessageReader, MessageWriter } from 'vscode-jsonrpc'
|
|
2
|
+
import {
|
|
3
|
+
BrowserMessageReader,
|
|
4
|
+
BrowserMessageWriter,
|
|
5
|
+
} from 'vscode-jsonrpc/browser'
|
|
6
|
+
import type { MdxpConnection } from '../connection.js'
|
|
7
|
+
import { createMdxpConnection } from '../connection.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Minimal structural subset of a `MessagePort` / `Worker` /
|
|
11
|
+
* `DedicatedWorkerGlobalScope` — anything the browser RAL's reader/writer
|
|
12
|
+
* accept. Consumers pass their worker or port as-is.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorkerLike {
|
|
15
|
+
postMessage(message: unknown): void
|
|
16
|
+
addEventListener(
|
|
17
|
+
type: 'message',
|
|
18
|
+
listener: (event: { readonly data: unknown }) => void
|
|
19
|
+
): void
|
|
20
|
+
removeEventListener(
|
|
21
|
+
type: 'message',
|
|
22
|
+
listener: (event: { readonly data: unknown }) => void
|
|
23
|
+
): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// vscode-jsonrpc types the port as `MessagePort | Worker | DedicatedWorkerGlobalScope`,
|
|
27
|
+
// which are DOM / WebWorker globals. This package targets `lib: ES2022` (no DOM),
|
|
28
|
+
// so re-type the constructors against the structural `WorkerLike` instead of
|
|
29
|
+
// pulling the DOM libs into every consumer.
|
|
30
|
+
const Reader = BrowserMessageReader as unknown as new (
|
|
31
|
+
port: WorkerLike
|
|
32
|
+
) => MessageReader
|
|
33
|
+
const Writer = BrowserMessageWriter as unknown as new (
|
|
34
|
+
port: WorkerLike
|
|
35
|
+
) => MessageWriter
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
|
|
39
|
+
* `postMessage` transport).
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const conn = fromWorker(self) // inside a Web Worker
|
|
43
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
44
|
+
* conn.listen()
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function fromWorker(port: WorkerLike): MdxpConnection {
|
|
48
|
+
return createMdxpConnection(new Reader(port), new Writer(port))
|
|
49
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataCallback,
|
|
3
|
+
Disposable,
|
|
4
|
+
Message,
|
|
5
|
+
MessageReader,
|
|
6
|
+
MessageWriter,
|
|
7
|
+
PartialMessageInfo,
|
|
8
|
+
} from 'vscode-jsonrpc'
|
|
9
|
+
import { Emitter } from 'vscode-jsonrpc'
|
|
10
|
+
import type { MdxpConnection } from '../connection.js'
|
|
11
|
+
import { createMdxpConnection } from '../connection.js'
|
|
12
|
+
|
|
13
|
+
/** `WebSocket.readyState` value for an open socket (browser and `ws` agree). */
|
|
14
|
+
const OPEN = 1
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Minimal structural subset of a WebSocket shared by the browser `WebSocket`
|
|
18
|
+
* and the Node [`ws`](https://www.npmjs.com/package/ws) package. Consumers pass
|
|
19
|
+
* their socket as-is — no wrapping required.
|
|
20
|
+
*/
|
|
21
|
+
export interface WebSocketLike {
|
|
22
|
+
readonly readyState: number
|
|
23
|
+
send(data: string): void
|
|
24
|
+
close(code?: number, reason?: string): void
|
|
25
|
+
addEventListener(
|
|
26
|
+
type: 'message' | 'close' | 'error',
|
|
27
|
+
listener: (event: { readonly data?: unknown }) => void
|
|
28
|
+
): void
|
|
29
|
+
removeEventListener(
|
|
30
|
+
type: 'message' | 'close' | 'error',
|
|
31
|
+
listener: (event: { readonly data?: unknown }) => void
|
|
32
|
+
): void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toText(data: unknown): string {
|
|
36
|
+
if (typeof data === 'string') return data
|
|
37
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data)
|
|
38
|
+
// A typed array / DataView / Node Buffer. TS 6's generic `ArrayBufferView`
|
|
39
|
+
// typing is stricter than `TextDecoder.decode`'s runtime contract (which
|
|
40
|
+
// accepts any view), so widen to satisfy the compiler.
|
|
41
|
+
if (ArrayBuffer.isView(data)) {
|
|
42
|
+
return new TextDecoder().decode(data as unknown as Uint8Array)
|
|
43
|
+
}
|
|
44
|
+
throw new Error('unsupported WebSocket message data type')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class WebSocketMessageReader implements MessageReader {
|
|
48
|
+
private readonly errorEmitter = new Emitter<Error>()
|
|
49
|
+
private readonly closeEmitter = new Emitter<void>()
|
|
50
|
+
private readonly partialEmitter = new Emitter<PartialMessageInfo>()
|
|
51
|
+
private callback: DataCallback | null = null
|
|
52
|
+
private attached = false
|
|
53
|
+
|
|
54
|
+
constructor(private readonly ws: WebSocketLike) {}
|
|
55
|
+
|
|
56
|
+
get onError() {
|
|
57
|
+
return this.errorEmitter.event
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get onClose() {
|
|
61
|
+
return this.closeEmitter.event
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get onPartialMessage() {
|
|
65
|
+
return this.partialEmitter.event
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private readonly onMessage = (event: { readonly data?: unknown }): void => {
|
|
69
|
+
if (!this.callback) return
|
|
70
|
+
let message: Message
|
|
71
|
+
try {
|
|
72
|
+
message = JSON.parse(toText(event.data)) as Message
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.errorEmitter.fire(
|
|
75
|
+
error instanceof Error ? error : new Error(String(error))
|
|
76
|
+
)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
this.callback(message)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private readonly onCloseEvent = (): void => {
|
|
83
|
+
this.closeEmitter.fire(undefined)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private readonly onErrorEvent = (): void => {
|
|
87
|
+
this.errorEmitter.fire(new Error('WebSocket error'))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
listen(callback: DataCallback): Disposable {
|
|
91
|
+
if (this.attached) {
|
|
92
|
+
throw new Error('WebSocketMessageReader is already listening')
|
|
93
|
+
}
|
|
94
|
+
this.callback = callback
|
|
95
|
+
this.ws.addEventListener('message', this.onMessage)
|
|
96
|
+
this.ws.addEventListener('close', this.onCloseEvent)
|
|
97
|
+
this.ws.addEventListener('error', this.onErrorEvent)
|
|
98
|
+
this.attached = true
|
|
99
|
+
return { dispose: () => this.dispose() }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
dispose(): void {
|
|
103
|
+
if (!this.attached) return
|
|
104
|
+
this.ws.removeEventListener('message', this.onMessage)
|
|
105
|
+
this.ws.removeEventListener('close', this.onCloseEvent)
|
|
106
|
+
this.ws.removeEventListener('error', this.onErrorEvent)
|
|
107
|
+
this.attached = false
|
|
108
|
+
this.callback = null
|
|
109
|
+
this.errorEmitter.dispose()
|
|
110
|
+
this.closeEmitter.dispose()
|
|
111
|
+
this.partialEmitter.dispose()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class WebSocketMessageWriter implements MessageWriter {
|
|
116
|
+
private readonly errorEmitter = new Emitter<
|
|
117
|
+
[Error, Message | undefined, number | undefined]
|
|
118
|
+
>()
|
|
119
|
+
private readonly closeEmitter = new Emitter<void>()
|
|
120
|
+
private attached = false
|
|
121
|
+
|
|
122
|
+
private readonly onCloseEvent = (): void => {
|
|
123
|
+
this.closeEmitter.fire(undefined)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
constructor(private readonly ws: WebSocketLike) {
|
|
127
|
+
this.ws.addEventListener('close', this.onCloseEvent)
|
|
128
|
+
this.attached = true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get onError() {
|
|
132
|
+
return this.errorEmitter.event
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get onClose() {
|
|
136
|
+
return this.closeEmitter.event
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async write(message: Message): Promise<void> {
|
|
140
|
+
if (this.ws.readyState !== OPEN) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`WebSocket is not open (readyState=${this.ws.readyState})`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
this.ws.send(JSON.stringify(message))
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
149
|
+
this.errorEmitter.fire([err, message, undefined])
|
|
150
|
+
throw err
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
end(): void {
|
|
155
|
+
if (this.ws.readyState === OPEN) this.ws.close()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
dispose(): void {
|
|
159
|
+
if (!this.attached) return
|
|
160
|
+
this.ws.removeEventListener('close', this.onCloseEvent)
|
|
161
|
+
this.attached = false
|
|
162
|
+
this.errorEmitter.dispose()
|
|
163
|
+
this.closeEmitter.dispose()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create an {@link MdxpConnection} over a WebSocket.
|
|
169
|
+
*
|
|
170
|
+
* Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
|
|
171
|
+
* the WebSocket protocol already frames messages, so there is no Content-Length
|
|
172
|
+
* framing. Works with the browser `WebSocket` and the Node `ws` package alike
|
|
173
|
+
* (both satisfy {@link WebSocketLike}).
|
|
174
|
+
*
|
|
175
|
+
* Register handlers first, then call `listen()`:
|
|
176
|
+
*
|
|
177
|
+
* ```ts
|
|
178
|
+
* const conn = fromWebSocket(ws)
|
|
179
|
+
* conn.onNotification('$/task/progress', (p) => {})
|
|
180
|
+
* conn.listen()
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function fromWebSocket(ws: WebSocketLike): MdxpConnection {
|
|
184
|
+
return createMdxpConnection(
|
|
185
|
+
new WebSocketMessageReader(ws),
|
|
186
|
+
new WebSocketMessageWriter(ws)
|
|
187
|
+
)
|
|
188
|
+
}
|