diff --git a/src/puppet-padchat/bridge.ts b/src/puppet-padchat/bridge.ts index d869698737214d52547c6c7e3b6202479403bd25..5b46e2de4e0d34c3193c3dd3d8689e1a834cbfe4 100644 --- a/src/puppet-padchat/bridge.ts +++ b/src/puppet-padchat/bridge.ts @@ -65,7 +65,8 @@ export class Bridge extends EventEmitter { private readonly padchatRpc: PadchatRpc private autoData : AutoDataType - private loginQrCode?: string + private loginScanQrCode? : string + private loginScanStatus? : number private loginTimer?: NodeJS.Timer @@ -114,9 +115,11 @@ export class Bridge extends EventEmitter { await this.loadAutoData() - await this.restoreLogin() + const restoreSucceed = await this.restoreLogin() - this.startLogin() + if (!restoreSucceed) { + this.startLogin() + } this.state.on(true) } @@ -130,7 +133,8 @@ export class Bridge extends EventEmitter { this.saveAutoData() - this.emit('login', this.selfId = username) + this.selfId = username + this.emit('login', this.selfId) } public logout(): void { @@ -149,7 +153,8 @@ export class Bridge extends EventEmitter { clearTimeout(this.loginTimer) this.loginTimer = undefined } - this.loginQrCode = undefined + this.loginScanQrCode = undefined + this.loginScanStatus = undefined } protected async startLogin(): Promise { @@ -169,14 +174,23 @@ export class Bridge extends EventEmitter { /** * 2. Wait user response */ - let lastStatus = WXCheckQRCodeStatus.Unknown - let loop = true - while (loop) { + let waitUser = true + while (waitUser) { const result = await this.padchatRpc.WXCheckQRCode() - if (lastStatus !== result.status && this.loginQrCode) { - lastStatus = result.status - this.emit('scan', this.loginQrCode, result.status) + if (this.loginScanStatus !== result.status && this.loginScanQrCode) { + this.loginScanStatus = result.status + this.emit( + 'scan', + this.loginScanQrCode, + this.loginScanStatus, + ) + } + + if (result.expired_time && result.expired_time < 10) { + // result.expire_time is second + // emit new qrcode before the old one expired + waitUser = false } switch (result.status) { @@ -205,29 +219,38 @@ export class Bridge extends EventEmitter { case WXCheckQRCodeStatus.Timeout: log.silly('PuppetPadchatBridge', 'checkQrcode: Timeout') - this.loginQrCode = undefined - loop = false + this.loginScanQrCode = undefined + this.loginScanStatus = undefined + waitUser = false break case WXCheckQRCodeStatus.Cancel: log.silly('PuppetPadchatBridge', 'user cancel') - this.loginQrCode = undefined - loop = false + this.loginScanQrCode = undefined + this.loginScanStatus = undefined + waitUser = false break default: - throw new Error('unknown status: ' + result.status) + log.warn('PadchatBridge', 'startLogin() unknown WXCheckQRCodeStatus: ' + result.status) + this.loginScanQrCode = undefined + this.loginScanStatus = undefined + waitUser = false + break } await new Promise(r => setTimeout(r, 1000)) } await this.emitLoginQrCode() - this.loginTimer = setTimeout(this.startLogin.bind(this), 1000) + this.loginTimer = setTimeout(() => { + this.loginTimer = undefined + this.startLogin() + }, 1000) return } - protected async restoreLogin(): Promise { + protected async restoreLogin(): Promise { /** * 1. The following `if/else` block: emit qrcode or send login request to the user. */ @@ -243,8 +266,8 @@ export class Bridge extends EventEmitter { /** * 1.1 Auto Login Success, return username as the result */ - this.selfId = autoLoginResult.user_name - return + this.login(autoLoginResult.user_name) + return true } else { /** @@ -266,13 +289,13 @@ export class Bridge extends EventEmitter { await this.emitLoginQrCode() } } - return + return false } protected async emitLoginQrCode(): Promise { log.verbose('PuppetPadchatBridge', `emitLoginQrCode()`) - if (this.loginQrCode) { + if (this.loginScanQrCode) { throw new Error('qrcode exist') } @@ -284,10 +307,13 @@ export class Bridge extends EventEmitter { const qrCodeText = await pfHelper.imageBase64ToQrCode(result.qr_code) + this.loginScanQrCode = qrCodeText + this.loginScanStatus = WXCheckQRCodeStatus.WaitScan + this.emit( 'scan', - this.loginQrCode = qrCodeText, - 0, + this.loginScanQrCode, + this.loginScanStatus, ) } @@ -310,13 +336,14 @@ export class Bridge extends EventEmitter { protected async loadAutoData(): Promise { log.verbose('PuppetPadchatBridge', `loadAutoData()`) - const autoData: AutoDataType = await this.options.memory.get(AUTO_DATA_SLOT) - this.autoData = { ...autoData || {} } + this.autoData = { + ...await this.options.memory.get(AUTO_DATA_SLOT), + } // Check for 62 data, if has, then use WXLoadWxDat - if (autoData.wxData) { + if (this.autoData.wxData) { log.silly('PuppetPadchatBridge', `start(), get 62 data`) - await this.padchatRpc.WXLoadWxDat(autoData.wxData) + await this.padchatRpc.WXLoadWxDat(this.autoData.wxData) } } @@ -346,13 +373,12 @@ export class Bridge extends EventEmitter { public async syncContactsAndRooms(): Promise { log.verbose('PuppetPadchatBridge', `syncContactsAndRooms()`) - let cont = true // const syncContactMap = new Map() // const syncRoomMap = new Map() // let contactIdList: string[] = [] // let roomIdList: string[] = [] - while (cont && this.state.on() && this.selfId) { + while (this.state.on() && this.selfId) { log.silly('PuppetPadchatBridge', `syncContactsAndRooms() while()`) const syncContactList = await this.padchatRpc.WXSyncContact() @@ -375,29 +401,27 @@ export class Bridge extends EventEmitter { ), ) - syncContactList - .forEach(syncContact => { + for (const syncContact of syncContactList) { if (syncContact.continue === PadchatContinue.Go) { if (syncContact.msg_type === PadchatContactMsgType.Contact) { console.log('syncContact:', syncContact.user_name, syncContact.nick_name) if (pfHelper.isRoomId(syncContact.user_name)) { // /@chatroom$/.test(syncContact.user_name)) { this.cacheRoomRawPayload[syncContact.user_name] = syncContact as PadchatRoomPayload // syncRoomMap.set(syncContact.user_name, syncContact as PadchatRoomPayload) - } else if (syncContact.user_name) { + } else if (pfHelper.isContactId(syncContact.user_name)) { this.cacheContactRawPayload[syncContact.user_name] = syncContact as PadchatContactPayload // syncContactMap.set(syncContact.user_name, syncContact as PadchatContactPayload) } else { - throw new Error('no user_name') + throw new Error('id is neither room nor contact') } } } else { - log.info('PuppetPadchatBridge', 'syncContactsAndRooms() sync contact done!') - cont = false - return + log.verbose('PuppetPadchatBridge', 'syncContactsAndRooms() sync contact done!') + break } - }) + log.silly('PuppetPadchatBridge', `syncContactsAndRooms(), continue to load via WXSyncContact ...`) + } - log.verbose('PuppetPadchatBridge', `syncContactsAndRooms(), continue to load via WXSyncContact ...`) } // contactIdList = contactIdList.filter(id => !!id) diff --git a/src/puppet-padchat/padchat-rpc.ts b/src/puppet-padchat/padchat-rpc.ts index 795130e62ae807c9c459d9a7cc6ef7af5f266e62..6a5e78336aa735ea0ee877401c7d947d428153cd 100644 --- a/src/puppet-padchat/padchat-rpc.ts +++ b/src/puppet-padchat/padchat-rpc.ts @@ -12,6 +12,8 @@ import { // JsonRpcPayloadError, // JsonRpcParamsSchemaByName, JsonRpcParamsSchemaByPositional, + + parse, } from 'json-rpc-peer' // import { MemoryCard } from 'memory-card' @@ -83,23 +85,36 @@ export class PadchatRpc extends EventEmitter { await this.initWebSocket() await this.initJsonRpcPeer() - await this.init() await this.WXInitialize() } protected async initJsonRpcPeer(): Promise { + log.verbose('PadchatRpc', 'initJsonRpcPeer()') + if (!this.socket) { throw new Error('socket had not been opened yet!') } - this.jsonRpc.on('data', (payload: JsonRpcPayloadRequest) => { + this.jsonRpc.on('data', (buffer: string | Buffer) => { + log.silly('PadchatRpc', 'initJsonRpcPeer() jsonRpc.on(data)') + if (!this.socket) { throw new Error('no web socket') } - console.log('jsonRpc.on(data) = ', payload.type, ', ', typeof payload, ': ', payload) + const text = String(buffer) + const payload = parse(text) as JsonRpcPayloadRequest + + log.silly('PadchatRpc', 'initJsonRpcPeer() jsonRpc.on(data) buffer="%s"', text) + /** + * A Gateway at here: + * + * 1. Convert Payload format from JsonRpc to Padchat, then + * 2. Send payload to padchat server + * + */ const encodedParam = (payload.params as JsonRpcParamsSchemaByPositional).map(encodeURIComponent) const message: PadchatRpcRequest = { @@ -109,6 +124,8 @@ export class PadchatRpc extends EventEmitter { param: encodedParam, } + log.silly('PadchatRpc', 'initJsonRpcPeer() jsonRpc.on(data) converted to padchat payload="%s"', JSON.stringify(message)) + this.socket.send(JSON.stringify(message)) }) } @@ -215,12 +232,12 @@ export class PadchatRpc extends EventEmitter { // }) } protected onServer(payload: PadchatPayload) { - console.log('server payload:', payload) - - log.verbose('PuppetPadchatBridge', 'onWebSocket(%s)', - JSON.stringify(payload).substr(0, 140), + log.verbose('PuppetPadchatBridge', 'onServer(payload.length=%d)', + JSON.stringify(payload).length, ) + // console.log('server payload:', payload) + // check logout: if (payload.type === PadchatPayloadType.Logout) { // this.emit('logout', this.selfId()) @@ -282,42 +299,46 @@ export class PadchatRpc extends EventEmitter { }) } - protected onServerPadchat(payload: PadchatPayload) { - console.log('onServerMessagePadChat:', payload) + protected onServerPadchat(padchatPayload: PadchatPayload) { + log.verbose('PuppetPadchatBridge', 'onServerPadchat({apiName="%s", msgId="%s", ...})', + padchatPayload.apiName, + padchatPayload.msgId, + ) + // padchatPayload: + // { + // "apiName": "WXHeartBeat", + // "data": "%7B%22status%22%3A0%2C%22message%22%3A%22ok%22%7D", + // "msgId": "abc231923912983", + // "userId": "test" + // } + log.silly('PuppetPadchatBridge', 'onServerPadchat(%s)', JSON.stringify(padchatPayload).substr(0, 500)) // check logout: - if (payload.type === PadchatPayloadType.Logout) { + if (padchatPayload.type === PadchatPayloadType.Logout) { // this.emit('logout', this.selfId()) this.emit('logout') } - log.silly('PuppetPadchatBridge', 'return apiName: %s, msgId: %s', payload.apiName, payload.msgId) - let result: any - if (payload.data) { - result = JSON.parse(decodeURIComponent(payload.data)) + if (padchatPayload.data) { + result = JSON.parse(decodeURIComponent(padchatPayload.data)) } else { - log.silly('PuppetPadchatBridge', 'onServerMessagePadchat() discard empty payload.data for apiName: %s', payload.apiName) + log.silly('PuppetPadchatBridge', 'onServerMessagePadchat() discard empty payload.data for apiName: %s', padchatPayload.apiName) result = {} } const jsonRpcResponse: JsonRpcPayloadResponse = { - id: payload.msgId, + id: padchatPayload.msgId, jsonrpc: '2.0', result: result, type: 'response', } - this.jsonRpc.write(jsonRpcResponse) + const responseText = JSON.stringify(jsonRpcResponse) + log.silly('PuppetPadchatBridge', 'onServerPadchat() converted to JsonRpc payload="%s"', responseText.substr(0, 500)) - // rawWebSocketData: - // { - // "apiName": "WXHeartBeat", - // "data": "%7B%22status%22%3A0%2C%22message%22%3A%22ok%22%7D", - // "msgId": "abc231923912983", - // "userId": "test" - // } + this.jsonRpc.write(responseText) // if (resolverDict[msgId]) { // const resolve = resolverDict[msgId] diff --git a/src/puppet-padchat/puppet-padchat.ts b/src/puppet-padchat/puppet-padchat.ts index 6b044eebd0642d8db61aa68a9a7a0f43d39a8c70..76387d82340638dce5432a3c2cb197cffc70df2c 100644 --- a/src/puppet-padchat/puppet-padchat.ts +++ b/src/puppet-padchat/puppet-padchat.ts @@ -141,32 +141,32 @@ export class PuppetPadchat extends Puppet { // feed the dog, heartbeat the puppet. // puppet.emit('heartbeat', food.data) - const feedAfterTenSeconds = async () => { - this.bridge.WXHeartBeat() - .then(() => { - this.emit('watchdog', { - data: 'WXHeartBeat()', - }) - }) - .catch(e => { - log.warn('PuppetPadchat', 'initWatchdogForPuppet() feedAfterTenSeconds rejected: %s', e && e.message || '') - }) - } - - setTimeout(feedAfterTenSeconds, 15 * 1000) + // const feedAfterTenSeconds = async () => { + // this.bridge.WXHeartBeat() + // .then(() => { + // this.emit('watchdog', { + // data: 'WXHeartBeat()', + // }) + // }) + // .catch(e => { + // log.warn('PuppetPadchat', 'initWatchdogForPuppet() feedAfterTenSeconds rejected: %s', e && e.message || '') + // }) + // } + + // setTimeout(feedAfterTenSeconds, 15 * 1000) }) - // this.watchdog.on('reset', async (food, timeout) => { - // log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s', - // food.data, timeout) + this.watchdog.on('reset', async (food, timeout) => { + log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s', + food.data, timeout) // try { // await this.stop() // await this.start() // } catch (e) { // puppet.emit('error', e) // } - // }) + }) this.emit('watchdog', { data: 'inited', diff --git a/src/wechaty.ts b/src/wechaty.ts index 02b85f50682b3f677d3d3b77870c10d5652761ab..b39c4fae4797587664c2c5bee8526cc9cb954d25 100644 --- a/src/wechaty.ts +++ b/src/wechaty.ts @@ -707,7 +707,7 @@ export class Wechaty extends Accessory implements Sayable { await this.puppet.start() } catch (e) { - // console.log(e) + console.error(e) log.error('Wechaty', 'start() exception: %s', e && e.message) Raven.captureException(e) throw e