UNPKG

@ipcom/asterisk-ari

Version:

JavaScript client for Asterisk REST Interface.

4 lines 219 kB
{ "version": 3, "sources": ["../../node_modules/exponential-backoff/src/options.ts", "../../node_modules/exponential-backoff/src/jitter/full/full.jitter.ts", "../../node_modules/exponential-backoff/src/jitter/no/no.jitter.ts", "../../node_modules/exponential-backoff/src/jitter/jitter.factory.ts", "../../node_modules/exponential-backoff/src/delay/delay.base.ts", "../../node_modules/exponential-backoff/src/delay/skip-first/skip-first.delay.ts", "../../node_modules/exponential-backoff/src/delay/always/always.delay.ts", "../../node_modules/exponential-backoff/src/delay/delay.factory.ts", "../../node_modules/exponential-backoff/src/backoff.ts", "../../src/index.ts", "../../src/ari-client/baseClient.ts", "../../src/ari-client/resources/applications.ts", "../../src/ari-client/resources/asterisk.ts", "../../src/ari-client/resources/bridges.ts", "../../src/ari-client/utils.ts", "../../src/ari-client/resources/channels.ts", "../../node_modules/uuid/dist/esm/stringify.js", "../../node_modules/uuid/dist/esm/rng.js", "../../node_modules/uuid/dist/esm/native.js", "../../node_modules/uuid/dist/esm/v4.js", "../../src/ari-client/resources/endpoints.ts", "../../src/ari-client/resources/playbacks.ts", "../../src/ari-client/resources/sounds.ts", "../../src/ari-client/websocketClient.ts", "../../src/ari-client/resources/recordings.ts", "../../src/ari-client/ariClient.ts"], "sourcesContent": [null, null, null, null, null, null, null, null, null, "/**\n * @file Main entry point for the Asterisk REST Interface (ARI) client package\n * @description This file exports all the necessary classes, types and interfaces for interacting with the ARI\n */\n\n/**\n * Main client class for interacting with Asterisk REST Interface\n * @packageDocumentation\n */\nexport { AriClient } from './ari-client/ariClient.js';\n\n/**\n * Resource Classes\n * These classes provide direct access to ARI resources\n */\nexport { Channels, ChannelInstance } from './ari-client/resources/channels.js';\n\nexport { Endpoints } from './ari-client/resources/endpoints.js';\nexport { Applications } from './ari-client/resources/applications.js';\nexport { Sounds } from './ari-client/resources/sounds.js';\nexport {\n Playbacks,\n PlaybackInstance,\n} from './ari-client/resources/playbacks.js';\nexport { Asterisk } from './ari-client/resources/asterisk.js';\nexport { Bridges, BridgeInstance } from './ari-client/resources/bridges.js';\n\n/**\n * Type Definitions\n * These types and interfaces define the shape of data structures used throughout the API\n */\n\n// Configuration Types\nexport type {\n AriClientConfig,\n AriApplication,\n} from './ari-client/interfaces/index.js';\n\n// Channel Related Types\nexport type {\n Channel,\n ChannelEvent,\n ChannelPlayback,\n ChannelVar,\n ChannelDialplan,\n OriginateRequest,\n RecordingOptions,\n SnoopOptions,\n ExternalMediaOptions,\n RTPStats,\n} from './ari-client/interfaces/index.js';\n\n// Playback Related Types\nexport type {\n Playback,\n PlaybackEvent,\n PlaybackOptions,\n PlaybackControlRequest,\n PlayMediaRequest,\n} from './ari-client/interfaces/index.js';\n\n// Bridge Related Types\nexport type {\n Bridge,\n BridgePlayback,\n CreateBridgeRequest,\n RemoveChannelRequest,\n AddChannelRequest,\n} from './ari-client/interfaces/index.js';\n\n// Endpoint Related Types\nexport type {\n Endpoint,\n EndpointDetails,\n} from './ari-client/interfaces/index.js';\n\n// Application Related Types\nexport type {\n Application,\n ApplicationDetails,\n} from './ari-client/interfaces/index.js';\n\n// Sound Related Types\nexport type { Sound, SoundListRequest } from './ari-client/interfaces/index.js';\n\n// Asterisk Related Types\nexport type {\n AsteriskInfo,\n Module,\n Logging,\n Variable,\n AsteriskPing,\n} from './ari-client/interfaces/index.js';\n\n// WebSocket Related Types\nexport type {\n WebSocketEvent,\n WebSocketEventType,\n // Eventos individuais\n ChannelDtmfReceived,\n ChannelDialplanEvent,\n ChannelVarset,\n StasisStart,\n PlaybackStarted,\n PlaybackContinuing,\n PlaybackFinished,\n BridgeCreated,\n BridgeDestroyed,\n ChannelCreated,\n ChannelDestroyed,\n ApplicationMoveFailed,\n RecordingStarted,\n RecordingFinished,\n RecordingFailed,\n DeviceStateChanged,\n BridgeMerged,\n BridgeBlindTransfer,\n BridgeAttendedTransfer,\n BridgeVideoSourceChanged,\n ChannelEnteredBridge,\n ChannelLeftBridge,\n ChannelStateChange,\n ChannelTalkingStarted,\n ChannelTalkingFinished,\n ChannelUnhold,\n ChannelHold,\n ContactStatusChange,\n EndpointStateChange,\n Dial,\n StasisEnd,\n TextMessageReceived,\n ChannelConnectedLine,\n ChannelHangupRequest,\n PeerStatusChange,\n} from './ari-client/interfaces/index.js';\n", "import axios, {\n type AxiosInstance,\n type AxiosRequestConfig,\n isAxiosError,\n} from 'axios';\n\n/**\n * Custom error class for HTTP-related errors\n */\nclass HTTPError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly method?: string,\n public readonly url?: string\n ) {\n super(message);\n this.name = 'HTTPError';\n }\n}\n\n/**\n * BaseClient handles HTTP communications with the ARI server.\n * Provides methods for making HTTP requests and manages authentication and error handling.\n */\nexport class BaseClient {\n private readonly client: AxiosInstance;\n\n /**\n * Creates a new BaseClient instance.\n *\n * @param {string} baseUrl - The base URL for the API\n * @param {string} username - Username for authentication\n * @param {string} password - Password for authentication\n * @param {boolean} [secure=false] - Whether to use secure connections (HTTPS/WSS)\n * @param {number} [timeout=5000] - Request timeout in milliseconds\n * @throws {Error} If the base URL format is invalid\n */\n constructor(\n private readonly baseUrl: string,\n private readonly username: string,\n private readonly password: string,\n private readonly secure: boolean = false,\n timeout = 5000\n ) {\n if (!/^https?:\\/\\/.+/.test(baseUrl)) {\n throw new Error(\n 'Invalid base URL. It must start with http:// or https://'\n );\n }\n\n this.client = axios.create({\n baseURL: baseUrl,\n auth: { username, password },\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n this.addInterceptors();\n }\n\n /**\n * Gets the base URL of the client.\n */\n public getBaseUrl(): string {\n return this.baseUrl;\n }\n\n /**\n * Gets the configured credentials including security settings.\n * Used by WebSocketClient to determine authentication method.\n */\n public getCredentials(): {\n baseUrl: string;\n username: string;\n password: string;\n secure: boolean;\n } {\n return {\n baseUrl: this.baseUrl,\n username: this.username,\n password: this.password,\n secure: this.secure,\n };\n }\n\n /**\n * Adds request and response interceptors to the Axios instance.\n */\n private addInterceptors(): void {\n this.client.interceptors.request.use(\n (config) => {\n return config;\n },\n (error: unknown) => {\n const message = this.getErrorMessage(error);\n console.error('[Request Error]', message);\n return Promise.reject(new HTTPError(message));\n }\n );\n\n this.client.interceptors.response.use(\n (response) => {\n return response;\n },\n (error: unknown) => {\n if (isAxiosError(error)) {\n const status = error.response?.status ?? 0;\n const method = error.config?.method?.toUpperCase() ?? 'UNKNOWN';\n const url = error.config?.url ?? 'unknown-url';\n const message =\n error.response?.data?.message || error.message || 'Unknown error';\n\n if (status === 404) {\n console.warn(`[404] Not Found: ${url}`);\n } else if (status >= 500) {\n console.error(`[${status}] Server Error: ${url}`);\n } else if (status > 0) {\n console.warn(`[${status}] ${method} ${url}: ${message}`);\n } else {\n console.error(`[Network] Request failed: ${message}`);\n }\n\n throw new HTTPError(message, status || undefined, method, url);\n }\n\n const message = this.getErrorMessage(error);\n console.error('[Unexpected Error]', message);\n throw new Error(message);\n }\n );\n }\n\n /**\n * Executes a GET request.\n *\n * @param path - API endpoint path\n * @param config - Optional Axios request configuration\n * @returns Promise with the response data\n */\n async get<T>(path: string, config?: AxiosRequestConfig): Promise<T> {\n try {\n const response = await this.client.get<T>(path, config);\n return response.data;\n } catch (error: unknown) {\n throw this.handleRequestError(error);\n }\n }\n\n /**\n * Executes a POST request.\n *\n * @param path - API endpoint path\n * @param data - Request payload\n * @param config - Optional Axios request configuration\n * @returns Promise with the response data\n */\n async post<T, D = unknown>(\n path: string,\n data?: D,\n config?: AxiosRequestConfig\n ): Promise<T> {\n try {\n const response = await this.client.post<T>(path, data, config);\n return response.data;\n } catch (error: unknown) {\n throw this.handleRequestError(error);\n }\n }\n\n /**\n * Executes a PUT request.\n *\n * @param path - API endpoint path\n * @param data - Request payload\n * @param config - Optional Axios request configuration\n * @returns Promise with the response data\n */\n async put<T, D = unknown>(\n path: string,\n data: D,\n config?: AxiosRequestConfig\n ): Promise<T> {\n try {\n const response = await this.client.put<T>(path, data, config);\n return response.data;\n } catch (error: unknown) {\n throw this.handleRequestError(error);\n }\n }\n\n /**\n * Executes a DELETE request.\n *\n * @param path - API endpoint path\n * @param config - Optional Axios request configuration\n * @returns Promise with the response data\n */\n async delete<T>(path: string, config?: AxiosRequestConfig): Promise<T> {\n try {\n const response = await this.client.delete<T>(path, config);\n return response.data;\n } catch (error: unknown) {\n throw this.handleRequestError(error);\n }\n }\n\n /**\n * Handles and formats error messages from various error types.\n */\n private getErrorMessage(error: unknown): string {\n if (isAxiosError(error)) {\n return error.response?.data?.message || error.message || 'HTTP Error';\n }\n if (error instanceof Error) {\n return error.message;\n }\n return 'An unknown error occurred';\n }\n\n /**\n * Handles errors from HTTP requests.\n */\n private handleRequestError(error: unknown): never {\n const message = this.getErrorMessage(error);\n if (isAxiosError(error)) {\n throw new HTTPError(\n message,\n error.response?.status,\n error.config?.method?.toUpperCase(),\n error.config?.url\n );\n }\n throw new Error(message);\n }\n\n /**\n * Sets custom headers for the client instance.\n */\n setHeaders(headers: Record<string, string>): void {\n this.client.defaults.headers.common = {\n ...this.client.defaults.headers.common,\n ...headers,\n };\n }\n\n /**\n * Gets the current request timeout setting.\n */\n getTimeout(): number {\n return this.client.defaults.timeout || 5000;\n }\n\n /**\n * Updates the request timeout setting.\n */\n setTimeout(timeout: number): void {\n this.client.defaults.timeout = timeout;\n }\n}\n", "import type { BaseClient } from '../baseClient.js';\nimport type {\n Application,\n ApplicationDetails,\n} from '../interfaces/applications.types.js';\n\nexport interface ApplicationMessage {\n event: string;\n data?: Record<string, any>;\n}\n\nexport class Applications {\n constructor(private client: BaseClient) {}\n\n /**\n * Lists all applications.\n *\n * @returns A promise that resolves to an array of Application objects.\n * @throws {Error} If the API response is not an array.\n */\n async list(): Promise<Application[]> {\n const applications = await this.client.get<unknown>('/applications');\n\n if (!Array.isArray(applications)) {\n throw new Error('Resposta da API /applications n\u00E3o \u00E9 um array.');\n }\n\n return applications as Application[];\n }\n\n /**\n * Retrieves details of a specific application.\n *\n * @param appName - The name of the application to retrieve details for.\n * @returns A promise that resolves to an ApplicationDetails object.\n * @throws {Error} If there's an error fetching the application details.\n */\n async getDetails(appName: string): Promise<ApplicationDetails> {\n try {\n return await this.client.get<ApplicationDetails>(\n `/applications/${appName}`\n );\n } catch (error) {\n console.error(`Erro ao obter detalhes do aplicativo ${appName}:`, error);\n throw error;\n }\n }\n\n /**\n * Sends a message to a specific application.\n *\n * @param appName - The name of the application to send the message to.\n * @param body - The message to be sent, containing an event and optional data.\n * @returns A promise that resolves when the message is successfully sent.\n */\n async sendMessage(appName: string, body: ApplicationMessage): Promise<void> {\n await this.client.post<void>(`/applications/${appName}/messages`, body);\n }\n}\n", "import type { BaseClient } from '../baseClient.js';\nimport type {\n AsteriskInfo,\n AsteriskPing,\n Logging,\n Module,\n Variable,\n} from '../interfaces';\n\nfunction toQueryParams<T>(options: T): string {\n return new URLSearchParams(\n Object.entries(options as Record<string, string>)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [key, String(value)])\n ).toString();\n}\n\nexport class Asterisk {\n constructor(private client: BaseClient) {}\n\n async ping(): Promise<AsteriskPing> {\n return this.client.get<AsteriskPing>('/asterisk/ping');\n }\n\n /**\n * Retrieves information about the Asterisk server.\n */\n async get(): Promise<AsteriskInfo> {\n return this.client.get<AsteriskInfo>('/asterisk/info');\n }\n\n /**\n * Lists all loaded modules in the Asterisk server.\n */\n async list(): Promise<Module[]> {\n return this.client.get<Module[]>('/asterisk/modules');\n }\n\n /**\n * Manages a specific module in the Asterisk server.\n *\n * @param moduleName - The name of the module to manage.\n * @param action - The action to perform on the module: \"load\", \"unload\", or \"reload\".\n * @returns A promise that resolves when the action is completed successfully.\n * @throws {Error} Throws an error if the HTTP method or action is invalid.\n */\n async manage(\n moduleName: string,\n action: 'load' | 'unload' | 'reload'\n ): Promise<void> {\n const url = `/asterisk/modules/${moduleName}`;\n switch (action) {\n case 'load':\n await this.client.post<void>(`${url}?action=load`);\n break;\n case 'unload':\n await this.client.delete<void>(url);\n break;\n case 'reload':\n await this.client.put<void>(url, {});\n break;\n default:\n throw new Error(`A\u00E7\u00E3o inv\u00E1lida: ${action}`);\n }\n }\n\n /**\n * Retrieves all configured logging channels.\n */\n async listLoggingChannels(): Promise<Logging[]> {\n return this.client.get<Logging[]>('/asterisk/logging');\n }\n\n /**\n * Adds or removes a log channel in the Asterisk server.\n */\n async manageLogChannel(\n logChannelName: string,\n action: 'add' | 'remove',\n configuration?: { type?: string; configuration?: string }\n ): Promise<void> {\n const queryParams = toQueryParams(configuration || {});\n return this.client.post<void>(\n `/asterisk/logging/${logChannelName}?action=${encodeURIComponent(action)}&${queryParams}`\n );\n }\n\n /**\n * Retrieves the value of a global variable.\n */\n async getGlobalVariable(variableName: string): Promise<Variable> {\n return this.client.get<Variable>(\n `/asterisk/variables?variable=${encodeURIComponent(variableName)}`\n );\n }\n\n /**\n * Sets a global variable.\n */\n async setGlobalVariable(variableName: string, value: string): Promise<void> {\n return this.client.post<void>(\n `/asterisk/variables?variable=${encodeURIComponent(variableName)}&value=${encodeURIComponent(value)}`\n );\n }\n}\n", "import { EventEmitter } from 'events';\nimport { isAxiosError } from 'axios';\nimport type { AriClient } from '../ariClient';\nimport type { BaseClient } from '../baseClient.js';\nimport type {\n AddChannelRequest,\n Bridge,\n BridgePlayback,\n CreateBridgeRequest,\n PlayMediaRequest,\n RemoveChannelRequest,\n WebSocketEvent,\n} from '../interfaces';\nimport { bridgeEvents } from '../interfaces/events.types';\nimport { toQueryParams } from '../utils';\n\n/**\n * Extracts an error message from various error types.\n *\n * This utility function attempts to retrieve the most relevant error message\n * from different error objects, including Axios errors and standard Error instances.\n *\n * @param error - The error object to extract the message from.\n * Can be of type AxiosError, Error, or any unknown type.\n *\n * @returns A string containing the extracted error message.\n * For Axios errors, it prioritizes the response data message,\n * then falls back to the error message, and finally a default Axios error message.\n * For standard Error instances, it returns the error message.\n * For unknown error types, it returns a generic error message.\n */\nconst getErrorMessage = (error: unknown): string => {\n if (isAxiosError(error)) {\n return (\n error.response?.data?.message ||\n error.message ||\n 'Um erro do axios ocorreu'\n );\n }\n if (error instanceof Error) {\n return error.message;\n }\n return 'Um erro desconhecido ocorreu';\n};\n\n/**\n * Represents an instance of a Bridge that provides methods to control\n * bridges, manage event listeners, and manipulate its state.\n */\nexport class BridgeInstance {\n private readonly eventEmitter = new EventEmitter();\n private readonly listenersMap = new Map<\n string,\n ((...args: any[]) => void)[]\n >(); // \uD83D\uDD39 Guarda listeners para remo\u00E7\u00E3o posterior\n private bridgeData: Bridge | null = null;\n public readonly id: string;\n\n /**\n * Creates a new BridgeInstance.\n *\n * @param client - The AriClient instance for making API calls.\n * @param baseClient - The BaseClient instance for making HTTP requests.\n * @param bridgeId - Optional. The ID of the bridge. If not provided, a new ID will be generated.\n */\n constructor(\n private readonly client: AriClient,\n private readonly baseClient: BaseClient,\n bridgeId?: string\n ) {\n this.id = bridgeId || `bridge-${Date.now()}`;\n }\n\n /**\n * Registers a listener for specific bridge events.\n *\n * @param event - The type of event to listen for.\n * @param listener - The callback function to be called when the event occurs.\n */\n /**\n * Registers a listener for specific bridge events.\n *\n * This method allows you to attach an event listener to the bridge instance for a specific event type.\n * When the specified event occurs, the provided listener function will be called with the event data.\n *\n * @template T - The specific type of WebSocketEvent to listen for.\n * It receives the event data as its parameter.\n * @returns {void}\n *\n * @example\n * bridge.on('BridgeCreated', (event) => {\n *\n * });\n * @param event\n * @param listener\n */\n on<T extends WebSocketEvent['type']>(\n event: T,\n listener: (data: Extract<WebSocketEvent, { type: T }>) => void\n ): void {\n if (!event) {\n throw new Error('Event type is required');\n }\n\n // \uD83D\uDD39 Verifica se o listener j\u00E1 est\u00E1 registrado\n const existingListeners = this.listenersMap.get(event) || [];\n if (existingListeners.includes(listener)) {\n console.warn(\n `Listener j\u00E1 registrado para evento ${event}, reutilizando.`\n );\n return;\n }\n\n const wrappedListener = (data: WebSocketEvent) => {\n if ('bridge' in data && data.bridge?.id === this.id) {\n listener(data as Extract<WebSocketEvent, { type: T }>);\n }\n };\n\n this.eventEmitter.on(event, wrappedListener);\n\n // \uD83D\uDD39 Armazena o listener para futura remo\u00E7\u00E3o\n if (!this.listenersMap.has(event)) {\n this.listenersMap.set(event, []);\n }\n this.listenersMap\n .get(event)!\n .push(wrappedListener as (...args: any[]) => void);\n }\n\n /**\n * Registers a one-time listener for specific bridge events.\n *\n * @param event - The type of event to listen for.\n * @param listener - The callback function to be called when the event occurs.\n */\n once<T extends WebSocketEvent['type']>(\n event: T,\n listener: (data: Extract<WebSocketEvent, { type: T }>) => void\n ): void {\n if (!event) {\n throw new Error('Event type is required');\n }\n\n const eventKey = `${event}-${this.id}`;\n\n // \uD83D\uDD39 Verifica se j\u00E1 existe um listener igual para evitar duplica\u00E7\u00E3o\n const existingListeners = this.listenersMap.get(eventKey) || [];\n if (existingListeners.includes(listener)) {\n console.warn(\n `One-time listener j\u00E1 registrado para evento ${eventKey}, reutilizando.`\n );\n return;\n }\n\n const wrappedListener = (data: WebSocketEvent) => {\n if ('bridge' in data && data.bridge?.id === this.id) {\n listener(data as Extract<WebSocketEvent, { type: T }>);\n\n // \uD83D\uDD39 Remove automaticamente o listener ap\u00F3s a primeira execu\u00E7\u00E3o\n this.off(event, wrappedListener);\n }\n };\n\n this.eventEmitter.once(event, wrappedListener);\n\n // \uD83D\uDD39 Armazena o listener para futura remo\u00E7\u00E3o\n if (!this.listenersMap.has(eventKey)) {\n this.listenersMap.set(eventKey, []);\n }\n this.listenersMap\n .get(eventKey)!\n .push(wrappedListener as (...args: any[]) => void);\n }\n\n /**\n * Removes event listener(s) from the bridge.\n *\n * @param event - The type of event to remove listeners for.\n * @param listener - Optional. The specific listener to remove. If not provided, all listeners for the event will be removed.\n */\n off<T extends WebSocketEvent['type']>(\n event: T,\n listener?: (data: Extract<WebSocketEvent, { type: T }>) => void\n ): void {\n if (!event) {\n throw new Error('Event type is required');\n }\n\n if (listener) {\n this.eventEmitter.off(event, listener);\n const storedListeners = this.listenersMap.get(event) || [];\n this.listenersMap.set(\n event,\n storedListeners.filter((l) => l !== listener)\n );\n } else {\n this.eventEmitter.removeAllListeners(event);\n this.listenersMap.delete(event);\n }\n }\n\n /**\n * Cleans up the BridgeInstance, resetting its state and clearing resources.\n */\n public cleanup(): void {\n // Limpar dados do bridge\n this.bridgeData = null;\n\n // Remover todos os listeners\n this.removeAllListeners();\n\n // Limpar o map de listeners\n this.listenersMap.clear();\n\n console.log(`Bridge instance ${this.id} cleaned up`);\n }\n\n /**\n * Emits an event if it corresponds to the current bridge.\n *\n * @param event - The WebSocketEvent to emit.\n */\n emitEvent(event: WebSocketEvent): void {\n if (!event) {\n console.warn('Invalid event received');\n return;\n }\n\n if ('bridge' in event && event.bridge?.id === this.id) {\n this.eventEmitter.emit(event.type, event);\n }\n }\n\n /**\n * Removes all event listeners from this bridge instance.\n */\n removeAllListeners(): void {\n console.log(`Removing all event listeners for bridge ${this.id}`);\n this.listenersMap.forEach((listeners, event) => {\n listeners.forEach((listener) => {\n this.eventEmitter.off(\n event as string,\n listener as (...args: any[]) => void\n );\n });\n });\n\n this.listenersMap.clear();\n this.eventEmitter.removeAllListeners();\n }\n\n /**\n * Retrieves the current details of the bridge.\n *\n * @returns A Promise that resolves to the Bridge object containing the current details.\n * @throws An error if the retrieval fails.\n */\n async get(): Promise<Bridge> {\n try {\n if (!this.id) {\n throw new Error('No bridge associated with this instance');\n }\n\n this.bridgeData = await this.baseClient.get<Bridge>(\n `/bridges/${this.id}`\n );\n return this.bridgeData;\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(`Error retrieving details for bridge ${this.id}:`, message);\n throw new Error(`Failed to get bridge details: ${message}`);\n }\n }\n\n /**\n * Adds channels to the bridge.\n *\n * @param request - The AddChannelRequest object containing the channels to add.\n * @throws An error if the operation fails.\n */\n async add(request: AddChannelRequest): Promise<void> {\n try {\n const queryParams = toQueryParams({\n channel: Array.isArray(request.channel)\n ? request.channel.join(',')\n : request.channel,\n ...(request.role && { role: request.role }),\n });\n\n await this.baseClient.post<void>(\n `/bridges/${this.id}/addChannel?${queryParams}`\n );\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(`Error adding channels to bridge ${this.id}:`, message);\n throw new Error(`Failed to add channels: ${message}`);\n }\n }\n\n /**\n * Removes channels from the bridge.\n *\n * @param request - The RemoveChannelRequest object containing the channels to remove.\n * @throws An error if the operation fails.\n */\n async remove(request: RemoveChannelRequest): Promise<void> {\n try {\n const queryParams = toQueryParams({\n channel: Array.isArray(request.channel)\n ? request.channel.join(',')\n : request.channel,\n });\n\n await this.baseClient.post<void>(\n `/bridges/${this.id}/removeChannel?${queryParams}`\n );\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(`Error removing channels from bridge ${this.id}:`, message);\n throw new Error(`Failed to remove channels: ${message}`);\n }\n }\n\n /**\n * Plays media on the bridge.\n *\n * @param request - The PlayMediaRequest object containing the media details to play.\n * @returns A Promise that resolves to a BridgePlayback object.\n * @throws An error if the operation fails.\n */\n async playMedia(request: PlayMediaRequest): Promise<BridgePlayback> {\n try {\n const queryParams = new URLSearchParams({\n ...(request.lang && { lang: request.lang }),\n ...(request.offsetms && { offsetms: request.offsetms.toString() }),\n ...(request.skipms && { skipms: request.skipms.toString() }),\n ...(request.playbackId && { playbackId: request.playbackId }),\n }).toString();\n\n const result = await this.baseClient.post<BridgePlayback>(\n `/bridges/${this.id}/play?${queryParams}`,\n { media: request.media }\n );\n\n return result;\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(`Error playing media on bridge ${this.id}:`, message);\n throw new Error(`Failed to play media: ${message}`);\n }\n }\n\n /**\n * Stops media playback on the bridge.\n *\n * @param playbackId - The ID of the playback to stop.\n * @throws An error if the operation fails.\n */\n async stopPlayback(playbackId: string): Promise<void> {\n try {\n await this.baseClient.delete<void>(\n `/bridges/${this.id}/play/${playbackId}`\n );\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(`Error stopping playback on bridge ${this.id}:`, message);\n throw new Error(`Failed to stop playback: ${message}`);\n }\n }\n\n /**\n * Sets the video source for the bridge.\n *\n * @param channelId - The ID of the channel to set as the video source.\n * @throws An error if the operation fails.\n */\n async setVideoSource(channelId: string): Promise<void> {\n try {\n await this.baseClient.post<void>(\n `/bridges/${this.id}/videoSource/${channelId}`\n );\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(\n `Error setting video source for bridge ${this.id}:`,\n message\n );\n throw new Error(`Failed to set video source: ${message}`);\n }\n }\n\n /**\n * Removes the video source from the bridge.\n *\n * @throws An error if the operation fails.\n */\n async clearVideoSource(): Promise<void> {\n try {\n await this.baseClient.delete<void>(`/bridges/${this.id}/videoSource`);\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.error(\n `Error removing video source from bridge ${this.id}:`,\n message\n );\n throw new Error(`Failed to remove video source: ${message}`);\n }\n }\n\n /**\n * Checks if the bridge has listeners for a specific event.\n *\n * @param event - The event type to check for listeners.\n * @returns A boolean indicating whether there are listeners for the event.\n */\n hasListeners(event: string): boolean {\n return this.eventEmitter.listenerCount(event) > 0;\n }\n\n /**\n * Retrieves the current bridge data without making an API call.\n *\n * @returns The current Bridge object or null if no data is available.\n */\n getCurrentData(): Bridge | null {\n return this.bridgeData;\n }\n}\nexport class Bridges {\n private readonly bridgeInstances = new Map<string, BridgeInstance>();\n private eventQueue = new Map<string, NodeJS.Timeout>();\n constructor(\n private readonly baseClient: BaseClient,\n private readonly client: AriClient\n ) {}\n /**\n * Creates or retrieves a Bridge instance.\n *\n * This method manages the creation and retrieval of BridgeInstance objects.\n * If an ID is provided and an instance with that ID already exists, it returns the existing instance.\n * If an ID is provided but no instance exists, it creates a new instance with that ID.\n * If no ID is provided, it creates a new instance with a generated ID.\n *\n * @param {Object} params - The parameters for creating or retrieving a Bridge instance.\n * @param {string} [params.id] - Optional. The ID of the Bridge instance to create or retrieve.\n *\n * @returns {BridgeInstance} A BridgeInstance object, either newly created or retrieved from existing instances.\n *\n * @throws {Error} If there's an error in creating or retrieving the Bridge instance.\n */\n Bridge({ id }: { id?: string }): BridgeInstance {\n try {\n if (!id) {\n const instance = new BridgeInstance(this.client, this.baseClient);\n this.bridgeInstances.set(instance.id, instance);\n return instance;\n }\n\n if (!this.bridgeInstances.has(id)) {\n const instance = new BridgeInstance(this.client, this.baseClient, id);\n this.bridgeInstances.set(id, instance);\n return instance;\n }\n\n return this.bridgeInstances.get(id)!;\n } catch (error: unknown) {\n const message = getErrorMessage(error);\n console.warn(`Error creating/retrieving bridge instance:`, message);\n throw new Error(`Failed to manage bridge instance: ${message}`);\n }\n }\n\n /**\n * Removes all bridge instances and cleans up their resources.\n * This method ensures proper cleanup of all bridges and their associated listeners.\n */\n public remove(): void {\n // Salvar os IDs antes de come\u00E7ar a limpeza\n const bridgeIds = Array.from(this.bridgeInstances.keys());\n\n for (const bridgeId of bridgeIds) {\n try {\n const instance = this.bridgeInstances.get(bridgeId);\n if (instance) {\n instance.cleanup(); // Usar o novo m\u00E9todo cleanup\n this.bridgeInstances.delete(bridgeId);\n console.log(`Bridge instance ${bridgeId} removed and cleaned up`);\n }\n } catch (error) {\n console.error(`Error cleaning up bridge ${bridgeId}:`, error);\n }\n }\n\n // Garantir que o map est\u00E1 vazio\n this.bridgeInstances.clear();\n console.log('All bridge instances have been removed and cleaned up');\n }\n\n /**\n * Removes a bridge instance from the collection of managed bridges.\n *\n * This function removes the specified bridge instance, cleans up its event listeners,\n * and logs the removal. If the bridge instance doesn't exist, it logs a warning.\n *\n * @param {string} bridgeId - The unique identifier of the bridge instance to be removed.\n * @throws {Error} Throws an error if the bridgeId is not provided.\n * @returns {void}\n */\n public removeBridgeInstance(bridgeId: string): void {\n if (!bridgeId) {\n throw new Error('Bridge ID is required');\n }\n\n const instance = this.bridgeInstances.get(bridgeId);\n if (instance) {\n try {\n instance.cleanup();\n this.bridgeInstances.delete(bridgeId);\n console.log(`Bridge instance ${bridgeId} removed from memory`);\n } catch (error) {\n console.error(`Error removing bridge instance ${bridgeId}:`, error);\n throw error;\n }\n } else {\n console.warn(`Attempt to remove non-existent instance: ${bridgeId}`);\n }\n }\n\n /**\n * Propagates a WebSocket event to a specific bridge instance.\n *\n * This function checks if the received event is valid and related to a bridge,\n * then emits the event to the corresponding bridge instance if it exists.\n *\n * @param {WebSocketEvent} event - The WebSocket event to be propagated.\n * This should be an object containing information about the event,\n * including the bridge ID and event type.\n *\n * @returns {void}\n *\n * @remarks\n * - If the event is invalid (null or undefined), a warning is logged and the function returns early.\n * - The function checks if the event is bridge-related and if the event contains a valid bridge ID.\n * - If a matching bridge instance is found, the event is emitted to that instance.\n * - If no matching bridge instance is found, a warning is logged.\n */\n public propagateEventToBridge(event: WebSocketEvent): void {\n if (!event || !('bridge' in event) || !event.bridge?.id) {\n console.warn('Invalid WebSocket event received');\n return;\n }\n\n const key = `${event.type}-${event.bridge.id}`;\n const existing = this.eventQueue.get(key);\n if (existing) {\n clearTimeout(existing);\n }\n\n this.eventQueue.set(\n key,\n setTimeout(() => {\n const instance = this.bridgeInstances.get(event.bridge!.id!);\n if (instance) {\n instance.emitEvent(event);\n } else {\n console.warn(\n `No instance found for bridge ${event.bridge!.id}. Event ignored.`\n );\n }\n this.eventQueue.delete(key);\n }, 100)\n );\n }\n /**\n * Performs a cleanup of the Bridges instance, clearing all event queues and removing all bridge instances.\n *\n * This method is responsible for:\n * 1. Clearing all pending timeouts in the event queue.\n * 2. Removing all bridge instances managed by this Bridges object.\n *\n * It should be called when the Bridges instance is no longer needed or before reinitializing\n * to ensure all resources are properly released.\n *\n * @returns {void}\n */\n public cleanup(): void {\n // Limpar event queue\n this.eventQueue.forEach((timeout) => clearTimeout(timeout));\n this.eventQueue.clear();\n\n // Limpar todas as inst\u00E2ncias\n this.remove();\n }\n\n /**\n * Lists all active bridges in the system.\n *\n * This asynchronous function retrieves a list of all currently active bridges\n * by making a GET request to the \"/bridges\" endpoint using the base client.\n *\n * @returns {Promise<Bridge[]>} A promise that resolves to an array of Bridge objects.\n * Each Bridge object represents an active bridge in the system.\n *\n * @throws {Error} If there's an error in fetching the bridges or if the request fails.\n *\n * @example\n * try {\n * const bridges = await bridgesInstance.list();\n *\n * } catch (error) {\n * console.error('Failed to fetch bridges:', error);\n * }\n */\n async list(): Promise<Bridge[]> {\n return this.baseClient.get<Bridge[]>('/bridges');\n }\n\n /**\n * Creates a new bridge in the system.\n *\n * This asynchronous function sends a POST request to create a new bridge\n * using the provided configuration details.\n *\n * @param request - The configuration details for creating the new bridge.\n * @param request.type - The type of bridge to create (e.g., 'mixing', 'holding').\n * @param request.name - Optional. A custom name for the bridge.\n * @param request.bridgeId - Optional. A specific ID for the bridge. If not provided, one will be generated.\n *\n * @returns A Promise that resolves to a Bridge object representing the newly created bridge.\n * The Bridge object contains details such as id, technology, bridge_type, bridge_class, channels, etc.\n *\n * @throws Will throw an error if the bridge creation fails or if there's a network issue.\n */\n async createBridge(request: CreateBridgeRequest): Promise<Bridge> {\n return this.baseClient.post<Bridge>('/bridges', request);\n }\n\n /**\n * Retrieves detailed information about a specific bridge.\n *\n * This asynchronous function fetches the complete details of a bridge\n * identified by its unique ID. It makes a GET request to the ARI endpoint\n * for the specified bridge.\n *\n * @param bridgeId - The unique identifier of the bridge to retrieve details for.\n * This should be a string that uniquely identifies the bridge in the system.\n *\n * @returns A Promise that resolves to a Bridge object containing all the details\n * of the specified bridge. This includes information such as the bridge's\n * ID, type, channels, and other relevant properties.\n *\n * @throws Will throw an error if the bridge cannot be found, if there's a network issue,\n * or if the server responds with an error.\n */\n async get(bridgeId: string): Promise<Bridge> {\n return this.baseClient.get<Bridge>(`/bridges/${bridgeId}`);\n }\n\n /**\n * Destroys (deletes) a specific bridge in the system.\n *\n * This asynchronous function sends a DELETE request to remove a bridge\n * identified by its unique ID. Once destroyed, the bridge and all its\n * associated resources are permanently removed from the system.\n *\n * @param bridgeId - The unique identifier of the bridge to be destroyed.\n * This should be a string that uniquely identifies the bridge in the system.\n *\n * @returns A Promise that resolves to void when the bridge is successfully destroyed.\n * If the operation is successful, the bridge no longer exists in the system.\n *\n * @throws Will throw an error if the bridge cannot be found, if there's a network issue,\n * or if the server responds with an error during the deletion process.\n */\n async destroy(bridgeId: string): Promise<void> {\n return this.baseClient.delete<void>(`/bridges/${bridgeId}`);\n }\n\n /**\n * Adds one or more channels to a specified bridge.\n *\n * This asynchronous function sends a POST request to add channels to an existing bridge.\n * It can handle adding a single channel or multiple channels in one operation.\n *\n * @param bridgeId - The unique identifier of the bridge to which channels will be added.\n * @param request - An object containing the details of the channel(s) to be added.\n * @param request.channel - A single channel ID or an array of channel IDs to add to the bridge.\n * @param request.role - Optional. Specifies the role of the channel(s) in the bridge.\n *\n * @returns A Promise that resolves to void when the operation is successful.\n *\n * @throws Will throw an error if the request fails, such as if the bridge doesn't exist\n * or if there's a network issue.\n */\n async addChannels(\n bridgeId: string,\n request: AddChannelRequest\n ): Promise<void> {\n const queryParams = toQueryParams({\n channel: Array.isArray(request.channel)\n ? request.channel.join(',')\n : request.channel,\n ...(request.role && { role: request.role }),\n });\n\n await this.baseClient.post<void>(\n `/bridges/${bridgeId}/addChannel?${queryParams}`\n );\n }\n\n /**\n * Removes one or more channels from a specified bridge.\n *\n * This asynchronous function sends a POST request to remove channels from an existing bridge.\n * It can handle removing a single channel or multiple channels in one operation.\n *\n * @param bridgeId - The unique identifier of the bridge from which channels will be removed.\n * @param request - An object containing the details of the channel(s) to be removed.\n * @param request.channel - A single channel ID or an array of channel IDs to remove from the bridge.\n *\n * @returns A Promise that resolves to void when the operation is successful.\n *\n * @throws Will throw an error if the request fails, such as if the bridge doesn't exist,\n * if the channels are not in the bridge, or if there's a network issue.\n */\n async removeChannels(\n bridgeId: string,\n request: RemoveChannelRequest\n ): Promise<void> {\n const queryParams = toQueryParams({\n channel: Array.isArray(request.channel)\n ? request.channel.join(',')\n : request.channel,\n });\n\n await this.baseClient.post<void>(\n `/bridges/${bridgeId}/removeChannel?${queryParams}`\n );\n }\n\n /**\n * Plays media on a specified bridge.\n *\n * This asynchronous function initiates media playback on a bridge identified by its ID.\n * It allows for customization of the playback through various options in the request.\n *\n * @param bridgeId - The unique identifier of the bridge on which to play the media.\n * @param request - An object containing the media playback request details.\n * @param request.media - The media to be played (e.g., sound file, URL).\n * @param request.lang - Optional. The language of the media content.\n * @param request.offsetms - Optional. The offset in milliseconds to start playing from.\n * @param request.skipms - Optional. The number of milliseconds to skip before playing.\n * @param request.playbackId - Optional. A custom ID for the playback session.\n *\n * @returns A Promise that resolves to a BridgePlayback object, containing details about the initiated playback.\n *\n * @throws Will throw an error if the playback request fails or if there's a network issue.\n */\n async playMedia(\n bridgeId: string,\n request: PlayMediaRequest\n ): Promise<BridgePlayback> {\n const queryParams = toQueryParams({\n ...(request.lang && { lang: request.lang }),\n ...(request.offsetms && { offsetms: request.offsetms.toString() }),\n ...(request.skipms && { skipms: request.skipms.toString() }),\n ...(request.playbackId && { playbackId: request.playbackId }),\n });\n\n return this.baseClient.post<BridgePlayback>(\n `/bridges/${bridgeId}/play?${queryParams}`,\n { media: request.media }\n );\n }\n\n /**\n * Stops media playback on a specified bridge.\n *\n * This asynchronous function sends a DELETE request to stop the playback of media\n * on a bridge identified by its ID and a specific playback session.\n *\n * @param bridgeId - The unique identifier of the bridge where the playback is to be stopped.\n * @param playbackId - The unique identifier of the playback session to be stopped.\n *\n * @returns A Promise that resolves to void when the playback is successfully stopped.\n *\n * @throws Will throw an error if the request fails, such as if the bridge or playback session\n * doesn't exist, or if there's a network issue.\n */\n async stopPlayback(bridgeId: string, playbackId: string): Promise<void> {\n await this.baseClient.delete<void>(\n `/bridges/${bridgeId}/play/${playbackId}`\n );\n }\n\n /**\n * Sets the video source for a specified bridge.\n *\n * This asynchronous function configures a channel as the video source for a given bridge.\n * It sends a POST request to the ARI endpoint to update the bridge's video source.\n *\n * @param bridgeId - The unique identifier of the bridge for which to set the video source.\n * @param channelId - The unique identifier of the channel to be set as the video source.\n *\n * @returns A Promise that resolves to void when the video source is successfully set.\n *\n * @throws Will throw an error if the request fails, such as if the bridge or channel\n * doesn't exist, or if there's a network issue.\n */\n async setVideoSource(bridgeId: string, channelId: string): Promise<void> {\n const queryParams = toQueryParams({ channelId });\n await this.baseClient.post<void>(\n `/bridges/${bridgeId}/videoSource?${queryParams}`\n );\n }\n\n /**\n * Clears the video source for a specified bridge.\n *\n * This asynchronous function removes the currently set video source from a bridge.\n * It sends a DELETE request to the ARI endpoint to clear the video source configuration.\n *\n * @param bridgeId - The unique identifier of the bridge from which to clear the video source.\n * This should be a string that uniquely identifies the bridge in the system.\n *\n * @returns A Promise that resolves to void when the video source is successfully cleared.\n * If the operation is successful, the bridge will no longer have a designated video source.\n *\n * @throws Will throw an error if the request fails, such as if the bridge doesn't exist,\n * if there's no video source set, or if there's a network issue.\n */\n async clearVideoSource(bridgeId: string): Promise<void> {\n await this.baseClient.delete<void>(`/bridges/${bridgeId}/videoSource`);\n }\n\n /**\n * Retrieves the count of active bridge instances.\n *\n * This function returns the total number of bridge instances currently\n * managed by the Bridges class. It provides a quick way to check how many\n * active bridges are present in the system.\n *\n * @returns {number} The count of active bridge instances.\n */\n getInstanceCount(): number {\n return this.bridgeInstances.size;\n }\n\n /**\n * Checks if a bridge instance exists in the collection of managed bridges.\n *\n * This function verifies whether a bridge instance with the specified ID\n * is currently being managed by the Bridges class.\n *\n * @param bridgeId - The unique identifier of the bridge instance to check.\n * This should be a string that uniquely identifies the bridge in the system.\n *\n * @returns A boolean value indicating whether the bridge instance exists.\n * Returns true if the bridge instance is found, false otherwise.\n */\n hasInstance(bridgeId: string): boolean {\n return this.bridgeInstances.has(bridgeId);\n }\n\n /**\n * Retrieves all active bridge instances currently managed by the Bridges class.\n *\n * This method provides a way to access all the BridgeInstance objects that are\n * currently active and being managed. It returns a new Map to prevent direct\n * modification of the internal bridgeInstances collection.\n *\n * @returns A new Map object containing all active bridge instances, where the keys\n * are the bridge IDs (strings) and the values are the corresponding\n * BridgeInstance objects. If no bridges are active, an empty Map is returned.\n */\n getAllInstances(): Map<string, BridgeInstance> {\n return new Map(this.bridgeInstances);\n }\n}\n", "import type { Channel, WebSocketEvent } from './interfaces';\n\nexport function toQueryParams<T>(options: T): string {\n return new URLSearchParams(\n Object.entries(options as Record<string, string>)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [key, value as string])\n ).toString();\n}\n\nexport function isPlaybackEvent(\n event: WebSocketEvent,\n playbackId?: string\n): event is Extract<WebSocketEvent, { playbackId: string }> {\n const hasPlayback = 'playback' in event && event.playback?.id !== undefined;\n return hasPlayback && (!playbackId || event.playback?.id === playbackId