@univerjs-pro/collaboration-transport-node 1.0.0-insiders.20260808-70eefdd → 1.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/README.md +244 -0
- package/README.zh-CN.md +261 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +52 -24
- package/dist/index.mjs +1 -1
- package/package.json +2 -5
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# @univerjs-pro/collaboration-transport-node
|
|
2
|
+
|
|
3
|
+
English | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
The Node.js HTTP/WebSocket ingress for a Univer Collaboration server assembly.
|
|
6
|
+
|
|
7
|
+
Transport attaches to a host Node HTTP/HTTPS server and handles matching HTTP requests and WebSocket connections. Applications integrate authentication, logging, and other ingress concerns through HTTP middleware, then registered protocol Endpoints handle Collaboration requests. Transport is not normally used by itself:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Node HTTP server
|
|
11
|
+
→ Transport
|
|
12
|
+
├─→ HTTP middleware login authentication, CORS, trace, network logs
|
|
13
|
+
│ → Endpoint HTTP protocol routes; calls Service APIs as needed
|
|
14
|
+
│ → Service middleware / Database
|
|
15
|
+
└─→ Endpoint WebSocket Sessions, JOIN, Presence, ACKs, broadcasts
|
|
16
|
+
→ Endpoint middleware realtime-room ACL and Presence controls
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Server assembly
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add \
|
|
23
|
+
@univerjs-pro/collaboration-service \
|
|
24
|
+
@univerjs-pro/collaboration-endpoint \
|
|
25
|
+
@univerjs-pro/collaboration-transport-node \
|
|
26
|
+
@univerjs-pro/collaboration-database-memory
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createServer } from 'node:http';
|
|
31
|
+
import { MemoryDatabaseAdapter } from '@univerjs-pro/collaboration-database-memory';
|
|
32
|
+
import { UniverCollabEndpoint } from '@univerjs-pro/collaboration-endpoint';
|
|
33
|
+
import { UniverCollabService } from '@univerjs-pro/collaboration-service';
|
|
34
|
+
import { createNodeTransport } from '@univerjs-pro/collaboration-transport-node';
|
|
35
|
+
|
|
36
|
+
const database = new MemoryDatabaseAdapter();
|
|
37
|
+
const collabService = new UniverCollabService({ dbAdapter: database });
|
|
38
|
+
const endpoint = new UniverCollabEndpoint(collabService);
|
|
39
|
+
const transport = createNodeTransport();
|
|
40
|
+
|
|
41
|
+
// use() handles HTTP requests only. Authenticate and attach request-scoped data here.
|
|
42
|
+
transport.use(async (ctx, next) => {
|
|
43
|
+
const user = await authenticate(ctx.incomingMessage);
|
|
44
|
+
if (!user) {
|
|
45
|
+
ctx.response.statusCode = 401;
|
|
46
|
+
ctx.response.end('Authentication required');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Map the application's stable user primary key at the SDK boundary.
|
|
51
|
+
ctx.userID = user.userId;
|
|
52
|
+
ctx.customData.user = user;
|
|
53
|
+
ctx.customData.traceId = readTraceId(ctx.incomingMessage);
|
|
54
|
+
await next();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// useUpgrade() runs once per WebSocket handshake and can reject before upgrade.
|
|
58
|
+
transport.useUpgrade(async (ctx, next) => {
|
|
59
|
+
if (!isAllowedOrigin(ctx.incomingMessage.headers.origin)) {
|
|
60
|
+
ctx.reject(403, 'Origin rejected');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await next();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
endpoint.use('joinUnit', async (ctx, next) => {
|
|
67
|
+
// Transport ctx.userID → ctx.session.userID
|
|
68
|
+
// Transport ctx.customData → ctx.session.customData
|
|
69
|
+
console.log(ctx.session.userID, ctx.session.customData.user, ctx.session.customData.traceId);
|
|
70
|
+
await next();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
collabService.use('readUnitData', async (ctx, next) => {
|
|
74
|
+
// Transport ctx.userID → Service ctx.userID
|
|
75
|
+
// Transport ctx.customData → Service ctx.customData
|
|
76
|
+
console.log(ctx.userID, ctx.customData.user, ctx.customData.traceId);
|
|
77
|
+
await next();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
transport.register(endpoint);
|
|
81
|
+
|
|
82
|
+
const server = createServer();
|
|
83
|
+
transport.attach(server);
|
|
84
|
+
server.listen(3010);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The Memory Adapter keeps this Transport example small; process exit loses all data. See
|
|
88
|
+
[Database Adapters](https://office.univer.ai/collaboration/database-adapters) for production
|
|
89
|
+
persistence choices.
|
|
90
|
+
|
|
91
|
+
Transport does not prescribe cookies, bearer tokens, user tables, or user types. The application validates its own credentials in HTTP middleware and explicitly writes the stable business identity to `ctx.userID`. On authentication failure, end the response without calling `next()`.
|
|
92
|
+
|
|
93
|
+
## How identity and customData reach Endpoint and Service
|
|
94
|
+
|
|
95
|
+
Transport creates a separate `ctx.customData` for every HTTP request. HTTP middleware may add the current request's user, tenant, trace, or ACL query objects.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
regular protocol HTTP request
|
|
99
|
+
→ Transport middleware sets userID/customData
|
|
100
|
+
→ Endpoint validates the protocol and calls Service
|
|
101
|
+
→ Service middleware reads the same userID/customData
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
WebSocket identity is inherited from a one-time ticket, not directly from the upgrade URL or client payload:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
userID/customData on the ticket HTTP request
|
|
108
|
+
→ Endpoint stores the association server-side and returns an opaque ticket string
|
|
109
|
+
→ WebSocket handshake consumes the association once
|
|
110
|
+
→ Endpoint creates Session { userID, memberID, customData }
|
|
111
|
+
→ Endpoint middleware reads ctx.session
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The ticket string contains neither `userID` nor `customData`. Only the session-ticket HTTP route extends the current Transport context into a WebSocket Session; other HTTP routes do not.
|
|
115
|
+
|
|
116
|
+
Transport network-data lifetimes are:
|
|
117
|
+
|
|
118
|
+
- HTTP `ctx.customData` belongs only to the current request; every later HTTP request runs authentication middleware again.
|
|
119
|
+
- `EndpointSession.customData` comes from the HTTP request that issued the ticket and keeps the same reference for the current WebSocket Session.
|
|
120
|
+
- For regular HTTP protocol routes, Service middleware receives that HTTP request's `ctx.customData`, not Session `customData` automatically.
|
|
121
|
+
|
|
122
|
+
`customData` is only passed in memory. It is not automatically sent to browsers, persisted in the collaboration database, or logged.
|
|
123
|
+
|
|
124
|
+
## Application framework integration
|
|
125
|
+
|
|
126
|
+
Attach Transport to your application's HTTP/HTTPS server to share a port between collaboration
|
|
127
|
+
and business APIs. The application framework handles business requests, while Transport handles
|
|
128
|
+
collaboration requests for registered Endpoints.
|
|
129
|
+
|
|
130
|
+
This Express example reuses the `transport` configured above with middleware and an Endpoint:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { createServer } from 'node:http';
|
|
134
|
+
import express from 'express';
|
|
135
|
+
|
|
136
|
+
const app = express();
|
|
137
|
+
app.get('/health', (_request, response) => {
|
|
138
|
+
response.send('OK');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const server = createServer(app);
|
|
142
|
+
transport.attach(server);
|
|
143
|
+
server.listen(3010);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`attach(server)` connects both collaboration HTTP requests and WebSocket connections, without
|
|
147
|
+
registering protocol routes again in Express. Requests are handled as follows:
|
|
148
|
+
|
|
149
|
+
| Request | Handling |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| HTTP request matching an Endpoint route | Transport middleware → Endpoint |
|
|
152
|
+
| Other HTTP request, such as `/health` | Express middleware → business route or 404 |
|
|
153
|
+
| WebSocket upgrade matching an Endpoint route | Transport upgrade middleware → Endpoint |
|
|
154
|
+
| Other WebSocket upgrade | Other WebSocket services in the application |
|
|
155
|
+
|
|
156
|
+
HTTP routes match by method and path template. Once a request is handed to the SDK, the SDK
|
|
157
|
+
completes its success, rejection, or error response. A 404 does not send it back to Express.
|
|
158
|
+
|
|
159
|
+
Express middleware and Transport middleware apply to the requests each handles. Express
|
|
160
|
+
authentication, body parsing, CORS, and logging therefore do not automatically apply to
|
|
161
|
+
collaboration requests.
|
|
162
|
+
|
|
163
|
+
Create the application's server, call `attach()`, then start that server. Business routes can
|
|
164
|
+
still be registered within the framework; do not directly add or remove the server's `request`
|
|
165
|
+
listeners while attached.
|
|
166
|
+
|
|
167
|
+
Each server uses one Transport, with multiple Endpoints registered on it. For example,
|
|
168
|
+
Collaboration, History, Comment, and Worktree can share the network entry point and Transport middleware.
|
|
169
|
+
|
|
170
|
+
If the application has other WebSocket services, each service's `upgrade` listener should handle
|
|
171
|
+
only its own paths. Transport claims only registered collaboration WebSocket routes and neither
|
|
172
|
+
responds to nor closes other upgrade requests.
|
|
173
|
+
|
|
174
|
+
### Custom request dispatch
|
|
175
|
+
|
|
176
|
+
If your application already has a shared gateway or request dispatch logic, it can call
|
|
177
|
+
`handleRequest(request, response)` and `handleUpgrade(request, socket, head)` directly. Your
|
|
178
|
+
application decides which requests to pass to Transport, without calling `attach()`.
|
|
179
|
+
|
|
180
|
+
Pass raw Node requests with their full URL before a body parser reads the request body.
|
|
181
|
+
Transport completes the responses for these requests. Unhandled HTTP requests or WebSocket
|
|
182
|
+
upgrades default to HTTP 404; they do not return to the application framework for further routing.
|
|
183
|
+
|
|
184
|
+
## API layers
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
transport.use(httpMiddleware);
|
|
188
|
+
transport.useUpgrade(upgradeMiddleware);
|
|
189
|
+
transport.register(endpoint);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `use()`: regular HTTP requests only; callbacks directly receive `incomingMessage`, `response`, `userID`, and request-scoped `customData`.
|
|
193
|
+
- `useUpgrade()`: WebSocket handshakes only; read `incomingMessage` or call `reject(status, message)`.
|
|
194
|
+
- `register()`: register and own a protocol Endpoint that handles matching HTTP requests and WebSocket connections.
|
|
195
|
+
|
|
196
|
+
## Routes and execution order
|
|
197
|
+
|
|
198
|
+
Register authentication and observation middleware before the Endpoints they apply to.
|
|
199
|
+
`use()`, `useUpgrade()`, and `register()` follow registration order. `await next()` waits for
|
|
200
|
+
later middleware and handlers; a middleware can end its response without calling `next()`.
|
|
201
|
+
|
|
202
|
+
HTTP and upgrade middleware can read decoded path parameters from `ctx.params` and the full
|
|
203
|
+
route template from `ctx.route?.path`. Before matching, params is empty and route is undefined.
|
|
204
|
+
Use the exact protocol paths documented by each Endpoint, including case and slashes.
|
|
205
|
+
|
|
206
|
+
To measure HTTP duration, read route metadata when the response finishes:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
transport.use(async (ctx, next) => {
|
|
210
|
+
const start = performance.now();
|
|
211
|
+
ctx.response.once('finish', () => {
|
|
212
|
+
recordHttpDuration({
|
|
213
|
+
method: ctx.incomingMessage.method,
|
|
214
|
+
route: ctx.route?.path ?? 'unmatched',
|
|
215
|
+
status: ctx.response.statusCode,
|
|
216
|
+
durationMs: performance.now() - start,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
await next();
|
|
220
|
+
});
|
|
221
|
+
transport.use(authenticationMiddleware);
|
|
222
|
+
transport.register(collabEndpoint);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`recordHttpDuration` is an application callback. Requests rejected before routing and unknown
|
|
226
|
+
paths have no route. Observe `close` as well, deduplicating it with `finish`, to track interrupted
|
|
227
|
+
responses. Handler completion after `await next()` can precede response completion.
|
|
228
|
+
|
|
229
|
+
## Request limits and disposal
|
|
230
|
+
|
|
231
|
+
HTTP bodies and WebSocket messages both default to a 16 MiB limit. `readBody()` and `readJson()` enforce the limit and ensure the request stream is consumed only once.
|
|
232
|
+
|
|
233
|
+
Dispose from the network boundary inward during shutdown:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await transport.dispose();
|
|
237
|
+
await collabService.dispose();
|
|
238
|
+
await database.dispose();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Transport detaches, closes its active connections, and disposes Endpoints it owns through `register()`. The registration returned by `register()` can also unregister and dispose that Endpoint early. Endpoint does not dispose Service, and Service does not dispose an externally injected Database Adapter.
|
|
242
|
+
|
|
243
|
+
See the [Collaboration documentation](https://office.univer.ai/collaboration/overview) for the
|
|
244
|
+
complete assembly, middleware, and production requirements.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# @univerjs-pro/collaboration-transport-node
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Univer Collaboration 服务端组装中的 Node.js HTTP/WebSocket 入口。
|
|
6
|
+
|
|
7
|
+
Transport 挂载到宿主 Node HTTP/HTTPS server,处理匹配的 HTTP 请求和 WebSocket 连接。
|
|
8
|
+
应用通过 HTTP middleware 接入认证、日志等入口逻辑,再由注册的协议 Endpoint 处理 Collaboration 请求。
|
|
9
|
+
它通常不单独使用:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Node HTTP server
|
|
13
|
+
→ Transport
|
|
14
|
+
├─→ HTTP middleware 登录认证、CORS、trace 和网络日志
|
|
15
|
+
│ → Endpoint HTTP 协议路由,必要时调用 Service API
|
|
16
|
+
│ → Service middleware / Database
|
|
17
|
+
└─→ Endpoint WebSocket Session、JOIN、Presence、ACK 和广播
|
|
18
|
+
→ Endpoint middleware 实时房间 ACL 和 Presence 控制
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 服务端组装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add \
|
|
25
|
+
@univerjs-pro/collaboration-service \
|
|
26
|
+
@univerjs-pro/collaboration-endpoint \
|
|
27
|
+
@univerjs-pro/collaboration-transport-node \
|
|
28
|
+
@univerjs-pro/collaboration-database-memory
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { createServer } from 'node:http';
|
|
33
|
+
import { MemoryDatabaseAdapter } from '@univerjs-pro/collaboration-database-memory';
|
|
34
|
+
import { UniverCollabEndpoint } from '@univerjs-pro/collaboration-endpoint';
|
|
35
|
+
import { UniverCollabService } from '@univerjs-pro/collaboration-service';
|
|
36
|
+
import { createNodeTransport } from '@univerjs-pro/collaboration-transport-node';
|
|
37
|
+
|
|
38
|
+
const database = new MemoryDatabaseAdapter();
|
|
39
|
+
const collabService = new UniverCollabService({ dbAdapter: database });
|
|
40
|
+
const endpoint = new UniverCollabEndpoint(collabService);
|
|
41
|
+
const transport = createNodeTransport();
|
|
42
|
+
|
|
43
|
+
// use() 只接收 HTTP request;这里由应用认证并挂载身份和请求级数据。
|
|
44
|
+
transport.use(async (ctx, next) => {
|
|
45
|
+
const user = await authenticate(ctx.incomingMessage);
|
|
46
|
+
if (!user) {
|
|
47
|
+
ctx.response.statusCode = 401;
|
|
48
|
+
ctx.response.end('Authentication required');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 在进入 Collaboration SDK 时映射应用的稳定用户主键。
|
|
53
|
+
ctx.userID = user.userId;
|
|
54
|
+
ctx.customData.user = user;
|
|
55
|
+
ctx.customData.traceId = readTraceId(ctx.incomingMessage);
|
|
56
|
+
await next();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// useUpgrade() 每次 WebSocket handshake 运行一次,可在升级前拒绝。
|
|
60
|
+
transport.useUpgrade(async (ctx, next) => {
|
|
61
|
+
if (!isAllowedOrigin(ctx.incomingMessage.headers.origin)) {
|
|
62
|
+
ctx.reject(403, 'Origin rejected');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await next();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// WebSocket Session 发送 JOIN 消息、尚未加入 Unit 房间时,
|
|
69
|
+
// 会经过 joinUnit middleware;Session 数据继承自签发 ticket 的 HTTP 请求。
|
|
70
|
+
endpoint.use('joinUnit', async (ctx, next) => {
|
|
71
|
+
// Transport 挂载的 ctx.userID → ctx.session.userID
|
|
72
|
+
// Transport 挂载的 ctx.customData → ctx.session.customData
|
|
73
|
+
console.log(ctx.session.userID, ctx.session.customData.user, ctx.session.customData.traceId);
|
|
74
|
+
await next();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Endpoint 通过 HTTP 读取 Unit、changesets 或 Sheet block,
|
|
78
|
+
// Service 尚未访问 Database Adapter 时,会经过 readUnitData middleware。
|
|
79
|
+
collabService.use('readUnitData', async (ctx, next) => {
|
|
80
|
+
// Transport 挂载的 ctx.userID → Service ctx.userID
|
|
81
|
+
// Transport 挂载的 ctx.customData → Service ctx.customData
|
|
82
|
+
console.log(ctx.userID, ctx.customData.user, ctx.customData.traceId);
|
|
83
|
+
await next();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Endpoint 通过独立入口注册,并由 Transport 管理生命周期。
|
|
87
|
+
transport.register(endpoint);
|
|
88
|
+
|
|
89
|
+
const server = createServer();
|
|
90
|
+
transport.attach(server);
|
|
91
|
+
server.listen(3010);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
这里使用 Memory Adapter 只是为了让 Transport 示例保持最小;进程退出后数据会丢失。
|
|
95
|
+
生产环境的持久化选择见文档站的
|
|
96
|
+
[Database Adapter](https://office.univer.ai/zh-CN/collaboration/database-adapters)。
|
|
97
|
+
|
|
98
|
+
Transport 不规定 Cookie、Bearer token、用户表或用户类型。应用在 HTTP
|
|
99
|
+
middleware 中验证自己的凭据,再把稳定业务身份显式写入
|
|
100
|
+
`ctx.userID`。认证失败时直接结束 response,不要继续调用 `next()`。
|
|
101
|
+
|
|
102
|
+
## 身份与 customData 如何进入 Endpoint 和 Service
|
|
103
|
+
|
|
104
|
+
Transport 为每个 HTTP 请求创建独立的 `ctx.customData`。HTTP middleware 可以在
|
|
105
|
+
其中放入当前请求需要的用户、tenant、trace 或 ACL 查询对象。
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
普通协议 HTTP 请求
|
|
109
|
+
→ Transport middleware 设置 userID/customData
|
|
110
|
+
→ Endpoint 验证协议并调用 Service
|
|
111
|
+
→ Service middleware 读取同一 userID/customData
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
WebSocket 身份不直接取自 upgrade URL 或客户端 payload,而是由 Endpoint 通过一次性
|
|
115
|
+
ticket 继承:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
ticket HTTP 请求上的 userID/customData
|
|
119
|
+
→ Endpoint 在服务端保存关联并返回 opaque ticket 字符串
|
|
120
|
+
→ WebSocket 握手时一次性消费关联记录
|
|
121
|
+
→ Endpoint 创建 Session { userID, memberID, customData }
|
|
122
|
+
→ Endpoint middleware 通过 ctx.session 读取
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
ticket 字符串本身不包含 `userID/customData`。只有 session-ticket HTTP 路由会把
|
|
126
|
+
当前 Transport context 延长到 WebSocket Session;其他 HTTP 路由不会。
|
|
127
|
+
|
|
128
|
+
Transport 的网络数据生命周期如下:
|
|
129
|
+
|
|
130
|
+
- HTTP `ctx.customData` 只属于当前请求;每个后续 HTTP 请求都会重新运行认证
|
|
131
|
+
middleware。
|
|
132
|
+
- `EndpointSession.customData` 来自签发 ticket 的那次 HTTP 请求,在当前
|
|
133
|
+
WebSocket Session 中保持同一引用。
|
|
134
|
+
- Endpoint 处理普通 HTTP 协议路由时,传给 Service middleware 的是该 HTTP 请求的
|
|
135
|
+
`ctx.customData`,不是自动取 Session `customData`。
|
|
136
|
+
|
|
137
|
+
`customData` 只在内存中传递,不会自动发送给浏览器、写入协同数据库或记录日志。
|
|
138
|
+
|
|
139
|
+
## 与应用框架集成
|
|
140
|
+
|
|
141
|
+
将 Transport 挂载到应用的 HTTP/HTTPS Server,即可让协同接口与业务接口共用一个端口。
|
|
142
|
+
应用框架处理业务请求,Transport 处理已注册 Endpoint 的协同请求。
|
|
143
|
+
|
|
144
|
+
下面复用上文已配置 middleware 和 Endpoint 的 `transport`,以 Express 为例:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { createServer } from 'node:http';
|
|
148
|
+
import express from 'express';
|
|
149
|
+
|
|
150
|
+
const app = express();
|
|
151
|
+
app.get('/health', (_request, response) => {
|
|
152
|
+
response.send('OK');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const server = createServer(app);
|
|
156
|
+
transport.attach(server);
|
|
157
|
+
server.listen(3010);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`attach(server)` 同时接入协同 HTTP 请求和 WebSocket 连接,无需在 Express 中重新注册
|
|
161
|
+
协议路由。挂载后的请求处理方式如下:
|
|
162
|
+
|
|
163
|
+
| 请求 | 处理方式 |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| 匹配 Endpoint 路由的 HTTP 请求 | Transport middleware → Endpoint |
|
|
166
|
+
| 其他 HTTP 请求,例如 `/health` | Express middleware → 业务路由或 404 |
|
|
167
|
+
| 匹配 Endpoint 路由的 WebSocket upgrade | Transport upgrade middleware → Endpoint |
|
|
168
|
+
| 其他 WebSocket upgrade | 由应用中的其他 WebSocket 服务处理 |
|
|
169
|
+
|
|
170
|
+
HTTP 路由按 method 和路径模板匹配。已经交给 SDK 的请求,其成功、拒绝和错误响应
|
|
171
|
+
都由 SDK 完成,不会因返回 404 而再次交给 Express。
|
|
172
|
+
|
|
173
|
+
Express middleware 与 Transport middleware 分别作用于各自处理的请求,因此 Express
|
|
174
|
+
的认证、body parser、CORS 和日志不会自动作用于协同请求。
|
|
175
|
+
|
|
176
|
+
先创建应用的 Server,再调用 `attach()`,最后启动这个 Server。框架内部仍可正常
|
|
177
|
+
注册业务路由;挂载后不要直接增删 Server 的 `request` listeners。
|
|
178
|
+
|
|
179
|
+
一个 Server 使用一个 Transport,多个 Endpoint 注册到同一个 Transport。例如
|
|
180
|
+
Collaboration、History、Comment 和 Worktree 可以共享网络入口及 Transport middleware。
|
|
181
|
+
|
|
182
|
+
如果应用还有其他 WebSocket 服务,各服务的 `upgrade` listener 应只处理自己的路径。
|
|
183
|
+
Transport 只接管已注册的协同 WebSocket 路由,不响应或关闭其他 upgrade 请求。
|
|
184
|
+
|
|
185
|
+
### 自定义请求分发
|
|
186
|
+
|
|
187
|
+
如果应用已有统一的网关或请求分发逻辑,可以直接调用 `handleRequest(request, response)`
|
|
188
|
+
和 `handleUpgrade(request, socket, head)`,由应用决定哪些请求交给 Transport,
|
|
189
|
+
无需调用 `attach()`。
|
|
190
|
+
|
|
191
|
+
传入原始 Node 请求并保留完整 URL,在 body parser 读取请求体之前完成转交。
|
|
192
|
+
Transport 负责完成这些请求的响应;未处理的 HTTP 请求或 WebSocket upgrade 默认返回
|
|
193
|
+
HTTP 404,不会返回应用框架继续匹配路由。
|
|
194
|
+
|
|
195
|
+
## API 分层
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
transport.use(httpMiddleware);
|
|
199
|
+
transport.useUpgrade(upgradeMiddleware);
|
|
200
|
+
transport.register(endpoint);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- `use()`:仅普通 HTTP request;回调直接获得 `incomingMessage`、`response`、
|
|
204
|
+
`userID` 和请求级 `customData`。
|
|
205
|
+
- `useUpgrade()`:仅 WebSocket handshake;可读取 `incomingMessage`,或调用
|
|
206
|
+
`reject(status, message)`。
|
|
207
|
+
- `register()`:注册并管理协议 Endpoint,由 Endpoint 处理匹配的 HTTP 请求和 WebSocket
|
|
208
|
+
连接。
|
|
209
|
+
|
|
210
|
+
## 路由与执行顺序
|
|
211
|
+
|
|
212
|
+
将认证、观测 middleware 注册在需要处理的 Endpoint 前。`use()`、`useUpgrade()` 和
|
|
213
|
+
`register()` 按注册顺序执行。`await next()` 等待后续 middleware 和 handler 完成;
|
|
214
|
+
middleware 也可以自行结束响应并省略 `next()`。
|
|
215
|
+
|
|
216
|
+
HTTP 和 upgrade middleware 可以通过 `ctx.params` 读取解码后的路径参数,通过
|
|
217
|
+
`ctx.route?.path` 读取完整路由模板。匹配前 params 为空,route 未定义。
|
|
218
|
+
请求应使用各 Endpoint 文档列出的精确协议路径,包括大小写和斜杠。
|
|
219
|
+
|
|
220
|
+
统计 HTTP 耗时时,在响应结束时读取路由元数据:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
transport.use(async (ctx, next) => {
|
|
224
|
+
const start = performance.now();
|
|
225
|
+
ctx.response.once('finish', () => {
|
|
226
|
+
recordHttpDuration({
|
|
227
|
+
method: ctx.incomingMessage.method,
|
|
228
|
+
route: ctx.route?.path ?? 'unmatched',
|
|
229
|
+
status: ctx.response.statusCode,
|
|
230
|
+
durationMs: performance.now() - start,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
await next();
|
|
234
|
+
});
|
|
235
|
+
transport.use(authenticationMiddleware);
|
|
236
|
+
transport.register(collabEndpoint);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`recordHttpDuration` 是应用提供的回调。路由匹配前被拒绝的请求和未知路径没有 route。
|
|
240
|
+
要记录中途断开的响应,可监听 `close` 并与 `finish` 去重。`await next()` 后的 handler
|
|
241
|
+
完成时机可能早于响应完成。
|
|
242
|
+
|
|
243
|
+
## 请求限制与资源释放
|
|
244
|
+
|
|
245
|
+
HTTP body 和 WebSocket message 限制默认都是 16 MiB。`readBody()` 和 `readJson()` 会
|
|
246
|
+
执行大小限制,并保证请求流只消费一次。
|
|
247
|
+
|
|
248
|
+
停止时从网络入口向持久化层反向释放:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
await transport.dispose();
|
|
252
|
+
await collabService.dispose();
|
|
253
|
+
await database.dispose();
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Transport 会解除挂载、终止自己的活动连接并释放它通过 `register()` 拥有的 Endpoint;
|
|
257
|
+
`register()` 返回的 registration 也可提前注销并释放该 Endpoint。Endpoint 不释放 Service,Service 也不释放
|
|
258
|
+
外部注入的 Database Adapter。
|
|
259
|
+
|
|
260
|
+
完整服务端装配、middleware 和生产环境要求见
|
|
261
|
+
[协同编辑文档](https://office.univer.ai/zh-CN/collaboration/overview)。
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object['defineProperty'](exports,Symbol['toStringTag'],{'value':'Module'});let e=require('node:crypto'),t=require('ws');function n(_0x292255){return typeof _0x292255=='function'?_0x292255:_0x292255['handle']['bind'](_0x292255);}async function r(_0x2548cb,_0x42813a){let _0x57f356=_0x2548cb['filter'](_0x48cacb=>!_0x48cacb['disposed'])['map'](_0x20fff2=>n(_0x20fff2['value'])),_0x35e739=async _0x1fa5c8=>{let _0x4a8e7b=_0x57f356[_0x1fa5c8];if(!_0x4a8e7b)return;let _0x4c9143=!0x1,_0x39ed79,_0x357e4c=()=>{if(_0x4c9143)throw Error('Transport\x20middleware\x20called\x20next()\x20more\x20than\x20once');return _0x4c9143=!0x0,_0x39ed79=_0x35e739(_0x1fa5c8+0x1),_0x39ed79;};try{await _0x4a8e7b(_0x42813a,_0x357e4c);}catch(_0x4a7a0e){if(_0x39ed79)try{await _0x39ed79;}catch{}throw _0x4a7a0e;}_0x39ed79&&await _0x39ed79;};await _0x35e739(0x0);}var i=0x10*0x400*0x400,a=class{['socket'];['id']=(0x0,e['randomUUID'])();['customData']={};['_closed']=!0x1;constructor(_0x44105b){this['socket']=_0x44105b;}['send'](_0x54f481){return this['_closed']||this['socket']['readyState']!==this['socket']['OPEN']?Promise['reject'](Error('WebSocket\x20connection\x20is\x20not\x20open')):new Promise((_0x4f3557,_0x586752)=>{this['socket']['send'](_0x54f481,_0x10c229=>{_0x10c229?_0x586752(_0x10c229):_0x4f3557();});});}['close'](_0x2dab0c,_0x4600c2){this['_closed']||(this['_closed']=!0x0,(this['socket']['readyState']===this['socket']['OPEN']||this['socket']['readyState']===this['socket']['CONNECTING'])&&this['socket']['close'](_0x2dab0c,_0x4600c2));}['terminate'](){this['_closed']=!0x0,this['socket']['readyState']!==this['socket']['CLOSED']&&this['socket']['terminate']();}};function o(_0x4721b5,_0x62c9c4){if(_0x4721b5===void 0x0)return _0x62c9c4;if(!Number['isSafeInteger'](_0x4721b5)||_0x4721b5<=0x0)throw TypeError('Transport\x20byte\x20limits\x20must\x20be\x20positive\x20safe\x20integers');return _0x4721b5;}function s(_0x45e6b7,_0x49dd56){let _0x83826f;return()=>(_0x83826f??=new Promise((_0x5b94c5,_0x20098d)=>{let _0x1bd198=_0x45e6b7['headers']['content-length'];if(_0x1bd198!==void 0x0&&Number['isFinite'](Number(_0x1bd198))&&Number(_0x1bd198)>_0x49dd56){_0x45e6b7['resume'](),_0x20098d(new f('REQUEST_BODY_TOO_LARGE','Request\x20body\x20exceeds\x20'+_0x49dd56+'\x20bytes'));return;}let _0x4764da=[],_0x316c49=0x0,_0x5aa051=!0x1,_0x5751a6=_0x36cf03=>{_0x5aa051||(_0x5aa051=!0x0,_0x20098d(_0x36cf03));};_0x45e6b7['on']('data',_0x3a8b91=>{if(_0x5aa051)return;let _0x43d353=typeof _0x3a8b91=='string'?Buffer['from'](_0x3a8b91):_0x3a8b91;if(_0x316c49+=_0x43d353['byteLength'],_0x316c49>_0x49dd56){_0x45e6b7['resume'](),_0x5751a6(new f('REQUEST_BODY_TOO_LARGE','Request\x20body\x20exceeds\x20'+_0x49dd56+'\x20bytes'));return;}_0x4764da['push'](_0x43d353);}),_0x45e6b7['once']('end',()=>{_0x5aa051||(_0x5aa051=!0x0,_0x5b94c5(Buffer['concat'](_0x4764da,_0x316c49)));}),_0x45e6b7['once']('aborted',()=>{_0x5751a6(new f('REQUEST_ABORTED','Request\x20was\x20aborted'));}),_0x45e6b7['once']('error',_0x3d27b1=>{_0x5751a6(new f('REQUEST_ABORTED','Request\x20stream\x20failed',{'cause':_0x3d27b1}));});}),_0x83826f);}function c(_0xa82add){return async()=>{let _0x4bf227=await _0xa82add();try{return JSON['parse'](Buffer['from'](_0x4bf227)['toString']('utf8'));}catch(_0x114fb8){throw new f('INVALID_JSON','Request\x20body\x20is\x20not\x20valid\x20JSON',{'cause':_0x114fb8});}};}function l(_0x707bda){return Array['isArray'](_0x707bda)?Buffer['concat'](_0x707bda):_0x707bda instanceof ArrayBuffer?Buffer['from'](_0x707bda):Buffer['from'](_0x707bda['buffer'],_0x707bda['byteOffset'],_0x707bda['byteLength']);}var u=class{['_entries']=[];['_webSocketServer'];['_connections']=new Set();['_eventQueues']=new Map();['_failedConnections']=new Set();['_connectionClosed']=new Map();['_maxRequestBodyBytes'];['_disposed']=!0x1;constructor(_0x23e8f9={}){this['_maxRequestBodyBytes']=o(_0x23e8f9['maxRequestBodyBytes'],i);let _0x583d89=o(_0x23e8f9['maxWebSocketMessageBytes'],this['_maxRequestBodyBytes']);this['_webSocketServer']=new t['WebSocketServer']({'noServer':!0x0,'maxPayload':_0x583d89});}['use'](_0x2439e3){this['_assertRunning']();let _0x35bdfc={'value':_0x2439e3,'disposed':!0x1};return this['_entries']['push'](_0x35bdfc),{'dispose':async()=>{_0x35bdfc['disposed']||(_0x35bdfc['disposed']=!0x0,await this['_disposeMiddleware'](_0x35bdfc['value']));}};}['handleRequest'](_0x6510,_0x5796ce){if(this['_disposed']){this['_writeHttpError'](_0x5796ce,new f('TRANSPORT_DISPOSED','Transport\x20is\x20disposed'));return;}let _0x30a51a=s(_0x6510,this['_maxRequestBodyBytes']),_0x2447ef={'kind':'http','incomingMessage':_0x6510,'response':_0x5796ce,'customData':{},'readBody':_0x30a51a,'readJson':c(_0x30a51a)};this['_dispatch'](_0x2447ef)['catch'](_0x27d36a=>{this['_writeHttpError'](_0x5796ce,_0x27d36a);});}['handleUpgrade'](_0x454e1c,_0x3d448d,_0x4673b9){if(this['_disposed']){_0x3d448d['destroy']();return;}try{this['_webSocketServer']['handleUpgrade'](_0x454e1c,_0x3d448d,_0x4673b9,_0x5ed814=>{if(this['_disposed']){_0x5ed814['terminate']();return;}this['_acceptWebSocket'](_0x454e1c,_0x5ed814);});}catch{_0x3d448d['destroy']();}}async['dispose'](){if(this['_disposed'])return;this['_disposed']=!0x0;let _0xb9fd24=[...this['_connectionClosed']['values']()]['map'](_0x502ff1=>_0x502ff1['promise']);for(let _0xfc6ce8 of this['_connections'])_0xfc6ce8['terminate']();await Promise['allSettled'](_0xb9fd24),await Promise['allSettled']([...this['_eventQueues']['values']()]);let _0x5408e6=[...this['_entries']]['reverse']();for(let _0x3d2d4b of _0x5408e6)_0x3d2d4b['disposed']||(_0x3d2d4b['disposed']=!0x0,await this['_disposeMiddleware'](_0x3d2d4b['value']));this['_entries']['length']=0x0;}['_acceptWebSocket'](_0x1bbdaf,_0x3b019a){let _0x549183=new a(_0x3b019a);this['_connections']['add'](_0x549183),this['_eventQueues']['set'](_0x549183,Promise['resolve']());let _0x58fa81,_0x94af53=new Promise(_0x2c41b6=>{_0x58fa81=_0x2c41b6;});this['_connectionClosed']['set'](_0x549183,{'promise':_0x94af53,'resolve':_0x58fa81}),_0x3b019a['on']('message',(_0x84c7ab,_0x3a45b4)=>{let _0x462b90=l(_0x84c7ab),_0x33c105=_0x3a45b4?new Uint8Array(_0x462b90):_0x462b90['toString']('utf8');this['_enqueue'](_0x549183,{'kind':'websocket-message','connection':_0x549183,'data':_0x33c105,'customData':{}});}),_0x3b019a['once']('close',(_0x37d291,_0x34a37f)=>{this['_enqueue'](_0x549183,{'kind':'websocket-close','connection':_0x549183,'code':_0x37d291,'reason':_0x34a37f['toString']('utf8'),'customData':{}},!0x0);}),_0x3b019a['on']('error',()=>{}),this['_enqueue'](_0x549183,{'kind':'websocket-open','incomingMessage':_0x1bbdaf,'connection':_0x549183,'customData':{}});}['_enqueue'](_0x2c0928,_0x2305d9,_0x4aea58=!0x1){let _0x579a5f=(this['_eventQueues']['get'](_0x2c0928)??Promise['resolve']())['catch'](()=>void 0x0)['then'](async()=>{this['_failedConnections']['has'](_0x2c0928)&&_0x2305d9['kind']!=='websocket-close'||await this['_dispatch'](_0x2305d9);})['catch'](()=>{this['_failedConnections']['add'](_0x2c0928),_0x2c0928['close'](0x3f3,'Transport\x20middleware\x20failure');})['finally'](()=>{_0x4aea58&&(this['_connections']['delete'](_0x2c0928),this['_eventQueues']['delete'](_0x2c0928),this['_failedConnections']['delete'](_0x2c0928),this['_connectionClosed']['get'](_0x2c0928)?.['resolve'](),this['_connectionClosed']['delete'](_0x2c0928));});this['_eventQueues']['set'](_0x2c0928,_0x579a5f);}async['_dispatch'](_0x2472d1){if(this['_disposed']&&_0x2472d1['kind']!=='websocket-close')throw new f('TRANSPORT_DISPOSED','Transport\x20is\x20disposed');await r([...this['_entries']],_0x2472d1);}['_writeHttpError'](_0x3f1734,_0x1c8680){if(_0x3f1734['writableEnded']||_0x3f1734['destroyed'])return;let _0x5c3959=_0x1c8680 instanceof f?_0x1c8680['code']==='REQUEST_BODY_TOO_LARGE'?0x19d:_0x1c8680['code']==='INVALID_JSON'||_0x1c8680['code']==='REQUEST_ABORTED'?0x190:0x1f7:0x1f4,_0x5dcb5e=_0x5c3959===0x19d?'Payload\x20Too\x20Large':_0x5c3959===0x190?'Bad\x20Request':_0x5c3959===0x1f7?'Service\x20Unavailable':'Internal\x20Server\x20Error';_0x3f1734['headersSent']?_0x3f1734['destroy'](_0x1c8680 instanceof Error?_0x1c8680:void 0x0):(_0x3f1734['statusCode']=_0x5c3959,_0x3f1734['setHeader']('content-type','text/plain;\x20charset=utf-8'),_0x3f1734['end'](_0x5dcb5e));}async['_disposeMiddleware'](_0x149cfe){typeof _0x149cfe!='function'&&await _0x149cfe['dispose']?.();}['_assertRunning'](){if(this['_disposed'])throw new f('TRANSPORT_DISPOSED','Transport\x20is\x20disposed');}};function d(_0x4f73c6){return new u(_0x4f73c6);}var f=class extends Error{['code'];constructor(_0x1eb163,_0x37b191,_0x1795f9){super(_0x37b191,_0x1795f9),this['code']=_0x1eb163,this['name']='NodeTransportError';}};exports['NodeTransportError']=f,exports['createNodeTransport']=d;
|
|
1
|
+
Object['defineProperty'](exports,Symbol['toStringTag'],{'value':'Module'});let e=require('node:crypto'),t=require('node:http'),n=require('ws');var r=class extends Error{['code'];constructor(_0x7c1ba8,_0xa92da0,_0x34bb9b){super(_0xa92da0,_0x34bb9b),this['code']=_0x7c1ba8,this['name']='NodeTransportError';}};function i(_0x435c84,_0x415974,_0x268109){let _0x4872ca=Promise['resolve']()['then'](_0x415974),_0x5c4a63=_0x268109&&!_0x268109['writableFinished']&&!_0x268109['destroyed']?new Promise(_0x49750f=>{let _0x202c21=()=>{_0x268109['off']('finish',_0x202c21),_0x268109['off']('close',_0x202c21),_0x49750f();};_0x268109['once']('finish',_0x202c21),_0x268109['once']('close',_0x202c21);}):Promise['resolve'](),_0x76300b=Promise['allSettled']([_0x4872ca,_0x5c4a63])['then'](()=>void 0x0);return _0x435c84['calls']['add'](_0x76300b),_0x76300b['then'](()=>_0x435c84['calls']['delete'](_0x76300b)),_0x4872ca;}async function a(_0x5e6c3b,_0x4c8d76){let _0xef01ff=async _0x4a492d=>{let _0x1b2d00=_0x5e6c3b[_0x4a492d];if(!_0x1b2d00)return;let _0x556957=!0x1,_0x25e0d6,_0x9b4da=()=>{if(_0x556957)throw Error('Transport\x20middleware\x20called\x20next()\x20more\x20than\x20once');return _0x556957=!0x0,_0x25e0d6=_0xef01ff(_0x4a492d+0x1),_0x25e0d6;};try{await _0x1b2d00(_0x4c8d76,_0x9b4da);}catch(_0x2307f3){if(_0x25e0d6)try{await _0x25e0d6;}catch{}throw _0x2307f3;}_0x25e0d6&&await _0x25e0d6;};await _0xef01ff(0x0);}async function o(_0x3d526d,_0xb1c2c5){await a(_0x3d526d['flatMap'](_0x143f2d=>_0x143f2d['disposed']?[]:_0x143f2d['kind']==='http'?[(_0x373371,_0x45b34a)=>_0x143f2d['disposed']?_0x45b34a():_0x143f2d['middleware'](_0x373371,_0x45b34a)]:_0x143f2d['kind']==='endpoint'?[async(_0x123a7a,_0x104c8d)=>{if(_0x143f2d['disposed'])return _0x104c8d();let _0x393eae=_0x143f2d['router']['matchHttp'](_0x123a7a);_0x393eae?await i(_0x143f2d,()=>_0x393eae(_0x123a7a),_0x123a7a['response']):await _0x104c8d();}]:[]),_0xb1c2c5);}async function s(_0x15ac31,_0x27c001){let _0x214487;return await a(_0x15ac31['flatMap'](_0x39a9d4=>{if(_0x39a9d4['disposed'])return[];if(_0x39a9d4['kind']==='upgrade'){let _0x11cc16=_0x39a9d4['middleware'];return[(_0x1f0286,_0x36ef16)=>_0x39a9d4['disposed']?_0x36ef16():_0x11cc16(_0x1f0286,_0x36ef16)];}return _0x39a9d4['kind']==='endpoint'?[async(_0x31b927,_0x2ba57e)=>{if(_0x39a9d4['disposed'])return _0x2ba57e();let _0x44b6b0=_0x39a9d4['router']['matchUpgrade'](_0x31b927);_0x44b6b0?(_0x214487=_0x39a9d4,await i(_0x39a9d4,()=>_0x44b6b0(_0x31b927))):await _0x2ba57e();}]:[];}),_0x27c001),_0x214487;}var c=class{['_http']=[];['_upgrade']=[];['_sealed']=!0x1;['get'](_0x5787ca,_0x2aea93){this['_add'](this['_http'],'GET',_0x5787ca,_0x2aea93);}['post'](_0x5a2d47,_0x382e7b){this['_add'](this['_http'],'POST',_0x5a2d47,_0x382e7b);}['delete'](_0x3200a8,_0x50d9b6){this['_add'](this['_http'],'DELETE',_0x3200a8,_0x50d9b6);}['upgrade'](_0x13aa2e,_0x52fc42){this['_add'](this['_upgrade'],'GET',_0x13aa2e,_0x52fc42);}['seal'](){this['_sealed']=!0x0;}['owns'](_0x3f61ae,_0x22eb6c){try{return this['_find'](_0x22eb6c?this['_upgrade']:this['_http'],_0x3f61ae)!==void 0x0;}catch(_0x447a56){if(!(_0x447a56 instanceof r)||_0x447a56['code']!=='INVALID_URL')throw _0x447a56;return!0x1;}}['matchHttp'](_0x14a4dd){return this['_match'](this['_http'],_0x14a4dd);}['matchUpgrade'](_0x2f0e12){return this['_match'](this['_upgrade'],_0x2f0e12);}['_add'](_0x1d9e35,_0x754d6a,_0x200ded,_0x12ef23){if(this['_sealed'])throw Error('Router\x20registration\x20has\x20completed');if(typeof _0x12ef23!='function')throw TypeError('Route\x20handler\x20must\x20be\x20a\x20function');if(typeof _0x200ded!='string'||!_0x200ded['startsWith']('/')||/[?#]/['test'](_0x200ded))throw TypeError('Route\x20path\x20must\x20be\x20an\x20absolute\x20pathname\x20template');let _0x5cc5f0=_0x200ded['split']('/'),_0xaf64f4=new Set();for(let _0xb50af of _0x5cc5f0){if(!_0xb50af['startsWith'](':'))continue;let _0x1661d8=_0xb50af['slice'](0x1);if(!/^[A-Za-z_][A-Za-z0-9_]*$/['test'](_0x1661d8)||_0xaf64f4['has'](_0x1661d8))throw TypeError('Route\x20parameters\x20must\x20have\x20unique\x20identifier\x20names');_0xaf64f4['add'](_0x1661d8);}_0x1d9e35['push']({'method':_0x754d6a,'segments':_0x5cc5f0,'handler':_0x12ef23,'route':Object['freeze']({'path':_0x200ded})});}['_find'](_0x3db838,_0x2e4b4b){if(_0x3db838['length']===0x0)return;let _0x47d2c0;try{_0x47d2c0=new URL(_0x2e4b4b['url']??'/','http://localhost')['pathname']['split']('/');}catch(_0x59d773){throw new r('INVALID_URL','Invalid\x20request\x20URL',{'cause':_0x59d773});}for(let _0x6a4628 of _0x3db838)if(!(_0x6a4628['method']!==_0x2e4b4b['method']||_0x6a4628['segments']['length']!==_0x47d2c0['length'])&&_0x6a4628['segments']['every']((_0x59a311,_0x113f38)=>_0x59a311['startsWith'](':')?_0x47d2c0[_0x113f38]!=='':_0x59a311===_0x47d2c0[_0x113f38]))return{'entry':_0x6a4628,'segments':_0x47d2c0};}['_match'](_0x5e54f2,_0x1e1873){let _0x2e82db=this['_find'](_0x5e54f2,_0x1e1873['incomingMessage']);if(_0x2e82db){let {entry:_0xbfee43,segments:_0xd1c3e5}=_0x2e82db;Object['defineProperty'](_0x1e1873,'route',{'value':_0xbfee43['route'],'enumerable':!0x0});let _0x4984b4=Object['create'](null);try{_0xbfee43['segments']['forEach']((_0x37071e,_0x3ddc03)=>{_0x37071e['startsWith'](':')&&(_0x4984b4[_0x37071e['slice'](0x1)]=decodeURIComponent(_0xd1c3e5[_0x3ddc03]));});}catch(_0x2a9535){throw new r('INVALID_URL','Route\x20contains\x20invalid\x20encoding',{'cause':_0x2a9535});}return Object['defineProperty'](_0x1e1873,'params',{'value':Object['freeze'](_0x4984b4),'enumerable':!0x0,'writable':!0x1,'configurable':!0x1}),_0xbfee43['handler'];}}},l=new WeakSet();function u(_0x5b0940,_0x2eb953,_0x2d05b2,_0x1f31a0){if(l['has'](_0x5b0940))throw Error('Server\x20already\x20has\x20an\x20attached\x20Transport');let _0xad1390=_0x5b0940['rawListeners']('request'),_0x19e0d7=_0xad1390['map'](_0x5d5507=>({'listener':_0x5d5507,'once':_0x5d5507['listener']!==void 0x0,'called':!0x1})),_0x2a452d=(_0x364b96,_0x3911b6)=>{if(_0x2eb953(_0x364b96,!0x1)){_0x2d05b2(_0x364b96,_0x3911b6);return;}for(let _0x214ad8 of _0x19e0d7)_0x214ad8['once']&&_0x214ad8['called']||(_0x214ad8['called']=!0x0,_0x214ad8['listener']['call'](_0x5b0940,_0x364b96,_0x3911b6));},_0x1f74a7=(_0x18b8a6,_0x13788f,_0x5b47b8)=>{_0x2eb953(_0x18b8a6,!0x0)&&_0x1f31a0(_0x18b8a6,_0x13788f,_0x5b47b8);};l['add'](_0x5b0940);for(let _0x42b70 of _0xad1390)_0x5b0940['removeListener']('request',_0x42b70);return _0x5b0940['on']('request',_0x2a452d),_0x5b0940['on']('upgrade',_0x1f74a7),()=>{_0x5b0940['off']('request',_0x2a452d),_0x5b0940['off']('upgrade',_0x1f74a7);for(let _0x23d948 of[..._0x19e0d7]['reverse']())_0x23d948['once']&&_0x23d948['called']||_0x5b0940['prependListener']('request',_0x23d948['listener']);l['delete'](_0x5b0940);};}var d=0x10*0x400*0x400,f=class{['socket'];['customData'];['id']=(0x0,e['randomUUID'])();['_closed']=!0x1;constructor(_0x5b5816,_0x31bf75){this['socket']=_0x5b5816,this['customData']=_0x31bf75;}['send'](_0x2fc7ed){return this['_closed']||this['socket']['readyState']!==this['socket']['OPEN']?Promise['reject'](Error('WebSocket\x20connection\x20is\x20not\x20open')):new Promise((_0x47b339,_0x5139a7)=>{this['socket']['send'](_0x2fc7ed,_0x34fc95=>{_0x34fc95?_0x5139a7(_0x34fc95):_0x47b339();});});}['close'](_0x5d894f,_0x49cef9){this['_closed']||(this['_closed']=!0x0,(this['socket']['readyState']===this['socket']['OPEN']||this['socket']['readyState']===this['socket']['CONNECTING'])&&this['socket']['close'](_0x5d894f,_0x49cef9));}['terminate'](){this['_closed']=!0x0,this['socket']['readyState']!==this['socket']['CLOSED']&&this['socket']['terminate']();}};function p(_0x333a0c,_0x323ad3){if(_0x333a0c===void 0x0)return _0x323ad3;if(!Number['isSafeInteger'](_0x333a0c)||_0x333a0c<=0x0)throw TypeError('Transport\x20byte\x20limits\x20must\x20be\x20positive\x20safe\x20integers');return _0x333a0c;}function m(_0x1eb3da,_0x447220){let _0x42274b;return()=>(_0x42274b??=new Promise((_0x2e5c00,_0x55abc2)=>{let _0x4c33c6=_0x1eb3da['headers']['content-length'];if(_0x4c33c6!==void 0x0&&Number['isFinite'](Number(_0x4c33c6))&&Number(_0x4c33c6)>_0x447220){_0x1eb3da['resume'](),_0x55abc2(new r('REQUEST_BODY_TOO_LARGE','Request\x20body\x20exceeds\x20'+_0x447220+'\x20bytes'));return;}let _0x49bc02=[],_0xceee3=0x0,_0x4db022=!0x1,_0x2067f0=_0x31ad0b=>{_0x4db022||(_0x4db022=!0x0,_0x55abc2(_0x31ad0b));};_0x1eb3da['on']('data',_0x5d7425=>{if(_0x4db022)return;let _0x23967b=typeof _0x5d7425=='string'?Buffer['from'](_0x5d7425):_0x5d7425;if(_0xceee3+=_0x23967b['byteLength'],_0xceee3>_0x447220){_0x1eb3da['resume'](),_0x2067f0(new r('REQUEST_BODY_TOO_LARGE','Request\x20body\x20exceeds\x20'+_0x447220+'\x20bytes'));return;}_0x49bc02['push'](_0x23967b);}),_0x1eb3da['once']('end',()=>{_0x4db022||(_0x4db022=!0x0,_0x2e5c00(Buffer['concat'](_0x49bc02,_0xceee3)));}),_0x1eb3da['once']('aborted',()=>{_0x2067f0(new r('REQUEST_ABORTED','Request\x20was\x20aborted'));}),_0x1eb3da['once']('error',_0xf870be=>{_0x2067f0(new r('REQUEST_ABORTED','Request\x20stream\x20failed',{'cause':_0xf870be}));});}),_0x42274b);}function h(_0xd2dc90){return async()=>{let _0x203eb2=await _0xd2dc90();try{return JSON['parse'](Buffer['from'](_0x203eb2)['toString']('utf8'));}catch(_0x59b2f0){throw new r('INVALID_JSON','Request\x20body\x20is\x20not\x20valid\x20JSON',{'cause':_0x59b2f0});}};}function g(_0x19217a){return Array['isArray'](_0x19217a)?Buffer['concat'](_0x19217a):_0x19217a instanceof ArrayBuffer?Buffer['from'](_0x19217a):Buffer['from'](_0x19217a['buffer'],_0x19217a['byteOffset'],_0x19217a['byteLength']);}var _=class{['_detach'];['_entries']=[];['_webSocketServer'];['_connections']=new Set();['_eventQueues']=new Map();['_failedConnections']=new Set();['_connectionClosed']=new Map();['_maxRequestBodyBytes'];['_disposed']=!0x1;['_disposePromise'];['_endpointInstances']=new WeakSet();['_connectionOwners']=new Map();constructor(_0x23e566={}){this['_maxRequestBodyBytes']=p(_0x23e566['maxRequestBodyBytes'],d);let _0x26bfb9=p(_0x23e566['maxWebSocketMessageBytes'],this['_maxRequestBodyBytes']);this['_webSocketServer']=new n['WebSocketServer']({'noServer':!0x0,'maxPayload':_0x26bfb9});}['use'](_0x1a1ac4){if(this['_assertRunning'](),typeof _0x1a1ac4!='function')throw TypeError('HTTP\x20Transport\x20middleware\x20must\x20be\x20a\x20function');let _0xb9035f={'kind':'http','middleware':_0x1a1ac4,'disposed':!0x1};return this['_entries']['push'](_0xb9035f),this['_registration'](_0xb9035f);}['useUpgrade'](_0x530221){if(this['_assertRunning'](),typeof _0x530221!='function')throw TypeError('WebSocket\x20upgrade\x20middleware\x20must\x20be\x20a\x20function');let _0x3110cb={'kind':'upgrade','middleware':_0x530221,'disposed':!0x1};return this['_entries']['push'](_0x3110cb),this['_registration'](_0x3110cb);}['register'](_0x398af1){if(this['_assertRunning'](),!_0x398af1||typeof _0x398af1['register']!='function')throw TypeError('Transport\x20Endpoint\x20must\x20provide\x20register(router)');if(this['_endpointInstances']['has'](_0x398af1))throw Error('Endpoint\x20is\x20already\x20registered');let _0x92bff1=new c();this['_endpointInstances']['add'](_0x398af1);try{let _0x3ca517=_0x398af1['register'](_0x92bff1);if(_0x3ca517&&typeof _0x3ca517['then']=='function')throw Promise['resolve'](_0x3ca517)['catch'](()=>void 0x0),TypeError('Endpoint\x20register(router)\x20must\x20be\x20synchronous');this['_assertRunning']();}catch(_0x7a0a48){throw this['_endpointInstances']['delete'](_0x398af1),_0x7a0a48;}finally{_0x92bff1['seal']();}let _0x454fc0={'kind':'endpoint','endpoint':_0x398af1,'router':_0x92bff1,'calls':new Set(),'disposed':!0x1};return this['_entries']['push'](_0x454fc0),this['_registration'](_0x454fc0);}['attach'](_0x145982){if(this['_assertRunning'](),this['_detach'])throw Error('Transport\x20is\x20already\x20attached\x20to\x20a\x20Server');this['_detach']=u(_0x145982,(_0x35dcc0,_0x47f4bf)=>this['_entries']['some'](_0x1ef27c=>!_0x1ef27c['disposed']&&_0x1ef27c['kind']==='endpoint'&&_0x1ef27c['router']['owns'](_0x35dcc0,_0x47f4bf)),(_0x15477a,_0x863014)=>this['handleRequest'](_0x15477a,_0x863014),(_0x3194d2,_0x89996b,_0x1a8825)=>this['handleUpgrade'](_0x3194d2,_0x89996b,_0x1a8825));}['handleRequest'](_0x517a37,_0x6ff548){if(this['_disposed']){this['_writeHttpError'](_0x6ff548,new r('TRANSPORT_DISPOSED','Transport\x20is\x20disposed'));return;}let _0x4f8bdb=m(_0x517a37,this['_maxRequestBodyBytes']),_0x496d5b={'incomingMessage':_0x517a37,'response':_0x6ff548,'customData':Object['create'](null),'params':Object['freeze'](Object['create'](null)),'readBody':_0x4f8bdb,'readJson':h(_0x4f8bdb)};this['_dispatchHttp'](_0x496d5b)['then'](()=>{this['_writeHttpNotFound'](_0x6ff548);})['catch'](_0x5f5869=>{this['_writeHttpError'](_0x6ff548,_0x5f5869);});}['handleUpgrade'](_0x164a12,_0x3128d2,_0xc12c66){if(this['_disposed']){_0x3128d2['destroy']();return;}this['_handleUpgrade'](_0x164a12,_0x3128d2,_0xc12c66);}['dispose'](){if(this['_disposePromise'])return this['_disposePromise'];this['_disposed']=!0x0;let _0x3c85b0=this['_detach'];this['_detach']=void 0x0,_0x3c85b0?.();for(let _0x5697bb of this['_entries'])_0x5697bb['disposed']=!0x0;return this['_disposePromise']=this['_dispose'](),this['_disposePromise'];}async['_dispose'](){let _0x349a85=[...this['_connectionClosed']['values']()]['map'](_0x2581dd=>_0x2581dd['promise']);for(let _0xb81b5f of this['_connections'])_0xb81b5f['terminate']();await Promise['allSettled'](_0x349a85),await Promise['allSettled']([...this['_eventQueues']['values']()]);let _0x35b1fe=[...this['_entries']]['reverse']();for(let _0xb3d965 of _0x35b1fe)await this['_disposeEntry'](_0xb3d965);this['_entries']['length']=0x0;}async['_handleUpgrade'](_0x121676,_0x4d4877,_0x289c22){let _0x5b7357,_0x2992b1=Object['create'](null),_0x565ec6=_0x3cfa77=>{if(_0x5b7357)throw Error('WebSocket\x20upgrade\x20was\x20already\x20accepted\x20or\x20rejected');_0x5b7357=_0x3cfa77;},_0x2235de={'incomingMessage':_0x121676,'customData':_0x2992b1,'params':Object['freeze'](Object['create'](null)),'reject':(_0xceda8=0x193,_0x639ed4)=>{if(!Number['isSafeInteger'](_0xceda8)||_0xceda8<0x190||_0xceda8>0x257)throw TypeError('WebSocket\x20rejection\x20status\x20must\x20be\x20an\x20integer\x20from\x20400\x20to\x20599');_0x565ec6({'kind':'reject','statusCode':_0xceda8,'message':_0x639ed4??t['STATUS_CODES'][_0xceda8]??'Upgrade\x20rejected'});},'accept':_0x28b32d=>{if(!_0x28b32d||typeof _0x28b32d!='object')throw TypeError('WebSocket\x20handler\x20must\x20be\x20an\x20object');_0x565ec6({'kind':'accept','handler':_0x28b32d});}};try{let _0x5afa7b=await s([...this['_entries']],_0x2235de);if(this['_disposed']||_0x5afa7b?.['disposed']||_0x4d4877['destroyed']){_0x4d4877['destroy']();return;}if(!_0x5b7357){this['_writeUpgradeResponse'](_0x4d4877,0x194,'Not\x20Found');return;}if(_0x5b7357['kind']==='reject'){this['_writeUpgradeResponse'](_0x4d4877,_0x5b7357['statusCode'],_0x5b7357['message']);return;}let _0x2bff54=_0x5b7357['handler'];this['_webSocketServer']['handleUpgrade'](_0x121676,_0x4d4877,_0x289c22,_0x4d969f=>{if(this['_disposed']||_0x5afa7b?.['disposed']){_0x4d969f['terminate']();return;}this['_acceptWebSocket'](_0x4d969f,_0x2992b1,_0x2bff54,_0x5afa7b);});}catch(_0x6feba5){let _0x9c1128=_0x6feba5 instanceof r&&_0x6feba5['code']==='INVALID_URL';this['_writeUpgradeResponse'](_0x4d4877,_0x9c1128?0x190:0x1f4,_0x9c1128?'Bad\x20Request':'Internal\x20Server\x20Error');}}['_acceptWebSocket'](_0x1ca90a,_0x38fce9,_0x114f21,_0x2fb4f7){let _0x1b3539=new f(_0x1ca90a,_0x38fce9);this['_connections']['add'](_0x1b3539),_0x2fb4f7&&this['_connectionOwners']['set'](_0x1b3539,_0x2fb4f7),this['_eventQueues']['set'](_0x1b3539,Promise['resolve']());let _0xc71490,_0x45229c=new Promise(_0x10ea91=>{_0xc71490=_0x10ea91;});this['_connectionClosed']['set'](_0x1b3539,{'promise':_0x45229c,'resolve':_0xc71490}),_0x1ca90a['on']('message',(_0x3b91d3,_0x9b2ba9)=>{let _0x2a6894=g(_0x3b91d3),_0x440ac0=_0x9b2ba9?new Uint8Array(_0x2a6894):_0x2a6894['toString']('utf8'),_0x207387={'connection':_0x1b3539,'data':_0x440ac0,'customData':Object['create'](null)};this['_enqueue'](_0x1b3539,()=>_0x114f21['message']?.(_0x207387));}),_0x1ca90a['once']('close',(_0x4bef0e,_0x58e007)=>{let _0xdf663f={'connection':_0x1b3539,'code':_0x4bef0e,'reason':_0x58e007['toString']('utf8')};this['_enqueue'](_0x1b3539,()=>_0x114f21['close']?.(_0xdf663f),!0x0);}),_0x1ca90a['on']('error',()=>{});let _0x21fd14={'connection':_0x1b3539};this['_enqueue'](_0x1b3539,()=>_0x114f21['open']?.(_0x21fd14));}['_enqueue'](_0x2fa2c4,_0x438193,_0x50d2ed=!0x1){let _0x793df8=(this['_eventQueues']['get'](_0x2fa2c4)??Promise['resolve']())['catch'](()=>void 0x0)['then'](async()=>{this['_failedConnections']['has'](_0x2fa2c4)&&!_0x50d2ed||await _0x438193();})['catch'](()=>{this['_failedConnections']['add'](_0x2fa2c4),_0x2fa2c4['close'](0x3f3,'WebSocket\x20handler\x20failure');})['finally'](()=>{_0x50d2ed&&(this['_connections']['delete'](_0x2fa2c4),this['_connectionOwners']['delete'](_0x2fa2c4),this['_eventQueues']['delete'](_0x2fa2c4),this['_failedConnections']['delete'](_0x2fa2c4),this['_connectionClosed']['get'](_0x2fa2c4)?.['resolve'](),this['_connectionClosed']['delete'](_0x2fa2c4));});this['_eventQueues']['set'](_0x2fa2c4,_0x793df8);}async['_dispatchHttp'](_0x27ad34){if(this['_disposed'])throw new r('TRANSPORT_DISPOSED','Transport\x20is\x20disposed');await o([...this['_entries']],_0x27ad34);}['_writeHttpError'](_0x33a933,_0x8d27ed){if(_0x33a933['writableEnded']||_0x33a933['destroyed'])return;let _0x556ac3=_0x8d27ed instanceof r?_0x8d27ed['code']==='REQUEST_BODY_TOO_LARGE'?0x19d:_0x8d27ed['code']==='INVALID_JSON'||_0x8d27ed['code']==='REQUEST_ABORTED'||_0x8d27ed['code']==='INVALID_URL'?0x190:0x1f7:0x1f4,_0x30a51b=_0x556ac3===0x19d?'Payload\x20Too\x20Large':_0x556ac3===0x190?'Bad\x20Request':_0x556ac3===0x1f7?'Service\x20Unavailable':'Internal\x20Server\x20Error';_0x33a933['headersSent']?_0x33a933['destroy'](_0x8d27ed instanceof Error?_0x8d27ed:void 0x0):(_0x33a933['statusCode']=_0x556ac3,_0x33a933['setHeader']('content-type','text/plain;\x20charset=utf-8'),_0x33a933['end'](_0x30a51b));}['_writeHttpNotFound'](_0x5c275b){_0x5c275b['headersSent']||_0x5c275b['writableEnded']||_0x5c275b['destroyed']||(_0x5c275b['statusCode']=0x194,_0x5c275b['setHeader']('content-type','text/plain;\x20charset=utf-8'),_0x5c275b['end']('Not\x20Found'));}['_writeUpgradeResponse'](_0x2b2f30,_0xb999fa,_0x36a797){if(_0x2b2f30['destroyed'])return;let _0x4aed2a=t['STATUS_CODES'][_0xb999fa]??'Error',_0x30618a=Buffer['from'](_0x36a797,'utf8');_0x2b2f30['end']('HTTP/1.1\x20'+_0xb999fa+'\x20'+_0x4aed2a+'\x0d\x0aConnection:\x20close\x0d\x0aContent-Type:\x20text/plain;\x20charset=utf-8\x0d\x0aContent-Length:\x20'+_0x30618a['byteLength']+'\x0d\x0a\x0d\x0a'+_0x30618a['toString']('utf8'));}['_registration'](_0x28891d){return{'dispose':()=>this['_disposeEntry'](_0x28891d)};}['_disposeEntry'](_0x665c8){return _0x665c8['disposed']=!0x0,_0x665c8['kind']==='endpoint'?(_0x665c8['disposePromise']||=((async()=>{let _0x1b6027=[];for(let [_0x234627,_0x51bc65]of this['_connectionOwners']){if(_0x51bc65!==_0x665c8)continue;let _0x4f048d=this['_connectionClosed']['get'](_0x234627);_0x4f048d&&_0x1b6027['push'](_0x4f048d['promise']),_0x234627['terminate']();}await Promise['allSettled']([..._0x665c8['calls'],..._0x1b6027]),await _0x665c8['endpoint']['dispose']?.();})()),_0x665c8['disposePromise']):Promise['resolve']();}['_assertRunning'](){if(this['_disposed'])throw new r('TRANSPORT_DISPOSED','Transport\x20is\x20disposed');}};function v(_0x288433){return new _(_0x288433);}exports['NodeTransportError']=r,exports['createNodeTransport']=v;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { IncomingMessage, ServerResponse } from "node:http";
|
|
1
|
+
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
+
import { Server as Server$1 } from "node:https";
|
|
2
3
|
import { Duplex } from "node:stream";
|
|
3
4
|
|
|
5
|
+
//#region src/errors.d.ts
|
|
6
|
+
type NodeTransportErrorCode = "INVALID_URL" | "INVALID_JSON" | "REQUEST_BODY_TOO_LARGE" | "REQUEST_ABORTED" | "TRANSPORT_DISPOSED";
|
|
7
|
+
declare class NodeTransportError extends Error {
|
|
8
|
+
readonly code: NodeTransportErrorCode;
|
|
9
|
+
constructor(code: NodeTransportErrorCode, message: string, options?: ErrorOptions);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
4
12
|
//#region src/node-transport.d.ts
|
|
5
13
|
declare function createNodeTransport(options?: NodeTransportOptions): INodeTransport;
|
|
6
14
|
//#endregion
|
|
@@ -9,49 +17,66 @@ type TransportCustomData = Record<string, unknown>;
|
|
|
9
17
|
interface TransportDisposable {
|
|
10
18
|
dispose(): void | Promise<void>;
|
|
11
19
|
}
|
|
12
|
-
type NodeTransportErrorCode = "INVALID_JSON" | "REQUEST_BODY_TOO_LARGE" | "REQUEST_ABORTED" | "TRANSPORT_DISPOSED";
|
|
13
|
-
declare class NodeTransportError extends Error {
|
|
14
|
-
readonly code: NodeTransportErrorCode;
|
|
15
|
-
constructor(code: NodeTransportErrorCode, message: string, options?: ErrorOptions);
|
|
16
|
-
}
|
|
17
20
|
interface NodeTransportConnection {
|
|
18
21
|
readonly id: string;
|
|
19
22
|
readonly customData: TransportCustomData;
|
|
20
23
|
send(data: string | Uint8Array): void | Promise<void>;
|
|
21
24
|
close(code?: number, reason?: string): void;
|
|
22
25
|
}
|
|
23
|
-
interface
|
|
24
|
-
readonly customData: TransportCustomData;
|
|
25
|
-
userId?: string;
|
|
26
|
-
}
|
|
27
|
-
interface NodeHttpTransportContext extends BaseNodeTransportContext {
|
|
28
|
-
readonly kind: "http";
|
|
26
|
+
interface NodeHttpTransportContext {
|
|
29
27
|
readonly incomingMessage: IncomingMessage;
|
|
30
28
|
readonly response: ServerResponse;
|
|
29
|
+
readonly customData: TransportCustomData;
|
|
30
|
+
readonly route?: NodeRoute;
|
|
31
|
+
readonly params: Readonly<Record<string, string | undefined>>;
|
|
32
|
+
userID?: string;
|
|
31
33
|
readBody(): Promise<Uint8Array>;
|
|
32
34
|
readJson<T = unknown>(): Promise<T>;
|
|
33
35
|
}
|
|
34
|
-
interface
|
|
35
|
-
readonly kind: "websocket-open";
|
|
36
|
+
interface NodeWebSocketUpgradeContext {
|
|
36
37
|
readonly incomingMessage: IncomingMessage;
|
|
38
|
+
readonly customData: TransportCustomData;
|
|
39
|
+
readonly route?: NodeRoute;
|
|
40
|
+
readonly params: Readonly<Record<string, string | undefined>>;
|
|
41
|
+
reject(statusCode?: number, message?: string): void;
|
|
42
|
+
}
|
|
43
|
+
interface NodeWebSocketEndpointContext extends NodeWebSocketUpgradeContext {
|
|
44
|
+
accept(handler: NodeWebSocketHandler): void;
|
|
45
|
+
}
|
|
46
|
+
interface NodeWebSocketOpenContext {
|
|
37
47
|
readonly connection: NodeTransportConnection;
|
|
38
48
|
}
|
|
39
|
-
interface
|
|
40
|
-
readonly kind: "websocket-message";
|
|
49
|
+
interface NodeWebSocketMessageContext {
|
|
41
50
|
readonly connection: NodeTransportConnection;
|
|
42
51
|
readonly data: string | Uint8Array;
|
|
52
|
+
readonly customData: TransportCustomData;
|
|
43
53
|
}
|
|
44
|
-
interface
|
|
45
|
-
readonly kind: "websocket-close";
|
|
54
|
+
interface NodeWebSocketCloseContext {
|
|
46
55
|
readonly connection: NodeTransportConnection;
|
|
47
56
|
readonly code?: number;
|
|
48
57
|
readonly reason?: string;
|
|
49
58
|
}
|
|
50
|
-
|
|
59
|
+
interface NodeWebSocketHandler {
|
|
60
|
+
open?(ctx: NodeWebSocketOpenContext): void | Promise<void>;
|
|
61
|
+
message?(ctx: NodeWebSocketMessageContext): void | Promise<void>;
|
|
62
|
+
close?(ctx: NodeWebSocketCloseContext): void | Promise<void>;
|
|
63
|
+
}
|
|
51
64
|
type NodeTransportNext = () => Promise<void>;
|
|
52
|
-
type
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
type NodeHttpTransportMiddleware = (ctx: NodeHttpTransportContext, next: NodeTransportNext) => void | Promise<void>;
|
|
66
|
+
type NodeWebSocketUpgradeMiddleware = (ctx: NodeWebSocketUpgradeContext, next: NodeTransportNext) => void | Promise<void>;
|
|
67
|
+
interface NodeRoute {
|
|
68
|
+
readonly path: string;
|
|
69
|
+
}
|
|
70
|
+
type NodeHttpHandler = (ctx: NodeHttpTransportContext) => void | Promise<void>;
|
|
71
|
+
type NodeUpgradeHandler = (ctx: NodeWebSocketEndpointContext) => void | Promise<void>;
|
|
72
|
+
interface NodeRouter {
|
|
73
|
+
get(path: string, handler: NodeHttpHandler): void;
|
|
74
|
+
post(path: string, handler: NodeHttpHandler): void;
|
|
75
|
+
delete(path: string, handler: NodeHttpHandler): void;
|
|
76
|
+
upgrade(path: string, handler: NodeUpgradeHandler): void;
|
|
77
|
+
}
|
|
78
|
+
interface NodeTransportEndpoint {
|
|
79
|
+
register(router: NodeRouter): void;
|
|
55
80
|
dispose?(): void | Promise<void>;
|
|
56
81
|
}
|
|
57
82
|
interface NodeTransportOptions {
|
|
@@ -59,10 +84,13 @@ interface NodeTransportOptions {
|
|
|
59
84
|
readonly maxWebSocketMessageBytes?: number;
|
|
60
85
|
}
|
|
61
86
|
interface INodeTransport {
|
|
62
|
-
|
|
87
|
+
attach(server: Server | Server$1): void;
|
|
88
|
+
use(middleware: NodeHttpTransportMiddleware): TransportDisposable;
|
|
89
|
+
useUpgrade(middleware: NodeWebSocketUpgradeMiddleware): TransportDisposable;
|
|
90
|
+
register(endpoint: NodeTransportEndpoint): TransportDisposable;
|
|
63
91
|
handleRequest(request: IncomingMessage, response: ServerResponse): void;
|
|
64
92
|
handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
65
93
|
dispose(): Promise<void>;
|
|
66
94
|
}
|
|
67
95
|
//#endregion
|
|
68
|
-
export { type INodeTransport, type NodeHttpTransportContext, type NodeTransportConnection, type
|
|
96
|
+
export { type INodeTransport, type NodeHttpHandler, type NodeHttpTransportContext, type NodeHttpTransportMiddleware, type NodeRoute, type NodeRouter, type NodeTransportConnection, type NodeTransportEndpoint, NodeTransportError, type NodeTransportErrorCode, type NodeTransportNext, type NodeTransportOptions, type NodeUpgradeHandler, type NodeWebSocketCloseContext, type NodeWebSocketEndpointContext, type NodeWebSocketHandler, type NodeWebSocketMessageContext, type NodeWebSocketOpenContext, type NodeWebSocketUpgradeContext, type NodeWebSocketUpgradeMiddleware, type TransportCustomData, type TransportDisposable, createNodeTransport };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
function _0x4dd9(_0x467017,_0x90024f){_0x467017=_0x467017-0xcf;const _0x488333=_0x4883();let _0x4dd96b=_0x488333[_0x467017];return _0x4dd96b;}const _0x41124a=_0x4dd9;function _0x4883(){const _0x333a69=['426ejxbEr','419904oMYlUv','2705JXrPYe','476367wFMNhu','NodeTransportError','245064ymUwTM','405111lJRLCM','230847HFCwxq','createNodeTransport','1800976dJNtzb'];_0x4883=function(){return _0x333a69;};return _0x4883();}(function(_0x1df63d,_0x3227cd){const _0x1fd1f1=_0x4dd9,_0xbeb02a=_0x1df63d();while(!![]){try{const _0x2483d1=-parseInt(_0x1fd1f1(0xd5))/0x1+-parseInt(_0x1fd1f1(0xd3))/0x2+parseInt(_0x1fd1f1(0xd1))/0x3+parseInt(_0x1fd1f1(0xcf))/0x4+-parseInt(_0x1fd1f1(0xd0))/0x5*(-parseInt(_0x1fd1f1(0xd8))/0x6)+-parseInt(_0x1fd1f1(0xd4))/0x7+parseInt(_0x1fd1f1(0xd7))/0x8;if(_0x2483d1===_0x3227cd)break;else _0xbeb02a['push'](_0xbeb02a['shift']());}catch(_0x12ba89){_0xbeb02a['push'](_0xbeb02a['shift']());}}}(_0x4883,0x1c54e));import _0x42aeb9 from'./index.cjs';const facadeExport0=_0x42aeb9[_0x41124a(0xd2)],facadeExport1=_0x42aeb9[_0x41124a(0xd6)];export{facadeExport0 as NodeTransportError,facadeExport1 as createNodeTransport};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@univerjs-pro/collaboration-transport-node",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -17,8 +17,5 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ws": "^8.21.1"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
20
|
}
|
|
24
|
-
}
|
|
21
|
+
}
|