@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
JavaScript
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 };