aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2021-09-27 01:37:03 +0700
committerShulhan <ms@kilabit.info>2021-09-27 01:38:23 +0700
commita3e4a44ba024f478192a807b94e858d523ad4599 (patch)
tree5d64f7f4574d59e1f57fbd0f86755f0e034a9eac
parent665bda4fe5ccf0e12b74262309da39695dd69317 (diff)
downloadpakakeh.ts-a3e4a44ba024f478192a807b94e858d523ad4599.tar.xz
all: implement WebSocket client
The WebSocket client have only one method "Send" that send request to the server based on predefined format WuiWebSocketRequest in synchronous way, which means it will wait for the response and pass it back to the caller based on the request ID.
-rw-r--r--websocket_client.d.ts45
-rw-r--r--websocket_client.ts167
2 files changed, 212 insertions, 0 deletions
diff --git a/websocket_client.d.ts b/websocket_client.d.ts
new file mode 100644
index 0000000..737fb21
--- /dev/null
+++ b/websocket_client.d.ts
@@ -0,0 +1,45 @@
+import { WuiResponseInterface } from "./response.js";
+interface RequestQueue {
+ req: WuiWebSocketRequest;
+ cbSuccess: (res: WuiWebSocketResponse) => void;
+ cbFail: (err: string) => void;
+}
+export interface WuiWebSocketOptions {
+ address: string;
+ insecure: boolean;
+ auto_reconnect: boolean;
+ auto_reconnect_interval: number;
+ onBroadcast: (res: WuiWebSocketResponse) => void;
+ onConnected: () => void;
+ onDisconnected: () => void;
+ onError: () => void;
+}
+export interface WuiWebSocketRequest {
+ id: number;
+ method: string;
+ target: string;
+ body?: string;
+}
+export interface WuiWebSocketResponse {
+ id: number;
+ code: number;
+ message: string;
+ body: string;
+}
+export declare class WuiWebSocketClient {
+ opts: WuiWebSocketOptions;
+ address: string;
+ conn: WebSocket;
+ requestQueue: RequestQueue[];
+ reconnect_id: number;
+ isOpen: boolean;
+ error: string;
+ constructor(opts: WuiWebSocketOptions);
+ Send(req: WuiWebSocketRequest): Promise<WuiResponseInterface>;
+ connect(): void;
+ onClose(ev: CloseEvent): void;
+ onError(ev: Event): void;
+ onMessage(ev: MessageEvent): void;
+ onOpen(ev: Event): void;
+}
+export {};
diff --git a/websocket_client.ts b/websocket_client.ts
new file mode 100644
index 0000000..62d11fc
--- /dev/null
+++ b/websocket_client.ts
@@ -0,0 +1,167 @@
+import { WuiResponseInterface } from "./response.js"
+
+const AUTO_RECONNECT_INTERVAL = 5000
+
+interface RequestQueue {
+ req: WuiWebSocketRequest
+ cbSuccess: (res: WuiWebSocketResponse) => void
+ cbFail: (err: string) => void
+}
+
+export interface WuiWebSocketOptions {
+ address: string
+ insecure: boolean // If true the client will connect without SSL.
+ auto_reconnect: boolean // If true the client will handle auto-reconnect.
+ auto_reconnect_interval: number // The interval for auto-reconnect, default to 5 seconds.
+ onBroadcast: (res: WuiWebSocketResponse) => void
+ onConnected: () => void
+ onDisconnected: () => void
+ onError: () => void
+}
+
+export interface WuiWebSocketRequest {
+ id: number
+ method: string
+ target: string
+ body?: string
+}
+
+export interface WuiWebSocketResponse {
+ id: number
+ code: number
+ message: string
+ body: string
+}
+
+export class WuiWebSocketClient {
+ address: string
+ conn!: WebSocket
+ requestQueue: RequestQueue[] = []
+ reconnect_id: number = 0
+ isOpen: boolean = false
+ error: string = ""
+
+ constructor(public opts: WuiWebSocketOptions) {
+ if (opts.insecure) {
+ this.address = "ws://" + opts.address
+ } else {
+ this.address = "wss://" + opts.address
+ }
+ if (opts.auto_reconnect) {
+ if (opts.auto_reconnect_interval <= 0) {
+ opts.auto_reconnect_interval =
+ AUTO_RECONNECT_INTERVAL
+ }
+ }
+ this.connect()
+ }
+
+ //
+ // Send the request and wait for response similar to HTTP
+ // request-response.
+ //
+ async Send(req: WuiWebSocketRequest): Promise<WuiResponseInterface> {
+ return new Promise((resolve, reject) => {
+ let wuiRes: WuiResponseInterface = {
+ code: 0,
+ message: "",
+ }
+ let reqQueue: RequestQueue = {
+ req: req,
+ cbSuccess: (res: WuiWebSocketResponse) => {
+ wuiRes.code = res.code
+ wuiRes.message = res.message
+ if (
+ res.code === 200 &&
+ res.body.length > 0
+ ) {
+ wuiRes.data = JSON.parse(
+ atob(res.body),
+ )
+ }
+ resolve(wuiRes)
+ },
+ cbFail: (err: string) => {
+ wuiRes.code = 500
+ wuiRes.message = err
+ resolve(wuiRes)
+ },
+ }
+ this.requestQueue.push(reqQueue)
+ this.conn.send(JSON.stringify(req))
+ })
+ }
+
+ connect() {
+ this.conn = new WebSocket(this.address)
+
+ this.conn.onclose = (ev: CloseEvent) => {
+ this.onClose(ev)
+ }
+ this.conn.onerror = (ev: Event) => {
+ this.onError(ev)
+ }
+ this.conn.onmessage = (ev: MessageEvent) => {
+ this.onMessage(ev)
+ }
+ this.conn.onopen = (ev: Event) => {
+ this.onOpen(ev)
+ }
+ }
+
+ // onClose handle connection closed by cleaning up the request
+ // queue.
+ onClose(ev: CloseEvent) {
+ for (let x = 0; x < this.requestQueue.length; x++) {
+ this.requestQueue[x].cbFail("connection closed")
+ }
+
+ this.isOpen = false
+ this.error = "connection is closed by server"
+
+ if (this.opts.auto_reconnect && !this.reconnect_id) {
+ this.reconnect_id = setInterval(() => {
+ this.connect()
+ }, this.opts.auto_reconnect_interval)
+ }
+ if (this.opts.onDisconnected) {
+ this.opts.onDisconnected()
+ }
+ }
+
+ onError(ev: Event) {
+ if (this.opts.onError) {
+ this.opts.onError()
+ }
+ }
+
+ onMessage(ev: MessageEvent) {
+ let res: WuiWebSocketResponse = JSON.parse(ev.data)
+
+ for (let x = 0; x < this.requestQueue.length; x++) {
+ let reqq = this.requestQueue[x]
+ if (reqq.req.id === res.id) {
+ reqq.cbSuccess(res)
+ this.requestQueue.splice(x, 1)
+ return
+ }
+ }
+
+ if (this.opts.onBroadcast) {
+ this.opts.onBroadcast(res)
+ }
+ }
+
+ onOpen(ev: Event) {
+ this.isOpen = true
+ this.error = ""
+
+ if (this.reconnect_id) {
+ clearInterval(this.reconnect_id)
+ this.reconnect_id = 0
+ }
+ if (this.opts.onConnected) {
+ this.opts.onConnected()
+ }
+ }
+}