156 lines
3.7 KiB
JavaScript
156 lines
3.7 KiB
JavaScript
const DEFAULT_OPTIONS = {
|
|
scheme: 'ws',
|
|
host: '127.0.0.1',
|
|
port: 8080,
|
|
key: 'game-local-key',
|
|
client: 'zombie-client',
|
|
version: '1.0.0',
|
|
};
|
|
|
|
export class SocketClient {
|
|
constructor(options = {}) {
|
|
this.opts = { ...DEFAULT_OPTIONS, ...options };
|
|
this.socket = null;
|
|
this.socketId = null;
|
|
this.connected = false;
|
|
this.closedByUser = false;
|
|
this.retries = 0;
|
|
this.handlers = new Map();
|
|
this.queue = [];
|
|
this.onConnectionChange = null;
|
|
}
|
|
|
|
get url() {
|
|
const { scheme, host, port, key, client, version } = this.opts;
|
|
return `${scheme}://${host}:${port}/app/${key}?protocol=7&client=${client}&version=${version}&flash=false`;
|
|
}
|
|
|
|
_connect() {
|
|
if (this.socket) return;
|
|
this.closedByUser = false;
|
|
const socket = new WebSocket(this.url);
|
|
this.socket = socket;
|
|
|
|
socket.onopen = () => {
|
|
this.retries = 0;
|
|
};
|
|
|
|
socket.onmessage = (event) => {
|
|
let msg;
|
|
try {
|
|
msg = JSON.parse(event.data);
|
|
} catch {
|
|
return;
|
|
}
|
|
const rawBytes = typeof event.data === 'string' ? new Blob([event.data]).size : 0;
|
|
this._handle(msg, rawBytes);
|
|
};
|
|
|
|
socket.onclose = () => {
|
|
this.socket = null;
|
|
if (this.socketId) {
|
|
this.connected = false;
|
|
this.socketId = null;
|
|
this.onConnectionChange?.(false);
|
|
}
|
|
if (this.closedByUser) return;
|
|
if (!this.handlers.size) return;
|
|
const delay = Math.min(5000, 300 * 2 ** this.retries);
|
|
this.retries += 1;
|
|
setTimeout(() => this._connect(), delay);
|
|
};
|
|
|
|
socket.onerror = () => {};
|
|
}
|
|
|
|
connect() {
|
|
if (this.socket) return;
|
|
this._connect();
|
|
}
|
|
|
|
close() {
|
|
this.closedByUser = true;
|
|
this.handlers.clear();
|
|
this.socket?.close();
|
|
this.socket = null;
|
|
this.connected = false;
|
|
this.socketId = null;
|
|
}
|
|
|
|
_handle(msg, rawBytes = 0) {
|
|
const { event, channel } = msg;
|
|
|
|
if (event === 'pusher:connection_established') {
|
|
try {
|
|
this.socketId = JSON.parse(msg.data).socket_id;
|
|
} catch {
|
|
this.socketId = null;
|
|
}
|
|
this.connected = true;
|
|
this.onConnectionChange?.(true);
|
|
for (const channel of this.handlers.keys()) {
|
|
this._sendSubscribe(channel);
|
|
}
|
|
this.queue = [];
|
|
return;
|
|
}
|
|
|
|
if (event === 'pusher_internal:subscription_succeeded') {
|
|
if (channel) this.handlers.get(channel)?.onSubscribed?.();
|
|
return;
|
|
}
|
|
|
|
if (event === 'pusher:ping') {
|
|
this.send({ event: 'pusher:pong', data: {} });
|
|
return;
|
|
}
|
|
|
|
if (event === 'pusher:pong') {
|
|
return;
|
|
}
|
|
|
|
if (channel && this.handlers.has(channel)) {
|
|
let payload = msg.data;
|
|
if (typeof payload === 'string') {
|
|
try {
|
|
payload = JSON.parse(payload);
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
this.handlers.get(channel)?.onEvent?.(event, payload, rawBytes);
|
|
}
|
|
}
|
|
|
|
_sendSubscribe(channel) {
|
|
this.send({ event: 'pusher:subscribe', data: { channel } });
|
|
}
|
|
|
|
subscribe(channel, { onEvent = null, onSubscribed = null } = {}) {
|
|
this.handlers.set(channel, { onEvent, onSubscribed });
|
|
if (this.connected) {
|
|
this._sendSubscribe(channel);
|
|
} else {
|
|
this.queue.push({ channel });
|
|
}
|
|
}
|
|
|
|
unsubscribe(channel) {
|
|
this.handlers.delete(channel);
|
|
this.queue = this.queue.filter((entry) => entry.channel !== channel);
|
|
if (this.connected) {
|
|
this.send({ event: 'pusher:unsubscribe', data: { channel } });
|
|
}
|
|
}
|
|
|
|
trigger(event, channel, data) {
|
|
if (!this.connected) return;
|
|
this.send({ event, channel, data });
|
|
}
|
|
|
|
send(payload) {
|
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
this.socket.send(JSON.stringify(payload));
|
|
}
|
|
}
|
|
} |