import { Log, LogLevel } from "./types.js";
import { createClient, Client } from 'graphql-ws';
export interface LogSubscriptionOptions {
onLog?: (log: Log) => void;
onError?: (error: Error) => void;
onComplete?: () => void;
traceId?: string;
includeDebug?: boolean;
}
export interface WebSocketSubscription {
unsubscribe: () => void;
}
export class WebSocketManager {
private wsEndpoint: string;
private apiKey: string;
private client: Client | null = null;
private subscriptions: Map<string, () => void> = new Map();
constructor(endpoint: string, apiKey: string) {
this.wsEndpoint = endpoint.replace('https:', 'wss:').replace('http:', 'ws:');
this.apiKey = apiKey;
}
private initClient(): Client {
if (!this.client) {
this.client = createClient({
url: this.wsEndpoint,
connectionParams: {
Authorization: `Bearer ${this.apiKey}`
},
retryAttempts: Infinity,
shouldRetry: () => true,
retryWait: (retries) => new Promise((resolve) =>
setTimeout(resolve, Math.min(retries * 1000, 5000))
),
keepAlive: 10000,
});
}
return this.client;
}
async subscribeToLogs(options: LogSubscriptionOptions = {}): Promise<WebSocketSubscription> {
const client = this.initClient();
const unsubscribe = client.subscribe(
{
query: `
subscription OnNewLog {
logs {
id
message
level
timestamp
traceId
}
}
`
},
{
next: (data: any) => {
if (data.data?.logs) {
const log: Log = {
...data.data.logs,
timestamp: new Date(data.data.logs.timestamp)
};
if (!options.traceId || log.traceId === options.traceId) {
if (options.includeDebug || log.level !== LogLevel.DEBUG) {
options.onLog?.(log);
}
}
}
},
error: (error: any) => {
options.onError?.(error);
},
complete: () => {
options.onComplete?.();
}
}
);
const subscriptionId = Math.random().toString(36).substring(2, 15);
this.subscriptions.set(subscriptionId, unsubscribe);
return {
unsubscribe: () => {
const unsub = this.subscriptions.get(subscriptionId);
if (unsub) {
unsub();
this.subscriptions.delete(subscriptionId);
}
}
};
}
async disconnect(): Promise<void> {
for (const [subscriptionId, unsubscribe] of this.subscriptions) {
unsubscribe();
}
this.subscriptions.clear();
if (this.client) {
this.client.dispose();
this.client = null;
}
}
}