livekit-client
Version:
JavaScript/TypeScript client SDK for LiveKit
340 lines (272 loc) • 8.56 kB
text/typescript
import { DisconnectReason, RequestResponse_Reason } from '@livekit/protocol';
/** Base error that all LiveKit specific custom errors inherit from. */
export class LivekitError extends Error {
code: number;
// More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
cause?: unknown;
constructor(code: number, message?: string, options?: { cause?: unknown }) {
super(message || 'an error has occurred');
this.name = 'LiveKitError';
this.code = code;
if (typeof options?.cause !== 'undefined') {
this.cause = options?.cause;
}
}
}
/**
* LiveKit specific error type representing an error with an associated set of reasons.
* Use this to represent an error with multiple different but contextually related variants.
* */
export abstract class LivekitReasonedError<Reason> extends LivekitError {
abstract reason: Reason;
abstract reasonName: string;
}
export class SimulatedError extends LivekitError {
readonly name = 'simulated';
constructor(message = 'Simulated failure') {
super(-1, message);
}
}
export enum ConnectionErrorReason {
NotAllowed,
ServerUnreachable,
InternalError,
Cancelled,
LeaveRequest,
Timeout,
WebSocket,
ServiceNotFound,
}
type NotAllowed = {
reason: ConnectionErrorReason.NotAllowed;
status: number;
context?: unknown;
};
type InternalError = {
reason: ConnectionErrorReason.InternalError;
status: never;
context?: { status?: number; statusText?: string };
};
type ConnectionTimeout = {
reason: ConnectionErrorReason.Timeout;
status: never;
context: never;
};
type LeaveRequest = {
reason: ConnectionErrorReason.LeaveRequest;
status: never;
context: DisconnectReason;
};
type Cancelled = {
reason: ConnectionErrorReason.Cancelled;
status: never;
context: never;
};
type ServerUnreachable = {
reason: ConnectionErrorReason.ServerUnreachable;
status?: number;
context?: never;
};
type WebSocket = {
reason: ConnectionErrorReason.WebSocket;
status?: number;
context?: string;
};
type ServiceNotFound = {
reason: ConnectionErrorReason.ServiceNotFound;
status: never;
context: string;
};
type ConnectionErrorVariants =
| NotAllowed
| ConnectionTimeout
| LeaveRequest
| InternalError
| Cancelled
| ServerUnreachable
| WebSocket
| ServiceNotFound;
export class ConnectionError<
Variant extends ConnectionErrorVariants = ConnectionErrorVariants,
> extends LivekitReasonedError<Variant['reason']> {
status?: Variant['status'];
context: Variant['context'];
reason: Variant['reason'];
reasonName: string;
readonly name = 'ConnectionError';
protected constructor(
message: string,
reason: Variant['reason'],
status?: Variant['status'],
context?: Variant['context'],
) {
super(1, message);
this.status = status;
this.reason = reason;
this.context = context;
this.reasonName = ConnectionErrorReason[reason];
}
static notAllowed(message: string, status: number, context?: unknown) {
return new ConnectionError<NotAllowed>(
message,
ConnectionErrorReason.NotAllowed,
status,
context,
);
}
static timeout(message: string) {
return new ConnectionError<ConnectionTimeout>(message, ConnectionErrorReason.Timeout);
}
static leaveRequest(message: string, context: DisconnectReason) {
return new ConnectionError<LeaveRequest>(
message,
ConnectionErrorReason.LeaveRequest,
undefined,
context,
);
}
static internal(message: string, context?: { status?: number; statusText?: string }) {
return new ConnectionError<InternalError>(
message,
ConnectionErrorReason.InternalError,
undefined,
context,
);
}
static cancelled(message: string) {
return new ConnectionError<Cancelled>(message, ConnectionErrorReason.Cancelled);
}
static serverUnreachable(message: string, status?: number) {
return new ConnectionError<ServerUnreachable>(
message,
ConnectionErrorReason.ServerUnreachable,
status,
);
}
static websocket(message: string, status?: number, reason?: string) {
return new ConnectionError<WebSocket>(message, ConnectionErrorReason.WebSocket, status, reason);
}
static serviceNotFound(message: string, serviceName: 'v0-rtc') {
return new ConnectionError<ServiceNotFound>(
message,
ConnectionErrorReason.ServiceNotFound,
undefined,
serviceName,
);
}
}
export class DeviceUnsupportedError extends LivekitError {
readonly name = 'DeviceUnsupportedError';
constructor(message?: string) {
super(21, message ?? 'device is unsupported');
}
}
export class TrackInvalidError extends LivekitError {
readonly name = 'TrackInvalidError';
constructor(message?: string) {
super(20, message ?? 'track is invalid');
}
}
export class UnsupportedServer extends LivekitError {
readonly name = 'UnsupportedServer';
constructor(message?: string) {
super(10, message ?? 'unsupported server');
}
}
export class UnexpectedConnectionState extends LivekitError {
readonly name = 'UnexpectedConnectionState';
constructor(message?: string) {
super(12, message ?? 'unexpected connection state');
}
}
export class NegotiationError extends LivekitError {
readonly name = 'NegotiationError';
constructor(message?: string) {
super(13, message ?? 'unable to negotiate');
}
}
export class PublishDataError extends LivekitError {
readonly name = 'PublishDataError';
constructor(message?: string) {
super(14, message ?? 'unable to publish data');
}
}
export class PublishTrackError extends LivekitError {
readonly name = 'PublishTrackError';
status: number;
constructor(message: string, status: number) {
super(15, message);
this.status = status;
}
}
export type RequestErrorReason =
| Exclude<RequestResponse_Reason, RequestResponse_Reason.OK>
| 'TimeoutError';
export class SignalRequestError extends LivekitReasonedError<RequestErrorReason> {
readonly name = 'SignalRequestError';
reason: RequestErrorReason;
reasonName: string;
constructor(message: string, reason: RequestErrorReason) {
super(15, message);
this.reason = reason;
this.reasonName = typeof reason === 'string' ? reason : RequestResponse_Reason[reason];
}
}
// NOTE: matches with https://github.com/livekit/client-sdk-swift/blob/f37bbd260d61e165084962db822c79f995f1a113/Sources/LiveKit/DataStream/StreamError.swift#L17
export enum DataStreamErrorReason {
// Unable to open a stream with the same ID more than once.
AlreadyOpened = 0,
// Stream closed abnormally by remote participant.
AbnormalEnd = 1,
// Incoming chunk data could not be decoded.
DecodeFailed = 2,
// Read length exceeded total length specified in stream header.
LengthExceeded = 3,
// Read length less than total length specified in stream header.
Incomplete = 4,
// Unable to register a stream handler more than once.
HandlerAlreadyRegistered = 7,
// Encryption type mismatch.
EncryptionTypeMismatch = 8,
}
export class DataStreamError extends LivekitReasonedError<DataStreamErrorReason> {
readonly name = 'DataStreamError';
reason: DataStreamErrorReason;
reasonName: string;
constructor(message: string, reason: DataStreamErrorReason) {
super(16, message);
this.reason = reason;
this.reasonName = DataStreamErrorReason[reason];
}
}
export class SignalReconnectError extends LivekitError {
readonly name = 'SignalReconnectError';
constructor(message?: string) {
super(18, message);
}
}
export enum MediaDeviceFailure {
// user rejected permissions
PermissionDenied = 'PermissionDenied',
// device is not available
NotFound = 'NotFound',
// device is in use. On Windows, only a single tab may get access to a device at a time.
DeviceInUse = 'DeviceInUse',
Other = 'Other',
}
export namespace MediaDeviceFailure {
export function getFailure(error: any): MediaDeviceFailure | undefined {
if (error && 'name' in error) {
if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
return MediaDeviceFailure.NotFound;
}
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
return MediaDeviceFailure.PermissionDenied;
}
if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
return MediaDeviceFailure.DeviceInUse;
}
return MediaDeviceFailure.Other;
}
}
}