Newer
Older
import EventEmitter from "eventemitter3";

Grant
committed
Subscription,
} from "@sc07-canvas/lib/src/net";
import { toast } from "react-toastify";
import { handleAlert, handleDismiss } from "./alerts";
connected: () => void;
disconnected: () => void;
standing: (standing: IAccountStanding) => void;
canvas: (
start: [x: number, y: number],
end: [x: number, y: number],
pixels: string[]
) => void;
pixels: (data: { available: number }) => void;
pixelLastPlaced: (time: number) => void;
square: (
start: [x: number, y: number],
end: [x: number, y: number],
color: number
) => void;
undo: (
data: { available: false } | { available: true; expireAt: number }
) => void;

Grant
committed
heatmap: (heatmap: string) => void;
}
type SentEventValue<K extends keyof INetworkEvents> = EventEmitter.ArgumentMap<
Exclude<INetworkEvents, string | symbol>
>[Extract<K, keyof INetworkEvents>];
class Network extends EventEmitter<INetworkEvents> {
socket: Socket<ServerToClientEvents, ClientToServerEvents> = io("", {
autoConnect: false,
withCredentials: true,
reconnection: true,
});
[key in keyof INetworkEvents]?: SentEventValue<key>;
} = {};
private canvasChunks: {
start: [number, number];
end: [number, number];
pixels: string[];
}[] = [];
this.socket.on("connect", () => {
console.log("Connected to server");
toast.success("Connected to server");
this.emit("connected");
});
this.socket.on("connect_error", (err) => {
// TODO: proper error handling
console.error("Failed to connect to server", err);
toast.error("Failed to connect: " + (err.message || err.name));
});
this.socket.on("disconnect", (reason, desc) => {
console.log("Disconnected from server", reason, desc);
toast.warn("Disconnected from server");
this.emit("disconnected");
});
this.socket.io.on("reconnect", (attempt) => {
console.log("Reconnected to server on attempt " + attempt);
});
this.socket.io.on("reconnect_attempt", (attempt) => {
console.log("Reconnect attempt " + attempt);
});
this.socket.io.on("reconnect_error", (err) => {
console.log("Reconnect error", err);
});
this.socket.io.on("reconnect_failed", () => {
console.log("Reconnect failed");
});
this.socket.on("recaptcha", (site_key) => {
Recaptcha.load(site_key);
});
this.socket.on("recaptcha_challenge", (ack) => {
Recaptcha.executeChallenge(ack);
});
this.socket.on("user", (user) => {
this.socket.on("standing", (standing) => {
this.acceptState("standing", standing);
});
console.info("Server sent config", config);
if (config.version !== __COMMIT_HASH__) {
toast.info("Client version does not match server, reloading...");
console.warn("Client version does not match server, reloading...", {
clientVersion: __COMMIT_HASH__,
serverVersion: config.version,
});
window.location.reload();
}
this.socket.on("canvas", (start, end, pixels) => {
// this.acceptState("canvas", start, end, pixels);
this.emit("canvas", start, end, pixels);
this.canvasChunks.push({ start, end, pixels });
});
this.socket.on("clearCanvasChunks", () => {
this.canvasChunks = [];
this.acceptState("pixels", { available: count });
this.acceptState("pixelLastPlaced", time);
this.acceptState("online", count);
this.socket.on("pixel", (pixel) => {
this.emit("pixel", pixel);
});
this.socket.on("square", (...square) => {
this.emit("square", ...square);
});
this.socket.on("undo", (undo) => {
this.emit("undo", undo);
});

Grant
committed
this.socket.on("heatmap", (heatmap) => {
this.emit("heatmap", heatmap);
});
this.socket.on("alert", handleAlert);
this.socket.on("alert_dismiss", handleDismiss);

Grant
committed
}
subscribe(subscription: Subscription) {
this.socket.emit("subscribe", subscription);
}
unsubscribe(subscription: Subscription) {
this.socket.emit("unsubscribe", subscription);
/**
* Track events that we only care about the most recent version of
*
* Used by #waitFor
*
* @param event
* @param args
* @returns
*/
acceptState: typeof this.emit = (event, ...args) => {
this.stateEvents[event] = args;
/**
* Discard the existing state-like event, if it exists in cache
* @param ev
*/
clearPreviousState<Ev extends keyof INetworkEvents & (string | symbol)>(
ev: Ev
) {
delete this.stateEvents[ev];
getCanvasChunks() {
return this.canvasChunks;
}
/**
* Wait for event, either being already sent, or new one
*
* Used for state-like events
*
* @param ev
* @returns
*/
waitForState<Ev extends keyof INetworkEvents & (string | symbol)>(
ev: Ev
): Promise<SentEventValue<Ev>> {
return new Promise((res) => {
if (this.stateEvents[ev]) return res(this.stateEvents[ev]!);
this.once(ev, (...data) => {
res(data);
});
});
}
/**
* Get current value of state event
* @param event
* @returns
*/
getState<Ev extends keyof INetworkEvents>(
event: Ev
): SentEventValue<Ev> | undefined {
return this.stateEvents[event];
}