提交 ba346a5a 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

working on IO...

上级 f469fd93
......@@ -17,18 +17,17 @@
*
*/
import * as WebSocket from 'ws'
import StateSwitch from 'state-switch'
import { StateSwitch } from 'state-switch'
import PuppetWeb from './puppet-web/'
import {
config,
// WechatyEventName
log,
} from './config'
} from './config'
import Wechaty from './wechaty'
import { Wechaty } from './wechaty'
export interface IoSetting {
export interface IoOptions {
wechaty: Wechaty,
token: string,
apihost?: string,
......@@ -55,9 +54,9 @@ interface IoEvent {
export class Io {
public uuid: string
private protocol: string
private eventBuffer: IoEvent[] = []
private ws: WebSocket
private protocol : string
private eventBuffer : IoEvent[] = []
private ws : WebSocket
private state = new StateSwitch<'online', 'offline'>('Io', 'offline', log)
......@@ -66,28 +65,30 @@ export class Io {
private onMessage: Function
constructor(private setting: IoSetting) {
if (!setting.wechaty || !setting.token) {
throw new Error('Io must has wechaty & token set')
}
setting.apihost = setting.apihost || config.apihost
setting.protocol = setting.protocol || config.DEFAULT_PROTOCOL
constructor(
private options: IoOptions,
) {
options.apihost = options.apihost || config.apihost
options.protocol = options.protocol || config.DEFAULT_PROTOCOL
this.uuid = setting.wechaty.uuid
this.uuid = options.wechaty.uuid
this.protocol = setting.protocol + '|' + setting.wechaty.uuid
this.protocol = options.protocol + '|' + options.wechaty.uuid
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], uuid[%s]',
setting.apihost,
setting.token,
setting.protocol,
this.uuid,
options.apihost,
options.token,
options.protocol,
this.uuid,
)
}
public toString() { return 'Class Io(' + this.setting.token + ')'}
public toString() {
return `Io<${this.options.token}>`
}
private connected() { return this.ws && this.ws.readyState === WebSocket.OPEN }
private connected() {
return this.ws && this.ws.readyState === WebSocket.OPEN
}
public async init(): Promise<void> {
log.verbose('Io', 'init()')
......@@ -97,9 +98,9 @@ export class Io {
try {
await this.initEventHook()
await this.initWebSocket()
this.ws = this.initWebSocket()
this.state.current('online')
this.state.current('online', true)
return
} catch (e) {
......@@ -109,194 +110,16 @@ export class Io {
}
}
private initWebSocket() {
log.verbose('Io', 'initWebSocket()')
this.state.current('online', false)
// const auth = 'Basic ' + new Buffer(this.setting.token + ':X').toString('base64')
const auth = 'Token ' + this.setting.token
const headers = { 'Authorization': auth }
if (!this.setting.apihost) {
throw new Error('no apihost')
}
let endpoint = 'wss://' + this.setting.apihost + '/v0/websocket'
// XXX quick and dirty: use no ssl for APIHOST other than official
if (!/api\.wechaty\.io/.test(this.setting.apihost)) {
endpoint = 'ws://' + this.setting.apihost + '/v0/websocket'
}
const ws = this.ws = new WebSocket(endpoint, this.protocol, { headers })
ws.on('open', () => {
if (this.protocol !== ws.protocol) {
log.error('Io', 'initWebSocket() require protocol[%s] failed', this.protocol)
// XXX deal with error?
}
log.verbose('Io', 'initWebSocket() connected with protocol [%s]', ws.protocol)
// this.currentState('connected')
this.state.current('online')
// FIXME: how to keep alive???
// ws._socket.setKeepAlive(true, 100)
this.reconnectTimeout = null
const initEvent: IoEvent = {
name: 'sys',
payload: 'Wechaty version ' + this.setting.wechaty.version() + ` with UUID: ${this.uuid}`,
}
this.send(initEvent)
})
ws.on('message', data => {
log.silly('Io', 'initWebSocket() ws.on(message): %s', data)
// flags.binary will be set if a binary data is received.
// flags.masked will be set if the data was masked.
if (typeof data !== 'string') {
throw new Error('data should be string...')
}
const ioEvent: IoEvent = {
name: 'raw',
payload: data,
}
try {
const obj = JSON.parse(data)
ioEvent.name = obj.name
ioEvent.payload = obj.payload
} catch (e) {
log.verbose('Io', 'on(message) recv a non IoEvent data[%s]', data)
}
switch (ioEvent.name) {
case 'botie':
const payload = ioEvent.payload
if (payload.onMessage) {
const script = payload.script
/* tslint:disable:no-eval */
const fn = eval(script)
if (typeof fn === 'function') {
this.onMessage = fn
} else {
log.warn('Io', 'server pushed function is invalid')
}
}
break
case 'reset':
log.verbose('Io', 'on(reset): %s', ioEvent.payload)
this.setting.wechaty.reset(ioEvent.payload)
break
case 'shutdown':
log.warn('Io', 'on(shutdown): %s', ioEvent.payload)
process.exit(0)
break
case 'update':
log.verbose('Io', 'on(report): %s', ioEvent.payload)
const user = this.setting.wechaty.puppet ? this.setting.wechaty.puppet.user : null
if (user) {
const loginEvent: IoEvent = {
name: 'login',
// , payload: user.obj
payload: user.obj,
}
this.send(loginEvent)
}
// XXX: Puppet should not has `scan` variable ...
const scan = this.setting.wechaty
&& this.setting.wechaty.puppet
&& this.setting.wechaty.puppet['scan']
if (scan) {
const scanEvent: IoEvent = {
name: 'scan',
payload: scan,
}
this.send(scanEvent)
}
break
case 'sys':
// do nothing
break
default:
log.warn('Io', 'UNKNOWN on(%s): %s', ioEvent.name, ioEvent.payload)
break
}
})
ws.on('error', e => {
log.warn('Io', 'initWebSocket() error event[%s]', e.message)
this.setting.wechaty.emit('error', e)
// when `error`, there must have already a `close` event
// we should not call this.reconnect() again
//
// this.close()
// this.reconnect()
})
.on('close', (code, message) => {
log.warn('Io', 'initWebSocket() close event[%d: %s]', code, message)
ws.close()
this.reconnect()
})
return Promise.resolve(ws)
}
private reconnect() {
log.verbose('Io', 'reconnect()')
if (this.state.target() === 'offline') {
log.warn('Io', 'reconnect() canceled because state.target() === offline')
return
}
if (this.connected()) {
log.warn('Io', 'reconnect() on a already connected io')
return
}
if (this.reconnectTimer) {
log.warn('Io', 'reconnect() on a already re-connecting io')
return
}
if (!this.reconnectTimeout) {
this.reconnectTimeout = 1
} else if (this.reconnectTimeout < 10000) {
this.reconnectTimeout *= 3
}
log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout / 1000))
this.reconnectTimer = setTimeout(_ => {
this.reconnectTimer = null
this.initWebSocket()
}, this.reconnectTimeout)// as any as NodeJS.Timer
}
private initEventHook() {
log.verbose('Io', 'initEventHook()')
const wechaty = this.setting.wechaty
wechaty.on('message', this.ioMessage)
wechaty.on('scan', (url, code) => this.send({ name: 'scan', payload: { url, code } }))
wechaty.on('login' , user => this.send({ name: 'login', payload: user }))
wechaty.on('logout' , user => this.send({ name: 'login', payload: user }))
const wechaty = this.options.wechaty
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { uuid: this.uuid, data } }))
wechaty.on('error' , error => this.send({ name: 'error', payload: error }))
wechaty.on('error' , error => this.send({ name: 'error', payload: error }))
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { uuid: this.uuid, data } }))
wechaty.on('login', user => this.send({ name: 'login', payload: user }))
wechaty.on('logout' , user => this.send({ name: 'login', payload: user }))
wechaty.on('message', message => this.ioMessage(message))
wechaty.on('scan', (url, code) => this.send({ name: 'scan', payload: { url, code } }))
// const hookEvents: WechatyEventName[] = [
// 'scan'
......@@ -351,6 +174,192 @@ export class Io {
return
}
private initWebSocket() {
log.verbose('Io', 'initWebSocket()')
// this.state.current('online', false)
// const auth = 'Basic ' + new Buffer(this.setting.token + ':X').toString('base64')
const auth = 'Token ' + this.options.token
const headers = { 'Authorization': auth }
if (!this.options.apihost) {
throw new Error('no apihost')
}
let endpoint = 'wss://' + this.options.apihost + '/v0/websocket'
// XXX quick and dirty: use no ssl for APIHOST other than official
if (!/api\.wechaty\.io/.test(this.options.apihost)) {
endpoint = 'ws://' + this.options.apihost + '/v0/websocket'
}
const ws = this.ws = new WebSocket(endpoint, this.protocol, { headers })
ws.on('open', () => this.wsOnOpen(ws))
ws.on('message', data => this.wsOnMessage(data))
ws.on('error', e => this.wsOnError(e))
ws.on('close', (code, message) => this.wsOnClose(ws, code, message))
return ws
}
private wsOnOpen(ws: WebSocket): void {
if (this.protocol !== ws.protocol) {
log.error('Io', 'initWebSocket() require protocol[%s] failed', this.protocol)
// XXX deal with error?
}
log.verbose('Io', 'initWebSocket() connected with protocol [%s]', ws.protocol)
// this.currentState('connected')
// this.state.current('online')
// FIXME: how to keep alive???
// ws._socket.setKeepAlive(true, 100)
this.reconnectTimeout = null
const name = 'sys'
const payload = 'Wechaty version ' + this.options.wechaty.version() + ` with UUID: ${this.uuid}`
const initEvent: IoEvent = {
name,
payload,
}
this.send(initEvent)
}
private wsOnMessage(data: WebSocket.Data) {
log.silly('Io', 'initWebSocket() ws.on(message): %s', data)
// flags.binary will be set if a binary data is received.
// flags.masked will be set if the data was masked.
if (typeof data !== 'string') {
throw new Error('data should be string...')
}
const ioEvent: IoEvent = {
name : 'raw',
payload : data,
}
try {
const obj = JSON.parse(data)
ioEvent.name = obj.name
ioEvent.payload = obj.payload
} catch (e) {
log.verbose('Io', 'on(message) recv a non IoEvent data[%s]', data)
}
switch (ioEvent.name) {
case 'botie':
const payload = ioEvent.payload
if (payload.onMessage) {
const script = payload.script
/* tslint:disable:no-eval */
const fn = eval(script)
if (typeof fn === 'function') {
this.onMessage = fn
} else {
log.warn('Io', 'server pushed function is invalid')
}
}
break
case 'reset':
log.verbose('Io', 'on(reset): %s', ioEvent.payload)
this.options.wechaty.reset(ioEvent.payload)
break
case 'shutdown':
log.warn('Io', 'on(shutdown): %s', ioEvent.payload)
process.exit(0)
break
case 'update':
log.verbose('Io', 'on(report): %s', ioEvent.payload)
const user = this.options.wechaty.puppet ? this.options.wechaty.puppet.user : null
if (user) {
const loginEvent: IoEvent = {
name : 'login',
payload : user.obj,
}
this.send(loginEvent)
}
const puppet = this.options.wechaty.puppet
if (puppet instanceof PuppetWeb) {
const scanInfo = puppet.scanInfo
if (scanInfo) {
const scanEvent: IoEvent = {
name: 'scan',
payload: scanInfo,
}
this.send(scanEvent)
}
}
break
case 'sys':
// do nothing
break
default:
log.warn('Io', 'UNKNOWN on(%s): %s', ioEvent.name, ioEvent.payload)
break
}
}
private wsOnError(e: Error) {
log.warn('Io', 'initWebSocket() error event[%s]', e.message)
this.options.wechaty.emit('error', e)
// when `error`, there must have already a `close` event
// we should not call this.reconnect() again
//
// this.close()
// this.reconnect()
}
private wsOnClose(
ws : WebSocket,
code : number,
message : string,
): void {
log.warn('Io', 'initWebSocket() close event[%d: %s]', code, message)
ws.close()
this.reconnect()
}
private reconnect() {
log.verbose('Io', 'reconnect()')
if (this.state.target() === 'offline') {
log.warn('Io', 'reconnect() canceled because state.target() === offline')
return
}
if (this.connected()) {
log.warn('Io', 'reconnect() on a already connected io')
return
}
if (this.reconnectTimer) {
log.warn('Io', 'reconnect() on a already re-connecting io')
return
}
if (!this.reconnectTimeout) {
this.reconnectTimeout = 1
} else if (this.reconnectTimeout < 10 * 1000) {
this.reconnectTimeout *= 3
}
log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout / 1000))
this.reconnectTimer = setTimeout(_ => {
this.reconnectTimer = null
this.initWebSocket()
}, this.reconnectTimeout)// as any as NodeJS.Timer
}
private async send(ioEvent?: IoEvent): Promise<void> {
if (ioEvent) {
log.silly('Io', 'send(%s: %s)', ioEvent.name, ioEvent.payload)
......@@ -384,18 +393,6 @@ export class Io {
}
}
private async close(): Promise<void> {
log.verbose('Io', 'close()')
this.state.target('offline')
this.state.current('offline', false)
this.ws.close()
this.state.current('offline')
// TODO: remove listener for this.setting.wechaty.on(message )
return Promise.resolve()
}
public async quit(): Promise<void> {
this.state.target('offline')
this.state.current('offline', false)
......@@ -408,10 +405,11 @@ export class Io {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
await this.close()
this.ws.close()
// this.currentState('disconnected')
this.state.current('offline')
this.state.current('offline', true)
return Promise.resolve()
}
......
......@@ -50,6 +50,8 @@ export interface BridgeOptions {
profile : Profile,
}
declare const WechatyBro
export class Bridge extends EventEmitter {
private browser : Browser
private page : Page
......@@ -210,6 +212,7 @@ export class Bridge extends EventEmitter {
public async quit(): Promise<void> {
log.verbose('PuppetWebBridge', 'quit()')
this.removeAllListeners()
try {
await this.page.close()
log.silly('PuppetWebBridge', 'quit() page.close()-ed')
......@@ -544,7 +547,9 @@ export class Bridge extends EventEmitter {
)
try {
const noWechaty = await this.page.evaluate('typeof WechatyBro === "undefined"')
const noWechaty = await this.page.evaluate(() => {
return typeof WechatyBro === 'undefined'
})
if (noWechaty) {
const e = new Error('there is no WechatyBro in browser(yet)')
throw e
......@@ -646,7 +651,7 @@ export class Bridge extends EventEmitter {
public async hostname(): Promise<string | null> {
log.verbose('PuppetWebBridge', 'hostname()')
const hostname = await this.page.evaluate('location.hostname')
const hostname = await this.page.evaluate(() => location.hostname) as any as string
log.silly('PuppetWebBridge', 'hostname() got %s', hostname)
return hostname
}
......@@ -717,9 +722,9 @@ export class Bridge extends EventEmitter {
return
}
public async evaluate(...args: any[]): Promise<string> {
public async evaluate(fn: () => any, ...args: any[]): Promise<() => any> {
log.silly('PuppetWebBridge', 'evaluate()')
return await this.page.evaluate.apply(this.page, args)
return await this.page.evaluate(fn, ...args)
}
}
......
......@@ -136,22 +136,31 @@ export class PuppetWeb extends Puppet {
}
public initWatchdogForPuppet(): void {
log.verbose('PuppetWeb', 'initWatchdogForPuppet()')
const puppet = this
const dog = this.puppetWatchdog
puppet.on('ding', data => this.puppetWatchdog.feed({
puppet.on('ding', data => dog.feed({
data,
type: 'ding',
}))
dog.on('feed', food => {
// feed the rat, heartbeat the puppet.
log.verbose('PuppetWeb', 'initWatchdogForPuppet() dog.on(feed)')
// feed the dog, heartbeat the puppet.
puppet.emit('heartbeat', food.data)
})
dog.on('reset', (food, left) => {
const e = new Error(`PuppetWeb Watchdog Reset, last food: ${food.data}`)
puppet.emit('error', e)
dog.on('reset', async (food, left) => {
log.warn('PuppetWeb', 'initWatchdogForPuppet() dog.on(reset) last food: %s',
food.data)
try {
await this.quit()
await this.init()
} catch (e) {
puppet.emit('error', e)
}
})
}
......@@ -163,6 +172,8 @@ export class PuppetWeb extends Puppet {
* so we need to refresh the page after a while
*/
public initWatchdogForScan(): void {
log.verbose('PuppetWeb', 'initWatchdogForScan()')
const puppet = this
const dog = this.scanWatchdog
......@@ -187,13 +198,21 @@ export class PuppetWeb extends Puppet {
}))
dog.on('reset', async (food, left) => {
log.warn('PuppetWeb', 'initSccanWatchdog() on(reset) lastFood: %s, timeLeft: %s',
log.warn('PuppetWeb', 'initScanWatchdog() on(reset) lastFood: %s, timeLeft: %s',
food.data, left)
try {
await this.bridge.reload()
} catch (e) {
log.error('PuppetWeb', 'initScanWatchdog() on(reset) exception: %s', e)
this.emit('error', e)
try {
log.error('PuppetWeb', 'initScanWatchdog() on(reset) try to recover by bridge.{quit,init}()', e)
await this.bridge.quit()
await this.bridge.init()
log.error('PuppetWeb', 'initScanWatchdog() on(reset) recover successful')
} catch (e) {
log.error('PuppetWeb', 'initScanWatchdog() on(reset) recover FAIL: %s', e)
this.emit('error', e)
}
}
})
}
......@@ -221,6 +240,7 @@ export class PuppetWeb extends Puppet {
this.state.target('dead')
this.state.current('dead', false)
this.removeAllListeners()
try {
if (this.bridge) {
......@@ -273,7 +293,10 @@ export class PuppetWeb extends Puppet {
throw e
}
const text = await this.bridge.evaluate('document.body.innerHTML')
const text = await this.bridge.evaluate(() => {
return document.body.innerHTML
}) as any as string
try {
// Test if Wechat account is blocked
// will throw exception if blocked
......
......@@ -45,8 +45,8 @@ export interface ScanInfo {
}
export type PuppetEvent = WechatyEvent
| 'ding'
| 'watchdog'
| 'ding'
| 'watchdog'
export interface PuppetOptions {
profile: Profile,
......@@ -77,7 +77,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public emit(event: 'room-topic', room: Room, topic: string, oldTopic: string, changer: Contact) : boolean
public emit(event: 'scan', url: string, code: number) : boolean
public emit(event: 'watchdog', food: WatchdogFood) : boolean
public emit(event: never, ...args: any[]) : boolean
public emit(event: never, ...args: never[]) : never
public emit(
event: PuppetEvent,
......@@ -98,11 +98,11 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public on(event: 'room-topic', listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void) : this
public on(event: 'scan', listener: (info: ScanInfo) => void) : this
public on(event: 'watchdog', listener: (data: WatchdogFood) => void) : this
public on(event: never, listener: any) : this
public on(event: never, listener: never) : never
public on(
event: PuppetEvent,
listener: ((...args: any[]) => void),
listener: (...args: any[]) => void,
): this {
super.on(event, listener)
return this
......
......@@ -41,10 +41,7 @@ import {
MediaMessage,
} from './message'
import Profile from './profile'
import {
Puppet,
PuppetEvent,
} from './puppet'
import Puppet from './puppet'
import PuppetWeb from './puppet-web/'
import Room from './room'
import Misc from './misc'
......@@ -64,8 +61,8 @@ export type WechatEvent = 'friend'
| 'scan'
export type WechatyEvent = WechatEvent
| 'heartbeat'
| 'error'
| 'heartbeat'
/**
* Main bot class.
......@@ -116,7 +113,9 @@ export class Wechaty extends EventEmitter implements Sayable {
* .on('message', message => console.log(`Message: ${message}`))
* .init()
*/
public static instance(options?: WechatyOptions) {
public static instance(
options?: WechatyOptions,
) {
if (options && this._instance) {
throw new Error('there has already a instance. no params will be allowed any more')
}
......@@ -129,7 +128,9 @@ export class Wechaty extends EventEmitter implements Sayable {
/**
* @private
*/
private constructor(private options: WechatyOptions = {}) {
private constructor(
private options: WechatyOptions = {},
) {
super()
log.verbose('Wechaty', 'contructor()')
......@@ -143,7 +144,7 @@ export class Wechaty extends EventEmitter implements Sayable {
/**
* @private
*/
public toString() { return `Class Wechaty(${this.options.puppet}, ${this.profile.name})`}
public toString() { return `Wechaty<${this.options.puppet}, ${this.profile.name}>`}
/**
* Return version of Wechaty
......@@ -172,18 +173,6 @@ export class Wechaty extends EventEmitter implements Sayable {
return Wechaty.version(forceNpm)
}
/**
* @private
*/
public async reset(reason?: string): Promise<void> {
log.verbose('Wechaty', 'reset() because %s', reason)
if (!this.puppet) {
throw new Error('no puppet')
}
await this.puppet.reset(reason)
return
}
/**
* Initialize the bot, return Promise.
*
......@@ -208,7 +197,11 @@ export class Wechaty extends EventEmitter implements Sayable {
try {
this.profile.load()
await this.initPuppet()
this.puppet = await this.initPuppet()
// set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
config.puppetInstance(this.puppet)
} catch (e) {
log.error('Wechaty', 'init() exception: %s', e && e.message)
Raven.captureException(e)
......@@ -362,6 +355,7 @@ export class Wechaty extends EventEmitter implements Sayable {
* @private
*/
public async initPuppet(): Promise<Puppet> {
log.verbose('Wechaty', 'initPuppet()')
let puppet: Puppet
switch (this.options.puppet) {
......@@ -375,7 +369,7 @@ export class Wechaty extends EventEmitter implements Sayable {
throw new Error('Puppet unsupport(yet?): ' + this.options.puppet)
}
const eventList: PuppetEvent[] = [
const eventList: WechatyEvent[] = [
'error',
'friend',
'heartbeat',
......@@ -388,21 +382,16 @@ export class Wechaty extends EventEmitter implements Sayable {
'scan',
]
eventList.map(e => {
// https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/
// We’ve lost () around the argument list when there’s just one argument (rest arguments are an exception, eg (...args) => ...)
puppet.on(e as any, (...args: any[]) => {
this.emit.apply(this, [e, ...args])
for (const event of eventList) {
log.verbose('Wechaty', 'initPuppet() puppet.on(%s) registered', event)
/// e as any ??? Maybe this is a bug of TypeScript v2.5.3
puppet.on(event as any, (...args: any[]) => {
this.emit(event, ...args)
})
})
// set puppet before init, because we need this.puppet if we quit() before init() finish
this.puppet = <Puppet>puppet // force to use base class Puppet interface for better encapsolation
// set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
config.puppetInstance(puppet)
}
await puppet.init()
return puppet
}
......@@ -423,6 +412,7 @@ export class Wechaty extends EventEmitter implements Sayable {
}
this.state.target('standby')
this.state.current('standby', false)
this.removeAllListeners()
if (!this.puppet) {
log.warn('Wechaty', 'quit() without this.puppet')
......@@ -430,7 +420,7 @@ export class Wechaty extends EventEmitter implements Sayable {
}
const puppetBeforeDie = this.puppet
this.puppet = null
this.puppet = null
config.puppetInstance(null)
try {
......@@ -439,8 +429,9 @@ export class Wechaty extends EventEmitter implements Sayable {
log.error('Wechaty', 'quit() exception: %s', e.message)
Raven.captureException(e)
throw e
} finally {
this.state.current('standby', true)
}
this.state.current('standby')
return
}
......@@ -551,6 +542,19 @@ export class Wechaty extends EventEmitter implements Sayable {
this.emit('error', e)
}
}
/**
* @private
*/
public async reset(reason?: string): Promise<void> {
log.verbose('Wechaty', 'reset() because %s', reason)
if (!this.puppet) {
throw new Error('no puppet')
}
await this.puppet.reset(reason)
return
}
}
export default Wechaty
......@@ -69,6 +69,8 @@ test('retryPromise()', async t => {
t.true(thenSpy.withArgs(EXPECTED_RESOLVE).calledOnce, 'should got EXPECTED_RESOLVE when wait enough')
})
declare const WechatyBro
test.only('WechatyBro.ding()', async t => {
const profile = new Profile(Math.random().toString(36).substr(2, 5))
const bridge = new Bridge({
......@@ -80,7 +82,10 @@ test.only('WechatyBro.ding()', async t => {
await bridge.init()
t.pass('should init Bridge')
const retDing = await bridge.evaluate('WechatyBro.ding()')
const retDing = await bridge.evaluate(() => {
return WechatyBro.ding()
}) as any as string
t.is(retDing, 'dong', 'should got dong after execute WechatyBro.ding()')
const retCode = await bridge.proxyWechaty('isLogin')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册