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) } } }