Created
August 12, 2023 19:06
-
-
Save TimonLukas/7c757a3b234344ad71e6bd5a6a10adc1 to your computer and use it in GitHub Desktop.
TRPC custom message encoding in WebSocket
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class WsClient { | |
#socket: WebSocket | |
#listeners: { | |
open: ((event: Event) => any)[] | |
close: ((event: CloseEvent) => any)[] | |
error: ((event: Event) => any)[] | |
message: ((event: MessageEvent) => any)[] | |
} = { | |
open: [], | |
close: [], | |
error: [], | |
message: [], | |
} | |
constructor(url: string) { | |
this.#socket = new WebSocket(url) | |
this.#socket.addEventListener("open", (e) => this.#listeners.open.forEach((callback) => callback(e))) | |
this.#socket.addEventListener("close", (e) => this.#listeners.close.forEach((callback) => callback(e))) | |
this.#socket.addEventListener("error", (e) => this.#listeners.error.forEach((callback) => callback(e))) | |
this.#socket.addEventListener("message", (e) => { | |
const [id, result] = JSON.parse(e.data) | |
const event = { ...e, data: JSON.stringify({ id, result })} | |
this.#listeners.message.forEach((callback) => callback(event)) | |
}) | |
} | |
addEventListener(event: string, callback: (e: Event) => any): this { | |
switch (event) { | |
case "open": | |
this.#listeners.open.push(callback) | |
break | |
case "close": | |
this.#listeners.close.push(callback) | |
break | |
case "error": | |
this.#listeners.error.push(callback) | |
break | |
case "message": | |
this.#listeners.message.push(callback) | |
break | |
default: | |
throw new Error(`Unexpected event type: '${event}'`) | |
} | |
return this | |
} | |
send(data: string): void { | |
const { id, method, params } = JSON.parse(data) | |
this.#socket.send(JSON.stringify([id, method, params])) | |
} | |
close(code?: number | undefined, reason?: string | undefined): void { | |
this.#socket.close(code, reason) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IncomingMessage, Server } from "http" | |
import { WebSocket, WebSocketServer, RawData } from "ws"; | |
export class WsServer { | |
#server: WebSocketServer | |
#listeners: { | |
connection: ((socket: WebSocket, request: IncomingMessage) => Promise<any>)[] | |
} = { | |
connection: [], | |
} | |
constructor(options: { | |
server: Server | |
}) { | |
this.#server = new WebSocketServer(options) | |
this.#server.on("connection", (socket, ...args) => { | |
const mySocket = new WsSocket(socket) | |
this.#listeners.connection.forEach((callback) => callback(mySocket as unknown as WebSocket, ...args)) | |
}) | |
} | |
on(event: string, callback: (...args: any[]) => any): this { | |
switch(event) { | |
case "connection": | |
this.#listeners.connection.push(callback) | |
break | |
default: | |
throw new Error(`Unexpected event type: '${event}'`) | |
} | |
return this | |
} | |
} | |
class WsSocket { | |
#socket: WebSocket | |
#listeners: { | |
message: ((message: RawData, isBinary: boolean) => any)[] | |
error: ((error: Error) => any)[] | |
close: ((code: number, reason: Buffer) => any)[] | |
} = { | |
message: [], | |
error: [], | |
close: [], | |
} | |
constructor(socket: WebSocket) { | |
this.#socket = socket | |
this.#socket.on("message", (data: RawData, isBinary: boolean) => { | |
// eslint-disable-next-line @typescript-eslint/no-base-to-string | |
const [id, method, params] = JSON.parse(data.toString()) | |
const reformatted = JSON.stringify({ id, method, params }) | |
this.#listeners.message.forEach((callback) => callback(reformatted as unknown as RawData, isBinary)) | |
}) | |
this.#socket.on("error", (error) => { | |
this.#listeners.error.forEach((callback) => callback(error)) | |
}) | |
this.#socket.on("close", (code: number, reason: Buffer) => { | |
this.#listeners.close.forEach((callback) => callback(code, reason)) | |
}) | |
} | |
on(event: string, callback: (...args: any[]) => any): this { | |
switch (event) { | |
case "message": | |
this.#listeners.message.push(callback) | |
break | |
case "error": | |
this.#listeners.error.push(callback) | |
break | |
case "close": | |
this.#listeners.close.push(callback) | |
break | |
default: | |
throw new Error(`Unexpected event type: '${event}'`) | |
} | |
return this | |
} | |
off(event: string, callback: (...args: any[]) => any): this { | |
switch (event) { | |
case "close": | |
const index = this.#listeners.close.indexOf(callback) | |
if (index !== -1) { | |
this.#listeners.close.splice(index, 1) | |
} | |
break | |
default: | |
throw new Error(`Unexpected event type: '${event}'`) | |
} | |
return this | |
} | |
once(event: string, callback: (...args: any[]) => any): this { | |
const handleCallback = (...args: any[]) => { | |
callback(...args) | |
this.off(event, handleCallback) | |
} | |
this.on(event, handleCallback) | |
return this | |
} | |
send( | |
data: BufferLike, | |
options: { mask?: boolean | undefined; binary?: boolean | undefined; compress?: boolean | undefined; fin?: boolean | undefined }, | |
cb?: (err?: Error) => void, | |
) { | |
// eslint-disable-next-line @typescript-eslint/no-base-to-string | |
const parsedData = JSON.parse(data.toString()) | |
const reformatted = JSON.stringify([parsedData.id, parsedData.result]) | |
return this.#socket.send(reformatted, options, cb) | |
} | |
} | |
type BufferLike = | |
| string | |
| Buffer | |
| DataView | |
| number | |
| ArrayBufferView | |
| Uint8Array | |
| ArrayBuffer | |
| SharedArrayBuffer | |
| { valueOf(): ArrayBuffer } | |
| { valueOf(): SharedArrayBuffer } | |
| { valueOf(): Uint8Array } | |
| { valueOf(): string } | |
| { [Symbol.toPrimitive](hint: string): string }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment