UNPKG

@huddle01/web-core

Version:

The Huddle01 Javascript SDK offers a comprehensive suite of methods and event listeners that allow for seamless real-time audio and video communication with minimal coding required.

537 lines (534 loc) 15.7 kB
import { Socket_default } from './chunk-RS7KXM4R.js'; import { checkPermissions } from './chunk-E2DU7IIE.js'; import { mainLogger } from './chunk-TOCFOGTC.js'; import { EnhancedEventEmitter } from './chunk-BW2DGP4D.js'; // src/Room.ts var logger = mainLogger.createSubLogger("Room.ts"); var Room = class _Room extends EnhancedEventEmitter { /** * Room Instance, Singleton class */ static __instance = null; /** * Socket Instance, Singleton class */ __socket = Socket_default.getInstance(); /** * Returns the instance of the socket connection */ get socket() { return this.__socket; } /** * Room Id of the current room */ __roomId = null; /** * session id */ __sessionId = null; /** * Lobby PeerIds */ __lobbyPeers = /* @__PURE__ */ new Map(); /** * Removed Lobby PeerId from the lobby * @param peerId - PeerId of the peer who joined the room */ removeLobbyPeer = (peerId) => { this.__lobbyPeers.delete(peerId); this.emit("lobby-peers-updated", this.lobbyPeerIds); }; /** * Room Config Object * - `allowProduce`: Allow non-admin Peers in the Room to produce Media Streams * - `allowConsume`: Allow non-admin Peers in the Room to consume Media Streams * - `allowSendData`: Allow non-admin Peers in the Room to send data message * - `roomLocked`: If the room is locked */ __config = { roomLocked: false, allowProduce: true, allowProduceSources: { cam: true, mic: true, screen: true }, allowConsume: true, allowSendData: true }; /** * Auto consume flag, if set to true, Peers Joining the Room will automatically consume the media streams of the remote peers * * @default true * * @remarks - This flag is used by the `useRoom` hook to automatically consume the media streams of the remote peers, * - if set to false, the user will have to manually consume the media streams of the remote peers * using the `consume` method of the `LocalPeer` instance `localPeer.consume({ peerId, label: "video", appData: {} });` */ autoConsume = true; /** * Default Active Speaker Size, if not provided in the room config object * @default 0 * @remarks - To update the active speaker size, you can use function `room.activeSpeakers.updateSize(size: number)` */ __defaultActiveSpeakerSize = 0; /** * Get the default active speaker size which can be updated using option `activeSpeaker` when initializing the HuddleClient. * @remarks - To update the active speaker while inside the meeting, use the function `room.activeSpeakers.updateSize(size: number)` */ get defaultActiveSpeakerSize() { return this.__defaultActiveSpeakerSize; } /** * Handler for the ActiveSpeaker */ __activeSpeakers = null; /** * Get the active speakers instance */ get activeSpeakers() { return this.__activeSpeakers; } set activeSpeakers(activeSpeakers) { if (this.__activeSpeakers) { logger.warn( "Active Speakers is already set, Ignoring the new active speakers, end this room and create a new room" ); } this.__activeSpeakers = activeSpeakers; } /** * Bot Instance, which is used to manage Volatile Messages inside the SDK. */ __bot = null; get bot() { return this.__bot; } set bot(bot) { if (this.__bot) { logger.warn( "Bot is already set, Ignoring the new bot, end this room and create a new room" ); } this.__bot = bot; this.__bot.on("received-volatile-data", (data) => { this.emit("received-volatile-data", data); }); } /** * State of the Room */ __state = "idle"; /** * Room Stats */ __stats = { startTime: 0 }; /** * Set the state of the room */ set state(newState) { if (this.state !== newState) { this.__state = newState; } } /** * State of the room */ get state() { return this.__state; } /** * Get the lobby peers in the form of map * @returns - Map of Lobby PeerIds, with the metadata, {peerId ==> {peerId, metadata}} * @example * ```ts * const lobbyPeers = room.lobbyPeersMap; * * for (const [peerId, metadata] of lobbyPeers) {} * * ``` */ get lobbyPeersMap() { return this.__lobbyPeers; } /** * Get lobby peers in the form of array */ get lobbyPeerIds() { return Array.from(this.__lobbyPeers.keys()); } /** * Get lobby peers in the form of array * @returns - Array of Lobby PeerIds */ get lobbyPeers() { return this.__lobbyPeers; } /** * Set lobby peers in the form of map * `NOTE: This function is an internal function of the SDK, Used to emit evnts based on changes` */ set lobbyPeersMap(peers) { this.__lobbyPeers = peers; this.emit("lobby-peers-updated", this.lobbyPeerIds); } /** * Get Room Stats */ get stats() { return this.__stats; } /** * Set Room Stats */ set stats(stats) { this.__stats = stats; } /** * Get the lobby peer metadata * @param peerId - PeerId of the Lobby Peer * @returns - Lobby Peer Metadata */ getLobbyPeerMetadata = (peerId) => { const lobbyPeer = this.lobbyPeers.get(peerId); let metadata = {}; if (lobbyPeer?.metadata) { metadata = JSON.parse(lobbyPeer.metadata); } return { peerId, metadata }; }; /** * Add Lobby Peers to the room, this will add the lobby peers to the existing lobby peers */ _addLobbyPeers = (peers) => { for (const peer of peers) { if (this.remotePeerExists(peer.peerId)) { logger.debug( `Peer with peerId: ${peer.peerId} already exists in the room` ); continue; } this.__lobbyPeers.set(peer.peerId, peer); this.emit("new-lobby-peer", peer); } this.emit("lobby-peers-updated", this.lobbyPeerIds); }; /** * Set the new lobby peers, this will clear the existing lobby peers and add the new lobby peers * @param peers - Array of Lobby Peers */ _setLobbyPeers(peers) { logger.debug("Setting New Lobby Peer"); this.__lobbyPeers.clear(); this._addLobbyPeers(peers); } /** * Room Config Object * - `allowProduce`: Allow non-admin Peers in the Room to produce Media Streams * - `allowConsume`: Allow non-admin Peers in the Room to consume Media Streams * - `allowSendData`: Allow non-admin Peers in the Room to send data message * - `roomLocked`: If the room is locked */ get config() { return this.__config; } set config(config) { this.__config = config; } /** * Remote Peers Map, Stores all the remote peers */ remotePeers = /* @__PURE__ */ new Map(); /** * Metadata of the room. */ __metadata = "{}"; /** * Setter function for the metadata of the room * `Note: This will not update the metadata of the room, this is just a setter function for the metadata` * `To notify everyone in the room about the metadata change, use the updateMetadata function` */ set metadata(metadata) { this.__metadata = metadata; const parse = JSON.parse(metadata); this.emit("metadata-updated", parse); } /** * Get the metadata of the room */ getMetadata = () => { const data = JSON.parse(this.__metadata || "{}"); return data; }; /** * Update Metadata of the room * @throws { Error } If Request Failed to Update Metadata */ updateMetadata = checkPermissions({ admin: true }).validate( async (data) => { try { if (this.state === "closed" || this.state === "failed" || this.state === "left") { logger.error( "\u274C Cannot Update Metadata, You have not joined the room yet" ); return; } const metadata = JSON.stringify(data); this.metadata = metadata; this.socket.publish("updateRoomMetadata", { metadata }); } catch (error) { logger.error("\u274C Error Updating Metadata"); logger.error(error); } } ); /** * Create a new Room Instance if not created, else return the existing Room Instance * * @returns - Room Instance */ static create(data) { if (_Room.__instance) { return _Room.__instance; } _Room.__instance = new _Room(data); return _Room.__instance; } /** * Get the Room Instance if its not initialized it will throw an error * @returns - Room Instance * @throws { Error } If the Room Instance is not initialized */ static getInstance = () => { if (!_Room.__instance) { throw new Error("\u274C Room Instance Not Initialized"); } return _Room.__instance; }; /** * RoomId of the currently joined room. */ get roomId() { return this.__roomId; } /** * */ get sessionId() { return this.__sessionId; } set roomId(roomId) { if (this.__roomId) { logger.warn( "RoomId is already set, Ignoring the new roomId, end this room and create a new room" ); return; } this.__roomId = roomId; } set sessionId(sessionId) { if (this.__sessionId) { logger.warn( "sessionId is already set, Ignoring the new sessionId, end this room and create a new room" ); return; } this.__sessionId = sessionId; } /** * Returns the PeerIds of the remote peers */ get peerIds() { return Array.from(this.remotePeers.keys()); } /** * @description Update room control booleans - roomLocked, allowProduce, allowConsume, allowSendData * @param data: TNewRoomControls * @throws { Error } If the Peer Calling this Functions is not an admin, then the permissions will not be updated */ updateRoomControls = checkPermissions({ admin: true }).validate(async (data) => { logger.info("\u{1F514} Updating Room Controls", data); this.config[data.type] = data.value; this.emit("room-controls-updated"); if (data.type === "allowProduceSources") { this.socket.publish("updateRoomControls", { control: { case: "produceSourcesControl", value: data } }); } else { this.socket.publish("updateRoomControls", { control: { case: "roomControl", value: data } }); } }); /** * Close a particular stream of remote peers * @param data: { label: string; peerIds?: string[] } * @param label: Label of the stream * @param peerIds: PeerIds of the remote peers, if not provided, it will close the stream of all the remote peers * @throws { Error } If the Peer Calling this Function is not an admin, then the permissions will not be updated */ closeStreamOfLabel = checkPermissions({ admin: true }).validate( async (data) => { logger.info("\u{1F514} Closing Stream of Label", data); this.socket.publish("closeStreamOfLabel", data); } ); /** * Mute everyone in the room. This will close the audio stream of all the remote peers who dont have admin permissions * * `NOTE: This will target all the audio stream in the room with the label "audio"` * * @throws { Error } If the Peer Calling this Function is not an admin, then the permissions will not be updated */ muteEveryone = checkPermissions({ admin: true }).validate(async () => { logger.info("\u{1F514} Muting Everyone"); this.socket.publish("closeStreamOfLabel", { label: "audio" }); }); /** * Returns the Remote Peer with the given peerId is present in the room. Returns null if the peer is not present in the room. * * @param peerId - PeerId of the remote peer * @returns - RemotePeer Instance * @return - null if the peer is not present in the room */ remotePeerExists = (peerId) => { const peer = this.remotePeers.get(peerId); if (!peer) { return null; } return peer; }; /** * Returns the Remote Peer if present in room. * @param peerId - PeerId of the remote peer * @returns - RemotePeer Instance * @throws { Error } If the Remote Peer is not found */ getRemotePeerById(peerId) { const peer = this.remotePeers.get(peerId); if (!peer) { throw new Error(`Remote Peer Not Found, peerId: ${peerId}`); } return peer; } constructor(data) { super(); if (data?.autoConsume !== void 0) { this.autoConsume = data.autoConsume; } if (data?.activeSpeakers && data.activeSpeakers.size !== this.__defaultActiveSpeakerSize) { this.__defaultActiveSpeakerSize = data.activeSpeakers.size; } } /** * Connects to the room and returns the instance of the room, * * @param data - roomId to connect to, make sure the roomId matches the roomId in the token used to connect the socket, else it will throw an error * @throws { Error } If the socket connection is not connected, or if the socket connection is connecting */ connect = (data) => { if (_Room.__instance === null) { throw new Error("Room Instance Not Initialized"); } if (!data.roomId) { throw new Error("Room Id is required to connect to the room"); } this.__roomId = data.roomId; if (!this.socket.connected) { throw new Error("Socket is Not Connected"); } if (this.socket.connecting) { throw new Error("Socket is Connecting, Wait for it to be connected"); } logger.info("\u{1F514} Connecting to the room"); this.socket.publish("connectRoom", { roomId: data.roomId }); this.__state = "connecting"; this.emit("room-connecting"); return _Room.__instance; }; /** * Admit a Peer to the room who is in the lobby * * @throws {Error} If the Peer Calling this Function is not an admin, then the permissions will not be updated */ admitPeer = checkPermissions({ admin: true }).validate( async (peerId) => { try { this.removeLobbyPeer(peerId); this.socket.publish("acceptLobbyPeer", { peerId }); } catch (error) { logger.error("\u{1F534} Error admitting peer", error); } } ); /** * Denies the peer from joining the room, who is in the lobby */ denyPeer = checkPermissions({ admin: true }).validate( async (peerId) => { try { this.removeLobbyPeer(peerId); this.socket.publish("denyLobbyPeer", { peerId }); } catch (error) { logger.error("\u{1F534} Error denying peer", error); } } ); /** * kick peer from room with respective peerId * @throws { Error } If the Peer Calling this Function is not an admin, then the permissions will not be updated */ kickPeer = checkPermissions({ admin: true }).validate( async (peerId) => { try { this.socket.publish("kickPeer", { peerId }); } catch (error) { logger.error("\u{1F534} Error denying peer", error); } } ); /** * closing the room for the current user, room will keep on running for the remote users */ close = (reason) => { try { logger.info("\u{1F534} Leaving the room"); this.__roomId = null; this.__sessionId = null; this.remotePeers.clear(); this.lobbyPeers.clear(); this.metadata = "{}"; if (this.__activeSpeakers) { this.__activeSpeakers.close(); } this.__activeSpeakers = null; this.__defaultActiveSpeakerSize = 8; this.__bot?.close(); this.__bot = null; this.state = "left"; this.emit("room-closed", { reason: reason || "LEFT" }); } catch (error) { logger.error("Error: Leaving the Room"); } }; }; var Room_default = Room; export { Room_default };