hexabot/frontend/src/websocket/SocketIoClient.ts

160 lines
5.0 KiB
TypeScript
Raw Normal View History

2024-09-10 09:50:11 +00:00
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { io, Socket, ManagerOptions, SocketOptions } from "socket.io-client";
import { IOIncomingMessage, IOOutgoingMessage } from "./types/io-message";
type SocketIoClientConfig = Partial<ManagerOptions & SocketOptions>;
export class SocketIoClient {
/**
* Default configuration for the socket client
* @static
*/
static defaultConfig: SocketIoClientConfig = {
// Socket options
ackTimeout: 1000,
// auth: undefined,
retries: 3,
// Manager options
autoConnect: true,
// parser: undefined,
// randomizationFactor:0.5,
reconnection: true,
reconnectionAttempts: 100,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000,
// Low Level Options
addTrailingSlash: true, // eg: https://domain.path/ => https://domain.path/
// autoUnref:false, // firefox only option
// path: "/socket.io", // This is the socket path in the server, leave it as default unless changed manually in server
transports: ["websocket", "polling"], // ["websocket","polling", "websocket"]
upgrade: true,
withCredentials: true,
};
private socket: Socket;
private config: SocketIoClientConfig;
private initialized: boolean = false;
2024-09-20 15:21:30 +00:00
constructor(apiUrl: string, socketConfig?: SocketIoClientConfig) {
2024-09-10 09:50:11 +00:00
this.config = {
...SocketIoClient.defaultConfig,
...socketConfig,
autoConnect: false,
};
2024-09-20 15:21:30 +00:00
const url = new URL(apiUrl);
2024-09-10 09:50:11 +00:00
this.socket = io(url.origin, this.config);
}
/**
* Initializes the socket client and sets up event handlers.
* @param handlers Event handlers for connection, disconnection, and connection errors
*/
public init({
onConnect,
onDisconnect,
onConnectError,
}: {
onConnect?: () => void;
onDisconnect?: (reason: string, details: any) => void;
onConnectError?: (error: Error) => void;
}) {
if (!this.initialized) this.socket.connect();
onConnect && this.uniqueOn("connect", onConnect);
onDisconnect && this.uniqueOn("disconnect", onDisconnect);
onConnectError && this.uniqueOn("connect_error", onConnectError);
this.initialized = true;
}
/**
* Registers an event handler for the specified event and removes any existing handlers.
* @param event The event name
* @param callback The callback function to handle the event
*/
private uniqueOn(event: string, callback: (...args: any) => void) {
this.socket.off(event);
this.socket.on(event, callback);
}
/**
* Disconnects the socket client.
*/
public disconnect() {
this.socket.disconnect();
}
/**
* Registers an event handler for the specified event.
* @param event The event name
* @param callback The callback function to handle the event
*/
public on<T>(event: string, callback: (data: T) => void) {
this.socket.on(event, callback);
}
/**
* Removes an event handler for the specified event.
* @param event The event name
* @param callback The callback function to remove
*/
public off(event: string, callback: (...args: any) => void) {
this.socket.off(event, callback);
}
/**
* Sends a request to the server and waits for an acknowledgment.
* @param options The request options including URL and method
* @returns The response from the server
* @throws Error if the request fails
*/
public async request<T>(
options: Pick<IOOutgoingMessage, "url" | "method"> &
Partial<IOOutgoingMessage>,
): Promise<IOIncomingMessage<T>> {
const response: IOIncomingMessage = await this.socket.emitWithAck(
options.method,
options,
);
if (response.statusCode >= 200 && response.statusCode < 300) {
return response;
}
throw new Error(
`Request failed with status code ${response.statusCode}: ${JSON.stringify(
response.body,
)}`,
);
}
/**
* Sends a GET request to the server.
* @param url The URL to send the request to
* @param options Optional request options
* @returns The response from the server
*/
public async get<T>(
url: string,
options?: Partial<Omit<IOOutgoingMessage, "url" | "method" | "data">>,
): Promise<IOIncomingMessage<T>> {
return this.request({
method: "get",
url,
...options,
});
}
}