xshell 1.0.113 → 1.0.115

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/file.d.ts CHANGED
@@ -71,8 +71,8 @@ interface FWriteOptions {
71
71
  - print?: `true` */
72
72
  export declare function fwrite(fp: string, data: string | Uint8Array | any, options?: FWriteOptions): Promise<string>;
73
73
  export declare function fwrite(fp: FileHandle, data: string | Uint8Array | any, options?: FWriteOptions): Promise<FileHandle>;
74
- export declare function fappend(fp: string, data: string | Uint8Array, { dir, print }?: {
75
- dir?: string;
74
+ /** 追加内容到文件末尾,如果文件不存在会自动创建 */
75
+ export declare function fappend(fp: string, data: string | Uint8Array, { print }?: {
76
76
  print?: boolean;
77
77
  }): Promise<void>;
78
78
  export type FStats = fs.BigIntStats & {
package/file.js CHANGED
@@ -99,16 +99,23 @@ export async function fwrite(fp, data, { print = true } = {}) {
99
99
  }
100
100
  return fp;
101
101
  }
102
- export async function fappend(fp, data, { dir, print = true } = {}) {
103
- if (dir) {
104
- assert(dir.isdir, t('dir 必须以 / 结尾'));
105
- fp = dir + fp;
106
- }
102
+ /** 追加内容到文件末尾,如果文件不存在会自动创建 */
103
+ export async function fappend(fp, data, { print = true } = {}) {
107
104
  assert(path.isAbsolute(fp), `${t('fp 必须是绝对路径,当前为:')} ${fp}`);
108
105
  if (print)
109
106
  console.log(t('追加'), fp);
110
107
  assert(isUint8Array(data) || typeof data === 'string');
111
- await fsp.appendFile(fp, data);
108
+ try {
109
+ await fsp.appendFile(fp, data);
110
+ }
111
+ catch (error) {
112
+ if (error.code === 'ENOENT') {
113
+ await fmkdir(fp.fdir, { print: false });
114
+ await fsp.appendFile(fp, data);
115
+ }
116
+ else
117
+ throw error;
118
+ }
112
119
  }
113
120
  export async function flist(fpd, options = {}) {
114
121
  const { filter, deep = false, absolute = false, print = true, stats = false } = options;
package/net.browser.d.ts CHANGED
@@ -130,6 +130,23 @@ export interface Message<TData extends any[] = any[]> {
130
130
  /** bins: data 中哪些下标对应的原始值是 Uint8Array 类型的,如: [0, 3] */
131
131
  bins?: number[];
132
132
  }
133
+ /** 自动保持 remote 连接,只对 initiator 且传入 url 有效,
134
+ - 在未连接,或 websocket on_error 检测到连接断开后以 reconnect_interval 间隔尝试 remote.call(func) 重连
135
+ - 在连接状态下以 heartbeat_interval 间隔发送心跳包确保连接状态
136
+
137
+ 配置选项:
138
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
139
+ - args?: 连接后调用函数时传递的参数,与 func 配合使用
140
+ - reconnect_interval?: `1000 * 5` 首次重连失败后的后续尝试间隔 (单位: ms)
141
+ - heartbeat_interval?: `1000 * 60` 发送心跳包确保连接的间隔 (单位: ms)
142
+ - error_delay?: `2000` 遇到错误时首次尝试重连的延时 (单位: ms) */
143
+ export interface RemoteKeeperOptions {
144
+ func?: string;
145
+ args?: any[];
146
+ reconnect_interval?: number;
147
+ heartbeat_interval?: number;
148
+ error_delay?: number;
149
+ }
133
150
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
134
151
  创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
135
152
  使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
@@ -167,22 +184,31 @@ export declare class Remote {
167
184
  /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
168
185
  一元 rpc 接收方不需要设置 handlers, 发送方需要 */
169
186
  handlers: Map<number, MessageHandler<any[]>>;
187
+ keeper?: RemoteKeeperOptions;
170
188
  /** `false` 打印所有交互的 rpc messages */
171
189
  print: boolean;
190
+ first_error: boolean;
191
+ keeping: boolean;
172
192
  reconnecting: boolean;
193
+ disconnected: boolean;
173
194
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
174
195
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
175
196
  static pack({ id, func, data, done, error }: Message): Uint8Array;
176
197
  /** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
177
198
  作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
178
- constructor({ url, funcs, print, websocket, on_error }?: {
199
+ constructor({ url, funcs, print, websocket, keeper, on_error, }?: {
179
200
  url?: string;
180
201
  funcs?: Remote['funcs'];
181
202
  print?: boolean;
182
203
  websocket?: WebSocket;
183
- on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
204
+ keeper?: RemoteKeeperOptions;
205
+ on_error?(error: WebSocketConnectionError, websocket?: WebSocket): void;
184
206
  });
185
- on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
207
+ /** 统一处理首次连接错误和连接后的错误 */
208
+ _on_error: (error: WebSocketConnectionError, websocket?: WebSocket) => void;
209
+ _on_message: (data: ArrayBuffer, websocket: WebSocket) => void;
210
+ /** 使用者自定义的在连接后出错时的处理 */
211
+ on_error?(error: WebSocketConnectionError, websocket?: WebSocket): void;
186
212
  /** 幂等,保证 websocket 已连接,否则抛出异常
187
213
  一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
188
214
  连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
@@ -205,18 +231,4 @@ export declare class Remote {
205
231
  作为 websocket 连接发起方,不需要传入 websocket
206
232
  作为 websocket 连接接收方,必传 websocket 参数 */
207
233
  call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
208
- /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
209
- 通常在 on_error 中和首次启动时调用
210
- - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
211
- - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
212
- - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
213
- - on_error?: 接收每次尝试连接的错误 */
214
- start_reconnecting({ func, args, interval, first_delay, on_error, }?: RemoteReconnectingOptions): Promise<void>;
215
- }
216
- export interface RemoteReconnectingOptions {
217
- func?: string;
218
- args?: any[];
219
- interval?: number;
220
- first_delay?: number;
221
- on_error?(error: Error): void;
222
234
  }
package/net.browser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t } from './i18n/instance.js';
2
2
  import './prototype.browser.js'; // to_time_str()
3
- import { assert, concat, genid, delay, Lock, encode, decode } from './utils.browser.js';
3
+ import { assert, concat, genid, delay, Lock, encode, decode, timeout } from './utils.browser.js';
4
4
  const drop_request_headers = new Set([
5
5
  // : 开头的 key
6
6
  // sec-*
@@ -277,9 +277,13 @@ export class Remote {
277
277
  /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
278
278
  一元 rpc 接收方不需要设置 handlers, 发送方需要 */
279
279
  handlers = new Map();
280
+ keeper;
280
281
  /** `false` 打印所有交互的 rpc messages */
281
282
  print = false;
283
+ first_error = true;
284
+ keeping = false;
282
285
  reconnecting = false;
286
+ disconnected = false;
283
287
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
284
288
  static parse(buffer) {
285
289
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -300,7 +304,7 @@ export class Remote {
300
304
  return message;
301
305
  }
302
306
  static pack({ id, func, data = [], done, error }) {
303
- assert('length' in data, t('message.data 必须是数组'));
307
+ assert('length' in data, 'message.data 必须是数组');
304
308
  let data_ = new Array(data.length);
305
309
  let bins = [];
306
310
  let bufs = [];
@@ -330,22 +334,72 @@ export class Remote {
330
334
  }
331
335
  /** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
332
336
  作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
333
- constructor({ url, funcs, print, websocket, on_error } = {}) {
334
- assert(!(url && websocket), t('构建 Remote 时 url 和 websocket 最多只能传一个'));
337
+ constructor({ url, funcs, print, websocket, keeper, on_error, } = {}) {
338
+ assert(!(url && websocket), '构建 Remote 时 url 和 websocket 最多只能传一个');
335
339
  this.initiator = Boolean(url || websocket);
336
340
  if (url)
337
341
  this.url = url;
338
- if (funcs)
339
- this.funcs = funcs;
342
+ assert(!funcs?.echo);
343
+ this.funcs = this.initiator ? funcs : {
344
+ ...funcs,
345
+ echo({ data }) {
346
+ return data;
347
+ }
348
+ };
340
349
  if (print !== undefined)
341
350
  this.print = print;
342
351
  if (on_error)
343
352
  this.on_error = on_error;
344
353
  if (this.initiator)
345
354
  this.lwebsocket = new Lock(websocket || null);
355
+ if (keeper) {
356
+ assert(this.initiator && url);
357
+ this.keeper = {
358
+ reconnect_interval: 1000 * 5,
359
+ heartbeat_interval: 1000 * 60,
360
+ error_delay: 1000 * 2,
361
+ ...keeper
362
+ };
363
+ }
346
364
  }
365
+ /** 统一处理首次连接错误和连接后的错误 */
366
+ _on_error = (error, websocket) => {
367
+ if (websocket) // 连接后出现的错误
368
+ this.on_error?.(error, websocket);
369
+ if (this.keeper && !this.reconnecting && !this.disconnected) // 在一段时间后调度错误重连
370
+ (async () => {
371
+ this.reconnecting = true;
372
+ const { error_delay, reconnect_interval } = this.keeper;
373
+ if (this.first_error) {
374
+ this.first_error = false;
375
+ await delay(error_delay);
376
+ }
377
+ else
378
+ await delay(reconnect_interval);
379
+ this.reconnecting = false;
380
+ if (!this.disconnected)
381
+ try {
382
+ await timeout(3000, async () => {
383
+ await this.connect();
384
+ });
385
+ this.first_error = true;
386
+ }
387
+ catch (error) {
388
+ // 重连失败的错误这里需要简单打印下,_on_error 不会打印,这里也不继续往上抛了
389
+ console.log(error.message);
390
+ // 重连由 this.connect 里面调用 this._on_error 处理
391
+ }
392
+ })();
393
+ };
394
+ _on_message = (data, websocket) => {
395
+ this.handle(new Uint8Array(data), websocket);
396
+ };
397
+ /** 使用者自定义的在连接后出错时的处理 */
347
398
  on_error(error, websocket) {
348
- throw error;
399
+ if (this.keeper)
400
+ console.log(error.message);
401
+ else
402
+ throw error;
349
403
  }
350
404
  /** 幂等,保证 websocket 已连接,否则抛出异常
351
405
  一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
@@ -357,28 +411,61 @@ export class Remote {
357
411
  if (this.lwebsocket.resource?.readyState === WebSocket.OPEN)
358
412
  return;
359
413
  else if (!this.url)
360
- throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
361
- else
414
+ throw new Error('创建 Remote 时传入的 websocket 连接已断开');
415
+ else {
416
+ let reconnected = false;
362
417
  // 假设有多个请求想要并发连接 websocket, 且此时 websocket 是断开的状态
363
418
  // 应该排队依次连接,而不是后续的连接直接使用第一次连接的 promise,后续调用还是应该尝试重连(不止连接一次)
364
- return this.lwebsocket.request(async (websocket) => {
419
+ await this.lwebsocket.request(async (websocket) => {
365
420
  // 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
366
421
  // 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
367
- if (websocket?.readyState === WebSocket.OPEN)
368
- return;
369
- else // 重连
370
- this.lwebsocket.resource = await connect_websocket(this.url, {
371
- on_message: (data, websocket) => {
372
- this.handle(new Uint8Array(data), websocket);
373
- },
374
- on_error: this.on_error.bind(this)
375
- });
422
+ if (websocket?.readyState !== WebSocket.OPEN) { // 重连
423
+ try {
424
+ this.lwebsocket.resource = await connect_websocket(this.url, {
425
+ on_message: this._on_message,
426
+ on_error: this._on_error
427
+ });
428
+ reconnected = true;
429
+ }
430
+ catch (error) {
431
+ this._on_error(error);
432
+ throw error;
433
+ }
434
+ }
376
435
  });
436
+ if (this.keeper) {
437
+ const { heartbeat_interval, func, args } = this.keeper;
438
+ // 首次连接成功时,开始心跳保活
439
+ if (!this.keeping) {
440
+ this.keeping = true;
441
+ (async () => {
442
+ for (;;) {
443
+ await delay(heartbeat_interval);
444
+ if (this.disconnected)
445
+ break;
446
+ if (!this.reconnecting)
447
+ try {
448
+ await timeout(1000 * 2, async () => {
449
+ await this.call('echo', []);
450
+ });
451
+ }
452
+ catch (error) {
453
+ console.log(error.message);
454
+ this._on_error(error);
455
+ }
456
+ }
457
+ })();
458
+ }
459
+ if (reconnected && func)
460
+ await this.call(func, args);
461
+ }
462
+ }
377
463
  else if (websocket.readyState !== WebSocket.OPEN)
378
- throw new Error(t('传入的 websocket 连接已断开'));
464
+ throw new Error('传入的 websocket 连接已断开');
379
465
  }
380
466
  /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
381
467
  disconnect() {
468
+ this.disconnected = true;
382
469
  this.lwebsocket.resource?.close(1000);
383
470
  }
384
471
  /** 发送 message 到对端 remote
@@ -386,7 +473,7 @@ export class Remote {
386
473
  作为 websocket 连接接收方,必传 websocket 参数
387
474
  发送或连接出错时自动清理 message.id 对应的 handler */
388
475
  async send(message, websocket) {
389
- assert(!message.data || message.data.every(arg => arg !== undefined), t('message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null'));
476
+ assert(!message.data || message.data.every(arg => arg !== undefined), `message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null. (func: ${message.func}, id: ${message.id})`);
390
477
  if (this.print)
391
478
  console.log('remote.send:', message);
392
479
  try {
@@ -425,7 +512,7 @@ export class Remote {
425
512
  await this.send({ id, data }, websocket);
426
513
  }
427
514
  else
428
- throw message.error || new Error(`${t('找不到 rpc handler')}: ${func ? `func: ${func.quote()}` : `id: ${id}`}`);
515
+ throw message.error || new Error(`找不到 rpc handler: ${func ? `func: ${func.quote()}` : `id: ${id}`}`);
429
516
  }
430
517
  catch (error) {
431
518
  // handler 出错并不意味着 rpc 一定会结束,可能 error 是运行中的正常数据,所以不能清理 handler
@@ -458,38 +545,5 @@ export class Remote {
458
545
  }
459
546
  });
460
547
  }
461
- /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
462
- 通常在 on_error 中和首次启动时调用
463
- - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
464
- - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
465
- - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
466
- - on_error?: 接收每次尝试连接的错误 */
467
- async start_reconnecting({ func, args, interval = 1000 * 10, first_delay = 0, on_error, } = {}) {
468
- assert(this.url);
469
- if (this.reconnecting)
470
- return;
471
- this.reconnecting = true;
472
- if (first_delay)
473
- await delay(first_delay);
474
- for (;;) {
475
- if (this.lwebsocket.resource?.readyState !== WebSocket.OPEN)
476
- try {
477
- if (func)
478
- await this.call(func, args);
479
- else
480
- await this.connect();
481
- this.reconnecting = false;
482
- break;
483
- }
484
- catch (error) {
485
- on_error?.(error);
486
- }
487
- else {
488
- this.reconnecting = false;
489
- break;
490
- }
491
- await delay(interval);
492
- }
493
- }
494
548
  }
495
549
  //# sourceMappingURL=net.browser.js.map
package/net.d.ts CHANGED
@@ -197,6 +197,23 @@ export interface Message<TData extends any[] = any[]> {
197
197
  /** bins: data 中哪些下标对应的原始值是 Uint8Array 类型的,如: [0, 3] */
198
198
  bins?: number[];
199
199
  }
200
+ /** 自动保持 remote 连接,只对 initiator 且传入 url 有效,
201
+ - 在未连接,或 websocket on_error 检测到连接断开后以 reconnect_interval 间隔尝试 remote.call(func) 重连
202
+ - 在连接状态下以 heartbeat_interval 间隔发送心跳包确保连接状态
203
+
204
+ 配置选项:
205
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
206
+ - args?: 连接后调用函数时传递的参数,与 func 配合使用
207
+ - reconnect_interval?: `1000 * 5` 首次重连失败后的后续尝试间隔 (单位: ms)
208
+ - heartbeat_interval?: `1000 * 60` 发送心跳包确保连接的间隔 (单位: ms)
209
+ - error_delay?: `2000` 遇到错误时首次尝试重连的延时 (单位: ms) */
210
+ export interface RemoteKeeperOptions {
211
+ func?: string;
212
+ args?: any[];
213
+ reconnect_interval?: number;
214
+ heartbeat_interval?: number;
215
+ error_delay?: number;
216
+ }
200
217
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
201
218
  创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
202
219
  使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
@@ -234,22 +251,31 @@ export declare class Remote {
234
251
  /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
235
252
  一元 rpc 接收方不需要设置 handlers, 发送方需要 */
236
253
  handlers: Map<number, MessageHandler<any[]>>;
254
+ keeper?: RemoteKeeperOptions;
237
255
  /** `false` 打印所有交互的 rpc messages */
238
256
  print: boolean;
257
+ first_error: boolean;
258
+ keeping: boolean;
239
259
  reconnecting: boolean;
260
+ disconnected: boolean;
240
261
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
241
262
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
242
263
  static pack({ id, func, data, done, error }: Message): Uint8Array;
243
264
  /** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
244
265
  作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
245
- constructor({ url, funcs, print, websocket, on_error }?: {
266
+ constructor({ url, funcs, print, websocket, keeper, on_error, }?: {
246
267
  url?: string;
247
268
  funcs?: Remote['funcs'];
248
269
  print?: boolean;
249
270
  websocket?: WebSocket;
250
- on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
271
+ keeper?: RemoteKeeperOptions;
272
+ on_error?(error: WebSocketConnectionError, websocket?: WebSocket): void;
251
273
  });
252
- on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
274
+ /** 统一处理首次连接错误和连接后的错误 */
275
+ _on_error: (error: WebSocketConnectionError, websocket?: WebSocket) => void;
276
+ _on_message: (data: ArrayBuffer, websocket: WebSocket) => void;
277
+ /** 使用者自定义的在连接后出错时的处理 */
278
+ on_error?(error: WebSocketConnectionError, websocket?: WebSocket): void;
253
279
  /** 幂等,保证 websocket 已连接,否则抛出异常
254
280
  一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
255
281
  连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
@@ -272,13 +298,6 @@ export declare class Remote {
272
298
  作为 websocket 连接发起方,不需要传入 websocket
273
299
  作为 websocket 连接接收方,必传 websocket 参数 */
274
300
  call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
275
- /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
276
- 通常在 on_error 中和首次启动时调用
277
- - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
278
- - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
279
- - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
280
- - on_error?: 接收每次尝试连接的错误 */
281
- start_reconnecting({ func, args, interval, first_delay, on_error, }?: RemoteReconnectingOptions): Promise<void>;
282
301
  }
283
302
  /** 为连接到 server 的 client 创建 RemoteClient,以便后续调用 client 中方法 */
284
303
  export declare class RemoteClient {
@@ -291,10 +310,3 @@ export declare class RemoteClient {
291
310
  发送或连接出错时自动清理 message.id 对应的 handler */
292
311
  send(message: Message): Promise<void>;
293
312
  }
294
- export interface RemoteReconnectingOptions {
295
- func?: string;
296
- args?: any[];
297
- interval?: number;
298
- first_delay?: number;
299
- on_error?(error: Error): void;
300
- }
package/net.js CHANGED
@@ -3,7 +3,7 @@ import { buffer as stream_to_buffer, text as stream_to_text } from 'stream/consu
3
3
  import { isReadable } from 'stream';
4
4
  import { t } from './i18n/instance.js';
5
5
  import './prototype.js';
6
- import { inspect, concat, assert, genid, delay, Lock, encode, decode, pipe_with_error, map_values, unique } from './utils.js';
6
+ import { inspect, concat, assert, genid, delay, Lock, encode, decode, pipe_with_error, map_values, unique, timeout } from './utils.js';
7
7
  export const WebSocketConnecting = 0;
8
8
  export const WebSocketOpen = 1;
9
9
  export const WebSocketClosing = 2;
@@ -488,9 +488,13 @@ export class Remote {
488
488
  /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
489
489
  一元 rpc 接收方不需要设置 handlers, 发送方需要 */
490
490
  handlers = new Map();
491
+ keeper;
491
492
  /** `false` 打印所有交互的 rpc messages */
492
493
  print = false;
494
+ first_error = true;
495
+ keeping = false;
493
496
  reconnecting = false;
497
+ disconnected = false;
494
498
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
495
499
  static parse(buffer) {
496
500
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -511,7 +515,7 @@ export class Remote {
511
515
  return message;
512
516
  }
513
517
  static pack({ id, func, data = [], done, error }) {
514
- assert('length' in data, t('message.data 必须是数组'));
518
+ assert('length' in data, 'message.data 必须是数组');
515
519
  let data_ = new Array(data.length);
516
520
  let bins = [];
517
521
  let bufs = [];
@@ -541,22 +545,72 @@ export class Remote {
541
545
  }
542
546
  /** 作为 websocket 连接发起方,传入 url 或 websocket,定义远程 Remote
543
547
  作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
544
- constructor({ url, funcs, print, websocket, on_error } = {}) {
545
- assert(!(url && websocket), t('构建 Remote 时 url 和 websocket 最多只能传一个'));
548
+ constructor({ url, funcs, print, websocket, keeper, on_error, } = {}) {
549
+ assert(!(url && websocket), '构建 Remote 时 url 和 websocket 最多只能传一个');
546
550
  this.initiator = Boolean(url || websocket);
547
551
  if (url)
548
552
  this.url = url;
549
- if (funcs)
550
- this.funcs = funcs;
553
+ assert(!funcs?.echo);
554
+ this.funcs = this.initiator ? funcs : {
555
+ ...funcs,
556
+ echo({ data }) {
557
+ return data;
558
+ }
559
+ };
551
560
  if (print !== undefined)
552
561
  this.print = print;
553
562
  if (on_error)
554
563
  this.on_error = on_error;
555
564
  if (this.initiator)
556
565
  this.lwebsocket = new Lock(websocket || null);
566
+ if (keeper) {
567
+ assert(this.initiator && url);
568
+ this.keeper = {
569
+ reconnect_interval: 1000 * 5,
570
+ heartbeat_interval: 1000 * 60,
571
+ error_delay: 1000 * 2,
572
+ ...keeper
573
+ };
574
+ }
557
575
  }
576
+ /** 统一处理首次连接错误和连接后的错误 */
577
+ _on_error = (error, websocket) => {
578
+ if (websocket) // 连接后出现的错误
579
+ this.on_error?.(error, websocket);
580
+ if (this.keeper && !this.reconnecting && !this.disconnected) // 在一段时间后调度错误重连
581
+ (async () => {
582
+ this.reconnecting = true;
583
+ const { error_delay, reconnect_interval } = this.keeper;
584
+ if (this.first_error) {
585
+ this.first_error = false;
586
+ await delay(error_delay);
587
+ }
588
+ else
589
+ await delay(reconnect_interval);
590
+ this.reconnecting = false;
591
+ if (!this.disconnected)
592
+ try {
593
+ await timeout(3000, async () => {
594
+ await this.connect();
595
+ });
596
+ this.first_error = true;
597
+ }
598
+ catch (error) {
599
+ // 重连失败的错误这里需要简单打印下,_on_error 不会打印,这里也不继续往上抛了
600
+ console.log(error.message);
601
+ // 重连由 this.connect 里面调用 this._on_error 处理
602
+ }
603
+ })();
604
+ };
605
+ _on_message = (data, websocket) => {
606
+ this.handle(new Uint8Array(data), websocket);
607
+ };
608
+ /** 使用者自定义的在连接后出错时的处理 */
558
609
  on_error(error, websocket) {
559
- throw error;
610
+ if (this.keeper)
611
+ console.log(error.message);
612
+ else
613
+ throw error;
560
614
  }
561
615
  /** 幂等,保证 websocket 已连接,否则抛出异常
562
616
  一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
@@ -568,28 +622,61 @@ export class Remote {
568
622
  if (this.lwebsocket.resource?.readyState === WebSocketOpen)
569
623
  return;
570
624
  else if (!this.url)
571
- throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
572
- else
625
+ throw new Error('创建 Remote 时传入的 websocket 连接已断开');
626
+ else {
627
+ let reconnected = false;
573
628
  // 假设有多个请求想要并发连接 websocket, 且此时 websocket 是断开的状态
574
629
  // 应该排队依次连接,而不是后续的连接直接使用第一次连接的 promise,后续调用还是应该尝试重连(不止连接一次)
575
- return this.lwebsocket.request(async (websocket) => {
630
+ await this.lwebsocket.request(async (websocket) => {
576
631
  // 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
577
632
  // 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
578
- if (websocket?.readyState === WebSocketOpen)
579
- return;
580
- else // 重连
581
- this.lwebsocket.resource = await connect_websocket(this.url, {
582
- on_message: (data, websocket) => {
583
- this.handle(new Uint8Array(data), websocket);
584
- },
585
- on_error: this.on_error.bind(this)
586
- });
633
+ if (websocket?.readyState !== WebSocketOpen) { // 重连
634
+ try {
635
+ this.lwebsocket.resource = await connect_websocket(this.url, {
636
+ on_message: this._on_message,
637
+ on_error: this._on_error
638
+ });
639
+ reconnected = true;
640
+ }
641
+ catch (error) {
642
+ this._on_error(error);
643
+ throw error;
644
+ }
645
+ }
587
646
  });
647
+ if (this.keeper) {
648
+ const { heartbeat_interval, func, args } = this.keeper;
649
+ // 首次连接成功时,开始心跳保活
650
+ if (!this.keeping) {
651
+ this.keeping = true;
652
+ (async () => {
653
+ for (;;) {
654
+ await delay(heartbeat_interval);
655
+ if (this.disconnected)
656
+ break;
657
+ if (!this.reconnecting)
658
+ try {
659
+ await timeout(1000 * 2, async () => {
660
+ await this.call('echo', []);
661
+ });
662
+ }
663
+ catch (error) {
664
+ console.log(error.message);
665
+ this._on_error(error);
666
+ }
667
+ }
668
+ })();
669
+ }
670
+ if (reconnected && func)
671
+ await this.call(func, args);
672
+ }
673
+ }
588
674
  else if (websocket.readyState !== WebSocketOpen)
589
- throw new Error(t('传入的 websocket 连接已断开'));
675
+ throw new Error('传入的 websocket 连接已断开');
590
676
  }
591
677
  /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
592
678
  disconnect() {
679
+ this.disconnected = true;
593
680
  this.lwebsocket.resource?.close(1000);
594
681
  }
595
682
  /** 发送 message 到对端 remote
@@ -597,7 +684,7 @@ export class Remote {
597
684
  作为 websocket 连接接收方,必传 websocket 参数
598
685
  发送或连接出错时自动清理 message.id 对应的 handler */
599
686
  async send(message, websocket) {
600
- assert(!message.data || message.data.every(arg => arg !== undefined), t('message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null'));
687
+ assert(!message.data || message.data.every(arg => arg !== undefined), `message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null. (func: ${message.func}, id: ${message.id})`);
601
688
  if (this.print)
602
689
  console.log('remote.send:', message);
603
690
  try {
@@ -636,7 +723,7 @@ export class Remote {
636
723
  await this.send({ id, data }, websocket);
637
724
  }
638
725
  else
639
- throw message.error || new Error(`${t('找不到 rpc handler')}: ${func ? `func: ${func.quote()}` : `id: ${id}`}`);
726
+ throw message.error || new Error(`找不到 rpc handler: ${func ? `func: ${func.quote()}` : `id: ${id}`}`);
640
727
  }
641
728
  catch (error) {
642
729
  // handler 出错并不意味着 rpc 一定会结束,可能 error 是运行中的正常数据,所以不能清理 handler
@@ -669,39 +756,6 @@ export class Remote {
669
756
  }
670
757
  });
671
758
  }
672
- /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
673
- 通常在 on_error 中和首次启动时调用
674
- - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
675
- - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
676
- - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
677
- - on_error?: 接收每次尝试连接的错误 */
678
- async start_reconnecting({ func, args, interval = 1000 * 10, first_delay = 0, on_error, } = {}) {
679
- assert(this.url);
680
- if (this.reconnecting)
681
- return;
682
- this.reconnecting = true;
683
- if (first_delay)
684
- await delay(first_delay);
685
- for (;;) {
686
- if (this.lwebsocket.resource?.readyState !== WebSocketOpen)
687
- try {
688
- if (func)
689
- await this.call(func, args);
690
- else
691
- await this.connect();
692
- this.reconnecting = false;
693
- break;
694
- }
695
- catch (error) {
696
- on_error?.(error);
697
- }
698
- else {
699
- this.reconnecting = false;
700
- break;
701
- }
702
- await delay(interval);
703
- }
704
- }
705
759
  }
706
760
  /** 为连接到 server 的 client 创建 RemoteClient,以便后续调用 client 中方法 */
707
761
  export class RemoteClient {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
package/path.d.ts CHANGED
@@ -54,7 +54,7 @@ export declare function extname(path: string): string;
54
54
  /** `/` */
55
55
  export declare const sep = "/";
56
56
  /** The platform-specific file delimiter. ';' or ':'. */
57
- export declare const delimiter: ":" | ";";
57
+ export declare const delimiter: ";" | ":";
58
58
  /** Returns an object from a path string - the opposite of format().
59
59
  @param path path to evaluate.
60
60
  @throws {TypeError} if `path` is not a string. */
@@ -81,7 +81,7 @@ export declare let path: {
81
81
  basename: typeof basename;
82
82
  extname: typeof extname;
83
83
  sep: string;
84
- delimiter: ":" | ";";
84
+ delimiter: ";" | ":";
85
85
  parse: typeof parse;
86
86
  format: typeof format;
87
87
  toNamespacedPath: typeof toNamespacedPath;