Adding web sockets support to Profectus

Web sockets are pretty straight-foward, and using something like socket.io is probably overkill for the client-side. Here’s a basic file you can just throw in the src/game folder (I named the file socket.ts) to use web sockets with proper typing:

import { createNanoEvents, EventsMap, Unsubscribe } from "nanoevents";
import { Ref, ref } from "vue";

export interface Socket<
    ServerToClientEvents extends EventsMap,
    ClientToServerEvents extends EventsMap
> {
    socket: WebSocket;
    connected: Ref<boolean>;
    // The rest are Emitter properties, but without the `this` type
    on<E extends keyof ServerToClientEvents>(event: E, cb: ServerToClientEvents[E]): Unsubscribe;
    events: Partial<{ [E in keyof ServerToClientEvents]: ServerToClientEvents[E][] }>;
    // Note that `emit` uses a different type param than the others
    emit<E extends keyof ClientToServerEvents>(
        event: E,
        ...args: Parameters<ClientToServerEvents[E]>
    ): void;
}

export function createSocket<
    ServerToClientEvents extends EventsMap,
    ClientToServerEvents extends EventsMap
>(address = "/ws"): Socket<ServerToClientEvents, ClientToServerEvents> {
    const socket = new WebSocket(address);
    const connected = ref<boolean>(false);
    const emitter = createNanoEvents<ServerToClientEvents>();

    socket.onopen = () => {
        connected.value = true;
    };

    socket.onmessage = event => {
        const [eventName, ...args] = JSON.parse(event.data);
        emitter.emit(eventName, ...args);
    };

    socket.onclose = () => {
        connected.value = false;
    };

    function emit<K extends keyof ClientToServerEvents>(
        event: K,
        ...args: Parameters<ClientToServerEvents[K]>
    ) {
        if (connected.value) {
            socket.send(JSON.stringify([event, ...args]));
        }
    }

    return {
        socket,
        connected,
        ...emitter,
        emit
    };
}

You could then use it like this:

const sockets = createSocket<
    {
        ping: (foo: string) => void;
    },
    {
        pong: (foo: string) => void;
    }
>();

sockets.on("ping", foo => {
    console.log("ping received", foo);
    sockets.emit("pong", foo);
});

The on function will have proper type inferencing for the defined events. The socket will also expose the WebSocket instance itself and a ref for whether or not you’re currently connected.