openvidu/backend/src/models/error.model.ts

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