|
|
@ -1,8 +1,35 @@ |
|
|
|
import EventEmitter from "events"; |
|
|
|
import { AbstractMethodMap } from "./AbstractMethodMap"; |
|
|
|
import { RequestTimeoutError } from "./errors"; |
|
|
|
import { IMethodMap, IPacket } from "./schema"; |
|
|
|
|
|
|
|
export abstract class AbstractConnection<T extends IMethodMap> extends EventEmitter |
|
|
|
/** |
|
|
|
* Declare EventEmitter types |
|
|
|
*/ |
|
|
|
interface Events { |
|
|
|
"disconnect": () => void |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Torrent IPC events |
|
|
|
*/ |
|
|
|
export declare interface AbstractConnection<RemoteMethods extends IMethodMap> { |
|
|
|
on<U extends keyof Events>(event: U, listener: Events[U]): this, |
|
|
|
emit<U extends keyof Events>(event: U, ...args: Parameters<Events[U]>): boolean |
|
|
|
} |
|
|
|
|
|
|
|
export abstract class AbstractConnection<RemoteMethods extends IMethodMap> extends EventEmitter |
|
|
|
{ |
|
|
|
/** |
|
|
|
* Disconnect from the server |
|
|
|
*/ |
|
|
|
protected abstract disconnect(): void; |
|
|
|
|
|
|
|
/** |
|
|
|
* Read the received packet |
|
|
|
*/ |
|
|
|
protected abstract read(rawPacket: any): IPacket; |
|
|
|
|
|
|
|
/** |
|
|
|
* Write the message |
|
|
|
*/ |
|
|
@ -11,33 +38,114 @@ export abstract class AbstractConnection<T extends IMethodMap> extends EventEmit |
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Encode the packet to send |
|
|
|
* Store a reference to the request handler |
|
|
|
*/ |
|
|
|
#requestHandler: AbstractMethodMap<any>; |
|
|
|
|
|
|
|
/** |
|
|
|
* The current request ID |
|
|
|
*/ |
|
|
|
#requestId: number = 0; |
|
|
|
|
|
|
|
/** |
|
|
|
* Store a map of the requests |
|
|
|
*/ |
|
|
|
#pendingRequests: Map<number, (result: any) => void> = new Map(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a new connection |
|
|
|
*/ |
|
|
|
public constructor(requestHandler: AbstractMethodMap<any>) { |
|
|
|
super(); |
|
|
|
this.#requestHandler = requestHandler; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the next request ID |
|
|
|
*/ |
|
|
|
#nextRequestId() { |
|
|
|
this.#requestId = (this.#requestId + 1) % Number.MAX_SAFE_INTEGER; |
|
|
|
return this.#requestId; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Receive and handle the packet |
|
|
|
*/ |
|
|
|
protected serializePacket(packet: any) { |
|
|
|
return packet; |
|
|
|
protected receive(rawPacket: any) { |
|
|
|
let packet = this.read(rawPacket); |
|
|
|
if (packet.method !== undefined) { |
|
|
|
this.fulfillRequest(packet.method, packet.args, packet.requestId); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (packet.requestId !== undefined) { |
|
|
|
this.fulfillResponse(packet.requestId, packet.result); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Deserialize the received packet |
|
|
|
* Handle the received request |
|
|
|
*/ |
|
|
|
protected async fulfillRequest(method: string, args?: any[], requestId?: number) { |
|
|
|
let result = await this.#requestHandler.invoke(method, args ?? []); |
|
|
|
if (requestId !== undefined) { |
|
|
|
this.write({ requestId, result }); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Fulfill a response |
|
|
|
*/ |
|
|
|
protected fulfillResponse(requestId: number, result?: any) { |
|
|
|
let resolve = this.#pendingRequests.get(requestId); |
|
|
|
if (resolve === undefined) { |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Public Interface ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Close the current connection |
|
|
|
*/ |
|
|
|
protected deserializePacket(packet: string) { |
|
|
|
return packet; |
|
|
|
public close() { |
|
|
|
this.disconnect(); |
|
|
|
this.emit("disconnect"); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Send a message |
|
|
|
*/ |
|
|
|
public send<K extends keyof T>(method: K, ...args: Parameters<T[K]>) { |
|
|
|
public send<K extends keyof RemoteMethods>(method: K, args: Parameters<RemoteMethods[K]>) { |
|
|
|
this.write({ |
|
|
|
method: method.toString(), |
|
|
|
args: args.length > 0 ? args : undefined |
|
|
|
}) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Send a request and return a promise of the result |
|
|
|
* Create a promise to send a request |
|
|
|
*/ |
|
|
|
public request<K extends keyof T>(method: K, ...args: Parameters<T[K]>) { |
|
|
|
return new Promise<>; |
|
|
|
public request<K extends keyof RemoteMethods>(method: K, args: Parameters<RemoteMethods[K]>, timeout: number|null = 5000) { |
|
|
|
return new Promise<ReturnType<RemoteMethods[K]>>((resolve, reject) => { |
|
|
|
let timeoutId: NodeJS.Timeout; |
|
|
|
const requestId = this.#nextRequestId(); |
|
|
|
const cleanup = () => { |
|
|
|
clearTimeout(timeoutId); |
|
|
|
this.#pendingRequests.delete(requestId); |
|
|
|
}; |
|
|
|
const fulfill = (result: ReturnType<RemoteMethods[K]>) => { |
|
|
|
cleanup(); |
|
|
|
resolve(result); |
|
|
|
}; |
|
|
|
if (timeout !== null) { |
|
|
|
timeoutId = setTimeout(() => { |
|
|
|
cleanup(); |
|
|
|
reject(new RequestTimeoutError()); |
|
|
|
}, timeout); |
|
|
|
} |
|
|
|
this.#pendingRequests.set(requestId, fulfill); |
|
|
|
this.send(method, args); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |