siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
150 lines (106 loc) • 4.53 kB
text/typescript
import {Constructable, Mixin} from "../util/Common.js";
import {CanLog} from "../util/role/CanLog.js";
import {Channel, Envelop, EnvelopId, Message} from "./Types.js";
// globally unique, to up to repeated usages of this module in different JS context
let ENVELOP_COUNTER : EnvelopId = 1
export const ChannelEndPoint = <T extends Constructable<CanLog>>(base : T) =>
class ChannelEndPoint extends base {
connectionTimeout : number
connectionInterval : number = 1000
awaitingResponses : Map<EnvelopId, [ Function, Function, Envelop, any ]> = new Map()
doConnect (channel : Channel) : Promise<any> {
throw "Abstract method `doConnect`"
}
doDisconnect () {
throw "Abstract method `doDisconnect`"
}
sendMessage (message : Message) {
throw "Abstract method `sendMessage`"
}
messageToEnvelop (message : Message) : Envelop | undefined {
throw "Abstract method `messageToEnvelop`"
}
envelopToMessage (envelop : Envelop) : Message {
throw "Abstract method `envelopToMessage`"
}
doProcessRawChannelMessage (message : Message, envelop : Envelop) {
throw "Abstract method `doProcessRawChannelMessage`"
}
doDispatchEnvelop (envelop : Envelop) {
throw "Abstract method `doDispatchEnvelop`"
}
connect(channel? : Channel) : Promise<any> {
let start = new Date().getTime()
return new Promise((resolve, reject) => {
const connectionAttempt = () => {
this.doConnect(channel).then(
resolve,
reason => {
if (new Date().getTime() - start > (this.connectionTimeout || 3000))
reject(reason)
else
setTimeout(connectionAttempt, this.connectionInterval)
}
)
}
connectionAttempt()
})
}
onRawChannelMessage (message : Message) {
this.info("Received raw message: " + JSON.stringify(message))
const envelop = this.messageToEnvelop(message)
if (envelop !== undefined && envelop.id != null) {
if (envelop.inResponseOf != null) {
let id = envelop.inResponseOf
const handlers = this.awaitingResponses.get(id)
if (!handlers) {
this.warn("Response for unknown envelop, timeout occurred?:\n" + JSON.stringify(envelop))
} else {
this.awaitingResponses.delete(id)
handlers[ envelop.isRejection ? 1 : 0 ](envelop.payload)
}
} else
this.doDispatchEnvelop(envelop)
} else {
this.doProcessRawChannelMessage(message, envelop)
}
}
disconnect () {
this.awaitingResponses.forEach((value, id) => {
value[ 1 ]("Disconnecting")
})
this.awaitingResponses.clear()
return this.doDisconnect()
}
getFreshEnvelopId () : EnvelopId {
return ENVELOP_COUNTER++
}
replyWith (envelop : Envelop, result : any, isRejection : boolean = false) {
this.sendMessage(this.envelopToMessage({
id : this.getFreshEnvelopId(),
inResponseOf : envelop.id,
isRejection : isRejection,
payload : result
}))
}
sendRpcCall (payload : object, timeout? : number) : Promise<any> {
this.debug("Sending command: " + payload.toString())
const id = this.getFreshEnvelopId()
const envelop : Envelop = { id : id, payload : payload }
return new Promise((resolve, reject) => {
let timeoutHandler
if (timeout != null) {
let timeoutHandler = setTimeout(() => {
this.debug("Timeout occurred for: " + JSON.stringify(envelop))
if (this.awaitingResponses.has(id)) {
this.awaitingResponses.delete(id)
reject("Timeout while waiting for command result: " + JSON.stringify(payload))
}
}, timeout)
}
this.awaitingResponses.set(id, [ resolve, reject, envelop, timeoutHandler ])
this.sendMessage(this.envelopToMessage(envelop))
})
}
}
export type ChannelEndPoint = Mixin<typeof ChannelEndPoint>