298 lines
10 KiB
TypeScript
298 lines
10 KiB
TypeScript
import { MeetRoomDeletionErrorCode } from '@typings-ce';
|
|
import { Response } from 'express';
|
|
import { z } from 'zod';
|
|
import { container } from '../config/index.js';
|
|
import { LoggerService } from '../services/index.js';
|
|
|
|
type StatusError = 400 | 401 | 402 | 403 | 404 | 409 | 415 | 416 | 422 | 500 | 503;
|
|
export class OpenViduMeetError extends Error {
|
|
name: string;
|
|
statusCode: StatusError;
|
|
|
|
constructor(error: string, message: string, statusCode: StatusError) {
|
|
super(message);
|
|
this.name = error;
|
|
this.statusCode = statusCode;
|
|
}
|
|
}
|
|
|
|
interface ErrorResponse {
|
|
error: string;
|
|
message: string;
|
|
details?: {
|
|
field: string;
|
|
message: string;
|
|
}[];
|
|
}
|
|
|
|
// General errors
|
|
|
|
export const errorMalformedBody = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Bad Request', 'Malformed body', 400);
|
|
};
|
|
|
|
export const errorProFeature = (operation: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Pro Feature Error',
|
|
`The operation '${operation}' is a PRO feature. Please, upgrade to OpenVidu PRO`,
|
|
402
|
|
);
|
|
};
|
|
|
|
export const errorUnsupportedMediaType = (supportedTypes: string[]): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Unsupported Media Type',
|
|
`Unsupported media type. Supported types: ${supportedTypes.join(', ')}`,
|
|
415
|
|
);
|
|
};
|
|
|
|
export const internalError = (operationDescription: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Internal Server Error', `Unexpected error while ${operationDescription}`, 500);
|
|
};
|
|
|
|
export const errorLivekitNotAvailable = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('LiveKit Error', 'LiveKit is not available', 503);
|
|
};
|
|
|
|
export const errorS3NotAvailable = (error: any): OpenViduMeetError => {
|
|
return new OpenViduMeetError('S3 Error', `S3 is not available ${error}`, 503);
|
|
};
|
|
|
|
export const errorAzureNotAvailable = (error: any): OpenViduMeetError => {
|
|
return new OpenViduMeetError('ABS Error', `Azure Blob Storage is not available ${error}`, 503);
|
|
};
|
|
|
|
// Auth errors
|
|
|
|
export const errorInvalidCredentials = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Login Error', 'Invalid username or password', 404);
|
|
};
|
|
|
|
export const errorInvalidPassword = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Change Password Error', 'Invalid current password', 400);
|
|
};
|
|
|
|
export const errorUnauthorized = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Authentication Error', 'Unauthorized', 401);
|
|
};
|
|
|
|
export const errorInvalidToken = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Authentication Error', 'Invalid token', 401);
|
|
};
|
|
|
|
export const errorInvalidTokenSubject = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Authorization Error', 'Invalid token subject', 403);
|
|
};
|
|
|
|
export const errorRefreshTokenNotPresent = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Refresh Token Error', 'No refresh token provided', 400);
|
|
};
|
|
|
|
export const errorInvalidRefreshToken = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Refresh Token Error', 'Invalid refresh token', 400);
|
|
};
|
|
|
|
export const errorInsufficientPermissions = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Authorization Error', 'Insufficient permissions to access this resource', 403);
|
|
};
|
|
|
|
export const errorInvalidApiKey = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Authentication Error', 'Invalid API key', 401);
|
|
};
|
|
|
|
export const errorApiKeyNotConfigured = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Authentication Error',
|
|
'There are no API keys configured yet. Please, create one to access the API',
|
|
401
|
|
);
|
|
};
|
|
|
|
// Recording errors
|
|
|
|
export const errorRecordingDisabled = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording is disabled for room '${roomId}'`, 403);
|
|
};
|
|
|
|
export const errorRecordingNotFound = (recordingId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording '${recordingId}' not found`, 404);
|
|
};
|
|
|
|
export const errorRecordingNotStopped = (recordingId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording '${recordingId}' is not stopped yet`, 409);
|
|
};
|
|
|
|
export const errorRecordingAlreadyStopped = (recordingId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording '${recordingId}' is already stopped`, 409);
|
|
};
|
|
|
|
export const errorRecordingCannotBeStoppedWhileStarting = (recordingId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording '${recordingId}' cannot be stopped while starting`, 409);
|
|
};
|
|
|
|
export const errorRecordingAlreadyStarted = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Room '${roomId}' is already being recorded`, 409);
|
|
};
|
|
|
|
export const errorRecordingStartTimeout = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Recording in room '${roomId}' timed out while starting`, 503);
|
|
};
|
|
|
|
export const errorRecordingRangeNotSatisfiable = (recordingId: string, fileSize: number): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Recording Error',
|
|
`Recording '${recordingId}' range not satisfiable. File size: ${fileSize}`,
|
|
416
|
|
);
|
|
};
|
|
|
|
export const errorRoomHasNoParticipants = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording Error', `Room '${roomId}' has no participants`, 409);
|
|
};
|
|
|
|
export const errorInvalidRecordingSecret = (recordingId: string, secret: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Recording Error',
|
|
`Secret '${secret}' is not recognized for recording '${recordingId}'`,
|
|
400
|
|
);
|
|
};
|
|
|
|
export const errorRecordingsNotFromSameRoom = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Recording Error',
|
|
`None of the provided recording IDs belong to room '${roomId}'`,
|
|
400
|
|
);
|
|
};
|
|
|
|
export const errorInvalidRecordingToken = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Recording', 'Invalid recording token', 400);
|
|
};
|
|
|
|
const isMatchingError = (error: OpenViduMeetError, originalError: OpenViduMeetError): boolean => {
|
|
return (
|
|
error instanceof OpenViduMeetError &&
|
|
error.name === originalError.name &&
|
|
error.statusCode === originalError.statusCode &&
|
|
error.message === originalError.message
|
|
);
|
|
};
|
|
|
|
export const isErrorRecordingAlreadyStopped = (error: OpenViduMeetError, recordingId: string): boolean => {
|
|
return isMatchingError(error, errorRecordingAlreadyStopped(recordingId));
|
|
};
|
|
|
|
export const isErrorRecordingNotFound = (error: OpenViduMeetError, recordingId: string): boolean => {
|
|
return isMatchingError(error, errorRecordingNotFound(recordingId));
|
|
};
|
|
|
|
export const isErrorRecordingCannotBeStoppedWhileStarting = (
|
|
error: OpenViduMeetError,
|
|
recordingId: string
|
|
): boolean => {
|
|
return isMatchingError(error, errorRecordingCannotBeStoppedWhileStarting(recordingId));
|
|
};
|
|
|
|
// Room errors
|
|
|
|
export const errorRoomNotFound = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Room Error', `Room '${roomId}' does not exist`, 404);
|
|
};
|
|
|
|
export const errorRoomClosed = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Room Error', `Room '${roomId}' is closed and cannot be joined`, 409);
|
|
};
|
|
|
|
export const errorRoomMetadataNotFound = (roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Room Error',
|
|
`Room metadata for '${roomId}' not found. Room '${roomId}' does not exist or has no recordings associated`,
|
|
404
|
|
);
|
|
};
|
|
|
|
export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Room Error', `Secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
|
};
|
|
|
|
export const errorDeletingRoom = (errorCode: MeetRoomDeletionErrorCode, message: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(errorCode, message, 409);
|
|
};
|
|
|
|
// Participant errors
|
|
|
|
export const errorParticipantNotFound = (participantIdentity: string, roomId: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Participant Error',
|
|
`Participant '${participantIdentity}' not found in room '${roomId}'`,
|
|
404
|
|
);
|
|
};
|
|
|
|
export const errorParticipantTokenNotPresent = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Participant Error', 'No participant token provided', 400);
|
|
};
|
|
|
|
export const errorInvalidParticipantToken = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Participant Error', 'Invalid participant token', 400);
|
|
};
|
|
|
|
export const errorInvalidParticipantRole = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Participant Error', 'No valid participant role provided', 400);
|
|
};
|
|
|
|
export const errorParticipantIdentityNotProvided = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Participant Error', 'No participant identity provided', 400);
|
|
};
|
|
|
|
// Webhook errors
|
|
|
|
export const errorInvalidWebhookUrl = (url: string, reason: string): OpenViduMeetError => {
|
|
return new OpenViduMeetError('Webhook Error', `Webhook URL '${url}' is invalid: ${reason}`, 400);
|
|
};
|
|
|
|
export const errorApiKeyNotConfiguredForWebhooks = (): OpenViduMeetError => {
|
|
return new OpenViduMeetError(
|
|
'Webhook Error',
|
|
'There are no API keys configured yet. Please, create one to use webhooks.',
|
|
400
|
|
);
|
|
};
|
|
|
|
// Handlers
|
|
|
|
export const handleError = (res: Response, error: OpenViduMeetError | unknown, operationDescription: string) => {
|
|
const logger = container.get(LoggerService);
|
|
logger.error(`Error while ${operationDescription}: ${error}`);
|
|
|
|
if (!(error instanceof OpenViduMeetError)) {
|
|
error = internalError(operationDescription);
|
|
}
|
|
|
|
return rejectRequestFromMeetError(res, error as OpenViduMeetError);
|
|
};
|
|
|
|
export const rejectRequestFromMeetError = (res: Response, error: OpenViduMeetError) => {
|
|
const errorResponse: ErrorResponse = {
|
|
error: error.name,
|
|
message: error.message
|
|
};
|
|
return res.status(error.statusCode).json(errorResponse);
|
|
};
|
|
|
|
export const rejectUnprocessableRequest = (res: Response, error: z.ZodError) => {
|
|
const errorDetails = error.errors.map((error) => ({
|
|
field: error.path.join('.'),
|
|
message: error.message
|
|
}));
|
|
|
|
const errorResponse: ErrorResponse = {
|
|
error: 'Unprocessable Entity',
|
|
message: 'Invalid request',
|
|
details: errorDetails
|
|
};
|
|
return res.status(422).json(errorResponse);
|
|
};
|