import type { IUserAttributes, ISessionAttributes, IResourceAttributes, } from '@multiplayer-app/session-recorder-common'; import { type ApiServiceConfig } from '../types'; import type { eventWithTime } from '@rrweb/types'; export interface StartSessionRequest { name?: string; stoppedAt?: string | number; sessionAttributes?: ISessionAttributes; resourceAttributes?: IResourceAttributes; userAttributes?: IUserAttributes; debugSessionData?: Record; tags?: { key?: string; value: string }[]; } export interface StopSessionRequest { sessionAttributes?: ISessionAttributes; stoppedAt: string | number; } export interface CheckRemoteSessionRequest { sessionAttributes?: ISessionAttributes; resourceAttributes?: IResourceAttributes; userAttributes?: IUserAttributes; } export interface CreateErrorSpanSessionRequest { span: any; } export class ApiService { private config?: ApiServiceConfig; private baseUrl: string = 'https://api.multiplayer.app'; constructor() { this.config = { apiKey: '', apiBaseUrl: '', exporterEndpoint: '', }; } init(config: ApiServiceConfig): void { this.config = { ...this.config, ...config, }; if (config.apiBaseUrl) { this.baseUrl = config.apiBaseUrl; } } /** * Update the API service configuration * @param config - Partial configuration to update */ public updateConfigs(config: Partial) { if (this.config) { this.config = { ...this.config, ...config }; } } /** * Set the API key * @param apiKey - The API key to set */ setApiKey(apiKey: string): void { if (this.config) { this.config.apiKey = apiKey; } } /** * Create a new error span session * @param request - Session create error span request data * @param signal - Optional AbortSignal for request cancellation */ createErrorSession( request: CreateErrorSpanSessionRequest, signal?: AbortSignal ): Promise { return this.makeRequest( '/debug-sessions/error-span/start', 'POST', request, signal ); } updateSessionAttributes( sessionId: string, requestBody: { name?: string; startedAt?: string; stoppedAt?: string; userAttributes?: IUserAttributes; sessionAttributes?: ISessionAttributes; resourceAttributes?: IResourceAttributes; }, signal?: AbortSignal ): Promise { return this.makeRequest( `/debug-sessions/${sessionId}`, 'PATCH', requestBody, signal ); } /** * Export events to the session debugger API * @param sessionId - ID of the session to export events * @param requestBody - Request body containing events * @param signal - Optional AbortSignal for request cancellation */ exportEvents( sessionId: string, requestBody: { events: eventWithTime[] }, signal?: AbortSignal ): Promise { return this.makeRequest( `/debug-sessions/${sessionId}/rrweb-events`, 'POST', requestBody, signal ); } /** * Start a new debug session * @param request - Session start request data * @param signal - Optional AbortSignal for request cancellation */ startSession( request: StartSessionRequest, signal?: AbortSignal ): Promise { return this.makeRequest('/debug-sessions/start', 'POST', request, signal); } /** * Stop an active debug session * @param sessionId - ID of the session to stop * @param request - Session stop request data */ stopSession(sessionId: string, request: StopSessionRequest): Promise { return this.makeRequest( `/debug-sessions/${sessionId}/stop`, 'PATCH', request ); } /** * Cancel an active debug session * @param sessionId - ID of the session to cancel */ cancelSession(sessionId: string): Promise { return this.makeRequest(`/debug-sessions/${sessionId}/cancel`, 'DELETE'); } /** * Start a new continuous debug session * @param request - Session start request data * @param signal - Optional AbortSignal for request cancellation */ startContinuousDebugSession( request: StartSessionRequest, signal?: AbortSignal ): Promise { return this.makeRequest( '/continuous-debug-sessions/start', 'POST', request, signal ); } /** * Save a continuous debug session * @param sessionId - ID of the session to save * @param request - Session save request data * @param signal - Optional AbortSignal for request cancellation */ saveContinuousDebugSession( sessionId: string, request: StartSessionRequest, signal?: AbortSignal ): Promise { return this.makeRequest( `/continuous-debug-sessions/${sessionId}/save`, 'POST', request, signal ); } /** * Stop an active continuous debug session * @param sessionId - ID of the session to stop */ stopContinuousDebugSession(sessionId: string): Promise { return this.makeRequest( `/continuous-debug-sessions/${sessionId}/cancel`, 'DELETE' ); } /** * Check debug session should be started remotely */ checkRemoteSession( requestBody: CheckRemoteSessionRequest, signal?: AbortSignal ): Promise<{ state: 'START' | 'STOP' }> { return this.makeRequest( '/remote-debug-session/check', 'POST', requestBody, signal ); } /** * Make a request to the session debugger API * @param path - API endpoint path (relative to the base URL) * @param method - HTTP method (GET, POST, PATCH, etc.) * @param body - request payload * @param signal - AbortSignal to set request's signal */ private async makeRequest( path: string, method: string, body?: any, signal?: AbortSignal ): Promise { const url = `${this.baseUrl}/v0/radar${path}`; const params = { method, body: body ? JSON.stringify(body) : null, headers: { 'Content-Type': 'application/json', ...(this.config?.apiKey && { 'X-Api-Key': this.config.apiKey }), }, }; try { const response = await fetch(url, { ...params, signal, }); if (!response.ok) { throw new Error('Network response was not ok: ' + response.statusText); } if (response.status === 204) { return null; } return await response.json(); } catch (error: any) { if (error?.name === 'AbortError') { throw new Error('Request aborted'); } throw new Error('Error making request: ' + error.message); } } }