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/net.common.js CHANGED
@@ -1,3 +1,8 @@
1
+ import { platform } from "./platform.common.js";
2
+ import { rethrow } from "./prototype.common.js"; // .bracket()
3
+ import { message_symbol, pack, parse } from "./io.common.js";
4
+ import { check, defer, delay, genid, Lock, set_error_message, timeout, TimeoutError } from "./utils.common.js";
5
+ import { t } from "./i18n/instance.js";
1
6
  /** 对于 request() 函数来说无意义的 headers,会自动过滤掉 */
2
7
  export const drop_request_headers = new Set([
3
8
  // : 开头的 key
@@ -10,4 +15,563 @@ export const drop_request_headers = new Set([
10
15
  'transfer-encoding',
11
16
  'upgrade',
12
17
  ]);
18
+ export const WebSocketConnecting = 0;
19
+ export const WebSocketOpen = 1;
20
+ export const WebSocketClosing = 2;
21
+ export const WebSocketClosed = 3;
22
+ export const websocket_states = ['connecting', 'open', 'closing', 'closed'];
23
+ export class WebSocketConnectionError extends Error {
24
+ name = 'WebSocketConnectionError';
25
+ // 这里不保留 websocket 引用,防止循环引用导致 JSON 序列化失败
26
+ url;
27
+ protocols;
28
+ type;
29
+ /** close 事件时为 close code (非 1000 的 number), error 事件为 error code (可能是 string) */
30
+ code;
31
+ reason;
32
+ event;
33
+ // --- node.js error event 独有
34
+ address;
35
+ errno;
36
+ port;
37
+ syscall;
38
+ static get_reason_string(code, reason) {
39
+ // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
40
+ if (!reason)
41
+ if (code === 1001)
42
+ reason = t('对端关闭');
43
+ else if (code === 1006)
44
+ reason = t('网络中断');
45
+ return reason ? `${reason} (${code})` : code;
46
+ }
47
+ constructor(url, protocols, event, message) {
48
+ let type, reason, code, error;
49
+ if (!message)
50
+ if (event)
51
+ if ((type = event.type) === 'error') {
52
+ ({ error } = event);
53
+ if (error) {
54
+ ({ code, reason } = error);
55
+ message = `${t('连接被关闭')}: ` + [
56
+ error.message,
57
+ WebSocketConnectionError.get_reason_string(code, reason),
58
+ error.syscall ? `${error.syscall}(${error.errno})` : ''
59
+ ].filter(Boolean).join(' ');
60
+ }
61
+ else
62
+ message = t('连接被关闭');
63
+ }
64
+ else {
65
+ ({ code, reason } = event);
66
+ message = `${t('连接被关闭')}: ${WebSocketConnectionError.get_reason_string(code, reason)}`;
67
+ }
68
+ else
69
+ message = t('连接被关闭');
70
+ super(new Date().to_time_str() + ' ' +
71
+ (url || '传入') +
72
+ (protocols ? ' ' + protocols.join(', ').bracket() : '') +
73
+ ' ' + message);
74
+ this.url = url;
75
+ this.protocols = protocols;
76
+ if (!event)
77
+ return;
78
+ this.type = type;
79
+ this.code = code;
80
+ this.reason = reason;
81
+ if (type === 'error' && platform.nodejs) {
82
+ this.cause = error;
83
+ this.address = error.address;
84
+ this.errno = error.errno;
85
+ this.port = error.port;
86
+ this.syscall = error.syscall;
87
+ }
88
+ this.event = event;
89
+ }
90
+ }
91
+ let websocket_proxy_agents = {};
92
+ /** 连接 websocket url, 设置各种事件监听器。在 open 事件后 resolve, 返回 websocket
93
+ 遇到 error, close 事件时会创建 WebSocketConnectionError:
94
+ - reject 掉返回的 promise (若此时未 settle)
95
+ - 作为参数调用 on_error (已 settle 且有 on_error 回调)
96
+ 可以用 WebSocket.bufferedAmount 来查询大消息的发送进度
97
+ https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/bufferedAmount
98
+ - url
99
+ - options:
100
+ - print?: 是否打印连接、关闭信息
101
+ - protocols?
102
+ - max_payload?: `8 gb` (仅 nodejs 环境有效)
103
+ - proxy?: string (仅 nodejs 环境有效)
104
+ - on_message: 根据 websocket frame 的 opcode 不同 (text frame 或 binary frame),event 中的 data 对应为 ArrayBuffer 或者 string
105
+ https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
106
+ - on_error?: 在 websocket 出错和非正常关闭 (close, error 事件) 时都调用,可以根据 error.type 来区分,error 的类型是 WebSocketConnectionError,
107
+ type 为 'close' 时有 code 和 reason 属性
108
+ - on_close?: 和 websocket 的 'close' 事件不相同,只在正常关闭 (close code 为 1000) 时才调用,否则都会调用 on_error
109
+ https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes */
110
+ export async function connect_websocket(url, { print = true, protocols, max_payload = 2 ** 33, // 8 GB
111
+ proxy, on_message, on_error, on_close, }) {
112
+ const { nodejs } = platform;
113
+ let websocket = new (await platform.get_websocket())(url, protocols,
114
+ // @ts-ignore
115
+ nodejs ?
116
+ {
117
+ maxPayload: max_payload,
118
+ skipUTF8Validation: true,
119
+ allowSynchronousEvents: true,
120
+ ...proxy ? {
121
+ agent: websocket_proxy_agents[proxy] ??= new (await platform.get_https_proxy_agent())(proxy)
122
+ } : {}
123
+ }
124
+ :
125
+ undefined);
126
+ // https://stackoverflow.com/questions/11821096/what-is-the-difference-between-an-arraybuffer-and-a-blob/39951543
127
+ websocket.binaryType = 'arraybuffer';
128
+ return new Promise((resolve, reject) => {
129
+ let settled = false;
130
+ // websocket error, close 事件只选一个最详细的构造 WebSocketConnectionError 并调用 on_error
131
+ let errored;
132
+ websocket.addEventListener('open', event => {
133
+ if (print)
134
+ console.log(websocket.url +
135
+ (websocket.protocol ? ' ' + websocket.protocol.bracket() : '') +
136
+ t(' 已连接'));
137
+ settled = true;
138
+ resolve(websocket);
139
+ }, { once: true });
140
+ // error 事件会先于 close 事件
141
+ // node.js 环境下 error 事件的错误信息比较多,浏览器环境下 close 的错误信息比较多
142
+ // 在浏览器环境下延后处理 error 事件,放到微任务队列之后的 timers 队列中
143
+ // https://blog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa
144
+ function on_close_or_error(event) {
145
+ websocket.removeEventListener('message', _on_message);
146
+ const { type } = event;
147
+ // 正常关闭
148
+ if (type === 'close' && event.code === 1000) {
149
+ if (on_close)
150
+ on_close(event, websocket);
151
+ else if (print)
152
+ console.log(`${websocket.url} 已正常关闭`);
153
+ return;
154
+ }
155
+ // 观察一段时间,在 nodejs 中应该没有先 close 再 error 的情况
156
+ if (errored === 'close' && platform.nodejs) {
157
+ console.warn('在 nodejs 中应该没有先 close 再 error 的情况', event.error);
158
+ errored = null;
159
+ }
160
+ // 已经调用过 on_error 了
161
+ if (errored)
162
+ return;
163
+ errored = type;
164
+ const error = new WebSocketConnectionError(websocket.url, protocols, event);
165
+ if (!settled) {
166
+ settled = true;
167
+ reject(error);
168
+ return;
169
+ }
170
+ if (on_error)
171
+ on_error(error, websocket);
172
+ else if (print)
173
+ console.log(error);
174
+ }
175
+ // 检查实际情况下 error 事件会先于 close 事件,观察一段时间后可删去
176
+ let closed = false;
177
+ websocket.addEventListener('error', (event) => {
178
+ if (closed)
179
+ console.warn('close 事件先于 error 事件发生,奇怪!');
180
+ if (nodejs)
181
+ on_close_or_error(event);
182
+ else
183
+ setTimeout(() => { on_close_or_error(event); });
184
+ }, { once: true });
185
+ websocket.addEventListener('close', event => {
186
+ closed = true;
187
+ on_close_or_error(event);
188
+ }, { once: true });
189
+ function _on_message(event) {
190
+ on_message(event.data, websocket);
191
+ }
192
+ websocket.addEventListener('message', _on_message);
193
+ });
194
+ }
195
+ /** 通过创建 remote 对象对 websocket rpc 进行抽象
196
+ 使用 remote.call() 进行一元 rpc
197
+ 使用 remote.send() 结合 message.id 进行复杂 rpc
198
+
199
+ 可传入 funcs 注册函数,使得对端能调用,传入后,
200
+ 连接发起方在检测到连接断开后间隔 2s (首次) / 10s 尝试重连
201
+
202
+ 可启用 probe 选项启用主动探测连接,进一步确保连接可靠性
203
+
204
+ 有两种创建方法:
205
+ - 传入 url 主动创建 websocket 连接
206
+ 创建后等到首个 remote.call 或 remote.send 时建立实际 websocket 连接
207
+ - 传入 websocket,监听事件,将已有的 websocket 连接转为 rpc 通道
208
+
209
+ rpc 状态与底层连接的状态无关,如果是发起方,remote.send 时检测到断线会自动尝试建立新的 websocket 连接 */
210
+ export class Remote {
211
+ /** 对端名称,方便接收端提供更明确的报错信息 */
212
+ name;
213
+ /** 作为 websocket 连接发起方,对端的 url 地址 */
214
+ url;
215
+ /** 能被对端调用的函数 */
216
+ funcs;
217
+ /** `false` 是否启用主动探测连接,在连接状态下以 30s 间隔发送心跳包确保连接状态 */
218
+ probe = false;
219
+ /** websocket 连接建立成功时调用对端 register 函数的参数 (仅发起方有效) */
220
+ args;
221
+ /** `true` 是否打印连接信息、错误信息 */
222
+ print = true;
223
+ /** `false` 打印所有交互的 rpc messages */
224
+ verbose = false;
225
+ // --- states
226
+ /** 防止作为 websocket 连接发起方时并发创建 websocket 连接 */
227
+ lwebsocket = new Lock();
228
+ /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
229
+ 一元 rpc 接收方不需要设置 handlers, 发送方需要 */
230
+ handlers = new Map();
231
+ /** 是否正在定时主动检测重连 */
232
+ probing = false;
233
+ /** websocket 检测到错误,_on_error 被调用,此时会尝试一段时间后重连 */
234
+ reconnecting;
235
+ /** 用户是否手动调用了 disconnect() 主动断开了连接 (仅发起方有效) */
236
+ disconnected = false;
237
+ /** on_error 接收到的,正常 websocket 连接发生的首个非 TimeoutError 错误 */
238
+ error;
239
+ /** 作为 websocket 连接发起方,传入 url
240
+ 作为 websocket 连接接收方,传入 websocket + name */
241
+ constructor({ name, url, websocket, funcs, print, verbose, probe, args, on_error, } = {}) {
242
+ if (name)
243
+ this.name = name;
244
+ if (url) {
245
+ check(!websocket, '构建 Remote 时 url 和 websocket 只能传其中一个');
246
+ this.url = url;
247
+ }
248
+ else { // 连接接收方,一定是 nodejs 环境
249
+ check(websocket, '构建 Remote 时需传入 url 或者 websocket');
250
+ this.lwebsocket.resource = websocket;
251
+ websocket.binaryType = 'arraybuffer';
252
+ // 参考 connect_websocket 实现
253
+ let errored = false;
254
+ const on_close_or_error = (event) => {
255
+ websocket.removeEventListener('message', _on_message);
256
+ // 正常关闭
257
+ if (event.type === 'close' && event.code === 1000) {
258
+ if (print)
259
+ console.log(`${this.name || websocket.url || '传入连接'} 已正常关闭`);
260
+ return;
261
+ }
262
+ // 已经调用过 on_error 了
263
+ if (errored)
264
+ return;
265
+ errored = true;
266
+ this._on_error(new WebSocketConnectionError(this.name || websocket.url, websocket.protocol ? [websocket.protocol] : undefined, event));
267
+ };
268
+ // 检查实际情况下 error 事件会先于 close 事件,观察一段时间后可删去
269
+ let closed = false;
270
+ websocket.addEventListener('error', (event) => {
271
+ if (closed)
272
+ console.warn('close 事件先于 error 事件发生,奇怪!');
273
+ on_close_or_error(event);
274
+ }, { once: true });
275
+ websocket.addEventListener('close', event => {
276
+ closed = true;
277
+ on_close_or_error(event);
278
+ }, { once: true });
279
+ const _on_message = (event) => {
280
+ this._on_message(event.data);
281
+ };
282
+ websocket.addEventListener('message', _on_message);
283
+ }
284
+ if (funcs) {
285
+ check(!funcs.echo);
286
+ this.funcs = funcs;
287
+ }
288
+ if (probe !== undefined)
289
+ this.probe = probe;
290
+ if (args)
291
+ this.args = args;
292
+ if (print !== undefined)
293
+ this.print = print;
294
+ if (verbose !== undefined)
295
+ this.verbose = verbose;
296
+ if (on_error)
297
+ this.on_error = on_error;
298
+ }
299
+ /** 统一处理首次连接和连接后的 websocket 错误 */
300
+ _on_error = (error) => {
301
+ if (this.disconnected)
302
+ return;
303
+ // 去重
304
+ if (error === this.error)
305
+ return;
306
+ // 重连
307
+ if (error instanceof WebSocketConnectionError &&
308
+ !this.reconnecting &&
309
+ this.url &&
310
+ (this.funcs || this.probe)) {
311
+ this.reconnecting = setTimeout(this.reconnect, this.error ? 20_000 : 2_000);
312
+ this.error = error;
313
+ }
314
+ this.on_error(error, this);
315
+ };
316
+ reconnect = () => {
317
+ this.reconnecting = null;
318
+ this.try_connect();
319
+ };
320
+ _on_message = (data) => {
321
+ this.handle(new Uint8Array(data));
322
+ };
323
+ /** 使用者自定义的在 websocket 连接出错时,或者 handlers 出错时的处理
324
+ 用户设置后会覆盖默认的 print 错误功能 */
325
+ on_error(error, remote) {
326
+ // 使用者未定义 Remote 如何处理 error 时,一般来说简单打印即可,因为 handlers 中报错了也会返回给对端
327
+ if (this.print)
328
+ this.print_error(error);
329
+ // 这里继续往上层抛没有太大意义,上面一般都是 websocket on_message 这些
330
+ }
331
+ print_error(error) {
332
+ console.warn(error instanceof WebSocketConnectionError ?
333
+ error.message
334
+ :
335
+ error);
336
+ }
337
+ /** 检查是否已经有 error, websocket 状态是否正常 (open | connecting)
338
+ 返回 true: 连接正常,false: 连接异常 */
339
+ check_connection() {
340
+ if (this.error)
341
+ return false;
342
+ const { readyState } = this.lwebsocket.resource || {};
343
+ return readyState === WebSocketOpen || readyState === WebSocketConnecting;
344
+ }
345
+ /** 幂等,保证 websocket 已连接,否则抛出异常
346
+ 通常用于手动建立 websocket 连接并确保成功
347
+ 连接断开后,作为发起方会自动创建新的 websocket 连接;
348
+ 作为接收方只会检查连接状态,在断开时抛出异常
349
+ 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
350
+ 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败 */
351
+ async connect() {
352
+ if (this.disconnected)
353
+ throw new Error(`remote (${this.name || this.url}) 已调用过 disconnect 断开连接,无法再次 connect`);
354
+ // 首次 connect 开始探测
355
+ if (this.probe && !this.probing)
356
+ this.probe_connection();
357
+ if (this.check_connection())
358
+ return;
359
+ try {
360
+ if (!this.url) {
361
+ if (this.error)
362
+ throw this.error;
363
+ const { resource: websocket } = this.lwebsocket;
364
+ throw new WebSocketConnectionError(this.name || websocket.url, websocket.protocol ? [websocket.protocol] : undefined, undefined, `连接状态异常: ${websocket_states[this.lwebsocket.resource.readyState]}`);
365
+ }
366
+ // 假设有多个请求想要并发连接 websocket, 且此时 websocket 是断开的状态
367
+ // 应该排队依次连接,而不是后续的连接直接使用第一次连接的 promise,后续调用还是应该尝试重连(不止连接一次)
368
+ await this.lwebsocket.request(this._connect);
369
+ }
370
+ catch (error) {
371
+ this._on_error(error);
372
+ throw error;
373
+ }
374
+ }
375
+ _connect = async () => {
376
+ if (this.check_connection())
377
+ return;
378
+ // 重连
379
+ this.lwebsocket.resource = await connect_websocket(this.url, {
380
+ on_message: this._on_message,
381
+ on_error: this._on_error,
382
+ print: this.print
383
+ });
384
+ this.error = null;
385
+ if (this.args)
386
+ await this.call('register', this.args);
387
+ };
388
+ /** 尝试建立连接,通常用于开始保活
389
+ 重连错误会在 this.connect 里面调用 this._on_error 处理 */
390
+ async try_connect() {
391
+ try {
392
+ await this.connect();
393
+ }
394
+ catch { }
395
+ }
396
+ /** 开始心跳保活 */
397
+ async probe_connection(print_timeout = true) {
398
+ // 手动调用该函数时也会启用 probe
399
+ this.probe = true;
400
+ this.probing = true;
401
+ for (let timeouted = false;;) {
402
+ await delay(timeouted ? 5_000 : 30_000);
403
+ if (this.disconnected || !this.url && !this.check_connection() || !this.probe)
404
+ break;
405
+ if (this.reconnecting)
406
+ continue;
407
+ // 产生的非 TimeoutError 应该都通过 _on_error 输出了,同时可能触发了重连
408
+ timeouted = (await this.test(print_timeout)) instanceof TimeoutError;
409
+ }
410
+ }
411
+ /** 检测连接,返回连接错误,出错时触发 on_error 打印错误信息
412
+ 返回值:
413
+ - 连接正常返回: undefined
414
+ - 连接错误返回: 实际的连接错误,通常是 WebSocketConnectionError | TimeoutError */
415
+ async test(print_timeout = true) {
416
+ try {
417
+ await timeout(5_000, this.call('echo'), undefined, false);
418
+ }
419
+ catch (error) {
420
+ if (error instanceof TimeoutError) {
421
+ let message;
422
+ const websocket = this.lwebsocket.resource;
423
+ if (websocket?.bufferedAmount)
424
+ message = `连接正忙,待发 ${websocket.bufferedAmount.to_fsize_str()}`;
425
+ else
426
+ message = '连接正在收数据或无响应';
427
+ set_error_message(error, message = `${new Date().to_time_str()} ${this.name || this.url} ${message}`);
428
+ if (this.print && print_timeout)
429
+ console.warn(message);
430
+ }
431
+ return error;
432
+ }
433
+ }
434
+ /** 手动关闭到对端的 websocket 连接 */
435
+ disconnect() {
436
+ this.disconnected = true;
437
+ this.lwebsocket.resource?.close(1000);
438
+ }
439
+ /** 发送 message 到对端 remote
440
+ 发送或连接出错时自动清理 message.id 对应的 handler */
441
+ async send(message) {
442
+ if (this.verbose)
443
+ console.log('remote.send:', message);
444
+ try {
445
+ await this.connect();
446
+ message[message_symbol] = true;
447
+ // 不需要独占 websocket
448
+ this.lwebsocket.resource.send(pack(message));
449
+ }
450
+ catch (error) {
451
+ if (message.id)
452
+ this.handlers.delete(message.id);
453
+ throw error;
454
+ }
455
+ }
456
+ /** 能发就发一下,忽略发送失败, best effort */
457
+ try_send(message) {
458
+ message[message_symbol] = true;
459
+ // websocket.send 本身通常不会 throw error
460
+ this.lwebsocket.resource?.send(pack(message));
461
+ }
462
+ /** 处理接收到的 websocket message 并解析, 根据 message.id 或 message.func 分发到对应的 handler 进行处理,
463
+ handler 处理完成后:
464
+ - 传了 func: 调用函数的情况下 (通常是一元 rpc),总是将返回值包装为 message 回传
465
+ - 未传 func: 通过 id 调用,如果 handler 返回非 undefined 的值,也包装为 message 回传
466
+
467
+ 如果 message.done == true 则对端指示当前 remote 可以清理 handler
468
+ 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214
469
+ 这个方法一般不会抛出错误,也不需要 await,一般在 websocket on_message 时使用 */
470
+ async handle(data) {
471
+ let message;
472
+ try {
473
+ check(data[0] === 0xcc, 'message 格式错误');
474
+ message = parse(data);
475
+ }
476
+ catch (error) {
477
+ this.on_error(error, this);
478
+ return;
479
+ }
480
+ const { id, func, done } = message;
481
+ if (this.verbose)
482
+ console.log('remote.handle:', message);
483
+ try {
484
+ if (func === 'echo') {
485
+ await this.send({ id, data: message.data });
486
+ return;
487
+ }
488
+ let handler;
489
+ if (func) {
490
+ handler = this.funcs[func];
491
+ // 传了 func 调用函数的情况下,如果 message.data 为 undefined, 默认为 [ ]
492
+ if (message.data === undefined)
493
+ message.data = [];
494
+ }
495
+ else {
496
+ handler = this.handlers.get(id);
497
+ if (done && handler)
498
+ this.handlers.delete(id);
499
+ }
500
+ if (handler) {
501
+ const data = await handler(message, this);
502
+ if (func || data !== undefined)
503
+ await this.send({ id, data });
504
+ }
505
+ else if (message.error)
506
+ throw message.error;
507
+ else if (func)
508
+ throw new Error(`找不到 func: ${func.quote()}`);
509
+ else
510
+ console.log(`忽略找不到的 handler id: ${id}`);
511
+ }
512
+ catch (error) {
513
+ // handler 出错并不意味着 rpc 一定会结束,可能 error 是运行中的正常数据,所以不能清理 handler
514
+ // 这里继续往上层抛没有太大意义,上面一般都是 websocket on_message 这些,交给自定义或默认的 on_error 处理
515
+ this._on_error(error);
516
+ if (this.check_connection() &&
517
+ !message.error // 防止无限循环往对方发送 error, 只有在对方无错误时才可以发送
518
+ )
519
+ try {
520
+ await this.send({ id, error /* 不能设置 done 清理对面 handler, 理由同上 */ });
521
+ }
522
+ catch { }
523
+ }
524
+ }
525
+ /** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
526
+ async call(func, args) {
527
+ let promise = defer();
528
+ const id = genid();
529
+ this.handlers.set(id, ({ error, data }) => {
530
+ if (error)
531
+ promise.reject(error);
532
+ else
533
+ promise.resolve(data);
534
+ this.handlers.delete(id);
535
+ });
536
+ await this.send({ id, func, data: args }); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
537
+ return promise;
538
+ }
539
+ /** 调用对端 remote 中的 func, 开始订阅并接收连续的消息 (订阅流)
540
+ - func: 订阅处理函数
541
+ - on_data: 接收开始订阅后的数据
542
+ - options?:
543
+ - on_error?: 处理开始订阅后的错误 */
544
+ async subscribe(func, on_data, on_error = rethrow) {
545
+ const id = genid();
546
+ let psubscribed = new Promise((resolve, reject) => {
547
+ let first = true;
548
+ this.handlers.set(id, ({ error, data }) => {
549
+ if (error) {
550
+ if (first) {
551
+ first = false;
552
+ this.handlers.delete(id);
553
+ reject(error);
554
+ }
555
+ else
556
+ on_error(error, this);
557
+ return;
558
+ }
559
+ if (first) {
560
+ first = false;
561
+ resolve({ id, data: data });
562
+ return;
563
+ }
564
+ on_data(data);
565
+ });
566
+ });
567
+ try {
568
+ await this.send({ id, func });
569
+ }
570
+ catch (error) {
571
+ this.handlers.delete(id);
572
+ throw error;
573
+ }
574
+ return psubscribed;
575
+ }
576
+ }
13
577
  //# sourceMappingURL=net.common.js.map