//
// "bun-types" is currently conflicting with "ws" types.
// @ts-ignore
import type { ServerWebSocket } from 'bun';
import EventEmitter from 'events';
import { Protocol, type Client, type ClientPrivate, ClientState, type ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';
export class WebSocketWrapper extends EventEmitter {
public ws: ServerWebSocket;
constructor(ws: ServerWebSocket) {
super();
this.ws = ws;
}
}
export class WebSocketClient implements Client, ClientPrivate {
'~messages': any;
public id: string;
public ref: WebSocketWrapper;
public sessionId: string;
public state: ClientState = ClientState.JOINING;
public reconnectionToken: string;
public _enqueuedMessages: any[] = [];
public _afterNextPatchQueue;
public _reconnectionToken: string;
public _joinedAt: number;
constructor(id: string, ref: WebSocketWrapper,) {
this.id = this.sessionId = id;
this.ref = ref;
}
public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {
debugMessage("send bytes(to %s): '%s' -> %j", this.sessionId, type, bytes);
this.enqueueRaw(
getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),
options,
);
}
public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {
debugMessage("send(to %s): '%s' -> %j", this.sessionId, messageOrType, messageOrOptions);
this.enqueueRaw(
getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),
options,
);
}
public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {
// use room's afterNextPatch queue
if (options?.afterNextPatch) {
this._afterNextPatchQueue.push([this, [data]]);
return;
}
if (this.state !== ClientState.JOINED) {
// sending messages during `onJoin` or `onReconnect`.
// - the client-side cannot register "onMessage" callbacks at this point.
// - enqueue the messages to be send after JOIN_ROOM message has been sent
// - create a new buffer for enqueued messages, as the underlying buffer might be modified
this._enqueuedMessages?.push(data);
return;
}
this.raw(data, options);
}
public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {
// skip if client not open
// WebSocket is globally available on Bun runtime
// @ts-ignore
if (this.ref.ws.readyState !== WebSocket.OPEN) {
return;
}
// Bun's sendBinary requires an ArrayBufferView (Uint8Array, etc).
// Ensure we don't pass a plain number[] array.
this.ref.ws.sendBinary(ArrayBuffer.isView(data) ? data : new Uint8Array(data));
}
public error(code: number, message: string = '', cb?: (err?: Error) => void) {
this.raw(getMessageBytes[Protocol.ERROR](code, message));
if (cb) {
// (same API as "ws" transport)
setTimeout(cb, 1);
}
}
get readyState() {
return this.ref.ws.readyState;
}
public leave(code?: number, data?: string) {
this.ref.ws.close(code, data);
}
public close(code?: number, data?: string) {
logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');
try {
throw new Error();
} catch (e: any) {
logger.info(e.stack);
}
this.leave(code, data);
}
public toJSON() {
return { sessionId: this.sessionId, readyState: this.readyState };
}
}