.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { ChannelId, ServerId, UserId } from '~/lib/api/types';
|
||||
import { createPrefixedLogger } from '~/lib/utils';
|
||||
import type { ChannelId, ServerId, UserId } from "~/lib/api/types";
|
||||
import { createPrefixedLogger } from "~/lib/utils";
|
||||
import {
|
||||
type ClientMessage,
|
||||
ClientMessageType,
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
type EventData,
|
||||
EventType,
|
||||
type ServerMessage,
|
||||
ServerMessageType
|
||||
} from './types';
|
||||
ServerMessageType,
|
||||
} from "./types";
|
||||
|
||||
export type GatewayEvents = {
|
||||
[K in EventType]: (data: Extract<EventData, { type: K }>['data']) => void;
|
||||
[K in EventType]: (data: Extract<EventData, { type: K }>["data"]) => void;
|
||||
};
|
||||
|
||||
export type ControlEvents = {
|
||||
@@ -48,21 +48,21 @@ export class GatewayClient {
|
||||
this.options = {
|
||||
reconnect: options.reconnect ?? true,
|
||||
reconnectDelay: options.reconnectDelay ?? 5000,
|
||||
maxReconnectAttempts: options.maxReconnectAttempts ?? 10
|
||||
maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
|
||||
};
|
||||
}
|
||||
|
||||
// Public methods
|
||||
public connect(token: string): void {
|
||||
logger.log('Connecting to %s', this.url);
|
||||
logger.log("Connecting to %s", this.url);
|
||||
|
||||
if (this.connectionLock) {
|
||||
logger.warn('Connection already in progress');
|
||||
logger.warn("Connection already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.token === token) {
|
||||
logger.warn('Token is the same as the current token');
|
||||
logger.warn("Token is the same as the current token");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class GatewayClient {
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
logger.log('Disconnecting');
|
||||
logger.log("Disconnecting");
|
||||
|
||||
this.closeInitiatedByClient = true;
|
||||
this.cleanupSocket();
|
||||
@@ -91,18 +91,21 @@ export class GatewayClient {
|
||||
public updateVoiceState(serverId: ServerId, channelId: ChannelId): void {
|
||||
this.sendMessage({
|
||||
type: ClientMessageType.VOICE_STATE_UPDATE,
|
||||
data: { serverId, channelId }
|
||||
data: { serverId, channelId },
|
||||
});
|
||||
}
|
||||
|
||||
public requestVoiceStates(serverId: ServerId): void {
|
||||
this.sendMessage({
|
||||
type: ClientMessageType.REQUEST_VOICE_STATES,
|
||||
data: { serverId }
|
||||
data: { serverId },
|
||||
});
|
||||
}
|
||||
|
||||
public onEvent<K extends keyof GatewayEvents>(event: K | string, handler: GatewayEvents[K]): void {
|
||||
public onEvent<K extends keyof GatewayEvents>(
|
||||
event: K | string,
|
||||
handler: GatewayEvents[K],
|
||||
): void {
|
||||
this.serverEventHandlers[event as K] = handler;
|
||||
}
|
||||
|
||||
@@ -110,7 +113,10 @@ export class GatewayClient {
|
||||
delete this.serverEventHandlers[event];
|
||||
}
|
||||
|
||||
public onControl<K extends keyof ControlEvents>(event: K, handler: ControlEvents[K]): void {
|
||||
public onControl<K extends keyof ControlEvents>(
|
||||
event: K,
|
||||
handler: ControlEvents[K],
|
||||
): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
|
||||
@@ -142,7 +148,7 @@ export class GatewayClient {
|
||||
this.socket.onerror = this.onSocketError.bind(this);
|
||||
this.socket.onclose = this.onSocketClose.bind(this);
|
||||
} catch (error) {
|
||||
this.emitError(new Error('Failed to create WebSocket connection'));
|
||||
this.emitError(new Error("Failed to create WebSocket connection"));
|
||||
this.setState(ConnectionState.ERROR);
|
||||
}
|
||||
}
|
||||
@@ -150,16 +156,16 @@ export class GatewayClient {
|
||||
private onSocketOpen(): void {
|
||||
this.connectionLock = false;
|
||||
|
||||
logger.log('Socket opened');
|
||||
logger.log("Socket opened");
|
||||
this.setState(ConnectionState.AUTHENTICATING);
|
||||
|
||||
if (this.token) {
|
||||
this.sendMessage({
|
||||
type: ClientMessageType.AUTHENTICATE,
|
||||
data: { token: this.token }
|
||||
data: { token: this.token },
|
||||
});
|
||||
} else {
|
||||
this.emitError(new Error('No authentication token provided'));
|
||||
this.emitError(new Error("No authentication token provided"));
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -169,19 +175,23 @@ export class GatewayClient {
|
||||
const message = JSON.parse(event.data) as ServerMessage;
|
||||
this.handleServerMessage(message);
|
||||
} catch (error) {
|
||||
this.emitError(new Error('Failed to parse WebSocket message', { cause: error }));
|
||||
this.emitError(
|
||||
new Error("Failed to parse WebSocket message", {
|
||||
cause: error,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketError(event: Event): void {
|
||||
this.connectionLock = false;
|
||||
logger.log('Socket error: %s', event);
|
||||
logger.log("Socket error: %s", event);
|
||||
|
||||
this.emitError(new Error('WebSocket error occurred'));
|
||||
this.emitError(new Error("WebSocket error occurred"));
|
||||
}
|
||||
|
||||
private onSocketClose(event: CloseEvent): void {
|
||||
logger.log('Socket closed: %s', event);
|
||||
logger.log("Socket closed: %s", event);
|
||||
|
||||
this.connectionLock = false;
|
||||
|
||||
@@ -190,7 +200,12 @@ export class GatewayClient {
|
||||
!this.closeInitiatedByClient &&
|
||||
this.reconnectAttempts < this.options.maxReconnectAttempts
|
||||
) {
|
||||
logger.log('Reconnecting in %d seconds (%d/%d)', this.options.reconnectDelay / 1000, this.reconnectAttempts + 1, this.options.maxReconnectAttempts);
|
||||
logger.log(
|
||||
"Reconnecting in %d seconds (%d/%d)",
|
||||
this.options.reconnectDelay / 1000,
|
||||
this.reconnectAttempts + 1,
|
||||
this.options.maxReconnectAttempts,
|
||||
);
|
||||
this.reconnectAttempts++;
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
@@ -204,23 +219,30 @@ export class GatewayClient {
|
||||
}
|
||||
|
||||
private handleServerMessage(message: ServerMessage): void {
|
||||
logger.log('Received message: ', message);
|
||||
logger.log("Received message: ", message);
|
||||
|
||||
switch (message.type) {
|
||||
case ServerMessageType.AUTHENTICATE_ACCEPTED:
|
||||
this.userId = message.data.userId;
|
||||
this.sessionKey = message.data.sessionKey;
|
||||
this.setState(ConnectionState.CONNECTED);
|
||||
this.emitControl('authenticated', message.data.userId, message.data.sessionKey);
|
||||
this.emitControl(
|
||||
"authenticated",
|
||||
message.data.userId,
|
||||
message.data.sessionKey,
|
||||
);
|
||||
break;
|
||||
|
||||
case ServerMessageType.AUTHENTICATE_DENIED:
|
||||
this.emitError(new Error('Authentication denied'));
|
||||
this.emitError(new Error("Authentication denied"));
|
||||
this.disconnect();
|
||||
break;
|
||||
|
||||
case ServerMessageType.ERROR:
|
||||
this.emitError(new Error(`Server error: ${message.data.code}`), message.data.code);
|
||||
this.emitError(
|
||||
new Error(`Server error: ${message.data.code}`),
|
||||
message.data.code,
|
||||
);
|
||||
break;
|
||||
|
||||
case ServerMessageType.EVENT:
|
||||
@@ -228,7 +250,7 @@ export class GatewayClient {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Unhandled server message type:', message);
|
||||
console.warn("Unhandled server message type:", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,39 +259,47 @@ export class GatewayClient {
|
||||
}
|
||||
|
||||
private sendMessage(message: ClientMessage): void {
|
||||
logger.log('Sending message: %o', message);
|
||||
logger.log("Sending message: %o", message);
|
||||
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(JSON.stringify(message));
|
||||
} else {
|
||||
this.emitError(new Error('Cannot send message: socket not connected'));
|
||||
this.emitError(
|
||||
new Error("Cannot send message: socket not connected"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private setState(state: ConnectionState): void {
|
||||
if (this.state !== state) {
|
||||
logger.log('State changed to %s', state);
|
||||
logger.log("State changed to %s", state);
|
||||
|
||||
this.state = state;
|
||||
this.emitControl('stateChange', state);
|
||||
this.emitControl("stateChange", state);
|
||||
}
|
||||
}
|
||||
|
||||
private emitError(error: Error, code?: ErrorCode): void {
|
||||
logger.error('Error: %s', error, error.cause);
|
||||
logger.error("Error: %s", error, error.cause);
|
||||
|
||||
this.setState(ConnectionState.ERROR);
|
||||
this.emitControl('error', error, code);
|
||||
this.emitControl("error", error, code);
|
||||
}
|
||||
|
||||
private emitControl<K extends keyof ControlEvents>(event: K, ...args: Parameters<ControlEvents[K]>): void {
|
||||
private emitControl<K extends keyof ControlEvents>(
|
||||
event: K,
|
||||
...args: Parameters<ControlEvents[K]>
|
||||
): void {
|
||||
const handler = this.eventHandlers[event];
|
||||
if (handler) {
|
||||
(handler as Function)(...args);
|
||||
}
|
||||
}
|
||||
|
||||
private emitEvent<K extends keyof GatewayEvents>(event: K, ...args: Parameters<GatewayEvents[K]>): void {
|
||||
private emitEvent<K extends keyof GatewayEvents>(
|
||||
event: K,
|
||||
...args: Parameters<GatewayEvents[K]>
|
||||
): void {
|
||||
const handler = this.serverEventHandlers[event];
|
||||
if (handler) {
|
||||
(handler as Function)(...args);
|
||||
@@ -277,7 +307,7 @@ export class GatewayClient {
|
||||
}
|
||||
|
||||
private cleanupSocket(): void {
|
||||
logger.log('Cleaning up socket');
|
||||
logger.log("Cleaning up socket");
|
||||
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
@@ -292,8 +322,10 @@ export class GatewayClient {
|
||||
this.socket.onclose = null;
|
||||
|
||||
// Close the connection if it's still open
|
||||
if (this.socket.readyState === WebSocket.OPEN ||
|
||||
this.socket.readyState === WebSocket.CONNECTING) {
|
||||
if (
|
||||
this.socket.readyState === WebSocket.OPEN ||
|
||||
this.socket.readyState === WebSocket.CONNECTING
|
||||
) {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
@@ -305,4 +337,7 @@ export class GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
const logger = createPrefixedLogger('%cGateway WS%c:', ['color: red; font-weight: bold;', '']);
|
||||
const logger = createPrefixedLogger("%cGateway WS%c:", [
|
||||
"color: red; font-weight: bold;",
|
||||
"",
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user