digital-samba-mcp-server
Version:
Digital Samba MCP Server - Model Context Protocol server for Digital Samba's video conferencing API
1,452 lines (1,451 loc) • 51.4 kB
JavaScript
/**
* Digital Samba API Client Module
*
* This module provides a comprehensive client for interacting with the Digital Samba API.
* It offers interfaces for all API entities and a client class that handles authentication,
* request processing, and provides methods for all available API endpoints.
*
* Key features include:
* - Authentication using either direct API key or the ApiKeyContext for session-based auth
* - Comprehensive coverage of all Digital Samba API endpoints
* - Type-safe interfaces for request and response data
* - Error handling and logging
* - Support for pagination, filtering, and other API parameters
*
* @module digital-samba-api
* @author Digital Samba Team
* @version 0.1.0
*/
// Local modules
import apiKeyContext from './auth.js';
import { ApiRequestError, ApiResponseError, AuthenticationError, ResourceNotFoundError, ValidationError } from './errors.js';
import logger from './logger.js';
/**
* Digital Samba API Client
*
* This class provides a comprehensive interface to the Digital Samba API. It handles
* authentication, request formation, response parsing, and error handling for all
* available API endpoints. The client supports both direct API key authentication
* and session-based authentication through the ApiKeyContext.
*
* @class DigitalSambaApiClient
* @example
* // Create a client with direct API key
* const client = new DigitalSambaApiClient('your-api-key');
*
* // Create a client that uses the ApiKeyContext
* const sessionClient = new DigitalSambaApiClient();
*
* // List all rooms
* const rooms = await client.listRooms();
*
* // Create a room
* const room = await client.createRoom({
* name: 'New Meeting Room',
* privacy: 'public'
* });
*/
export class DigitalSambaApiClient {
/**
* Creates an instance of the Digital Samba API Client
*
* @constructor
* @param {string} [apiKey] - Optional API key for direct authentication. If not provided,
* the client will use the ApiKeyContext for session-based authentication
* @param {string} [apiBaseUrl='https://api.digitalsamba.com/api/v1'] - Base URL for the Digital Samba API
* @example
* // Create a client with the default API URL
* const client = new DigitalSambaApiClient('your-api-key');
*
* // Create a client with a custom API URL
* const customClient = new DigitalSambaApiClient('your-api-key', 'https://custom-api.example.com/v1');
*/
constructor(apiKey, apiBaseUrl = 'https://api.digitalsamba.com/api/v1', cache) {
// Store the API key in ApiKeyContext if provided
if (apiKey) {
// For direct usage outside of MCP context
this._apiKey = apiKey;
}
this.apiBaseUrl = apiBaseUrl;
this.cache = cache;
}
/**
* Get the API key from context or direct value
*
* This method retrieves the API key using a prioritized approach:
* 1. First tries to get the API key from the ApiKeyContext (for session-based auth)
* 2. If not found, falls back to using the direct API key if provided during construction
* 3. If neither source provides an API key, throws an AuthenticationError
*
* @protected
* @returns {string} The API key to use for authentication
* @throws {AuthenticationError} If no API key is available from any source
*/
getApiKey() {
// Try to get API key from context first
const contextApiKey = apiKeyContext.getCurrentApiKey();
if (contextApiKey) {
return contextApiKey;
}
// Fall back to direct API key if set
if (this._apiKey) {
return this._apiKey;
}
// No API key available
throw new AuthenticationError('No API key found in context or provided directly. Please include an Authorization header with a Bearer token.');
}
/**
* Make an authenticated request to the Digital Samba API
*
* This method handles all API requests including authentication, error handling, and response parsing.
* It automatically adds the Authorization header with the API key, logs request details (excluding sensitive
* information), and processes the response. It also handles special cases like 204 No Content responses and
* adds array-like properties to ApiResponse objects for easier consumption.
*
* @protected
* @template T - The expected response type
* @param {string} endpoint - The API endpoint path (without the base URL)
* @param {RequestInit} [options={}] - Request options, including method, body, and additional headers
* @returns {Promise<T>} A promise resolving to the parsed response data
* @throws {AuthenticationError} If no API key is available for authentication
* @throws {ApiRequestError} If a network error occurs during the request
* @throws {ApiResponseError} If the API returns a non-2xx status code
* @throws {ValidationError} If the API returns a 400 Bad Request with validation errors
* @throws {ResourceNotFoundError} If the API returns a 404 Not Found response
* @example
* // Example internal usage
* const rooms = await this.request<ApiResponse<Room>>('/rooms');
*/
async request(endpoint, options = {}) {
// Handle case where endpoint is already a full URL (starts with http:// or https://)
const url = endpoint.startsWith('http') ? endpoint : `${this.apiBaseUrl}${endpoint}`;
const method = options.method || 'GET';
const isCacheable = this.cache && method === 'GET';
// Generate a cache key based on endpoint and API key (to avoid cross-client leakage)
const cacheNamespace = 'api';
const cacheKey = endpoint;
// Check cache first for GET requests
if (isCacheable) {
const cachedResponse = this.cache.get(cacheNamespace, cacheKey);
if (cachedResponse) {
logger.debug(`Cache hit for ${endpoint}`);
// Record cache hit in metrics if available
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.cacheHitsTotal.inc({ namespace: cacheNamespace });
}
catch (error) {
// Ignore metrics errors - they shouldn't affect normal operation
}
return cachedResponse.value;
}
else if (this.cache) {
// Record cache miss in metrics if available
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.cacheMissesTotal.inc({ namespace: cacheNamespace });
}
catch (error) {
// Ignore metrics errors
}
}
}
// Start timer for metrics
const startTime = Date.now();
let metricsLabels = { endpoint, method: method.toLowerCase() };
try {
const apiKey = this.getApiKey();
const headers = {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...options.headers
};
// Log the request details (excluding sensitive info)
logger.debug(`Making API request to: ${url}`, {
method,
headers: { ...headers, Authorization: '[REDACTED]' },
cacheStatus: isCacheable ? 'miss' : 'disabled'
});
// Track API request in metrics
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiRequestsTotal.inc(metricsLabels);
}
catch (error) {
// Ignore metrics errors
}
let response;
try {
response = await fetch(url, {
...options,
headers
});
}
catch (error) {
// Handle network errors
logger.error('Network error in API request', {
url,
method,
error: error instanceof Error ? error.message : String(error)
});
// Track API error in metrics
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiErrorsTotal.inc({
...metricsLabels,
error_type: 'network_error'
});
}
catch (metricsError) {
// Ignore metrics errors
}
throw new ApiRequestError(`Network error while connecting to Digital Samba API: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
}
// Log response details
logger.debug(`Response status: ${response.status} ${response.statusText}`);
// Update metrics labels with status code
metricsLabels = { ...metricsLabels, status: response.status.toString() };
if (!response.ok) {
const errorText = await response.text();
logger.error(`API Error Response: ${errorText}`, {
status: response.status,
statusText: response.statusText
});
// Track API error in metrics
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiErrorsTotal.inc({
...metricsLabels,
error_type: `status_${response.status}`
});
}
catch (metricsError) {
// Ignore metrics errors
}
// Parse error text as JSON if possible
let errorData;
try {
errorData = JSON.parse(errorText);
}
catch {
// Not JSON, use as plain text
errorData = { message: errorText };
}
// Handle specific error types based on status code
if (response.status === 400) {
// Bad Request - typically validation errors
const validationErrors = errorData.errors || {};
throw new ValidationError(`Validation error: ${errorData.message || errorText}`, { validationErrors: validationErrors });
}
else if (response.status === 401 || response.status === 403) {
// Authentication or authorization error
throw new AuthenticationError(`Authentication error: ${errorData.message || errorText}`);
}
else if (response.status === 404) {
// Not Found error
// Try to extract resource type and ID from the endpoint
const matches = endpoint.match(/\/([^\/]+)\/([^\/]+)/);
const resourceType = matches ? matches[1] : 'resource';
const resourceId = matches ? matches[2] : 'unknown';
throw new ResourceNotFoundError(`Resource not found: ${errorData.message || errorText}`, { resourceType, resourceId });
}
else {
// Generic API error
throw new ApiResponseError(`Digital Samba API error (${response.status}): ${errorData.message || errorText}`, {
statusCode: response.status,
apiErrorMessage: errorData.message || errorText,
apiErrorData: errorData
});
}
}
// Return empty object for 204 No Content responses
if (response.status === 204) {
// Record request duration in metrics
try {
const duration = (Date.now() - startTime) / 1000; // Convert to seconds
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiRequestDuration.observe(metricsLabels, duration);
}
catch (error) {
// Ignore metrics errors
}
return {};
}
const responseData = await response.json();
// Add array-like properties to ApiResponse objects
if (responseData && responseData.data && Array.isArray(responseData.data)) {
// Add length property
responseData.length = responseData.data.length;
// Add map function that forwards to the data array
responseData.map = function (callback) {
return this.data.map(callback);
};
}
// Record request duration in metrics
try {
const duration = (Date.now() - startTime) / 1000; // Convert to seconds
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiRequestDuration.observe(metricsLabels, duration);
}
catch (error) {
// Ignore metrics errors
}
// Store successful GET responses in cache
if (isCacheable) {
logger.debug(`Caching response for ${endpoint}`);
this.cache.set(cacheNamespace, cacheKey, responseData);
// Update cache metrics
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.cacheEntriesCount.inc({ namespace: cacheNamespace });
}
catch (error) {
// Ignore metrics errors
}
}
return responseData;
}
catch (error) {
// Record request duration in metrics even for errors
try {
const duration = (Date.now() - startTime) / 1000; // Convert to seconds
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiRequestDuration.observe(metricsLabels, duration);
}
catch (metricsError) {
// Ignore metrics errors
}
// Catch and re-throw errors that aren't already one of our custom types
if (!(error instanceof AuthenticationError) &&
!(error instanceof ApiRequestError) &&
!(error instanceof ApiResponseError) &&
!(error instanceof ValidationError) &&
!(error instanceof ResourceNotFoundError)) {
logger.error('Unexpected error in API request', {
url,
method: options.method || 'GET',
error: error instanceof Error ? error.message : String(error)
});
// Track API error in metrics
try {
const metricsRegistry = await import('./metrics.js').then(m => m.default);
metricsRegistry.apiErrorsTotal.inc({
...metricsLabels,
error_type: 'unexpected'
});
}
catch (metricsError) {
// Ignore metrics errors
}
throw new ApiRequestError(`Unexpected error in Digital Samba API request: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
}
// Re-throw custom error types
throw error;
}
}
// Default Room Settings
/**
* Get default room settings
*/
async getDefaultRoomSettings() {
return this.request('');
}
/**
* Update default room settings
*/
async updateDefaultRoomSettings(settings) {
return this.request('', {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
// Rooms
/**
* List all rooms
*/
async listRooms(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms${query}`);
}
/**
* Get details for a specific room
*/
async getRoom(roomId) {
return this.request(`/rooms/${roomId}`);
}
/**
* Create a new room
*/
async createRoom(settings) {
// Make sure name is defined (it's required by the API)
const roomSettings = {
...settings,
name: settings.name || 'New Meeting Room'
};
return this.request('/rooms', {
method: 'POST',
body: JSON.stringify(roomSettings)
});
}
/**
* Update an existing room
*/
async updateRoom(roomId, settings) {
return this.request(`/rooms/${roomId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a room
*/
async deleteRoom(roomId, options) {
// Invalidate cache when deleting resources
if (this.cache) {
this.cache.invalidateNamespace('api');
}
await this.request(`/rooms/${roomId}`, {
method: 'DELETE',
body: options ? JSON.stringify(options) : undefined
});
}
/**
* Generate a token for joining a room
*/
async generateRoomToken(roomId, options) {
return this.request(`/rooms/${roomId}/token`, {
method: 'POST',
body: JSON.stringify(options)
});
}
/**
* Delete all resources for a room
*/
async deleteRoomResources(roomId) {
await this.request(`/rooms/${roomId}/resources`, {
method: 'DELETE'
});
}
// Live Participants
/**
* Get rooms with live participants count
*/
async getLiveRooms(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/live${query}`);
}
/**
* Get rooms with live participants data
*/
async getLiveRoomsWithParticipants(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/live/participants${query}`);
}
/**
* Get single room with live participants count
*/
async getRoomLiveParticipantsCount(roomId) {
return this.request(`/rooms/${roomId}/live`);
}
/**
* Get single room with live participants data
*/
async getRoomLiveParticipantsData(roomId) {
return this.request(`/rooms/${roomId}/live/participants`);
}
// Participants
/**
* List all participants
*/
async listParticipants(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/participants${query}`);
}
/**
* Get details for a specific participant
*/
async getParticipant(participantId) {
return this.request(`/participants/${participantId}`);
}
/**
* List participants in a room
*/
async listRoomParticipants(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/participants${query}`);
}
/**
* List participants in a session
*/
async listSessionParticipants(sessionId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/sessions/${sessionId}/participants${query}`);
}
/**
* Phone participants joined
*/
async phoneParticipantsJoined(roomId, participants) {
await this.request(`/rooms/${roomId}/phone-participants/joined`, {
method: 'POST',
body: JSON.stringify(participants)
});
}
/**
* Phone participants left
*/
async phoneParticipantsLeft(roomId, callIds) {
await this.request(`/rooms/${roomId}/phone-participants/left`, {
method: 'POST',
body: JSON.stringify(callIds)
});
}
// Recordings
/**
* List all recordings
*/
async listRecordings(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/recordings${query}`);
}
/**
* List archived recordings
*/
async listArchivedRecordings(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/recordings/archived${query}`);
}
/**
* Get a specific recording
*/
async getRecording(recordingId) {
return this.request(`/recordings/${recordingId}`);
}
/**
* Delete a recording
*/
async deleteRecording(recordingId) {
await this.request(`/recordings/${recordingId}`, {
method: 'DELETE'
});
}
/**
* Get a download link for a recording
*/
async getRecordingDownloadLink(recordingId, validForMinutes) {
const queryParams = new URLSearchParams();
if (validForMinutes !== undefined) {
queryParams.append('valid_for_minutes', String(validForMinutes));
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/recordings/${recordingId}/download${query}`);
}
/**
* Archive a recording
*/
async archiveRecording(recordingId) {
await this.request(`/recordings/${recordingId}/archive`, {
method: 'POST'
});
}
/**
* Unarchive a recording
*/
async unarchiveRecording(recordingId) {
await this.request(`/recordings/${recordingId}/unarchive`, {
method: 'POST'
});
}
/**
* Start recording in a room
*/
async startRecording(roomId) {
await this.request(`/rooms/${roomId}/recordings/start`, {
method: 'POST'
});
}
/**
* Stop recording in a room
*/
async stopRecording(roomId) {
await this.request(`/rooms/${roomId}/recordings/stop`, {
method: 'POST'
});
}
// Webhooks
/**
* List available event types for webhooks
*/
async listWebhookEvents() {
return this.request('/events');
}
/**
* List all webhooks
*/
async listWebhooks(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/webhooks${query}`);
}
/**
* Create a new webhook
*/
async createWebhook(settings) {
return this.request('/webhooks', {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific webhook
*/
async getWebhook(webhookId) {
return this.request(`/webhooks/${webhookId}`);
}
/**
* Update a webhook
*/
async updateWebhook(webhookId, settings) {
return this.request(`/webhooks/${webhookId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a webhook
*/
async deleteWebhook(webhookId) {
await this.request(`/webhooks/${webhookId}`, {
method: 'DELETE'
});
}
// Roles and Permissions
/**
* List all roles
*/
async listRoles(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/roles${query}`);
}
/**
* Create a new role
*/
async createRole(settings) {
return this.request('/roles', {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific role
*/
async getRole(roleId) {
return this.request(`/roles/${roleId}`);
}
/**
* Update a role
*/
async updateRole(roleId, settings) {
return this.request(`/roles/${roleId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a role
*/
async deleteRole(roleId) {
await this.request(`/roles/${roleId}`, {
method: 'DELETE'
});
}
/**
* List all available permissions
*/
async listPermissions() {
return this.request('/permissions');
}
// Sessions
/**
* List all sessions
*/
async listSessions(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/sessions${query}`);
}
/**
* List sessions for a specific room
*/
async listRoomSessions(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/sessions${query}`);
}
/**
* Get session statistics
*/
async getSessionStatistics(sessionId, metrics) {
const queryParams = new URLSearchParams();
if (metrics) {
queryParams.append('metrics', metrics);
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/sessions/${sessionId}${query}`);
}
/**
* End a live session
*/
async endSession(sessionId) {
await this.request(`/sessions/${sessionId}/end`, {
method: 'POST'
});
}
/**
* Get session summary
*/
async getSessionSummary(sessionId) {
return this.request(`/sessions/${sessionId}/summary`);
}
/**
* Delete session data
*/
async deleteSessionData(sessionId, dataType) {
await this.request(`/sessions/${sessionId}/${dataType}`, {
method: 'DELETE'
});
}
// Chat and Q&A
/**
* Get chat messages
*/
async getChatMessages(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/chat${query}`);
}
/**
* Delete chat messages
*/
async deleteChatMessages(roomId) {
await this.request(`/rooms/${roomId}/chat`, {
method: 'DELETE'
});
}
/**
* Export chat messages
*/
async exportChatMessages(roomId, options) {
const queryParams = new URLSearchParams();
if (options) {
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
const apiKey = this.getApiKey();
const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/chat/export${query}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Digital Samba API error (${response.status}): ${errorText}`);
}
return response.text();
}
/**
* Get Q&A
*/
async getQuestionsAndAnswers(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/questions${query}`);
}
/**
* Delete Q&A
*/
async deleteQA(roomId) {
await this.request(`/rooms/${roomId}/questions`, {
method: 'DELETE'
});
}
/**
* Export Q&A
*/
async exportQA(roomId, options) {
const queryParams = new URLSearchParams();
if (options) {
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
const apiKey = this.getApiKey();
const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/questions/export${query}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Digital Samba API error (${response.status}): ${errorText}`);
}
return response.text();
}
// Polls
/**
* Get polls
*/
async getPolls(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/polls${query}`);
}
/**
* Create a poll
*/
async createPoll(roomId, settings) {
return this.request(`/rooms/${roomId}/polls`, {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific poll
*/
async getPoll(roomId, pollId) {
return this.request(`/rooms/${roomId}/polls/${pollId}`);
}
/**
* Update a poll
*/
async updatePoll(roomId, pollId, settings) {
return this.request(`/rooms/${roomId}/polls/${pollId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a poll
*/
async deletePoll(roomId, pollId) {
await this.request(`/rooms/${roomId}/polls/${pollId}`, {
method: 'DELETE'
});
}
/**
* Get poll results
*/
async getPollResults(roomId, pollId, sessionId) {
const queryParams = new URLSearchParams();
if (sessionId) {
queryParams.append('session_id', sessionId);
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/polls/${pollId}/results${query}`);
}
/**
* Export polls
*/
async exportPolls(roomId, options) {
const queryParams = new URLSearchParams();
if (options) {
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
const apiKey = this.getApiKey();
const response = await fetch(`${this.apiBaseUrl}/rooms/${roomId}/polls/export${query}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Digital Samba API error (${response.status}): ${errorText}`);
}
return response.text();
}
// Libraries
/**
* List all libraries
*/
async listLibraries(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/libraries${query}`);
}
/**
* Create a new library
*/
async createLibrary(settings) {
return this.request('/libraries', {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific library
*/
async getLibrary(libraryId) {
return this.request(`/libraries/${libraryId}`);
}
/**
* Update a library
*/
async updateLibrary(libraryId, settings) {
return this.request(`/libraries/${libraryId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a library
*/
async deleteLibrary(libraryId) {
await this.request(`/libraries/${libraryId}`, {
method: 'DELETE'
});
}
/**
* Get library hierarchy
*/
async getLibraryHierarchy(libraryId) {
return this.request(`/libraries/${libraryId}/hierarchy`);
}
/**
* List library folders
*/
async listLibraryFolders(libraryId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/libraries/${libraryId}/folders${query}`);
}
/**
* Create a library folder
*/
async createLibraryFolder(libraryId, settings) {
return this.request(`/libraries/${libraryId}/folders`, {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific library folder
*/
async getLibraryFolder(libraryId, folderId) {
return this.request(`/libraries/${libraryId}/folders/${folderId}`);
}
/**
* Update a library folder
*/
async updateLibraryFolder(libraryId, folderId, settings) {
return this.request(`/libraries/${libraryId}/folders/${folderId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a library folder
*/
async deleteLibraryFolder(libraryId, folderId) {
await this.request(`/libraries/${libraryId}/folders/${folderId}`, {
method: 'DELETE'
});
}
/**
* List library files
*/
async listLibraryFiles(libraryId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/libraries/${libraryId}/files${query}`);
}
/**
* Create a new library file (get upload URL and token)
*/
async createLibraryFile(libraryId, settings) {
return this.request(`/libraries/${libraryId}/files`, {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Get a specific library file
*/
async getLibraryFile(libraryId, fileId) {
return this.request(`/libraries/${libraryId}/files/${fileId}`);
}
/**
* Update a library file
*/
async updateLibraryFile(libraryId, fileId, settings) {
return this.request(`/libraries/${libraryId}/files/${fileId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Delete a library file
*/
async deleteLibraryFile(libraryId, fileId) {
await this.request(`/libraries/${libraryId}/files/${fileId}`, {
method: 'DELETE'
});
}
/**
* Get file links
*/
async getFileLinks(libraryId, fileId) {
return this.request(`/libraries/${libraryId}/files/${fileId}/links`);
}
// Moderation
/**
* Remove a participant from a room
*/
async removeParticipant(roomId, participantId) {
await this.request(`/rooms/${roomId}/participants/${participantId}/remove`, {
method: 'POST'
});
}
/**
* Set participant mute status
*/
async setParticipantMute(roomId, participantId, options) {
const { mute, type = 'all' } = options;
await this.request(`/rooms/${roomId}/participants/${participantId}/mute`, {
method: 'POST',
body: JSON.stringify({ mute, type })
});
}
/**
* Set participant role
*/
async setParticipantRole(roomId, participantId, role) {
await this.request(`/rooms/${roomId}/participants/${participantId}/role`, {
method: 'POST',
body: JSON.stringify({ role })
});
}
/**
* Ban a participant from a room
*/
async banParticipant(roomId, participantId) {
await this.request(`/rooms/${roomId}/participants/${participantId}/ban`, {
method: 'POST'
});
}
/**
* Unban a participant from a room
*/
async unbanParticipant(roomId, participantId) {
await this.request(`/rooms/${roomId}/participants/${participantId}/unban`, {
method: 'POST'
});
}
/**
* Get banned participants
*/
async getBannedParticipants(roomId) {
return this.request(`/rooms/${roomId}/banned-participants`);
}
// Statistics
/**
* Get team global statistics by period
*/
async getTeamStatistics(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/statistics${query}`);
}
/**
* Get team global statistics by current period
*/
async getTeamCurrentStatistics(metrics) {
const queryParams = new URLSearchParams();
if (metrics) {
queryParams.append('metrics', metrics);
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/statistics/team/current${query}`);
}
/**
* Get team statistics for current period (simplified)
*/
async getSimplifiedTeamCurrentStatistics() {
return this.request('/statistics/current');
}
/**
* Get room statistics by period
*/
async getRoomStatistics(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/statistics${query}`);
}
/**
* Get room statistics for current period
*/
async getRoomCurrentStatistics(roomId, metrics) {
const queryParams = new URLSearchParams();
if (metrics) {
queryParams.append('metrics', metrics);
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/statistics/current${query}`);
}
/**
* Get participant statistics
*/
async getParticipantStatistics(participantId) {
return this.request(`/participants/${participantId}/statistics`);
}
// Breakout Rooms
/**
* List breakout rooms for a parent room
*/
async listBreakoutRooms(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/breakout-rooms${query}`);
}
/**
* Get a specific breakout room
*/
async getBreakoutRoom(roomId, breakoutRoomId) {
return this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}`);
}
/**
* Create breakout rooms
*/
async createBreakoutRooms(roomId, settings) {
return this.request(`/rooms/${roomId}/breakout-rooms`, {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Delete a breakout room
*/
async deleteBreakoutRoom(roomId, breakoutRoomId) {
// Invalidate cache when deleting resources
if (this.cache) {
this.cache.invalidateNamespace('api');
}
await this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}`, {
method: 'DELETE'
});
}
/**
* Delete all breakout rooms
*/
async deleteAllBreakoutRooms(roomId) {
// Invalidate cache when deleting resources
if (this.cache) {
this.cache.invalidateNamespace('api');
}
await this.request(`/rooms/${roomId}/breakout-rooms`, {
method: 'DELETE'
});
}
/**
* List participants in a breakout room
*/
async listBreakoutRoomParticipants(roomId, breakoutRoomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/rooms/${roomId}/breakout-rooms/${breakoutRoomId}/participants${query}`);
}
/**
* Assign participants to breakout rooms
*/
async assignParticipantsToBreakoutRooms(roomId, assignments) {
await this.request(`/rooms/${roomId}/breakout-rooms/assignments`, {
method: 'POST',
body: JSON.stringify(assignments)
});
}
/**
* Return all participants to the main room
*/
async returnAllParticipantsToMainRoom(roomId) {
await this.request(`/rooms/${roomId}/breakout-rooms/return-all`, {
method: 'POST'
});
}
/**
* Broadcast message to all breakout rooms
*/
async broadcastToBreakoutRooms(roomId, options) {
await this.request(`/rooms/${roomId}/breakout-rooms/broadcast`, {
method: 'POST',
body: JSON.stringify(options)
});
}
/**
* Open breakout rooms (start breakout sessions)
*/
async openBreakoutRooms(roomId) {
await this.request(`/rooms/${roomId}/breakout-rooms/open`, {
method: 'POST'
});
}
/**
* Close breakout rooms
*/
async closeBreakoutRooms(roomId) {
await this.request(`/rooms/${roomId}/breakout-rooms/close`, {
method: 'POST'
});
}
// Meeting Scheduling
/**
* List all scheduled meetings
*/
async listScheduledMeetings(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/meetings${query}`);
}
/**
* Get a specific scheduled meeting
*/
async getScheduledMeeting(meetingId) {
return this.request(`/meetings/${meetingId}`);
}
/**
* Create a new scheduled meeting
*/
async createScheduledMeeting(settings) {
return this.request('/meetings', {
method: 'POST',
body: JSON.stringify(settings)
});
}
/**
* Update a scheduled meeting
*/
async updateScheduledMeeting(meetingId, settings) {
return this.request(`/meetings/${meetingId}`, {
method: 'PATCH',
body: JSON.stringify(settings)
});
}
/**
* Cancel a scheduled meeting
*/
async cancelScheduledMeeting(meetingId, options) {
await this.request(`/meetings/${meetingId}/cancel`, {
method: 'POST',
body: options ? JSON.stringify(options) : undefined
});
}
/**
* Delete a scheduled meeting
*/
async deleteScheduledMeeting(meetingId) {
await this.request(`/meetings/${meetingId}`, {
method: 'DELETE'
});
}
/**
* List upcoming meetings
*/
async listUpcomingMeetings(params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, String(value));
}
});
}
const query = queryParams.toString() ? `?${queryParams.toString()}` : '';
return this.request(`/meetings/upcoming${query}`);
}
/**
* List meetings for a specific room
*/
async listRoomMeetings(roomId, params) {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {