UNPKG

uyem

Version:
1,335 lines 49.3 kB
"use strict"; /****************************************************************************************** * Repository: https://github.com/kolserdav/werift-sfu-react.git * File name: rtc.ts * Author: Sergey Kolmiller * Email: <uyem.ru@gmail.com> * License: MIT * License text: See in LICENSE file * Copyright: kolserdav, All rights reserved (c) * Create Date: Wed Aug 24 2022 14:14:09 GMT+0700 (Krasnoyarsk Standard Time) ******************************************************************************************/ // eslint-disable-next-line import/no-relative-packages //import * as werift from '../werift-webrtc/packages/webrtc/lib/webrtc/src/index'; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const werift = __importStar(require("werift")); const interfaces_1 = require("../types/interfaces"); const lib_1 = require("../utils/lib"); const constants_1 = require("../utils/constants"); const db_1 = __importDefault(require("./db")); // eslint-disable-next-line prefer-const let trackCount1 = 0; class RTC extends db_1.default { peerConnectionsServer = {}; delimiter = '_'; rooms = {}; ssrcIntervals = {}; muteds = {}; offVideo = {}; askeds = {}; adminMuteds = {}; banneds = {}; muteForAll = {}; onRoomConnect; onRoomDisconnect; onChangeVideoTrack; onChangeMute; icePortRange = constants_1.ICE_PORT_MAX && constants_1.ICE_PORT_MAX ? [constants_1.ICE_PORT_MIN, constants_1.ICE_PORT_MAX] : undefined; ws; streams = {}; constructor({ ws, prisma }) { super({ prisma }); this.ws = ws; (0, lib_1.log)('info', 'Ice port range env.(ICE_PORT_MIN, ICE_PORT_MAX) is', this.icePortRange, true); } getPeerId(userId, target, connId) { return `${userId}${this.delimiter}${target || 0}${this.delimiter}${connId}`; } closePeerConnectionHandler({ id, data: { target, roomId }, connId, }) { this.closeVideoCall({ roomId, userId: id, target, connId, eventName: 'close-peer-handler' }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_CLOSE_PEER_CONNECTION, id, data: { target, roomId, }, connId, }); } createRTCServer = async (opts) => { const { roomId, userId, target, connId, mimeType } = opts; const peerId = this.getPeerId(userId, target, connId); if (!this.peerConnectionsServer[roomId]) { this.peerConnectionsServer[roomId] = {}; } this.cleanDuplicateConnections({ roomId: roomId.toString(), userId: userId.toString(), target: target.toString(), }); (0, lib_1.log)('log', 'Creating peer connection', opts); this.peerConnectionsServer[roomId][peerId] = new werift.RTCPeerConnection({ codecs: { audio: [ new werift.RTCRtpCodecParameters({ mimeType: 'audio/opus', clockRate: 48000, channels: 2, }), ], video: [ new werift.RTCRtpCodecParameters({ mimeType, clockRate: 90000, rtcpFeedback: [ { type: 'ccm', parameter: 'fir' }, { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb' }, ], }), ], }, bundlePolicy: 'disable', iceTransportPolicy: 'all', icePortRange: this.icePortRange, }); }; getRevPeerId(peerId) { const peer = peerId.split(this.delimiter); return { peerId: `${peer[1]}${this.delimiter}${peer[0]}${this.delimiter}${peer[2]}`, userId: peer[1], target: peer[0], connId: peer[2], }; } handleIceCandidate = ({ roomId, userId, target, connId, }) => { const name = this.rooms[roomId].find((item) => item.id === userId)?.name || 'Err get name'; const peerId = this.getPeerId(userId, target, connId); if (!this.peerConnectionsServer[roomId]?.[peerId]) { (0, lib_1.log)('warn', 'Handle ice candidate without peerConnection', { peerId }); return; } // eslint-disable-next-line @typescript-eslint/no-this-alias const core = this; const { ws, delimiter, rooms, addTracksServer, peerConnectionsServer, onRoomConnect } = this; this.peerConnectionsServer[roomId][peerId].onsignalingstatechange = function handleSignalingStateChangeEvent() { if (!core.peerConnectionsServer[roomId][peerId]) { (0, lib_1.log)('warn', 'On signalling state change without peer connection', { peerId }); return; } const state = peerConnectionsServer[roomId][peerId]?.signalingState; (0, lib_1.log)('log', 'On connection state change', { peerId, state, target }); // Add tracks from remote offer if (state === 'have-remote-offer' && target.toString() !== '0') { addTracksServer({ roomId, userId, target, connId }, () => { // }); } (0, lib_1.log)('info', '! WebRTC signaling state changed to:', { state, target, roomId, peerId }); switch (core.peerConnectionsServer[roomId][peerId].signalingState) { case 'closed': core.onClosedCall({ roomId, userId, target, connId, command: 'signalingState' }); break; default: } }; this.peerConnectionsServer[roomId][peerId].onRemoteTransceiverAdded.subscribe(async (transceiver) => { if (target.toString() === '0') { const [track] = await transceiver.onTrack.asPromise(); this.ssrcIntervals[peerId] = setInterval(() => { if (track?.ssrc) { transceiver.receiver.sendRtcpPLI(track.ssrc); } }, constants_1.SENT_RTCP_INTERVAL); } }); const isChanged = false; this.peerConnectionsServer[roomId][peerId].ontrack = (e) => { const peer = peerId.split(delimiter); const isRoom = peer[1] === '0'; const stream = e.streams[0]; if (!this.streams[roomId]) { this.streams[roomId] = {}; } const isNew = !this.streams[roomId][peerId]?.length; (0, lib_1.log)('info', 'ontrack', { peerId, isRoom, si: stream.id, isNew, userId, target, tracks: stream.getTracks().map((item) => item.kind), isChanged, }); if (isRoom) { if (!this.streams[roomId][peerId]) { this.streams[roomId][peerId] = []; } this.streams[roomId][peerId].push(stream.getTracks()[0]); (0, lib_1.log)('info', 'Track ids', this.streams[roomId][peerId].map((i) => i.id)); const room = rooms[roomId]; if (room && isNew && !isChanged) { if (onRoomConnect) { onRoomConnect({ roomId, userId, roomUsers: rooms[roomId], }); } room.forEach((item) => { ws.sendMessage({ type: interfaces_1.MessageType.SET_CHANGE_UNIT, id: item.id, data: { target: userId, name, eventName: 'add', roomLength: rooms[roomId]?.length || 0, muteds: this.muteds[roomId], asked: this.askeds[roomId], adminMuteds: this.adminMuteds[roomId], isOwner: this.rooms[roomId]?.find((_item) => _item.id === userId)?.isOwner || false, banneds: this.banneds[roomId], }, connId, }); }); } else if (!room) { (0, lib_1.log)('error', 'Room missing in memory', { roomId }); } } }; }; getVideoTrackHandler = ({ id, data: { command, target, userId }, }) => { let index = -1; switch (command) { case 'add': if (this.offVideo[id].indexOf(target) === -1) { this.offVideo[id].push(target); } else { (0, lib_1.log)('warn', 'Duplicate off video', { id, target }); } break; case 'delete': index = this.offVideo[id].indexOf(target); if (index !== -1) { this.offVideo[id].splice(index, 1); } else { (0, lib_1.log)('warn', 'Deleted offVideo is missing', { id, target }); } break; default: } if (this.onChangeVideoTrack) { this.onChangeVideoTrack({ roomId: id, command, target }); } this.rooms[id].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_VIDEO_TRACK, id: item.id, connId: '', data: { offVideo: this.offVideo[id], command, target, userId, }, }); }); }; handleCandidateMessage = async (msg, cb) => { const { id, connId, data: { candidate, userId, target, roomId }, } = msg; const peerId = this.getPeerId(userId, target, connId); const cand = new werift.RTCIceCandidate(candidate); (0, lib_1.log)('log', 'Trying to add ice candidate:', { peerId, connId, id, userId, target, }); if (!this.peerConnectionsServer[roomId]?.[peerId]) { (0, lib_1.log)('info', 'Skiping add ice candidate', { connId, id, userId, peerId, target, state: this.peerConnectionsServer[roomId][peerId]?.connectionState, ice: this.peerConnectionsServer[roomId][peerId]?.iceConnectionState, ss: this.peerConnectionsServer[roomId][peerId]?.signalingState, }); return; } this.peerConnectionsServer[roomId][peerId].addIceCandidate(cand) .then(() => { (0, lib_1.log)('log', '!! Adding received ICE candidate:', { userId, id, target }); if (cb) { cb(cand); } }) .catch((e) => { (0, lib_1.log)('error', 'Set ice candidate error', { error: e, connId, id, userId, target, state: this.peerConnectionsServer[roomId][peerId]?.connectionState, ice: this.peerConnectionsServer[roomId][peerId]?.iceConnectionState, ss: this.peerConnectionsServer[roomId][peerId]?.signalingState, }); if (cb) { cb(null); } }); }; handleOfferMessage = async (msg) => { const { id, connId, data: { sdp, userId, target, mimeType, roomId }, } = msg; if (!sdp) { (0, lib_1.log)('warn', 'Message offer error because sdp is:', sdp); return; } const peerId = this.getPeerId(userId, target, connId); await this.createRTCServer({ roomId: id, userId, target, connId, mimeType, }); const opts = { roomId: id, userId, target, connId, peerId, peers: constants_1.IS_DEV ? this.getPeerConnectionKeys(roomId) : undefined, is: this.peerConnectionsServer[roomId][peerId]?.iceConnectionState, cs: this.peerConnectionsServer[roomId][peerId]?.connectionState, ss: this.peerConnectionsServer[roomId][peerId]?.signalingState, }; (0, lib_1.log)('info', '--> Creating answer', opts); if (!this.peerConnectionsServer[roomId]?.[peerId]) { (0, lib_1.log)('warn', 'Handle offer message without peer connection', opts); return; } this.handleIceCandidate({ roomId: id, userId, target, connId, }); const desc = new werift.RTCSessionDescription(sdp.sdp, 'offer'); let error = false; if (this.peerConnectionsServer[roomId][peerId].getSenders().length !== 0) { (0, lib_1.log)('warn', 'Skipping set remote desc for answer, tracks exists', {}); error = true; return; } let sendersLength = 0; await this.peerConnectionsServer[roomId][peerId].setRemoteDescription(desc).catch((e) => { sendersLength = this.peerConnectionsServer[roomId][peerId].getSenders().length; (0, lib_1.log)('error', 'Error set remote description', { e: e.message, stack: e.stack, ...opts }); error = true; }); if (!this.peerConnectionsServer[roomId]?.[peerId]) { (0, lib_1.log)('warn', 'Create answer without peer connection', opts); return; } const signalingState = this.peerConnectionsServer[roomId][peerId]?.signalingState || 'closed'; if (!(0, lib_1.checkSignallingState)(signalingState)) { (0, lib_1.log)('info', 'Skiping create answer', { signalingState, peerId, roomId }); return; } const answ = await this.peerConnectionsServer[roomId][peerId].createAnswer().catch((e) => { (0, lib_1.log)('error', 'Error create answer', { e: e.message, stack: e.stack, ...opts, }); error = true; }); if (!answ) { (0, lib_1.log)('warn', 'Answer is', answ); error = true; return; } (0, lib_1.log)('info', '---> Setting local description after creating answer', { ...opts }); if (!this.peerConnectionsServer[roomId]?.[peerId] || error) { (0, lib_1.log)('warn', 'Skipping set local description for answer', { error, ...opts, sendersLength, }); return; } await this.peerConnectionsServer[roomId][peerId].setLocalDescription(answ).catch((err) => { (0, lib_1.log)('error', 'Error set local description for answer 2', { message: err.message, stack: err.stack, ...opts, }); error = true; }); const { localDescription } = this.peerConnectionsServer[roomId][peerId]; if (localDescription) { (0, lib_1.log)('info', 'Sending answer packet back to other peer', opts); this.ws.sendMessage({ id: userId, type: interfaces_1.MessageType.ANSWER, data: { sdp: localDescription, userId: id, target, }, connId, }); } else { (0, lib_1.log)('warn', 'Failed send answer because localDescription is', localDescription); } }; sendCloseMessages({ roomId, userId, }) { const keys = this.getPeerConnectionKeys(roomId); let connId = ''; this.rooms[roomId].forEach((_item) => { keys.every((i) => { const peer = i.split(this.delimiter); if ((peer[0] === _item.id.toString() && peer[1] === userId) || (peer[0] === userId && peer[1] === _item.id.toString())) { // eslint-disable-next-line prefer-destructuring connId = peer[2]; return false; } return true; }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_CHANGE_UNIT, id: _item.id, data: { roomLength: this.rooms[roomId].length, muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], target: userId, name: _item.name, eventName: 'delete', isOwner: _item.isOwner, asked: this.askeds[roomId], banneds: this.banneds[roomId], }, connId, }); }); } deleteRoomItem({ roomId, target }) { let index = -1; let roomUser = null; this.rooms[roomId].every((item, i) => { if (item.id === target) { index = i; roomUser = { ...item }; return false; } return true; }); if (index !== -1) { this.rooms[roomId].splice(index, 1); } else { (0, lib_1.log)('warn', 'Room user is missing for delete', { target, roomId }); } return roomUser; } async getToAdminHandler({ data: { target, userId, command }, id, connId, }) { const roomId = id.toString(); const unitId = target.toString(); const locale = this.ws.getLocale({ userId }); let admins = await this.adminsFindFirst({ where: { AND: [ { roomId, }, { unitId, }, ], }, }); if (typeof admins === 'undefined') { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId, data: { type: 'error', code: interfaces_1.ErrorCode.errorSetAdmin, message: locale.error, }, }); return; } const room = await this.roomFindFirst({ where: { id: roomId, }, }); if (typeof room === 'undefined') { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId, data: { type: 'error', code: interfaces_1.ErrorCode.errorSetAdmin, message: locale.error, }, }); return; } let roomUser = null; switch (command) { case 'add': if (admins !== null) { (0, lib_1.log)('warn', 'Duplicate room admin', { userId, target, id }); return; } admins = await this.adminsCreate({ data: { unitId, roomId, }, }); if (typeof admins === 'undefined') { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId, data: { type: 'error', code: interfaces_1.ErrorCode.errorSetAdmin, message: locale.error, }, }); return; } roomUser = this.deleteRoomItem({ roomId, target: unitId }); if (roomUser === null) { return; } roomUser.isOwner = true; this.rooms[id].push(roomUser); break; case 'delete': if (target.toString() === room?.authorId) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId, data: { type: 'warn', code: interfaces_1.ErrorCode.errorSetAdmin, message: locale.ownerCanNotBeDeleted, }, }); return; } if (admins === null) { (0, lib_1.log)('warn', 'Delete missing admin', { userId, target, id }); return; } admins = await this.adminsDelete({ where: { id: admins.id, }, }); if (typeof admins === 'undefined') { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId, data: { type: 'error', code: interfaces_1.ErrorCode.errorSetAdmin, message: locale.error, }, }); return; } roomUser = this.deleteRoomItem({ roomId, target: unitId }); if (roomUser === null) { return; } roomUser.isOwner = false; this.rooms[id].push(roomUser); break; default: } this.rooms[roomId].forEach((item) => { this.ws.sendMessage({ id: item.id, type: interfaces_1.MessageType.SET_TO_ADMIN, connId: '', data: { target, command, }, }); }); } getStreamConnId(roomId, userId) { let _connId = ''; const keys = this.getKeysStreams(roomId); keys.every((element) => { const str = element.split(this.delimiter); const isTarget = str[0] === userId.toString() && str[1] === '0'; if (isTarget) { // eslint-disable-next-line prefer-destructuring _connId = str[2]; return false; } return true; }); return _connId; } getPeerConnId(roomId, userId, target) { let _connId = ''; const keys = this.getPeerConnectionKeys(roomId); keys.every((element) => { const str = element.split(this.delimiter); const isTarget = str[0] === userId.toString() && str[1] === target.toString(); if (isTarget) { // eslint-disable-next-line prefer-destructuring _connId = str[2]; return false; } return true; }); return _connId; } getKeysStreams(roomId) { return Object.keys(this.streams[roomId]); } addTracksServer = ({ roomId, connId, userId, target }, cb) => { const _connId = this.getStreamConnId(roomId, target); const _connId1 = this.getPeerConnId(roomId, userId, target); const peerId = this.getPeerId(userId, target, _connId1); const _peerId = this.getPeerId(target, 0, _connId); const tracks = this.streams[roomId][_peerId]; const streams = this.getKeysStreams(roomId); const opts = { roomId, userId, target, connId, _peerId, peerId, tracksL: tracks?.length, tracks: tracks?.map((item) => item.kind), ssL: streams.length, peers: constants_1.IS_DEV ? this.getPeerConnectionKeys(roomId) : undefined, cS: this.peerConnectionsServer[roomId][peerId]?.connectionState, sS: this.peerConnectionsServer[roomId][peerId]?.signalingState, iS: this.peerConnectionsServer[roomId][peerId]?.iceConnectionState, }; if (!tracks || tracks?.length === 0) { (0, lib_1.log)('warn', 'Skiping add track', { ...opts, tracks, allTracks: constants_1.IS_DEV ? Object.keys(this.streams[roomId]) : undefined, }); if (cb) { cb(1); } return; } if (this.peerConnectionsServer[roomId][peerId]) { (0, lib_1.log)('info', 'Add tracks', opts); tracks.forEach((track) => { const sender = this.peerConnectionsServer[roomId][peerId] ?.getSenders() .find((item) => item.track?.kind === track.kind); if (sender) { this.peerConnectionsServer[roomId][peerId].removeTrack(sender); } this.peerConnectionsServer[roomId][peerId].addTrack(track); }); if (cb) { cb(0); } } else { if (cb) { cb(1); } (0, lib_1.log)('error', 'Can not add tracks', { opts }); } }; cleanStream({ roomId, peerId, target, }) { if (this.streams[roomId]?.[peerId]) { delete this.streams[roomId][peerId]; } else if (target.toString() === '0') { (0, lib_1.log)('warn', 'Delete undefined stream', { peerId }); } } closeVideoCall = ({ roomId, userId, target, connId, eventName, }) => { const peerId = this.getPeerId(userId, target, connId); this.cleanStream({ roomId, peerId, target }); if (!this.peerConnectionsServer[roomId]?.[peerId]) { (0, lib_1.log)('warn', 'Close video call without peer connection', { peerId, peers: constants_1.IS_DEV ? this.getPeerConnectionKeys(roomId) : undefined, eventName, }); return; } (0, lib_1.log)('info', '| Closing the call', { peerId, eventName, }); this.peerConnectionsServer[roomId][peerId].onsignalingstatechange = null; this.peerConnectionsServer[roomId][peerId].onnegotiationneeded = null; this.peerConnectionsServer[roomId][peerId].ontrack = null; this.peerConnectionsServer[roomId][peerId].getSenders().forEach((item) => { this.peerConnectionsServer[roomId][peerId].removeTrack(item); }); this.peerConnectionsServer[roomId][peerId].close(); delete this.peerConnectionsServer[roomId][peerId]; if (target.toString() === '0') { clearInterval(this.ssrcIntervals[peerId]); } }; // TODO check errors async addUserToRoom({ userId, roomId, onRoomOpen, isPublic, }) { let room = await this.roomFindFirst({ where: { id: roomId.toString(), }, }); const locale = this.ws.getLocale({ userId }); let isOwner = room?.authorId === userId.toString(); if (!room) { const authorId = userId.toString(); room = await this.roomCreate({ data: { id: roomId.toString(), authorId: isPublic ? undefined : authorId, Guests: { create: { unitId: authorId, }, }, }, }); isOwner = true; this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId: '', data: { message: locale.connected, type: 'log', code: interfaces_1.ErrorCode.initial, }, }); } else { const unitId = userId.toString(); if (room.authorId !== null) { const admins = await this.adminsFindFirst({ where: { AND: [ { roomId: room.id, }, { unitId, }, ], }, }); if (admins) { isOwner = true; } } if (room.archive) { isOwner = !isOwner ? room.authorId === null : isOwner; if (!isOwner && room?.authorId !== null) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId: '', data: { message: locale.roomInactive, type: 'warn', code: interfaces_1.ErrorCode.roomIsInactive, }, }); return { error: 1, isOwner, }; } if (isOwner) { await this.changeRoomArchive({ roomId: roomId.toString(), archive: false }); } } this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId: '', data: { message: locale.connected, type: 'log', code: interfaces_1.ErrorCode.initial, }, }); this.saveGuest({ roomId: roomId.toString(), userId: userId.toString() }); } const { name } = this.ws.users[userId]; if (!this.rooms[roomId]) { this.rooms[roomId] = [ { id: userId, name, isOwner, }, ]; this.muteds[roomId] = []; this.adminMuteds[roomId] = []; this.banneds[roomId] = []; this.offVideo[roomId] = []; } else if (!this.rooms[roomId].find((item) => userId === item.id)) { this.rooms[roomId].push({ id: userId, name, isOwner, }); } else { (0, lib_1.log)('info', 'Room exists and user added before.', { roomId, userId }); } if (isOwner && onRoomOpen) { onRoomOpen({ roomId, ownerId: userId }); } return { error: 0, isOwner }; } getRoomLenght() { return Object.keys(this.rooms).length; } async handleGetRoomMessage({ message, port, cors, onRoomConnect, onRoomOpen, }) { (0, lib_1.log)('log', 'Get room message', message); const { data: { userId: uid, mimeType }, id, connId, } = message; if ((0, lib_1.checkDefaultAuth)({ unitId: uid.toString() })) { return; } if (!this.rooms[id]) { this.rooms[id] = []; this.askeds[id] = []; this.muteds[id] = []; this.adminMuteds[id] = []; this.banneds[id] = []; this.offVideo[id] = []; } let index = -1; this.banneds[id].every((item, i) => { if (item.id === uid) { index = i; return false; } return true; }); const locale = this.ws.getLocale({ userId: uid }); if (index !== -1) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: uid, connId, data: { type: 'warn', code: interfaces_1.ErrorCode.youAreBanned, message: locale.youAreBanned, }, }); return; } const { error, isOwner } = await this.addUserToRoom({ roomId: id, userId: uid, onRoomOpen, isPublic: message.data.isPublic, }); if (error) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ROOM, id: uid, data: { isOwner, asked: this.askeds[id], }, connId, }); (0, lib_1.log)('warn', 'Can not add user to room', { id, uid }); return; } const connection = new this.ws.WebSocket(`ws://localhost:${port}`, { headers: { origin: cors.split(',')[0], }, }); connection.onopen = () => { (0, lib_1.log)('info', 'On open room', { roomId: id, userId: uid, target: 0, connId, mimeType }); connection.send(JSON.stringify({ type: interfaces_1.MessageType.GET_USER_ID, id, data: { isRoom: true, }, connId: '', })); connection.onmessage = (mess) => { const msg = this.ws.parseMessage(mess.data); if (msg) { const { type } = msg; switch (type) { case interfaces_1.MessageType.OFFER: this.handleOfferMessage(msg); break; case interfaces_1.MessageType.CANDIDATE: this.handleCandidateMessage(msg); break; default: } } }; }; if (onRoomConnect) { onRoomConnect({ roomId: id, userId: uid, roomUsers: this.rooms[id], }); } if (this.muteds[id].indexOf(uid) === -1) { this.muteds[id].push(uid); } if (this.offVideo[id].indexOf(uid) === -1) { this.offVideo[id].push(uid); } if (this.muteForAll[id] === undefined) { this.muteForAll[id] = false; } if (this.adminMuteds[id].indexOf(uid) === -1 && !this.rooms[id].find((item) => item.id === uid)?.isOwner && this.muteForAll[id]) { this.adminMuteds[id].push(uid); } this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ROOM, id: uid, data: { isOwner, asked: this.askeds[id], }, connId, }); this.rooms[id].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_VIDEO_TRACK, id: item.id, data: { offVideo: this.offVideo[id], command: 'add', target: item.id, userId: item.id, }, connId, }); }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_LIST, id: uid, data: { muteds: this.muteds[id], adminMuteds: this.adminMuteds[id], askeds: this.askeds[id], }, connId, }); if (isOwner) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_BAN_LIST, id: uid, data: { banneds: this.banneds[id], }, connId, }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_FOR_ALL, id: uid, data: { value: this.muteForAll[id], }, connId, }); } } // eslint-disable-next-line class-methods-use-this onClosedCall = (args) => { (0, lib_1.log)('warn', 'Call is closed', { ...args }); }; getPeerConnectionKeys(roomId) { return Object.keys(this.peerConnectionsServer[roomId] || {}); } cleanDuplicateConnections({ roomId, userId, target, }) { this.getPeerConnectionKeys(roomId).forEach((__item) => { const peer = __item.split(this.delimiter); if (peer[0] === userId && peer[1] === target) { (0, lib_1.log)('warn', 'Duplicate peer connection', { roomId, peerId: __item, peers: constants_1.IS_DEV ? this.getPeerConnectionKeys(roomId) : undefined, }); this.closeVideoCall({ roomId, userId, target, connId: peer[2], eventName: 'clean-duplicate', }); } }); } cleanConnections(roomId, userId) { this.getPeerConnectionKeys(roomId).forEach((__item) => { const peer = __item.split(this.delimiter); if (peer[0] === userId) { this.closeVideoCall({ roomId, userId, target: peer[1], connId: peer[2], eventName: 'clean-connection-1', }); } else if (peer[1] === userId) { this.closeVideoCall({ roomId, userId: peer[0], target: userId, connId: peer[2], eventName: 'clean-connection-2', }); } }); } getMuteHandler({ id, data: { muted, roomId } }) { const index = this.muteds[roomId].indexOf(id); if (muted) { if (index === -1) { this.muteds[roomId].push(id); } } else { this.muteds[roomId].splice(index, 1); } if (this.onChangeMute) { this.onChangeMute({ roomId, target: id, command: muted ? 'add' : 'delete' }); } this.rooms[roomId].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE, id: item.id, connId: '', data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], }, }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_LIST, id: item.id, data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], askeds: this.askeds[roomId], }, connId: '', }); }); } getMuteForAllHandler = ({ id, data: { value }, }) => { this.muteForAll[id] = value; this.rooms[id].forEach((item) => { if (item.isOwner) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_FOR_ALL, connId: '', id: item.id, data: { value, }, }); } }); }; setAskedFloorHandler = ({ id, data: { userId, command }, }) => { let index = -1; switch (command) { case 'add': if (this.askeds[id].indexOf(userId) === -1) { this.askeds[id].push(userId); } else { (0, lib_1.log)('warn', 'Duplicate asked user', { id, userId }); } break; case 'delete': index = this.askeds[id].indexOf(userId); if (index !== -1) { this.askeds[id].splice(index, 1); } else { (0, lib_1.log)('warn', 'Remove missing askeds for the floor', { id, userId }); } break; default: } this.rooms[id].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ASK_FLOOR, id: item.id, connId: '', data: { userId, roomId: id, asked: this.askeds[id], }, }); }); }; getToMuteHandler({ id: roomId, data: { target }, }) { if (!this.adminMuteds[roomId]) { this.adminMuteds[roomId] = []; } if (this.adminMuteds[roomId].indexOf(target) === -1) { this.adminMuteds[roomId].push(target); } else { (0, lib_1.log)('warn', 'Duplicate to mute command', { roomId, target }); } if (this.onChangeMute) { this.onChangeMute({ roomId, target, command: 'add' }); } this.rooms[roomId].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE, id: item.id, connId: '', data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], }, }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_LIST, id: item.id, data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], askeds: this.askeds[roomId], }, connId: '', }); }); } async handleGetToBan({ id: roomId, data: { target, userId }, }) { const locale = this.ws.getLocale({ userId: target }); const id = roomId.toString(); const room = await this.roomFindFirst({ where: { id, }, }); if (typeof room === 'undefined') { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId: '', data: { type: 'error', code: interfaces_1.ErrorCode.errorToBan, message: locale.error, }, }); return; } if (room?.authorId === target.toString()) { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: userId, connId: '', data: { type: 'warn', code: interfaces_1.ErrorCode.errorToBan, message: locale.ownerCanNotBeBanned, }, }); return; } if (!this.banneds[roomId]) { this.banneds[roomId] = []; } let index = -1; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.banneds[roomId].every((item, i) => { if (item.id === target) { index = i; return false; } return true; }); if (index === -1) { const user = this.rooms[roomId].find((item) => item.id === target); if (user) { this.banneds[roomId].push(user); } else { (0, lib_1.log)('warn', 'Banned user not found in room', { userId, target }); } } else { (0, lib_1.log)('warn', 'Duplicate to ban command', { roomId, target }); } const connId = this.ws.users[target]?.connId; this.ws.sendMessage({ type: interfaces_1.MessageType.SET_BAN_LIST, id: userId, data: { banneds: this.banneds[roomId], }, connId: '', }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_ERROR, id: target, connId, data: { type: 'warn', code: interfaces_1.ErrorCode.youAreBanned, message: locale.youAreBanned, }, }, false, () => { const socketId = this.ws.getSocketId(target, connId); if (connId && socketId) { this.ws.sockets[socketId].close(); } else { (0, lib_1.log)('warn', 'Banned for not connected', { target, connId, socketId }); } }); } handleGetToUnMute({ id: roomId, data: { target }, }) { if (!this.adminMuteds[roomId]) { this.adminMuteds[roomId] = []; } const index = this.adminMuteds[roomId].indexOf(target); if (index !== -1) { this.adminMuteds[roomId].splice(index, 1); } else { (0, lib_1.log)('warn', 'Unmute of not muted', { roomId, target }); } if (this.onChangeMute) { this.onChangeMute({ roomId, target, command: 'delete' }); } this.rooms[roomId].forEach((item) => { this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE, id: item.id, connId: '', data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], }, }); this.ws.sendMessage({ type: interfaces_1.MessageType.SET_MUTE_LIST, id: item.id, data: { muteds: this.muteds[roomId], adminMuteds: this.adminMuteds[roomId], askeds: this.askeds[roomId], }, connId: '', }); }); } handleGetToUnBan({ id: roomId, data: { target, userId }, }) { if (!this.banneds[roomId]) { this.banneds[roomId] = []; } let index = -1; this.banneds[roomId].every((it, i) => { if (it.id === target) { index = i; return false; } return true; }); if (index !== -1) { this.banneds[roomId].splice(index, 1); } else { (0, lib_1.log)('warn', 'Unban of not banned', { roomId, target }); } this.ws.sendMessage({ type: interfaces_1.MessageType.SET_BAN_LIST, id: userId, data: { banneds: this.banneds[roomId], }, connId: '', }); } } exports.default = RTC; //# sourceMappingURL=rtc.js.map