bagman
Version:
js/ts client for Bagman
104 lines (89 loc) • 3.62 kB
text/typescript
import { io } from "socket.io-client"
import { Channel } from "./channel";
import { BagmanConfig, ClientSocket, SubscribeOptions } from "./types";
import { SecurityContext } from "./security-context";
export type GlobalEvent = "disconnect" | "disconnecting" | "connect" | "connect_error";
const GLOBAL_EVENT: GlobalEvent[] = ["connect", "connect_error", "disconnect", "disconnecting"];
export class Bagman {
/**
* Configuration for this Bagman Client
* @private
*/
private config: BagmanConfig;
/**
* The socket connection.
* @private
*/
private socket: ClientSocket;
/**
* The Holder for Authentication
* @private
*/
private securityCtx: SecurityContext;
/**
* Creates a new `Bagman` instance.
* @param {BagmanArgs} - The URL for the websocket server.
*/
constructor(config: BagmanConfig) {
this.config = config;
this.securityCtx = new SecurityContext(config);
this.socket = io(this.config.url || "http://localhost:8080", {
transports: ["websocket"],
autoConnect: false,
});
// connect to bagman server if authorized
if (this.securityCtx.isAuthorized()) {
this.socket.auth = {
'apiKey': this.securityCtx.token()
};
this.socket.connect();
}
}
async authorize() {
await this.securityCtx.authorize();
// connect to bagman server with token
if (this.securityCtx.isAuthorized()) {
// add api key headers to client
// after authorisation
this.socket.auth = {
'apiKey': this.securityCtx.token()
};
this.socket.connect();
}
}
/**
* Subscribes to a channel.
* @async
* @param {string} channel - The name of the channel to subscribe to.
* @param {object} [options] - Optional parameters for the subscription.
* @param {boolean} [options.withPresence=false] - Whether to subscribe to the channel with presence functionality.
* @throws {Error} Throws an error if the client is not authorized.
* @throws {Error} Throws an error if the subscription fails.
* @returns {Promise<Channel>} A Promise that resolves with a Channel instance representing the subscribed channel.
*/
async subscribe(channel: string, { withPresence }: SubscribeOptions = { withPresence: false }): Promise<Channel> {
if (!this.securityCtx.isAuthorized()) throw new Error("Unauthorized. Please authorize client and proceed again.");
const ack = await this.socket.emitWithAck('client:subscribe', { channel, withPresence });
if (ack.status === "error") {
throw new Error(ack.message);
}
return new Channel(this.socket, channel);
}
/**
* Listens to a specific global event.
* @template T The type of the arguments to be passed to the callback.
* @param {GlobalEvent} event - The global event to listen to.
* @param {(...args: T) => Promise<void> | void} cb - The callback to be called when the event is emitted.
* @throws {Error} When an invalid event is provided.
*/
listen<T extends [...any[]]>(event: GlobalEvent, cb: (...args: T) => Promise<void> | void) {
if (!GLOBAL_EVENT.includes(event)) throw new Error(`Invalid Global event: ${event}`);
this.socket.on(event, cb);
}
close() {
// defensive on not connected socket
if (this.socket.connected) {
this.socket.disconnect();
}
}
}