Репозиторий budu-webrtc - внутренняя библиотека для реализации консультаций на базе WebRTC. Библиотека представляет собой внутренний npm-пакет, который содержит:
- ✅ класс
RTCDoctor
для работы консультаций на стороне ЛК Врача - ✅ класс
RTCClient
для работы консультаций на стороне клиента
🚨 Обновление версии библиотеки происходит через интерфейс gitlab, поэтому поднимать версию пакета вручную в package.json НЕ требуется.
// Импортируем класс webrtc для ЛК Врача, события и инстанс логерра
import {
RTCDoctor,
CallError,
EVENT_LIST,
SOCKET_MESSAGES_EVENT_LIST,
CONNECTION_EVENT_LIST,
logger,
} from '@frontend/budu-webrtc';
// Создаем инстанс доктора
const wrtc_doctor = new RTCDoctor(params);
// Устанавливает состояние девайсов (Опционально)
wrtc_doctor.changeDeviceState({
isHidden: true,
isMute: false,
});
// Подписываемся на необходимые события
wrtc_doctor.emitter.on(EVENT_LIST.HANG_UP, event => {...});
wrtc_doctor.emitter.on(CONNECTION_EVENT_LIST.CONNECTION_ICE_STATE, event => {...});
wrtc_doctor.emitter.on(SOCKET_MESSAGES_EVENT_LIST.ENTER, event => {...});
// ...Или можно подписаться на все события и фильтровать уже в обработчике
wrtc_doctor.emitter.on(EVENT_LIST.ALL, event => {
switch (event.eventType) {
case EVENT_LIST.HANG_UP: return HangUpHandler(event)
case CONNECTION_EVENT_LIST.CONNECTION_ICE_STATE: return ConnectionIceStateHandler(event)
case SOCKET_MESSAGES_EVENT_LIST.ENTER: return SocketEnterHandler(event)
}
});
// Чтобы сделать вызов необходимо вызвать метод `call`
try {
const localStream = await wrtc_doctor.call();
} catch (error: CallError) {
switch (err.type) {
case 'enter': ...
case 'answer': ...
case 'streamError': ...
}
}
// Чтобы получить логи используйте инстанс логера `logger`
console.log(logger.getConnectionEvents())
console.log(logger.filter( l => l.event === 'enter'))
// Импортируем класс webrtc для ЛК Врача, события и инстанс логерра
import {
RTCClient,
CallError,
EVENT_LIST,
SOCKET_MESSAGES_EVENT_LIST,
CONNECTION_EVENT_LIST,
logger,
} from '@frontend/budu-webrtc';
// Создаем инстанс доктора
const wrtc_client = new RTCClient(params);
// Устанавливаем состояние девайсов (Опционально)
wrtc_client.changeDeviceState({
isHidden: true,
isMute: false,
});
// Подписываемся на необходимые события
wrtc_client.emitter.on(EVENT_LIST.HANG_UP, event => {...});
wrtc_client.emitter.on(CONNECTION_EVENT_LIST.CONNECTION_ICE_STATE, event => {...});
wrtc_client.emitter.on(SOCKET_MESSAGES_EVENT_LIST.ENTER, event => {...});
// ...Или можно подписаться на все события и фильтровать уже в обработчике
wrtc_client.emitter.on(EVENT_LIST.ALL, event => {
switch (event.eventType) {
case EVENT_LIST.HANG_UP: return HangUpHandler(event)
case CONNECTION_EVENT_LIST.CONNECTION_ICE_STATE: return ConnectionIceStateHandler(event)
case SOCKET_MESSAGES_EVENT_LIST.ENTER: return SocketEnterHandler(event)
}
});
// Чтобы ответить вызов необходимо вызвать метод `call`
try {
const localStream = await wrtc_client.call();
} catch (error: CallError) {
switch (err.type) {
case 'enter': ...
case 'streamError': ...
}
}
// Чтобы получить логи используйте инстанс логера `logger`
console.log(logger.getConnectionEvents())
console.log(logger.filter( l => l.event === 'enter'))
class RTCCore
Обеспечивает пул соединений на стороне.
export interface RTCCoreInterface {
connectionConfig: RTCConfiguration;
emitter: IEventsEmitter;
params: RTCParams;
mediaStream: MediaStream | null;
allMediaStreams: MediaStream[];
connection: RTCConnection | null;
socketMessenger: ISocketMessenger;
hasEntered: boolean;
hasAnswered: boolean;
call(): Promise<MediaStream>;
socketMessageHandler(message: SocketMessage): Promise<void>;
changeDeviceState(props: DeviceState): void;
hangUp(): void;
destroy(): void;
}
param RTCCore.connectionConfig
- RTCConfiguration
Конфиг для создания инстанса RTCPeerConnection
new RTCPeerConnection(this.connectionConfig);
param RTCCore.emitter
- EventsEmitter
Инстанс класса EventsEmitter
для работы с событиями
param RTCCore.params
- RTCParams
Вспомогательная информация
смотреть описания типа
RTCParams
param RTCCore.mediaStream
- MediaStream
| null
param RTCCore.allMediaStreams
- MediaStream
[]
param RTCCore.connection
- RTCConnection
| null
Инстанс RTC соединения
param RTCCore.socketMessenger
- ISocketMessenger
Инстанс класса SocketMessenger
для генерации сокетных сообщений
param RTCCore.hasEntered
- boolean
Флаг говорящий о успешном вхождение в комнату
param RTCCore.hasAnswered
- boolean
Флаг говорящий о успешном ответе кандидата
method RTCCore.call(): Promise<MediaStream>
Инициация звонка кандидату:
- Вхождение в комнату
- Ожидание офера и ответ на него
Возвращает Promise с локальным
MediaStream
Возможны ошибки типа CallError
(смотреть описание типа)
method RTCCore.socketMessageHandler(message: SocketMessage): Promise<void>
Обработка серверных сообщений.
Принимает сокетное сообщение SocketMessage
method RTCCore.changeDeviceState(props: DeviceState): void
Функция для изменения состояния audio и video треков по флагам DeviceState
method RTCCore.hangUp(): void
Остановка треков стрима и отправка сокетного события об отключении
method RTCCore.destroy(): void
Закрытие всех соединений
class CallError
Класс ошибки вызова кандидата
export class CallError extends Error implements CallErrorType {
name: string;
message: string;
type: CallErrorMessage;
stack?: string | undefined;
description?: string | null;
constructor(message: string, type: CallErrorMessage) {
super(message);
this.type = type;
this.message = message;
this.name = 'CallError';
this.description = null;
}
}
class EventsEmitter
Класс для работы с событиями библиотеки. Возможность подписываться, отписываться и вызывать сообщения
export interface IEventsEmitter {
events: EventsDict;
on<K extends keyof EventCallbackMap>(
event: K,
callback: EventCallbackMap[K]
): void;
off<K extends keyof EventCallbackMap>(
event: K,
callback: EventCallbackMap[K]
): void;
emit<K extends keyof EventMap>(
eventType: K,
payload?: EventDataMap[K],
emitType?: EMIT_TYPE_LIST
): Promise<void>;
}
param EventsEmitter.events
- EventsDict
Объект событий, где ключ - EventList
, а значение - массив EventCallback
для этого события
class SocketMessenger
createPublishMessage<K extends SOCKET_MESSAGES_EVENT_LIST>(
type: K
): IPublishMessage<K>;
createTransientMessage(
connectionId: string | null,
data: any
): TransientMessage;
type IPublishMessage
export type IPublishMessage<T> = {
data: {
type: T;
sender: {
chatId: uuid;
clientId: uuid;
connectionId: uuid;
};
consultationId: uuid;
};
route: 'vcm';
};
type TransientMessage
export type TransientMessage = {
type: string;
chatId: string;
data: {
sender: {
chatId: string;
clientId: string;
connectionId: string;
};
consultationId: string;
};
connectionIds?: string[];
};
type RTCParams
export type RTCParams = {
name: string;
clientId: string;
connectionId: string | null;
connectionsId?: string[];
consultationId: string;
doctor: DoctorInfo;
isAudio: boolean;
enterTimeoutSecond?: number;
answerTimeoutSecond?: number;
};
export type DoctorInfo = {
avatarId: string | null;
fullName: string;
specialityName: string;
};
type RTCConnection
Тип расширяющий тип RTC соединения RTCPeerConnection
export type RTCConnection = RTCPeerConnection & {
clientId: string;
connectionId: string;
name: string;
iceCandidatesQueue: RTCIceCandidate[];
isInviter: boolean;
isClosing: boolean;
negotiating: boolean;
};
type CallErrorType
export type CallErrorType = Error & {
type: CallErrorMessage;
description?: string | null;
};
type CallErrorMessage
export type CallErrorMessage = 'streamError' | 'enter' | 'answer';
type SocketMessage
export type SocketMessage = { type: string; data: any };
type DeviceState
export type DeviceState = {
isHidden: boolean;
isMute: boolean;
};
type EventList
export type EventList =
| EVENT_LIST
| SOCKET_MESSAGES_EVENT_LIST
| CONNECTION_EVENT_LIST;
type EventCallback
export type EventCallback<T> = (event: T) => void;
type EventsDict
export type EventsDict = {
[K in keyof EventCallbackMap]: Array<EventCallbackMap[K]>;
};
Пример
type EventsDict = {
allEvent: AnyCallback[];
hangUpReceiveEvent: EventCallback<IEvent<EVENT_LIST.HANG_UP, void>>[];
rtcRespondentPropsEvent: EventCallback<IEvent<EVENT_LIST.RTC_RESPONDENT_PROPS, IRtcSendPropsData>>[];
... 12 more ...;
hangup: EventCallback<...>[];
}
type EventCallbackMap
export type EventCallbackMap = {
[K in keyof EventMap]: K extends EVENT_LIST.ALL
? AnyCallback
: EventCallback<EventMap[K]>;
};
Пример
type EventCallbackMap = {
allEvent: AnyCallback;
hangUpReceiveEvent: EventCallback<IEvent<EVENT_LIST.HANG_UP, void>>;
rtcRespondentPropsEvent: EventCallback<IEvent<EVENT_LIST.RTC_RESPONDENT_PROPS, IRtcSendPropsData>>;
... 12 more ...;
hangup: EventCallback<...>;
}
type EventMap
export type EventMap = {
[K in keyof EventDataMap]: IEvent<K, EventDataMap[K]>;
};
Пример
type EventMap = {
allEvent: IEvent<EVENT_LIST.ALL, any>;
hangUpReceiveEvent: IEvent<EVENT_LIST.HANG_UP, void>;
rtcRespondentPropsEvent: IEvent<EVENT_LIST.RTC_RESPONDENT_PROPS, IRtcSendPropsData>;
... 12 more ...;
hangup: IEvent<...>;
}
type IEvent
export type IEvent<T, P> = {
emitType: EMIT_TYPE_LIST;
eventType: T;
payload: P;
};
type ISocketMessenger
export type ISocketMessenger = {
createPublishMessage<K extends SOCKET_MESSAGES_EVENT_LIST>(
type: K
): IPublishMessage<K>;
createTransientMessage(
connectionId: string | null,
data: any
): TransientMessage;
};
enum EMIT_TYPE_LIST
<a name="enum-emit-type-list)>
export enum EMIT_TYPE_LIST {
EVENT = 'event',
ERROR = 'error',
}
Подробное описание по особенностям работы с внутренними npm-пакетами можно прочесть вот здесь: https://confluence.renhealth.com/pages/viewpage.action?pageId=46654678
Обычная возникают проблемы с установкой npm пакетов локально на компьютерах разработчиков. Локально пакеты из нашего npm-хранилища можно устанавливать только при наличии авторизации, иначе при запросе npm i @frontend/budu-webrtc можно получить ошибку 404.
Для правильной авторизации создаем (если еще нету) файл .npmrc в домашней директории (для Windows это папка C:\Users\ваше.имя, а для unix-систем /home/{username}/.npmrc) и добавляем в него:
@frontend:registry=https://gitlab.dev.renhealth.com/api/v4/packages/npm/ //gitlab.dev.renhealth.com/api/v4/packages/npm/:_authToken= "ACCESS_TOKEN" //gitlab.dev.renhealth.com/api/v4/projects/248/packages/npm/:_authToken= "ACCESS_TOKEN"
Заменяем ACCESS_TOKEN на ваш персональный. Персональный токен формируем в gitlab на странице https://gitlab.dev.renhealth.com/-/profile/keys Для токена достаточно чтобы были права только на чтение