xshell 1.2.89 → 1.3.1
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/antd.sass +4 -31
- package/apps.js +1 -1
- package/builder.js +12 -17
- package/development.js +1 -1
- package/file.d.ts +11 -7
- package/file.js +8 -4
- package/i18n/dict.json +12 -0
- package/i18n/scanner/index.d.ts +1 -0
- package/i18n/scanner/index.js +14 -5
- package/net.browser.d.ts +1 -142
- package/net.browser.js +34 -443
- package/net.common.d.ts +178 -0
- package/net.common.js +564 -0
- package/net.d.ts +6 -175
- package/net.js +65 -530
- package/package.json +29 -30
- package/path.d.ts +2 -2
- package/platform.browser.js +9 -1
- package/platform.common.d.ts +10 -1
- package/platform.js +11 -1
- package/process.d.ts +2 -2
- package/process.js +2 -2
- package/prototype.common.d.ts +2 -2
- package/prototype.common.js +8 -8
- package/prototype.js +1 -1
- package/react.development.js +9199 -6036
- package/react.development.js.map +1 -1
- package/react.production.js +4958 -4306
- package/react.production.js.map +1 -1
- package/repl.js +7 -3
- package/server.d.ts +27 -12
- package/server.js +209 -71
- package/utils.browser.d.ts +1 -1
- package/utils.browser.js +3 -1
- package/utils.common.d.ts +29 -10
- package/utils.common.js +102 -35
- package/utils.d.ts +2 -2
- package/utils.js +10 -4
- package/i18n/utils.d.ts +0 -1
- package/i18n/utils.js +0 -11
package/net.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import zlib from 'zlib';
|
|
2
|
-
import { buffer as stream_to_buffer, text as stream_to_text } from 'stream/consumers';
|
|
3
|
-
import { isReadable } from 'stream';
|
|
4
|
-
import {
|
|
5
|
-
import { rethrow } from "./prototype.js";
|
|
6
|
-
import { message_symbol, pack, parse } from "./io.common.js";
|
|
7
|
-
import { inspect, assert, genid, delay, Lock, pipe_with_error, map_values, unique, timeout, check, colored } from "./utils.js";
|
|
1
|
+
import zlib from 'node:zlib';
|
|
2
|
+
import { buffer as stream_to_buffer, text as stream_to_text } from 'node:stream/consumers';
|
|
3
|
+
import { isReadable } from 'node:stream';
|
|
4
|
+
import { inspect, assert, delay, pipe_with_error, map_values, unique, timeout, check, colored, encode } from "./utils.js";
|
|
8
5
|
import { drop_request_headers } from "./net.common.js";
|
|
9
6
|
export * from "./net.common.js";
|
|
10
|
-
export const WebSocketConnecting = 0;
|
|
11
|
-
export const WebSocketOpen = 1;
|
|
12
|
-
export const WebSocketClosing = 2;
|
|
13
|
-
export const WebSocketClosed = 3;
|
|
14
|
-
export const websocket_states = ['connecting', 'open', 'closing', 'closed'];
|
|
15
7
|
export var MyProxy;
|
|
16
8
|
(function (MyProxy) {
|
|
17
9
|
MyProxy["socks5"] = "http://127.0.0.1:10080";
|
|
@@ -32,9 +24,9 @@ export const cookies = {
|
|
|
32
24
|
this.jar = new CookieJar(this.store = new MemoryCookieStore());
|
|
33
25
|
},
|
|
34
26
|
};
|
|
35
|
-
let
|
|
27
|
+
let undici;
|
|
28
|
+
let dispatchers = {};
|
|
36
29
|
async function request_retry(url, options, _timeout, retries = 0, count = 0, print) {
|
|
37
|
-
const { default: undici } = await import('undici');
|
|
38
30
|
try {
|
|
39
31
|
if (_timeout > 0) {
|
|
40
32
|
// 设置给 undici 设置 timeout, signal 不一定管用,还是得自己兜底
|
|
@@ -73,15 +65,13 @@ export class StatusCodeError extends Error {
|
|
|
73
65
|
}
|
|
74
66
|
}
|
|
75
67
|
export async function request(url, options = {}) {
|
|
76
|
-
|
|
77
|
-
UndiciProxyAgent ??= undici.ProxyAgent;
|
|
78
|
-
UndiciFormData ??= undici.FormData;
|
|
68
|
+
undici ??= (await import('undici')).default;
|
|
79
69
|
const { Cookie } = await import('tough-cookie');
|
|
80
70
|
await cookies.init();
|
|
81
71
|
const { queries, headers: _headers, body, type = 'application/json', timeout = 5 * 1000, auth, cookies: _cookies, raw = false, full = false, redirect = 'follow', decode = true, print = {
|
|
82
72
|
timeout: true,
|
|
83
73
|
retry: true
|
|
84
|
-
} } = options;
|
|
74
|
+
}, long_connection } = options;
|
|
85
75
|
let { method, retries, encoding, proxy, } = options;
|
|
86
76
|
url = new URL(url);
|
|
87
77
|
if (queries)
|
|
@@ -134,23 +124,11 @@ export async function request(url, options = {}) {
|
|
|
134
124
|
headers[key] = _headers[key];
|
|
135
125
|
}
|
|
136
126
|
}
|
|
127
|
+
if (proxy === true)
|
|
128
|
+
proxy = MyProxy.socks5;
|
|
137
129
|
let undici_options = {
|
|
138
130
|
...method ? { method } : {},
|
|
139
|
-
dispatcher:
|
|
140
|
-
const { default: {
|
|
141
|
-
// @ts-ignore
|
|
142
|
-
ProxyAgent, Agent, interceptors } } = await import('undici');
|
|
143
|
-
if (proxy === true)
|
|
144
|
-
proxy = MyProxy.socks5;
|
|
145
|
-
return agents[`${proxy || 'direct'}.${redirect}`] ??= (() => {
|
|
146
|
-
let dispatcher = proxy ? new ProxyAgent({ uri: proxy }) : new Agent();
|
|
147
|
-
if (redirect === 'follow')
|
|
148
|
-
dispatcher = dispatcher.compose(
|
|
149
|
-
// todo: 强制手动处理重定向,来正确处理 cookie ?
|
|
150
|
-
interceptors.redirect({ maxRedirections: 5 }));
|
|
151
|
-
return dispatcher;
|
|
152
|
-
})();
|
|
153
|
-
})(),
|
|
131
|
+
dispatcher: get_dispatcher(proxy, redirect, long_connection),
|
|
154
132
|
// 下面这些 timeout 都不是总的时间
|
|
155
133
|
headersTimeout: timeout,
|
|
156
134
|
// 从收完 headers 开始算
|
|
@@ -158,30 +136,7 @@ export async function request(url, options = {}) {
|
|
|
158
136
|
// @ts-ignore 没有类型声明,实际可用
|
|
159
137
|
connectTimeout: timeout,
|
|
160
138
|
headers,
|
|
161
|
-
|
|
162
|
-
body: (() => {
|
|
163
|
-
if (body === undefined)
|
|
164
|
-
return;
|
|
165
|
-
if (typeof body?.read === 'function' && isReadable(body))
|
|
166
|
-
return body;
|
|
167
|
-
switch (type) {
|
|
168
|
-
case 'application/json': // 可能的类型 string | Record<string, any> | Uint8Array
|
|
169
|
-
if (typeof body === 'string')
|
|
170
|
-
return body;
|
|
171
|
-
if (body instanceof Uint8Array)
|
|
172
|
-
return body;
|
|
173
|
-
assert(!(body instanceof ArrayBuffer || ArrayBuffer.isView(body)));
|
|
174
|
-
return JSON.stringify(body);
|
|
175
|
-
case 'application/x-www-form-urlencoded':
|
|
176
|
-
return (body instanceof URLSearchParams ? body : new URLSearchParams(body)).toString();
|
|
177
|
-
case 'multipart/form-data': {
|
|
178
|
-
let form = new UndiciFormData();
|
|
179
|
-
for (const key in body)
|
|
180
|
-
form.set(key, body[key]);
|
|
181
|
-
return form;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
})()
|
|
139
|
+
body: get_request_body(body, type)
|
|
185
140
|
};
|
|
186
141
|
let response;
|
|
187
142
|
try {
|
|
@@ -273,25 +228,62 @@ export async function request(url, options = {}) {
|
|
|
273
228
|
}
|
|
274
229
|
if (raw)
|
|
275
230
|
return response;
|
|
276
|
-
const body_ = await (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (encoding === 'binary')
|
|
280
|
-
return stream_to_buffer(response.body);
|
|
281
|
-
encoding ||= /charset=(.*)/.exec(response.headers['content-type'])?.[1] || 'utf-8';
|
|
282
|
-
if (/utf-?8/i.test(encoding))
|
|
283
|
-
return stream_to_text(response.body);
|
|
284
|
-
return new TextDecoder(encoding)
|
|
285
|
-
.decode(await stream_to_buffer(response.body));
|
|
286
|
-
})();
|
|
287
|
-
return full ? {
|
|
288
|
-
status: response.status,
|
|
289
|
-
headers: response.headers,
|
|
290
|
-
body: body_
|
|
291
|
-
}
|
|
231
|
+
const body_ = await get_response_body(response.body, encoding, response.headers);
|
|
232
|
+
return full ?
|
|
233
|
+
{ status: response.status, headers: response.headers, body: body_ }
|
|
292
234
|
:
|
|
293
235
|
body_;
|
|
294
236
|
}
|
|
237
|
+
function get_dispatcher(proxy, redirect, long_connection) {
|
|
238
|
+
const key = `${proxy || (long_connection ? 'direct.long' : 'direct')}.${redirect}`;
|
|
239
|
+
let dispatcher = dispatchers[key];
|
|
240
|
+
if (dispatcher)
|
|
241
|
+
return dispatcher;
|
|
242
|
+
dispatcher = proxy ?
|
|
243
|
+
// @ts-ignore
|
|
244
|
+
new undici.ProxyAgent({ uri: proxy })
|
|
245
|
+
:
|
|
246
|
+
new undici.Agent({ keepAliveTimeout: long_connection ? 30_000 : undefined });
|
|
247
|
+
if (redirect === 'follow')
|
|
248
|
+
dispatcher = dispatcher.compose(
|
|
249
|
+
// todo: 强制手动处理重定向,来正确处理 cookie ?
|
|
250
|
+
undici.interceptors.redirect({ maxRedirections: 5 }));
|
|
251
|
+
return dispatchers[key] = dispatcher;
|
|
252
|
+
}
|
|
253
|
+
function get_request_body(body, type) {
|
|
254
|
+
if (body === undefined)
|
|
255
|
+
return undefined;
|
|
256
|
+
if (typeof body?.read === 'function' && isReadable(body))
|
|
257
|
+
return body;
|
|
258
|
+
if (type === 'application/json') {
|
|
259
|
+
// 可能的类型 string | Record<string, any> | Uint8Array
|
|
260
|
+
if (typeof body === 'string')
|
|
261
|
+
return encode(body);
|
|
262
|
+
if (body instanceof Uint8Array)
|
|
263
|
+
return body;
|
|
264
|
+
check(!(body instanceof ArrayBuffer || ArrayBuffer.isView(body)));
|
|
265
|
+
return JSON.stringify(body);
|
|
266
|
+
}
|
|
267
|
+
if (type === 'application/x-www-form-urlencoded')
|
|
268
|
+
return encode((body instanceof URLSearchParams ? body : new URLSearchParams(body)).toString());
|
|
269
|
+
check(type === 'multipart/form-data');
|
|
270
|
+
// undici.FormData 会和 globalThis.FormData 不一致,禁止传入 FormData 类型
|
|
271
|
+
let form = new undici.FormData();
|
|
272
|
+
for (const key in body)
|
|
273
|
+
form.set(key, body[key]);
|
|
274
|
+
return form;
|
|
275
|
+
}
|
|
276
|
+
async function get_response_body(body, encoding, headers) {
|
|
277
|
+
if (!body)
|
|
278
|
+
return encoding === 'binary' ? Buffer.from([]) : '';
|
|
279
|
+
if (encoding === 'binary')
|
|
280
|
+
return stream_to_buffer(body);
|
|
281
|
+
encoding ||= /charset=(.*)/.exec(headers['content-type'])?.[1] || 'utf-8';
|
|
282
|
+
if (/utf-?8/i.test(encoding))
|
|
283
|
+
return stream_to_text(body);
|
|
284
|
+
return new TextDecoder(encoding)
|
|
285
|
+
.decode(await stream_to_buffer(body));
|
|
286
|
+
}
|
|
295
287
|
/** 发起 http 请求并将响应体作为 json 解析 */
|
|
296
288
|
export async function request_json(url, options) {
|
|
297
289
|
const body = await request(url, options);
|
|
@@ -305,461 +297,4 @@ export async function request_json(url, options) {
|
|
|
305
297
|
throw error;
|
|
306
298
|
}
|
|
307
299
|
}
|
|
308
|
-
export class WebSocketConnectionError extends Error {
|
|
309
|
-
name = 'WebSocketConnectionError';
|
|
310
|
-
// 这里不保留 websocket 引用,防止循环引用导致 JSON 序列化失败
|
|
311
|
-
url;
|
|
312
|
-
protocols;
|
|
313
|
-
event;
|
|
314
|
-
type;
|
|
315
|
-
address;
|
|
316
|
-
errno;
|
|
317
|
-
port;
|
|
318
|
-
syscall;
|
|
319
|
-
/** close 事件时为 close code, error 事件为 error code */
|
|
320
|
-
code;
|
|
321
|
-
reason;
|
|
322
|
-
constructor(url, protocols, event, message = '') {
|
|
323
|
-
super(`${url}${protocols ? ' ' + protocols.join(', ').bracket() : ''} ${t('连接出错了')}. ${message}`);
|
|
324
|
-
this.url = url;
|
|
325
|
-
this.protocols = protocols;
|
|
326
|
-
this.event = event;
|
|
327
|
-
this.type = event.type;
|
|
328
|
-
if (this.type === 'error') {
|
|
329
|
-
const { error } = event;
|
|
330
|
-
this.address = error.address;
|
|
331
|
-
this.code = error.code;
|
|
332
|
-
this.errno = error.errno;
|
|
333
|
-
this.port = error.port;
|
|
334
|
-
this.syscall = error.syscall;
|
|
335
|
-
this.reason = error.reason;
|
|
336
|
-
this.stack = `${this.name}: ${this.message}\n` +
|
|
337
|
-
error.stack.slice(error.stack.indexOf('\n') + 1) + '\n';
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
this.code = event.code;
|
|
341
|
-
this.reason = event.reason;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
let websocket_proxy_agents = {};
|
|
346
|
-
/** 连接 websocket url, 设置各种事件监听器。在 open 事件后 resolve, 返回 websocket
|
|
347
|
-
遇到 error 时会创建 WebSocketConnectionError:
|
|
348
|
-
- reject 掉返回的 promise (若此时未 settle)
|
|
349
|
-
- 作为参数调用 on_error (已 settle 且有 on_error 回调)
|
|
350
|
-
可以用 WebSocket.bufferedAmount 来显示大消息的发送进度
|
|
351
|
-
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/bufferedAmount
|
|
352
|
-
- url
|
|
353
|
-
- options:
|
|
354
|
-
- protocols?
|
|
355
|
-
- max_payload?: `8 GB`
|
|
356
|
-
- on_message: 根据 websocket frame 的 opcode 不同 (text frame 或 binary frame),event 中的 data 对应为 ArrayBuffer 或者 string
|
|
357
|
-
https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
|
358
|
-
- on_error?: 在 websocket 出错和非正常关闭 (close, error 事件) 时都调用,可以根据 error.type 来区分,error 的类型是 WebSocketConnectionError,
|
|
359
|
-
type 为 'close' 时有 code 和 reason 属性
|
|
360
|
-
- on_close?: 和 websocket 的 'close' 事件不相同,只在正常关闭 (close code 为 1000) 时才调用,否则都会调用 on_error
|
|
361
|
-
https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
|
|
362
|
-
- print?: 是否打印连接、关闭信息 */
|
|
363
|
-
export async function connect_websocket(url, { protocols, max_payload = 2 ** 33, // 8 GB
|
|
364
|
-
on_message, on_error, on_close, proxy, print = true, }) {
|
|
365
|
-
const { WebSocket } = await import('ws');
|
|
366
|
-
const { HttpsProxyAgent } = await import('https-proxy-agent');
|
|
367
|
-
let websocket = new WebSocket(url, protocols, {
|
|
368
|
-
maxPayload: max_payload,
|
|
369
|
-
skipUTF8Validation: true,
|
|
370
|
-
allowSynchronousEvents: true,
|
|
371
|
-
...proxy ? {
|
|
372
|
-
agent: websocket_proxy_agents[proxy] ??= new HttpsProxyAgent(proxy)
|
|
373
|
-
} : {}
|
|
374
|
-
});
|
|
375
|
-
// https://stackoverflow.com/questions/11821096/what-is-the-difference-between-an-arraybuffer-and-a-blob/39951543
|
|
376
|
-
websocket.binaryType = 'arraybuffer';
|
|
377
|
-
return new Promise((resolve, reject) => {
|
|
378
|
-
let settled = false;
|
|
379
|
-
websocket.addEventListener('open', event => {
|
|
380
|
-
if (print)
|
|
381
|
-
console.log(websocket.url +
|
|
382
|
-
(websocket.protocol ? ' ' + websocket.protocol.bracket() : '') +
|
|
383
|
-
t(' 已连接'));
|
|
384
|
-
settled = true;
|
|
385
|
-
resolve(websocket);
|
|
386
|
-
});
|
|
387
|
-
websocket.addEventListener('close', event => {
|
|
388
|
-
// https://blog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa
|
|
389
|
-
// error 事件会先发生,然后 reject(error) 被执行,此时还未轮到外层的 await connect_websocket 执行(在微任务队列中),
|
|
390
|
-
// 接着马上 close 事件也被调用,此时 settled,马上调用了 on_error 函数,弄乱了顺序
|
|
391
|
-
// error 的错误信息比较多,而且通过 await 得到的栈也比较清晰,这里延后调用 on_close 和 on_error,放到微任务队列之后的 timers 队列中
|
|
392
|
-
setTimeout(() => {
|
|
393
|
-
if (event.code === 1000) { // 正常关闭
|
|
394
|
-
if (on_close)
|
|
395
|
-
on_close(event, websocket);
|
|
396
|
-
else if (print)
|
|
397
|
-
console.log(`${websocket.url} ${t('已正常关闭')}`);
|
|
398
|
-
}
|
|
399
|
-
else { // 异常关闭,认为发生了错误,进行错误处理
|
|
400
|
-
// websocket close 事件时已经 settled
|
|
401
|
-
const error = new WebSocketConnectionError(websocket.url, protocols, event, `${t('连接被关闭')}, code: ${event.code}${event.reason ? `, ${t('原因')}: ${event.reason}` : ''}`);
|
|
402
|
-
if (on_error)
|
|
403
|
-
on_error(error, websocket);
|
|
404
|
-
else // 既然用户不传 on_error, 就当 unhandled error 抛出来
|
|
405
|
-
throw error;
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
websocket.addEventListener('error', event => {
|
|
410
|
-
const error = new WebSocketConnectionError(websocket.url, protocols, event, event.error?.message);
|
|
411
|
-
if (settled)
|
|
412
|
-
if (on_error)
|
|
413
|
-
on_error(error, websocket);
|
|
414
|
-
else // 既然用户不传 on_error, 就当 unhandled error 抛出来
|
|
415
|
-
throw error;
|
|
416
|
-
else {
|
|
417
|
-
settled = true;
|
|
418
|
-
reject(error);
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
websocket.addEventListener('message', event => {
|
|
422
|
-
on_message(event.data, websocket);
|
|
423
|
-
});
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
/** 通过创建 remote 对象对 websocket rpc 进行抽象
|
|
427
|
-
创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
|
|
428
|
-
使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
|
|
429
|
-
使用 remote.call 进行一元 rpc
|
|
430
|
-
使用 remote.send 结合 message.id 进行复杂 rpc
|
|
431
|
-
创建后等到首个 remote.call 或 remote.send 时自动建立实际 websocket 连接
|
|
432
|
-
rpc 状态与底层连接的状态无关,如果是传入 url 创建的 remote 实例,remote.send 时检测到断线会自动建立新的 websocket 连接
|
|
433
|
-
|
|
434
|
-
@example
|
|
435
|
-
// Zero 继承自 Remote 并通过 call 实现了一些方法
|
|
436
|
-
let zero = new Zero({ local: true })
|
|
437
|
-
|
|
438
|
-
// 一元 rpc
|
|
439
|
-
await zero.repl_ts('1234')
|
|
440
|
-
|
|
441
|
-
// 订阅流
|
|
442
|
-
const id = genid()
|
|
443
|
-
|
|
444
|
-
zero.handlers.set(id, ({ data: [chunk] }: Message<[Uint8Array]>) => {
|
|
445
|
-
term.write(chunk)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
zero.send({ id, func: 'subscribe_stdio' }) */
|
|
449
|
-
export class Remote {
|
|
450
|
-
/** 在构造 Remote 时, this.initiator = Boolean(url || websocket)
|
|
451
|
-
- true: 作为 websocket 连接发起方
|
|
452
|
-
- false: 作为 websocket 连接接收方 */
|
|
453
|
-
initiator;
|
|
454
|
-
/** 作为 websocket 连接发起方,对端的 url 地址 */
|
|
455
|
-
url;
|
|
456
|
-
/** 作为 websocket 连接发起方有 websocket lock */
|
|
457
|
-
lwebsocket;
|
|
458
|
-
/** websocket 连接发起方,接收方,都能被对端通过 (rpc message).func 调用的 rpc 函数 */
|
|
459
|
-
funcs;
|
|
460
|
-
/** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
|
|
461
|
-
一元 rpc 接收方不需要设置 handlers, 发送方需要 */
|
|
462
|
-
handlers = new Map();
|
|
463
|
-
keeper;
|
|
464
|
-
/** `true` 是否打印连接信息、错误信息 */
|
|
465
|
-
print = true;
|
|
466
|
-
/** `false` 打印所有交互的 rpc messages */
|
|
467
|
-
verbose = false;
|
|
468
|
-
first_error = true;
|
|
469
|
-
keeping = false;
|
|
470
|
-
reconnecting = false;
|
|
471
|
-
disconnected = false;
|
|
472
|
-
/** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
|
|
473
|
-
作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
|
|
474
|
-
constructor({ url, funcs, print, verbose, websocket, keeper, on_error, } = {}) {
|
|
475
|
-
check(!(url && websocket), '构建 Remote 时 url 和 websocket 最多只能传一个');
|
|
476
|
-
this.initiator = Boolean(url || websocket);
|
|
477
|
-
if (url)
|
|
478
|
-
this.url = url;
|
|
479
|
-
check(!funcs?.echo);
|
|
480
|
-
this.funcs = {
|
|
481
|
-
...funcs,
|
|
482
|
-
echo({ data }) {
|
|
483
|
-
return data;
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
if (print !== undefined)
|
|
487
|
-
this.print = print;
|
|
488
|
-
if (verbose !== undefined)
|
|
489
|
-
this.verbose = verbose;
|
|
490
|
-
if (on_error)
|
|
491
|
-
this.on_error = on_error;
|
|
492
|
-
if (this.initiator)
|
|
493
|
-
this.lwebsocket = new Lock(websocket || null);
|
|
494
|
-
if (keeper) {
|
|
495
|
-
check(this.initiator && url);
|
|
496
|
-
this.keeper = {
|
|
497
|
-
reconnect_interval: 5000,
|
|
498
|
-
heartbeat_interval: 1000 * 60,
|
|
499
|
-
error_delay: 2000,
|
|
500
|
-
...keeper
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
/** 统一处理首次连接和连接后的 websocket 错误 */
|
|
505
|
-
_on_error = (error, websocket) => {
|
|
506
|
-
if (this.keeper && !this.reconnecting && !this.disconnected) // 在一段时间后调度错误重连
|
|
507
|
-
(async () => {
|
|
508
|
-
this.reconnecting = true;
|
|
509
|
-
const { error_delay, reconnect_interval } = this.keeper;
|
|
510
|
-
if (this.first_error) {
|
|
511
|
-
this.first_error = false;
|
|
512
|
-
await delay(error_delay);
|
|
513
|
-
}
|
|
514
|
-
else
|
|
515
|
-
await delay(reconnect_interval);
|
|
516
|
-
this.reconnecting = false;
|
|
517
|
-
if (!this.disconnected)
|
|
518
|
-
try {
|
|
519
|
-
await timeout(3000, this.connect(), undefined, this.print);
|
|
520
|
-
this.first_error = true;
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
// 重连失败的错误这里需要简单打印下,_on_error 不会打印,这里也不继续往上抛了
|
|
524
|
-
if (this.print)
|
|
525
|
-
console.log(error.message);
|
|
526
|
-
// 重连由 this.connect 里面调用 this._on_error 处理
|
|
527
|
-
}
|
|
528
|
-
})();
|
|
529
|
-
this.on_error(error, websocket);
|
|
530
|
-
};
|
|
531
|
-
_on_message = (data, websocket) => {
|
|
532
|
-
this.handle(new Uint8Array(data), websocket);
|
|
533
|
-
};
|
|
534
|
-
/** 使用者自定义的在 websocket 连接出错时,或者 handlers 出错时的处理 */
|
|
535
|
-
on_error(error, websocket) {
|
|
536
|
-
// 使用者未定义 Remote 如何处理 error 时,一般来说直接忽略即可,因为 handlers 中报错了也会返回给对端
|
|
537
|
-
if (this.print)
|
|
538
|
-
console.log(error && error instanceof WebSocketConnectionError ? error.message : error);
|
|
539
|
-
// 这里继续往上层抛没有太大意义,上面一般都是 websocket on_message 这些
|
|
540
|
-
}
|
|
541
|
-
/** 幂等,保证 websocket 已连接,否则抛出异常
|
|
542
|
-
一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
|
|
543
|
-
连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
|
|
544
|
-
作为 websocket 连接发起方,不需要传入 websocket
|
|
545
|
-
作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
|
|
546
|
-
async connect(websocket) {
|
|
547
|
-
if (!this.initiator)
|
|
548
|
-
if (websocket.readyState !== WebSocketOpen)
|
|
549
|
-
throw new Error('传入的 websocket 连接已断开');
|
|
550
|
-
else
|
|
551
|
-
return;
|
|
552
|
-
if (this.lwebsocket.resource?.readyState === WebSocketOpen)
|
|
553
|
-
return;
|
|
554
|
-
if (!this.url)
|
|
555
|
-
throw new Error('创建 Remote 时传入的 websocket 连接已断开');
|
|
556
|
-
let reconnected = false;
|
|
557
|
-
// 假设有多个请求想要并发连接 websocket, 且此时 websocket 是断开的状态
|
|
558
|
-
// 应该排队依次连接,而不是后续的连接直接使用第一次连接的 promise,后续调用还是应该尝试重连(不止连接一次)
|
|
559
|
-
await this.lwebsocket.request(async (websocket) => {
|
|
560
|
-
// 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
|
|
561
|
-
// 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
|
|
562
|
-
if (websocket?.readyState === WebSocketOpen)
|
|
563
|
-
return;
|
|
564
|
-
// 重连
|
|
565
|
-
try {
|
|
566
|
-
this.lwebsocket.resource = await connect_websocket(this.url, {
|
|
567
|
-
on_message: this._on_message,
|
|
568
|
-
on_error: this._on_error,
|
|
569
|
-
print: this.print
|
|
570
|
-
});
|
|
571
|
-
reconnected = true;
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
this._on_error(error);
|
|
575
|
-
throw error;
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
if (!this.keeper)
|
|
579
|
-
return;
|
|
580
|
-
const { heartbeat_interval, func, args } = this.keeper;
|
|
581
|
-
// 首次连接成功时,开始心跳保活
|
|
582
|
-
if (!this.keeping) {
|
|
583
|
-
this.keeping = true;
|
|
584
|
-
(async () => {
|
|
585
|
-
for (;;) {
|
|
586
|
-
await delay(heartbeat_interval);
|
|
587
|
-
if (this.disconnected)
|
|
588
|
-
break;
|
|
589
|
-
if (!this.reconnecting)
|
|
590
|
-
try {
|
|
591
|
-
await timeout(1000 * 2, this.call('echo'), undefined, this.print);
|
|
592
|
-
}
|
|
593
|
-
catch (error) {
|
|
594
|
-
if (this.print)
|
|
595
|
-
console.log(error.message);
|
|
596
|
-
this._on_error(error);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
})();
|
|
600
|
-
}
|
|
601
|
-
if (reconnected && func)
|
|
602
|
-
await this.call(func, args);
|
|
603
|
-
}
|
|
604
|
-
/** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
|
|
605
|
-
disconnect() {
|
|
606
|
-
this.disconnected = true;
|
|
607
|
-
this.lwebsocket.resource?.close(1000);
|
|
608
|
-
}
|
|
609
|
-
/** 发送 message 到对端 remote
|
|
610
|
-
作为 websocket 连接发起方,不需要传入 websocket
|
|
611
|
-
作为 websocket 连接接收方,必传 websocket 参数
|
|
612
|
-
发送或连接出错时自动清理 message.id 对应的 handler */
|
|
613
|
-
async send(message, websocket) {
|
|
614
|
-
if (this.verbose)
|
|
615
|
-
console.log('remote.send:', message);
|
|
616
|
-
try {
|
|
617
|
-
await this.connect(websocket);
|
|
618
|
-
message[message_symbol] = true;
|
|
619
|
-
(websocket || this.lwebsocket.resource).send(pack(message));
|
|
620
|
-
}
|
|
621
|
-
catch (error) {
|
|
622
|
-
if (message.id)
|
|
623
|
-
this.handlers.delete(message.id);
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
/** 处理接收到的 websocket message 并解析, 根据 message.id 或 message.func 分发到对应的 handler 进行处理,
|
|
628
|
-
handler 处理完成后:
|
|
629
|
-
- 传了 func: 调用函数的情况下 (通常是一元 rpc),总是将返回值包装为 message 回传
|
|
630
|
-
- 未传 func: 通过 id 调用,如果 handler 返回非 undefined 的值,也包装为 message 回传
|
|
631
|
-
|
|
632
|
-
如果 message.done == true 则对端指示当前 remote 可以清理 handler
|
|
633
|
-
使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214
|
|
634
|
-
这个方法一般不会抛出错误,也不需要 await,一般在 websocket on_message 时使用 */
|
|
635
|
-
async handle(data, websocket) {
|
|
636
|
-
let message;
|
|
637
|
-
try {
|
|
638
|
-
check(data[0] === 0xcc, 'message 格式错误');
|
|
639
|
-
message = parse(data);
|
|
640
|
-
}
|
|
641
|
-
catch (error) {
|
|
642
|
-
this.on_error(error);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const { id, func, done } = message;
|
|
646
|
-
if (this.verbose)
|
|
647
|
-
console.log('remote.handle:', message);
|
|
648
|
-
let handler;
|
|
649
|
-
if (func) {
|
|
650
|
-
handler = this.funcs[func];
|
|
651
|
-
// 传了 func 调用函数的情况下,如果 message.data 为 undefined, 默认为 [ ]
|
|
652
|
-
if (message.data === undefined)
|
|
653
|
-
message.data = [];
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
handler = this.handlers.get(id);
|
|
657
|
-
if (done && handler)
|
|
658
|
-
this.handlers.delete(id);
|
|
659
|
-
}
|
|
660
|
-
try {
|
|
661
|
-
if (handler) {
|
|
662
|
-
const data = await handler(message, websocket);
|
|
663
|
-
if (func || data !== undefined)
|
|
664
|
-
await this.send({ id, data }, websocket);
|
|
665
|
-
}
|
|
666
|
-
else
|
|
667
|
-
throw message.error || new Error(`找不到 rpc handler: ${func ? `func: ${func.quote()}` : `id: ${id}`}`);
|
|
668
|
-
}
|
|
669
|
-
catch (error) {
|
|
670
|
-
// handler 出错并不意味着 rpc 一定会结束,可能 error 是运行中的正常数据,所以不能清理 handler
|
|
671
|
-
if (websocket.readyState === WebSocketOpen &&
|
|
672
|
-
!message.error // 防止无限循环往对方发送 error, 只有在对方无错误时才可以发送
|
|
673
|
-
)
|
|
674
|
-
await this.send({ id, error, /* 不能设置 done 清理对面 handler, 理由同上 */ }, websocket);
|
|
675
|
-
// 这里继续往上层抛没有太大意义,上面一般都是 websocket on_message 这些,交给自定义或默认的 on_error 处理
|
|
676
|
-
this.on_error(error);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
/** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应)
|
|
680
|
-
作为 websocket 连接发起方,不需要传入 websocket
|
|
681
|
-
作为 websocket 连接接收方,必传 websocket 参数 */
|
|
682
|
-
async call(func, args, websocket) {
|
|
683
|
-
return new Promise(async (resolve, reject) => {
|
|
684
|
-
const id = genid();
|
|
685
|
-
this.handlers.set(id, ({ error, data }) => {
|
|
686
|
-
if (error)
|
|
687
|
-
reject(error);
|
|
688
|
-
else
|
|
689
|
-
resolve(data);
|
|
690
|
-
this.handlers.delete(id);
|
|
691
|
-
});
|
|
692
|
-
try {
|
|
693
|
-
await this.send({ id, func, data: args }, websocket); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
|
|
694
|
-
}
|
|
695
|
-
catch (error) {
|
|
696
|
-
reject(error);
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
/** 调用对端 remote 中的 func, 开始订阅并接收连续的消息 (订阅流)
|
|
701
|
-
- func: 订阅处理函数
|
|
702
|
-
- on_data: 接收开始订阅后的数据
|
|
703
|
-
- options?:
|
|
704
|
-
- on_error?: 处理开始订阅后的错误
|
|
705
|
-
- websocket?: 作为 websocket 连接接收方,必传 websocket 参数 */
|
|
706
|
-
async subscribe(func, on_data, { on_error = rethrow, websocket } = {}) {
|
|
707
|
-
const id = genid();
|
|
708
|
-
let psubscribed = new Promise((resolve, reject) => {
|
|
709
|
-
let first = true;
|
|
710
|
-
this.handlers.set(id, ({ error, data }) => {
|
|
711
|
-
if (error) {
|
|
712
|
-
if (first) {
|
|
713
|
-
first = false;
|
|
714
|
-
this.handlers.delete(id);
|
|
715
|
-
reject(error);
|
|
716
|
-
}
|
|
717
|
-
else
|
|
718
|
-
on_error(error);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
if (first) {
|
|
722
|
-
first = false;
|
|
723
|
-
resolve({ id, data: data });
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
on_data(data);
|
|
727
|
-
});
|
|
728
|
-
});
|
|
729
|
-
try {
|
|
730
|
-
await this.send({ id, func }, websocket);
|
|
731
|
-
}
|
|
732
|
-
catch (error) {
|
|
733
|
-
this.handlers.delete(id);
|
|
734
|
-
throw error;
|
|
735
|
-
}
|
|
736
|
-
return psubscribed;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
/** 为连接到 server 的 client 创建 RemoteClient,以便后续调用 client 中方法 */
|
|
740
|
-
export class RemoteClient {
|
|
741
|
-
remote;
|
|
742
|
-
websocket;
|
|
743
|
-
constructor(remote, websocket) {
|
|
744
|
-
this.remote = remote;
|
|
745
|
-
this.websocket = websocket;
|
|
746
|
-
}
|
|
747
|
-
/** 调用 client 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
|
|
748
|
-
async call(func, args) {
|
|
749
|
-
return this.remote.call(func, args, this.websocket);
|
|
750
|
-
}
|
|
751
|
-
/** 发送 message 到 client
|
|
752
|
-
发送或连接出错时自动清理 message.id 对应的 handler */
|
|
753
|
-
async send(message) {
|
|
754
|
-
return this.remote.send(message, this.websocket);
|
|
755
|
-
}
|
|
756
|
-
[inspect.custom]() {
|
|
757
|
-
const { [inspect.custom]: _inspect, websocket, remote, ...others } = this;
|
|
758
|
-
return {
|
|
759
|
-
remote: '<Remote(receiver)>',
|
|
760
|
-
websocket: `<WebSocket(${websocket_states[websocket.readyState]})>`,
|
|
761
|
-
...others
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
300
|
//# sourceMappingURL=net.js.map
|