wechaty-puppet-matrix 0.0.8 → 0.0.10
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/dist/cjs/src/matrix/events/event-friendship.js +17 -7
- package/dist/cjs/src/matrix/schema-mapper/contact.js +17 -7
- package/dist/cjs/src/matrix/schema-mapper/message/message-parser-appmsg.js +17 -7
- package/dist/cjs/src/matrix/schema-mapper/message/message-parser-refermsg.js +17 -7
- package/dist/cjs/src/matrix/schema-mapper/message/message-parser-type.js +17 -7
- package/dist/cjs/src/matrix/schema-mapper/message.js +17 -7
- package/dist/cjs/src/matrix/service/request.d.ts +18 -0
- package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
- package/dist/cjs/src/matrix/service/request.js +163 -48
- package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
- package/dist/cjs/src/puppet-matrix.js +21 -7
- package/dist/cjs/tests/tap.spec.js +17 -7
- package/dist/esm/src/matrix/service/request.d.ts +18 -0
- package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
- package/dist/esm/src/matrix/service/request.js +146 -41
- package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
- package/dist/esm/src/puppet-matrix.js +4 -0
- package/package.json +1 -1
- package/src/matrix/service/request.ts +184 -85
- package/src/puppet-matrix.ts +4 -0
|
@@ -330,9 +330,21 @@ async function getAtWxidList (source: string): Promise<string[]> {
|
|
|
330
330
|
}
|
|
331
331
|
return []
|
|
332
332
|
}
|
|
333
|
+
interface ConnectionStatus {
|
|
334
|
+
status: 'disconnected' | 'connected' | 'connecting'
|
|
335
|
+
}
|
|
333
336
|
class Client extends EventEmitter {
|
|
334
337
|
|
|
335
338
|
private readonly options: PuppetMatrixOptions
|
|
339
|
+
private connectionStatus: ConnectionStatus = { status: 'disconnected' }
|
|
340
|
+
private readonly MAX_RECONNECT_ATTEMPTS = 10
|
|
341
|
+
private readonly INITIAL_RECONNECT_DELAY = 1000 // 1秒
|
|
342
|
+
private readonly MAX_RECONNECT_DELAY = 30000 // 30秒
|
|
343
|
+
private readonly HEARTBEAT_INTERVAL = 40 * 1000 // 1分钟心跳间隔
|
|
344
|
+
private readonly HEARTBEAT_TIMEOUT = 5000 // 5秒超时
|
|
345
|
+
private reconnectAttempts = 0
|
|
346
|
+
private heartbeatTimer?: any
|
|
347
|
+
private heartbeatTimeoutTimer?: any
|
|
336
348
|
socket: any
|
|
337
349
|
server: any
|
|
338
350
|
tokenInfo: any
|
|
@@ -386,102 +398,177 @@ class Client extends EventEmitter {
|
|
|
386
398
|
|
|
387
399
|
async initServer () {
|
|
388
400
|
await this.getTokenInfo()
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
})
|
|
401
|
+
if (this.socket) {
|
|
402
|
+
log.error('socket had already been opened!')
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
await this.createWebSocket()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async createWebSocket (): Promise<void> {
|
|
410
|
+
if (!this.tokenInfo) {
|
|
411
|
+
log.error('Token info not available')
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
const ws = new WebSocket(
|
|
415
|
+
`${this.tokenInfo.endpoint}?guid=${this.tokenInfo.guid}`,
|
|
416
|
+
{
|
|
417
|
+
perMessageDeflate: true,
|
|
418
|
+
maxPayload: 100 * 1024 * 1024,
|
|
419
|
+
},
|
|
420
|
+
)
|
|
410
421
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
// return
|
|
419
|
-
// }
|
|
420
|
-
// if (this.connectionStatus.status === CONNECTED) {
|
|
421
|
-
// this.debounceQueue.next('ws.on(message)')
|
|
422
|
-
// this.throttleQueue.next('ws.on(message)')
|
|
423
|
-
// }
|
|
422
|
+
// 连接建立前的 Promise
|
|
423
|
+
await new Promise<void>((resolve, reject) => {
|
|
424
|
+
ws.once('open', () => {
|
|
425
|
+
this.connectionStatus.status = 'connected'
|
|
426
|
+
this.reconnectAttempts = 0
|
|
427
|
+
log.info('WebSocket connection opened')
|
|
428
|
+
resolve()
|
|
424
429
|
})
|
|
425
430
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
ws.on('error', (e: any) => {
|
|
430
|
-
if (e.message.indexOf('ECONNREFUSED') !== -1) {
|
|
431
|
-
// Can not connect to remote server, if this is triggered when puppet-padchat is connected,
|
|
432
|
-
// an close event must be emitted, so ignore this error
|
|
433
|
-
// If this is triggered when puppet-padchat is trying to reconnect, also ignore this error
|
|
434
|
-
} else {
|
|
435
|
-
log.verbose(PRE, 'initWebSocket() ws.on(error) %s', e)
|
|
436
|
-
this.emit('error', e)
|
|
437
|
-
}
|
|
431
|
+
ws.once('error', (error: any) => {
|
|
432
|
+
log.warn('WebSocket connection error', error)
|
|
433
|
+
reject(error)
|
|
438
434
|
})
|
|
439
435
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
ws.on('close', (code: any, reason: any) => {
|
|
444
|
-
log.warn(
|
|
445
|
-
PRE,
|
|
446
|
-
'initWebSocket() ws.on(close) code: %s, reason: %s',
|
|
447
|
-
code,
|
|
448
|
-
reason,
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
// if (!this.reconnectThrottleQueue) {
|
|
452
|
-
// log.warn(PRE, 'initWebSocket() ws.on(close) reconnectThrottleQueue not exist')
|
|
453
|
-
// return
|
|
454
|
-
// }
|
|
455
|
-
// if (this.connectionStatus.status === CONNECTED) {
|
|
456
|
-
// this.reconnectThrottleQueue.next('ws.on(close, ' + code + ')')
|
|
457
|
-
// }
|
|
436
|
+
ws.once('close', (code: any, reason: any) => {
|
|
437
|
+
void this.handleWebSocketClose(code, reason)
|
|
438
|
+
reject(new Error(`WebSocket closed: ${code} - ${reason}`))
|
|
458
439
|
})
|
|
440
|
+
})
|
|
441
|
+
this.socket = ws
|
|
459
442
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
await new Promise((resolve, reject) => {
|
|
464
|
-
ws.once('open', () => {
|
|
465
|
-
log.silly(PRE, 'initWebSocket() Promise() ws.on(open)')
|
|
466
|
-
return resolve({})
|
|
467
|
-
})
|
|
443
|
+
// 设置事件处理器
|
|
444
|
+
this.setupWebSocketListeners(ws)
|
|
445
|
+
}
|
|
468
446
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
447
|
+
private setupWebSocketListeners (ws: WebSocket): void {
|
|
448
|
+
// 消息处理
|
|
449
|
+
ws.on('message', (data: string) => {
|
|
450
|
+
this.resetHeartbeatTimeout() // 收到任何消息都重置心跳超时
|
|
451
|
+
log.silly(PRE, 'initWebSocket() ws.on(message): %s', data)
|
|
452
|
+
if (data.toString() === 'pong') {
|
|
453
|
+
log.info('Received heartbeat')
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const payload = JSON.parse(data)
|
|
458
|
+
log.info('Received payload', JSON.stringify(payload))
|
|
459
|
+
void this.eventParse(payload)
|
|
460
|
+
} catch (error) {
|
|
461
|
+
log.warn(PRE, 'initWebSocket() ws.on(message) exception: %s', error)
|
|
462
|
+
// @ts-ignore
|
|
463
|
+
this.emit('error', (error as Error).message)
|
|
464
|
+
}
|
|
465
|
+
})
|
|
478
466
|
|
|
479
|
-
|
|
480
|
-
|
|
467
|
+
// 错误处理
|
|
468
|
+
ws.on('error', (error: Error) => {
|
|
469
|
+
if ((error as any).code === 'ECONNREFUSED') {
|
|
470
|
+
log.verbose('Connection refused, potential reconnect scenario')
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
log.verbose(PRE, 'initWebSocket() ws.on(error) %s', error)
|
|
475
|
+
// @ts-ignore
|
|
476
|
+
this.emit('error', error)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
// 关闭处理
|
|
480
|
+
ws.on('close', (code:any, reason: any) => {
|
|
481
|
+
this.stopHeartbeat()
|
|
482
|
+
void this.handleWebSocketClose(code, reason)
|
|
483
|
+
})
|
|
484
|
+
// 启动心跳
|
|
485
|
+
this.startHeartbeat()
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private startHeartbeat (): void {
|
|
489
|
+
this.stopHeartbeat() // 确保清理现有定时器
|
|
490
|
+
this.socket?.send('ping')
|
|
491
|
+
this.heartbeatTimer = setInterval(() => {
|
|
492
|
+
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
493
|
+
try {
|
|
494
|
+
this.socket.send('ping')
|
|
495
|
+
this.setHeartbeatTimeout()
|
|
496
|
+
} catch (error) {
|
|
497
|
+
log.error('Failed to send heartbeat:', error)
|
|
498
|
+
void this.handleWebSocketClose(1006, 'Heartbeat failed')
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}, this.HEARTBEAT_INTERVAL)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private stopHeartbeat (): void {
|
|
505
|
+
if (this.heartbeatTimer) {
|
|
506
|
+
// @ts-ignore
|
|
507
|
+
clearInterval(this.heartbeatTimer)
|
|
508
|
+
this.heartbeatTimer = undefined
|
|
509
|
+
}
|
|
510
|
+
if (this.heartbeatTimeoutTimer) {
|
|
511
|
+
// @ts-ignore
|
|
512
|
+
clearTimeout(this.heartbeatTimeoutTimer)
|
|
513
|
+
this.heartbeatTimeoutTimer = undefined
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private setHeartbeatTimeout (): void {
|
|
518
|
+
if (this.heartbeatTimeoutTimer) {
|
|
519
|
+
// @ts-ignore
|
|
520
|
+
clearTimeout(this.heartbeatTimeoutTimer)
|
|
521
|
+
}
|
|
522
|
+
this.heartbeatTimeoutTimer = setTimeout(() => {
|
|
523
|
+
log.warn('Heartbeat timeout, reconnecting...')
|
|
524
|
+
void this.handleWebSocketClose(1006, 'Heartbeat timeout')
|
|
525
|
+
}, this.HEARTBEAT_TIMEOUT)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private resetHeartbeatTimeout (): void {
|
|
529
|
+
if (this.heartbeatTimeoutTimer) {
|
|
530
|
+
// @ts-ignore
|
|
531
|
+
clearTimeout(this.heartbeatTimeoutTimer)
|
|
532
|
+
this.heartbeatTimeoutTimer = undefined
|
|
481
533
|
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private async handleWebSocketClose (code?: number, reason?: Buffer | String) {
|
|
537
|
+
this.connectionStatus.status = 'disconnected'
|
|
538
|
+
log.warn(`WebSocket closed: Code ${code}, Reason ${reason}`)
|
|
482
539
|
if (this.socket) {
|
|
483
|
-
|
|
540
|
+
this.socket.close()
|
|
541
|
+
this.socket = null
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 触发重连
|
|
545
|
+
await this.reconnect()
|
|
546
|
+
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private async reconnect (): Promise<void> {
|
|
550
|
+
if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
|
|
551
|
+
log.error('Max reconnect attempts reached. Stopping reconnection.')
|
|
552
|
+
return
|
|
484
553
|
}
|
|
554
|
+
|
|
555
|
+
// 指数退避重连策略
|
|
556
|
+
const delay = Math.min(
|
|
557
|
+
this.INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts),
|
|
558
|
+
this.MAX_RECONNECT_DELAY,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
this.reconnectAttempts++
|
|
562
|
+
log.info(`Reconnecting in ${delay}ms (Attempt ${this.reconnectAttempts})`)
|
|
563
|
+
|
|
564
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
565
|
+
setTimeout(async () => {
|
|
566
|
+
try {
|
|
567
|
+
await this.initServer()
|
|
568
|
+
} catch (error) {
|
|
569
|
+
log.warn('Reconnection failed', error)
|
|
570
|
+
}
|
|
571
|
+
}, delay)
|
|
485
572
|
}
|
|
486
573
|
|
|
487
574
|
async eventParse (eventData: any) {
|
|
@@ -734,6 +821,7 @@ class Client extends EventEmitter {
|
|
|
734
821
|
* 获取登录二维码
|
|
735
822
|
*/
|
|
736
823
|
public async getQrcode (): Promise<{
|
|
824
|
+
isLogin: boolean;
|
|
737
825
|
base64: string;
|
|
738
826
|
qrcodeUrl: string;
|
|
739
827
|
expiredTime: number;
|
|
@@ -747,12 +835,23 @@ class Client extends EventEmitter {
|
|
|
747
835
|
|
|
748
836
|
if (res?.qrcode) {
|
|
749
837
|
return {
|
|
838
|
+
isLogin: false,
|
|
750
839
|
base64: `data:image/jpeg;base64,${res?.qrcode?.buffer}`,
|
|
751
840
|
qrcodeUrl: `http://weixin.qq.com/x/${res?.uuid}`,
|
|
752
841
|
uuid: res?.uuid,
|
|
753
842
|
expiredTime: res?.expiredTime,
|
|
754
843
|
}
|
|
755
844
|
}
|
|
845
|
+
if (res.errcode === -1 && res.errmsg === 'user is login status, please logout') {
|
|
846
|
+
log.info(PRE, 'getQrcode tips: %s', JSON.stringify(res))
|
|
847
|
+
return {
|
|
848
|
+
isLogin: true,
|
|
849
|
+
base64: '',
|
|
850
|
+
qrcodeUrl: '',
|
|
851
|
+
uuid: '',
|
|
852
|
+
expiredTime: 0,
|
|
853
|
+
}
|
|
854
|
+
}
|
|
756
855
|
log.info(PRE, 'getQrcode error: %s', JSON.stringify(res))
|
|
757
856
|
return null
|
|
758
857
|
} catch (e) {
|
|
@@ -865,7 +964,7 @@ class Client extends EventEmitter {
|
|
|
865
964
|
labelIdlist: contact?.labelIdlist,
|
|
866
965
|
}
|
|
867
966
|
}
|
|
868
|
-
log.info(PRE, 'get self info error:(%s)', res
|
|
967
|
+
log.info(PRE, 'get self info error:(%s)', JSON.stringify(res))
|
|
869
968
|
return false
|
|
870
969
|
} catch (e) {
|
|
871
970
|
log.error(PRE, 'getSelfInfo(): %s', e)
|
package/src/puppet-matrix.ts
CHANGED
|
@@ -224,6 +224,10 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
224
224
|
private async _getQrcode () {
|
|
225
225
|
const qrcode = await this._client?.getQrcode()
|
|
226
226
|
if (qrcode) {
|
|
227
|
+
if (qrcode.isLogin) {
|
|
228
|
+
await this.checkIsLogin()
|
|
229
|
+
return
|
|
230
|
+
}
|
|
227
231
|
if (this._qrcodeStatuasInterval) {
|
|
228
232
|
clearInterval(this._qrcodeStatuasInterval)
|
|
229
233
|
this._qrcodeStatuasInterval = null
|