UNPKG

@signalwire/js

Version:
1,706 lines (1,662 loc) 143 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/chat/index.ts var chat_exports = {}; __export(chat_exports, { ChatMember: () => ChatMember, ChatMessage: () => ChatMessage, Client: () => Client2 }); // src/createClient.ts import { configureStore, connect as connect6, getEventEmitter } from "@signalwire/core"; // src/Client.ts import { BaseClient, actions as actions3, Chat as ChatNamespace, PubSub as PubSubNamespace } from "@signalwire/core"; // src/features/mediaElements/mediaElementsSagas.ts import { getLogger, actions as actions2, sagaEffects, LOCAL_EVENT_PREFIX } from "@signalwire/core"; import { setMediaElementSinkId } from "@signalwire/webrtc"; // src/utils/audioElement.ts var setAudioMediaTrack = ({ track, element }) => { element.autoplay = true; element.playsinline = true; element.srcObject = new MediaStream([track]); track.addEventListener("ended", () => { element.srcObject = null; element.remove(); }); return element; }; // src/features/actions.ts import { actions } from "@signalwire/core"; var audioSetSpeakerAction = actions.createAction( "swJs/audioSetSpeakerAction" ); // src/features/mediaElements/mediaElementsSagas.ts var makeAudioElementSaga = ({ speakerId }) => { return function* audioElementSaga({ instance: room, runSaga }) { if (typeof Audio === "undefined") { getLogger().warn("`Audio` is not supported on this environment."); return; } try { const audioEl = room.getAudioEl(); let audioTask; const trackHandler = function(event) { switch (event.track.kind) { case "audio": { audioTask = runSaga(audioElementSetupWorker, { track: event.track, element: audioEl, speakerId, room }); break; } } }; room.on("track", trackHandler); room.once("destroy", () => { audioTask == null ? void 0 : audioTask.cancel(); }); } catch (error) { getLogger().error("audioElementSaga", error); } }; }; function* audioElementActionsWatcher({ element, room }) { const setSpeakerActionType = actions2.getCustomSagaActionType( room.__uuid, audioSetSpeakerAction ); while (true) { const action = yield sagaEffects.take([setSpeakerActionType]); try { switch (action.type) { case setSpeakerActionType: const response = yield sagaEffects.call( setMediaElementSinkId, element, action.payload ); room.emit( // @ts-expect-error `${LOCAL_EVENT_PREFIX}.speaker.updated`, action.payload ); room.settleCustomSagaTrigger({ dispatchId: action.dispatchId, payload: response, kind: "resolve" }); break; } } catch (error) { room.settleCustomSagaTrigger({ dispatchId: action.dispatchId, payload: error, kind: "reject" }); getLogger().error(error); } } } function* audioElementSetupWorker({ track, element, speakerId, room }) { setAudioMediaTrack({ track, element }); if (speakerId) { setMediaElementSinkId(element, speakerId).catch(() => { }); } yield sagaEffects.fork(audioElementActionsWatcher, { element, room }); } // src/cantina/VideoManager.ts import { connect, validateEventsToSubscribe, BaseConsumer } from "@signalwire/core"; // src/cantina/workers/videoManagerWorker.ts import { getLogger as getLogger4, sagaEffects as sagaEffects2 } from "@signalwire/core"; // src/cantina/workers/videoManagerRoomsWorker.ts import { getLogger as getLogger2, toExternalJSON, stripNamespacePrefix } from "@signalwire/core"; var videoManagerRoomsWorker = function* (options) { getLogger2().trace("videoManagerRoomsWorker started"); const { instance: client, action: { type, payload } } = options; const modPayload = { rooms: payload.rooms.map((row) => toExternalJSON(row)) }; client.emit( stripNamespacePrefix(type), // @ts-expect-error modPayload ); getLogger2().trace("videoManagerRoomsWorker ended"); }; // src/cantina/workers/videoManagerRoomWorker.ts import { getLogger as getLogger3, toExternalJSON as toExternalJSON2, stripNamespacePrefix as stripNamespacePrefix2 } from "@signalwire/core"; var videoManagerRoomWorker = function* (options) { getLogger3().trace("videoManagerRoomWorker started"); const { instance: client, action: { type, payload } } = options; client.emit( stripNamespacePrefix2(type), toExternalJSON2(payload) ); getLogger3().trace("videoManagerRoomWorker ended"); }; // src/cantina/workers/videoManagerWorker.ts var videoManagerWorker = function* (options) { getLogger4().trace("videoManagerWorker started"); const { channels: { swEventChannel } } = options; function* worker(action) { const { type } = action; switch (type) { case "video-manager.rooms.subscribed": yield sagaEffects2.fork(videoManagerRoomsWorker, __spreadValues({ action }, options)); break; case "video-manager.room.added": case "video-manager.room.deleted": case "video-manager.room.ended": case "video-manager.room.started": case "video-manager.room.updated": yield sagaEffects2.fork(videoManagerRoomWorker, __spreadValues({ action }, options)); break; default: getLogger4().warn(`Unknown video-manager event: "${type}"`); break; } } while (true) { const action = yield sagaEffects2.take( swEventChannel, (action2) => { return action2.type.startsWith("video-manager."); } ); yield sagaEffects2.fork(worker, action); } getLogger4().trace("videoManagerWorker ended"); }; // src/cantina/VideoManager.ts var VideoManagerAPI = class extends BaseConsumer { constructor(options) { super(options); this.runWorker("videoManagerWorker", { worker: videoManagerWorker }); } /** @internal */ getSubscriptions() { const eventNamesWithPrefix = this.eventNames().map( (event) => `video-manager.${event}` ); return validateEventsToSubscribe(eventNamesWithPrefix); } }; var createVideoManagerObject = (params) => { const manager = connect({ store: params.store, Component: VideoManagerAPI })(params); const proxy = new Proxy(manager, { get(target, property, receiver) { if (property === "_eventsNamespace") { return ""; } else if (property === "eventChannel") { return "video-manager.rooms"; } return Reflect.get(target, property, receiver); } }); return proxy; }; // src/buildVideoElement.ts import { getLogger as getLogger21, uuid as uuid2 } from "@signalwire/core"; // src/utils/videoElement.ts import { getLogger as getLogger20, debounce, uuid } from "@signalwire/core"; // src/VideoOverlays.ts import { getLogger as getLogger19 } from "@signalwire/core"; // src/video/VideoRoomSession.ts import { connect as connect3, extendComponent as extendComponent3, Rooms as Rooms6, validateEventsToSubscribe as validateEventsToSubscribe3 } from "@signalwire/core"; // src/BaseRoomSession.ts import { connect as connect2, LOCAL_EVENT_PREFIX as LOCAL_EVENT_PREFIX2 } from "@signalwire/core"; import { BaseConnection as BaseConnection2, createSpeakerDeviceWatcher, getDisplayMedia, getSpeakerById, supportsMediaOutput } from "@signalwire/webrtc"; // src/utils/constants.ts var SCREENSHARE_AUDIO_CONSTRAINTS = { echoCancellation: true, noiseSuppression: false, autoGainControl: false, // @ts-expect-error googAutoGainControl: false }; // src/utils/roomSession.ts import { getLogger as getLogger5 } from "@signalwire/core"; var getJoinMediaParams = (options) => { const { authState, audio = true, video = true, sendAudio, sendVideo, receiveAudio, receiveVideo } = options; getLogger5().debug("getJoinMediaParams options", __spreadValues({}, options)); const { audio_allowed, video_allowed, join_as } = authState; const joinAs = join_as != null ? join_as : "member"; const canSend = joinAs === "member"; const canSendAudio = canSend && audio_allowed === "both"; const canSendVideo = canSend && video_allowed === "both"; const canReceiveAudio = audio_allowed !== "none"; const canReceiveVideo = video_allowed !== "none"; const reqToSendAudio = Boolean(sendAudio != null ? sendAudio : audio); const reqToSendVideo = Boolean(sendVideo != null ? sendVideo : video); const reqToReceiveAudio = Boolean(receiveAudio != null ? receiveAudio : audio); const reqToReceiveVideo = Boolean(receiveVideo != null ? receiveVideo : video); if (!canSendAudio && reqToSendAudio) { getLogger5().info( "Not allowed to send audio on this room. Default values will be used." ); } if (!canSendVideo && reqToSendVideo) { getLogger5().info( "Not allowed to send video on this room. Default values will be used." ); } if (!canReceiveAudio && reqToReceiveAudio) { getLogger5().info( "Not allowed to receive video from the room. Default values will be used." ); } if (!canReceiveVideo && reqToReceiveVideo) { getLogger5().info( "Not allowed to receive video from the room. Default values will be used." ); } return { mustSendAudio: canSendAudio && reqToSendAudio, mustSendVideo: canSendVideo && reqToSendVideo, mustRecvAudio: canReceiveAudio && reqToReceiveAudio, mustRecvVideo: canReceiveVideo && reqToReceiveVideo }; }; var checkMediaParams = (options) => { return Object.values(options).some(Boolean); }; var SDK_PREFIX = "sw-sdk-"; var addSDKPrefix = (id) => { return `${SDK_PREFIX}${id}`; }; var OVERLAY_PREFIX = "sw-overlay-"; var addOverlayPrefix = (id) => { return `${OVERLAY_PREFIX}${id}`; }; // src/RoomSessionScreenShare.ts import { Rooms, extendComponent } from "@signalwire/core"; import { BaseConnection } from "@signalwire/webrtc"; var RoomSessionScreenShareConnection = class extends BaseConnection { join() { return super.invite(); } leave() { return super.hangup(); } }; var RoomSessionScreenShareAPI = extendComponent(RoomSessionScreenShareConnection, { audioMute: Rooms.audioMuteMember, audioUnmute: Rooms.audioUnmuteMember, videoMute: Rooms.videoMuteMember, videoUnmute: Rooms.videoUnmuteMember, setMicrophoneVolume: Rooms.setInputVolumeMember, setInputVolume: Rooms.setInputVolumeMember, setInputSensitivity: Rooms.setInputSensitivityMember }); // src/video/workers/memberListUpdatedWorker.ts import { sagaEffects as sagaEffects3, toSyntheticEvent, validateEventsToSubscribe as validateEventsToSubscribe2, toInternalEventName } from "@signalwire/core"; var noop = () => { }; var EXTERNAL_MEMBER_LIST_UPDATED_EVENT = "memberList.updated"; var INTERNAL_MEMBER_LIST_UPDATED_EVENT = toInternalEventName({ event: EXTERNAL_MEMBER_LIST_UPDATED_EVENT }); var SYNTHETIC_MEMBER_LIST_UPDATED_EVENT = toSyntheticEvent( INTERNAL_MEMBER_LIST_UPDATED_EVENT ); var MEMBER_LIST_EVENTS = [ /** Alias to `video.room.subscribed` */ "video.room.joined", "video.member.joined", "video.member.left", "video.member.updated" ]; var isMemberListEvent = (event) => { return MEMBER_LIST_EVENTS.includes(event); }; var getMemberListEventsToSubscribe = (subscriptions) => { return validateEventsToSubscribe2(MEMBER_LIST_EVENTS).filter((event) => { return !subscriptions.includes(event); }); }; var shouldHandleMemberList = (subscriptions) => { return subscriptions.some( (event) => event.includes(EXTERNAL_MEMBER_LIST_UPDATED_EVENT) ); }; var getMembersFromAction = (action) => { if (action.type === "video.room.joined") { return action.payload.room_session.members; } return [action.payload.member]; }; var getUpdatedMembers = ({ action, memberList }) => { const actionMembers = getMembersFromAction(action); switch (action.type) { case "video.member.left": actionMembers.forEach((member) => { memberList.delete(member.id); }); break; default: actionMembers.forEach((member) => { memberList.set(member.id, member); }); } return Array.from(memberList.values()); }; var initMemberListSubscriptions = (room, subscriptions) => { const events = getMemberListEventsToSubscribe(subscriptions); events.forEach((event) => { room.once(event, noop); }); const eventBridgeHandler = ({ members }) => { room.emit(EXTERNAL_MEMBER_LIST_UPDATED_EVENT, { members }); }; room.on(SYNTHETIC_MEMBER_LIST_UPDATED_EVENT, eventBridgeHandler); const cleanup = () => { room.off(SYNTHETIC_MEMBER_LIST_UPDATED_EVENT, eventBridgeHandler); }; return { cleanup }; }; function* membersListUpdatedWatcher({ swEventChannel, instance }) { const memberList = /* @__PURE__ */ new Map(); function* worker(pubSubAction) { const roomSessionId = pubSubAction.type === "video.room.joined" ? pubSubAction.payload.room_session.id : pubSubAction.payload.room_session_id; const members = getUpdatedMembers({ action: pubSubAction, memberList }); const memberListPayload = { /** * At this point it's needed to send the * `room_session_id` so the pubSubSaga can properly * infer the namespace for emitting the events to the * appropiate room. */ room_session_id: roomSessionId, members }; instance.emit(SYNTHETIC_MEMBER_LIST_UPDATED_EVENT, memberListPayload); } while (true) { const pubSubAction = yield sagaEffects3.take( swEventChannel, ({ type }) => { return isMemberListEvent(type); } ); yield sagaEffects3.fork(worker, pubSubAction); } } var memberListUpdatedWorker = function* membersChangedWorker({ channels: { swEventChannel }, instance }) { const subscriptions = instance.getSubscriptions(); if (!shouldHandleMemberList(subscriptions)) { return; } const { cleanup } = initMemberListSubscriptions(instance, subscriptions); yield sagaEffects3.fork(membersListUpdatedWatcher, { swEventChannel, instance }); instance.once("destroy", () => { cleanup(); }); }; // src/video/workers/childMemberJoinedWorker.ts import { getLogger as getLogger6, sagaEffects as sagaEffects4, componentSelectors, componentActions } from "@signalwire/core"; var childMemberJoinedWorker = function* (options) { getLogger6().trace("childMemberJoinedWorker started"); const { channels, instance, initialState, onDone, onFail } = options; const { swEventChannel } = channels; const { parentId } = initialState; if (!parentId) { throw new Error("Missing parentId for childMemberJoinedWorker"); } const action = yield sagaEffects4.take(swEventChannel, (action2) => { if (action2.type === "video.member.joined") { return action2.payload.member.parent_id === parentId; } return false; }); const { member } = action.payload; if (member == null ? void 0 : member.parent_id) { const byId = yield sagaEffects4.select( componentSelectors.getComponentsById ); const parent = Object.values(byId).find((row) => { return "memberId" in row && row.memberId === member.parent_id; }); if (parent) { yield sagaEffects4.put( componentActions.upsert({ id: instance.callId, roomId: action.payload.room_id, roomSessionId: action.payload.room_session_id, memberId: member.id }) ); onDone == null ? void 0 : onDone(); } else { onFail == null ? void 0 : onFail({ error: new Error("Unknown parent_id") }); } } getLogger6().trace("childMemberJoinedWorker ended"); }; // src/video/workers/videoWorker.ts import { getLogger as getLogger10, sagaEffects as sagaEffects5, MemberPosition, stripNamespacePrefix as stripNamespacePrefix5 } from "@signalwire/core"; // src/video/workers/videoStreamWorker.ts import { getLogger as getLogger7, Rooms as Rooms2 } from "@signalwire/core"; var videoStreamWorker = function* (options) { getLogger7().trace("videoStreamWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let streamInstance = get(payload.stream.id); if (!streamInstance) { streamInstance = Rooms2.createRoomSessionStreamObject({ store: roomSession.store, payload }); } else { streamInstance.setPayload(payload); } set(payload.stream.id, streamInstance); switch (type) { case "video.stream.started": roomSession.emit("stream.started", streamInstance); break; case "video.stream.ended": roomSession.emit("stream.ended", streamInstance); remove(payload.stream.id); break; default: getLogger7().warn(`Unknown video.stream event: "${type}"`); break; } getLogger7().trace("videoStreamWorker ended"); }; // src/video/workers/videoRecordWorker.ts import { getLogger as getLogger8, Rooms as Rooms3, stripNamespacePrefix as stripNamespacePrefix3 } from "@signalwire/core"; var videoRecordWorker = function* (options) { getLogger8().trace("videoRecordWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let recordingInstance = get(payload.recording.id); if (!recordingInstance) { recordingInstance = Rooms3.createRoomSessionRecordingObject({ store: roomSession.store, payload }); } else { recordingInstance.setPayload(payload); } set(payload.recording.id, recordingInstance); const event = stripNamespacePrefix3(type); switch (type) { case "video.recording.started": case "video.recording.updated": { roomSession.emit(event, recordingInstance); break; } case "video.recording.ended": roomSession.emit(event, recordingInstance); remove(payload.recording.id); break; default: getLogger8().warn(`Unknown video.stream event: "${type}"`); break; } getLogger8().trace("videoRecordWorker ended"); }; // src/video/workers/videoPlaybackWorker.ts import { getLogger as getLogger9, Rooms as Rooms4, stripNamespacePrefix as stripNamespacePrefix4 } from "@signalwire/core"; var videoPlaybackWorker = function* (options) { getLogger9().trace("videoPlaybackWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let playbackInstance = get(payload.playback.id); if (!playbackInstance) { playbackInstance = Rooms4.createRoomSessionPlaybackObject({ store: roomSession.store, payload }); } else { playbackInstance.setPayload(payload); } set(payload.playback.id, playbackInstance); const event = stripNamespacePrefix4(type); switch (type) { case "video.playback.started": case "video.playback.updated": { roomSession.emit(event, playbackInstance); break; } case "video.playback.ended": roomSession.emit(event, playbackInstance); remove(payload.playback.id); break; default: getLogger9().warn(`Unknown video.stream event: "${type}"`); break; } getLogger9().trace("videoPlaybackWorker ended"); }; // src/video/workers/videoWorker.ts var videoWorker = function* (options) { getLogger10().trace("videoWorker started"); const { channels, instance: roomSession } = options; const { swEventChannel } = channels; function* worker(action) { const { type, payload } = action; switch (type) { case "video.room.subscribed": yield sagaEffects5.fork(MemberPosition.memberPositionWorker, __spreadProps(__spreadValues({}, options), { instance: roomSession, initialState: payload })); return; case "video.playback.started": case "video.playback.updated": case "video.playback.ended": yield sagaEffects5.fork(videoPlaybackWorker, __spreadValues({ action }, options)); return; // Return since we don't need to handle the raw event for this case "video.recording.started": case "video.recording.updated": case "video.recording.ended": yield sagaEffects5.fork(videoRecordWorker, __spreadValues({ action }, options)); return; case "video.stream.ended": case "video.stream.started": yield sagaEffects5.fork(videoStreamWorker, __spreadValues({ action }, options)); return; case "video.room.audience_count": { roomSession.emit("room.audienceCount", payload); return; } case "video.member.talking": { const { member } = payload; if ("talking" in member) { const suffix = member.talking ? "started" : "ended"; roomSession.emit(`member.talking.${suffix}`, payload); const deprecatedSuffix = member.talking ? "start" : "stop"; roomSession.emit(`member.talking.${deprecatedSuffix}`, payload); } break; } case "video.layout.changed": { roomSession.currentLayoutEvent = action.payload; break; } default: break; } const event = stripNamespacePrefix5(type, "video"); roomSession.emit(event, payload); } const isVideoEvent = (action) => { return action.type.startsWith("video."); }; while (true) { const action = yield sagaEffects5.take( swEventChannel, isVideoEvent ); yield sagaEffects5.fork(worker, action); } getLogger10().trace("videoWorker ended"); }; // src/BaseRoomSession.ts var BaseRoomSessionConnection = class extends BaseConnection2 { constructor() { super(...arguments); __publicField(this, "_screenShareList", /* @__PURE__ */ new Set()); __publicField(this, "_audioEl"); __publicField(this, "_overlayMap"); __publicField(this, "_localVideoOverlay"); } get audioEl() { return this._audioEl; } set overlayMap(map) { this._overlayMap = map; } get overlayMap() { return this._overlayMap; } set localVideoOverlay(overlay) { this._localVideoOverlay = overlay; } get localVideoOverlay() { return this._localVideoOverlay; } get screenShareList() { return Array.from(this._screenShareList); } _attachSpeakerTrackListener() { if (!supportsMediaOutput()) return; createSpeakerDeviceWatcher().then((deviceWatcher) => { deviceWatcher.on("removed", async (data) => { var _a, _b; const sinkId = this._audioEl.sinkId; const disconnectedSpeaker = data.changes.find((device) => { const payloadDeviceId = device.payload.deviceId; return payloadDeviceId === sinkId || payloadDeviceId === "" && sinkId === "default" || payloadDeviceId === "default" && sinkId === ""; }); if (disconnectedSpeaker) { this.emit("speaker.disconnected", { deviceId: disconnectedSpeaker.payload.deviceId, label: disconnectedSpeaker.payload.label }); await ((_b = (_a = this._audioEl).setSinkId) == null ? void 0 : _b.call(_a, "")); const defaultSpeakers = await getSpeakerById("default"); if (!(defaultSpeakers == null ? void 0 : defaultSpeakers.deviceId)) return; this.emit("speaker.updated", { previous: { deviceId: disconnectedSpeaker.payload.deviceId, label: disconnectedSpeaker.payload.label }, current: { deviceId: defaultSpeakers.deviceId, label: defaultSpeakers.label } }); } }); }); } /** @internal */ _finalize() { this._screenShareList.clear(); super._finalize(); } /** @internal */ async hangup(id) { this._screenShareList.forEach((screenShare) => { screenShare.leave(); }); return super.hangup(id); } leave() { return this.hangup(); } /** * This method will be called by `join()` right before the * `connect()` happens and it's a way for us to control * exactly when the workers are attached. * @internal */ attachPreConnectWorkers() { this.runWorker("memberListUpdated", { worker: memberListUpdatedWorker }); } /** @internal */ getAudioEl() { if (this._audioEl) return this._audioEl; this._audioEl = new Audio(); this._attachSpeakerTrackListener(); return this._audioEl; } getMemberOverlay(memberId) { return this.overlayMap.get(addOverlayPrefix(memberId)); } /** * Allow sharing the screen within the room. */ async startScreenShare(opts = {}) { return new Promise(async (resolve, reject) => { var _a; const { autoJoin = true, audio = false, video = true, layout, positions } = opts; try { const displayStream = await getDisplayMedia({ audio: audio === true ? SCREENSHARE_AUDIO_CONSTRAINTS : audio, video }); const options = __spreadProps(__spreadValues({}, this.options), { screenShare: true, recoverCall: false, localStream: displayStream, remoteStream: void 0, userVariables: __spreadProps(__spreadValues({}, ((_a = this.options) == null ? void 0 : _a.userVariables) || {}), { memberCallId: this.callId, memberId: this.memberId }), layout, positions }); const screenShare = connect2({ store: this.store, Component: RoomSessionScreenShareAPI })(options); displayStream.getVideoTracks().forEach((t) => { t.addEventListener("ended", () => { if (screenShare && screenShare.active) { screenShare.leave(); } }); }); screenShare.once("destroy", () => { screenShare.emit("room.left"); this._screenShareList.delete(screenShare); }); screenShare.runWorker("childMemberJoinedWorker", { worker: childMemberJoinedWorker, onDone: () => resolve(screenShare), onFail: reject, initialState: { parentId: this.memberId } }); this._screenShareList.add(screenShare); if (autoJoin) { return await screenShare.join(); } return resolve(screenShare); } catch (error) { this.logger.error("ScreenShare Error", error); reject(error); } }); } updateSpeaker({ deviceId }) { const prevId = this.audioEl.sinkId; this.once( // @ts-expect-error `${LOCAL_EVENT_PREFIX2}.speaker.updated`, async (newId) => { const prevSpeaker = await getSpeakerById(prevId); const newSpeaker = await getSpeakerById(newId); const isSame = (newSpeaker == null ? void 0 : newSpeaker.deviceId) === (prevSpeaker == null ? void 0 : prevSpeaker.deviceId); if (!(newSpeaker == null ? void 0 : newSpeaker.deviceId) || isSame) return; this.emit("speaker.updated", { previous: { deviceId: prevSpeaker == null ? void 0 : prevSpeaker.deviceId, label: prevSpeaker == null ? void 0 : prevSpeaker.label }, current: { deviceId: newSpeaker.deviceId, label: newSpeaker.label } }); } ); return this.triggerCustomSaga(audioSetSpeakerAction(deviceId)); } }; // src/RoomSessionDevice.ts import { Rooms as Rooms5, extendComponent as extendComponent2 } from "@signalwire/core"; import { BaseConnection as BaseConnection3 } from "@signalwire/webrtc"; var RoomSessionDeviceConnection = class extends BaseConnection3 { join() { return super.invite(); } leave() { return super.hangup(); } }; var RoomSessionDeviceAPI = extendComponent2(RoomSessionDeviceConnection, { audioMute: Rooms5.audioMuteMember, audioUnmute: Rooms5.audioUnmuteMember, videoMute: Rooms5.videoMuteMember, videoUnmute: Rooms5.videoUnmuteMember, setInputVolume: Rooms5.setInputVolumeMember, setMicrophoneVolume: Rooms5.setInputVolumeMember, setInputSensitivity: Rooms5.setInputSensitivityMember }); // src/video/VideoRoomSession.ts var VideoRoomSessionConnection = class extends BaseRoomSessionConnection { constructor(options) { super(options); __publicField(this, "_deviceList", /* @__PURE__ */ new Set()); __publicField(this, "_currentLayoutEvent"); this.initWorker(); } set currentLayoutEvent(event) { this._currentLayoutEvent = event; } get currentLayoutEvent() { return this._currentLayoutEvent; } get currentLayout() { var _a; return (_a = this._currentLayoutEvent) == null ? void 0 : _a.layout; } get currentPosition() { var _a, _b; return (_b = (_a = this._currentLayoutEvent) == null ? void 0 : _a.layout.layers.find( (layer) => layer.member_id === this.memberId )) == null ? void 0 : _b.position; } get deviceList() { return Array.from(this._deviceList); } get interactivityMode() { return this.select(({ session }) => { var _a; const { authState } = session; return (_a = authState == null ? void 0 : authState.join_as) != null ? _a : ""; }); } get permissions() { return this.select(({ session }) => { var _a, _b; const { authState } = session; return (_b = (_a = authState == null ? void 0 : authState.room) == null ? void 0 : _a.scopes) != null ? _b : []; }); } initWorker() { this.runWorker("videoWorker", { worker: videoWorker }); } /** @internal */ getSubscriptions() { const eventNamesWithPrefix = this.eventNames().map((event) => { return `video.${String(event)}`; }); return validateEventsToSubscribe3( eventNamesWithPrefix ); } /** @internal */ _finalize() { this._deviceList.clear(); super._finalize(); } /** @internal */ async hangup(id) { this._deviceList.forEach((device) => { device.leave(); }); return super.hangup(id); } join() { return super.invite(); } /** * @deprecated Use {@link getLayouts} instead. `getLayoutList` will * be removed in v3.0.0 */ getLayoutList() { return this.getLayouts(); } /** * @deprecated Use {@link getMembers} instead. `getMemberList` will * be removed in v3.0.0 */ getMemberList() { return this.getMembers(); } /** @deprecated Use {@link startScreenShare} instead. */ async createScreenShareObject(opts = {}) { return this.startScreenShare(opts); } /** * Allow to add a camera to the room. */ addCamera(opts = {}) { const _a = opts, { autoJoin = true } = _a, video = __objRest(_a, ["autoJoin"]); return this.addDevice({ autoJoin, video }); } /** * Allow to add a microphone to the room. */ addMicrophone(opts = {}) { const _a = opts, { autoJoin = true } = _a, audio = __objRest(_a, ["autoJoin"]); return this.addDevice({ autoJoin, audio }); } /** * Allow to add additional devices to the room like cameras or microphones. */ async addDevice(opts = {}) { return new Promise(async (resolve, reject) => { var _a; const { autoJoin = true, audio = false, video = false } = opts; if (!audio && !video) { throw new TypeError( "At least one of `audio` or `video` must be requested." ); } const options = __spreadProps(__spreadValues({}, this.options), { localStream: void 0, remoteStream: void 0, audio, video, additionalDevice: true, recoverCall: false, userVariables: __spreadProps(__spreadValues({}, ((_a = this.options) == null ? void 0 : _a.userVariables) || {}), { memberCallId: this.callId, memberId: this.memberId }) }); const roomDevice = connect3({ store: this.store, Component: RoomSessionDeviceAPI })(options); roomDevice.once("destroy", () => { roomDevice.emit("room.left"); this._deviceList.delete(roomDevice); }); try { roomDevice.runWorker("childMemberJoinedWorker", { worker: childMemberJoinedWorker, onDone: () => resolve(roomDevice), onFail: reject, initialState: { parentId: this.memberId } }); this._deviceList.add(roomDevice); if (autoJoin) { return await roomDevice.join(); } return resolve(roomDevice); } catch (error) { this.logger.error("RoomDevice Error", error); reject(error); } }); } }; var VideoRoomSessionAPI = extendComponent3(VideoRoomSessionConnection, { audioMute: Rooms6.audioMuteMember, audioUnmute: Rooms6.audioUnmuteMember, videoMute: Rooms6.videoMuteMember, videoUnmute: Rooms6.videoUnmuteMember, deaf: Rooms6.deafMember, undeaf: Rooms6.undeafMember, setInputVolume: Rooms6.setInputVolumeMember, setOutputVolume: Rooms6.setOutputVolumeMember, setMicrophoneVolume: Rooms6.setInputVolumeMember, setSpeakerVolume: Rooms6.setOutputVolumeMember, setInputSensitivity: Rooms6.setInputSensitivityMember, removeMember: Rooms6.removeMember, removeAllMembers: Rooms6.removeAllMembers, getMembers: Rooms6.getMembers, getLayouts: Rooms6.getLayouts, setLayout: Rooms6.setLayout, setPositions: Rooms6.setPositions, setMemberPosition: Rooms6.setMemberPosition, hideVideoMuted: Rooms6.hideVideoMuted, showVideoMuted: Rooms6.showVideoMuted, getRecordings: Rooms6.getRecordings, startRecording: Rooms6.startRecording, getPlaybacks: Rooms6.getPlaybacks, play: Rooms6.play, setHideVideoMuted: Rooms6.setHideVideoMuted, getMeta: Rooms6.getMeta, setMeta: Rooms6.setMeta, updateMeta: Rooms6.updateMeta, deleteMeta: Rooms6.deleteMeta, getMemberMeta: Rooms6.getMemberMeta, setMemberMeta: Rooms6.setMemberMeta, updateMemberMeta: Rooms6.updateMemberMeta, deleteMemberMeta: Rooms6.deleteMemberMeta, promote: Rooms6.promote, demote: Rooms6.demote, getStreams: Rooms6.getStreams, startStream: Rooms6.startStream, lock: Rooms6.lock, unlock: Rooms6.unlock, setRaisedHand: Rooms6.setRaisedHand, setPrioritizeHandraise: Rooms6.setPrioritizeHandraise }); var isVideoRoomSession = (room) => { return room instanceof VideoRoomSessionConnection; }; var createVideoRoomSessionObject = (params) => { const room = connect3({ store: params.store, customSagas: params.customSagas, Component: VideoRoomSessionAPI })(params); return room; }; // src/fabric/FabricRoomSession.ts import { connect as connect5 } from "@signalwire/core"; // src/utils/storage.ts import jwtDecode from "jwt-decode"; import { getLogger as getLogger11 } from "@signalwire/core"; var getStorage = () => { if (window && window.sessionStorage) { return window.sessionStorage; } return void 0; }; var sessionStorageManager = (token) => { var _a; let roomName = ""; try { const jwtPayload = jwtDecode(token); roomName = (_a = jwtPayload == null ? void 0 : jwtPayload.r) != null ? _a : ""; } catch (e) { try { const jwtPayload = jwtDecode(token, { header: true }); roomName = jwtPayload.typ || ""; } catch (e2) { if (process.env.NODE_ENV !== "production") { getLogger11().error("[sessionStorageManager] error decoding JWT", token); } roomName = ""; } } const valid = Boolean(roomName); return { authStateKey: valid && `as-${roomName}`, protocolKey: valid && `pt-${roomName}`, callIdKey: valid && `ci-${roomName}` }; }; // src/fabric/utils/constants.ts var PREVIOUS_CALLID_STORAGE_KEY = "ci-SAT"; // src/fabric/workers/wsClientWorker.ts import { getLogger as getLogger12, sagaEffects as sagaEffects6 } from "@signalwire/core"; var wsClientWorker = function* (options) { getLogger12().debug("wsClientWorker started"); const { channels, initialState, instance: client } = options; const { swEventChannel } = channels; const { handleIncomingInvite } = initialState; function* fireHoseWorker(action) { client.emit(action.type, action.payload); } function* vertoInviteWorker(action) { handleIncomingInvite(action.payload.params); } const isVertoInvite = (action) => { if (action.type === "webrtc.message") { return action.payload.method === "verto.invite"; } return false; }; try { while (true) { const action = yield sagaEffects6.take( swEventChannel, () => true ); yield sagaEffects6.fork(fireHoseWorker, action); if (isVertoInvite(action)) { getLogger12().debug("Receiving a call over WebSocket", action); yield sagaEffects6.fork( vertoInviteWorker, action ); } } } finally { getLogger12().trace("wsClientWorker ended"); } }; // src/fabric/workers/conversationWorker.ts import { getLogger as getLogger13, sagaEffects as sagaEffects7 } from "@signalwire/core"; var conversationWorker = function* (options) { getLogger13().debug("conversationWorker started"); const { channels: { swEventChannel }, initialState } = options; const { conversation } = initialState; const isConversationEvent = (action) => { return action.type.startsWith("conversation."); }; while (true) { const action = yield sagaEffects7.take( swEventChannel, isConversationEvent ); conversation.handleEvent(action.payload); } getLogger13().trace("conversationWorker ended"); }; // src/fabric/workers/fabricWorker.ts import { getLogger as getLogger18, sagaEffects as sagaEffects10 } from "@signalwire/core"; // src/fabric/FabricRoomSessionMember.ts import { connect as connect4, BaseComponent } from "@signalwire/core"; var FabricRoomSessionMemberAPI = class extends BaseComponent { constructor(options) { super(options); __publicField(this, "_payload"); this._payload = options.payload; } get id() { return this._payload.member.member_id; } get callId() { return this._payload.member.call_id; } get nodeId() { return this._payload.member.node_id; } get memberId() { return this.id; } get roomSessionId() { return this._payload.room_session_id; } get roomId() { return this._payload.room_id; } get parentId() { return this._payload.member.parent_id; } get name() { return this._payload.member.name; } get type() { return this._payload.member.type; } get requestedPosition() { return this._payload.member.requested_position; } get currentPosition() { return this._payload.member.current_position; } get meta() { return this._payload.member.meta; } get handraised() { return this._payload.member.handraised; } get talking() { return this._payload.member.talking; } get audioMuted() { return this._payload.member.audio_muted; } get videoMuted() { return this._payload.member.video_muted; } get deaf() { return this._payload.member.deaf; } get visible() { return this._payload.member.visible; } get inputVolume() { return this._payload.member.input_volume; } get outputVolume() { return this._payload.member.output_volume; } get inputSensitivity() { return this._payload.member.input_sensitivity; } get subscriberData() { return this._payload.member.subscriber_data; } /** @internal */ setPayload(payload) { const newPayload = __spreadProps(__spreadValues(__spreadValues({}, this._payload), payload), { member: __spreadValues(__spreadValues({}, this._payload.member), payload.member) }); this._payload = newPayload; } }; var createFabricRoomSessionMemberObject = (params) => { const member = connect4({ store: params.store, Component: FabricRoomSessionMemberAPI })(params); return member; }; // src/fabric/workers/callSegmentWorker.ts import { getLogger as getLogger17, sagaEffects as sagaEffects9 } from "@signalwire/core"; // src/fabric/workers/callLeftWorker.ts import { getLogger as getLogger14 } from "@signalwire/core"; var callLeftWorker = function* (options) { getLogger14().trace("callLeftWorker started"); const { action: { payload }, instance: cfRoomSession, instanceMap } = options; const { room_session_id } = payload; instanceMap.getAll().forEach(([key, obj]) => { if (obj instanceof FabricRoomSessionMemberAPI && obj.roomSessionId === room_session_id) { instanceMap.remove(key); } }); cfRoomSession.emit("call.left", payload); cfRoomSession.emit("room.left", payload); getLogger14().trace("callLeftWorker ended"); }; // src/fabric/workers/callJoinWorker.ts import { getLogger as getLogger16, sagaEffects as sagaEffects8, MemberPosition as MemberPosition2, stripNamespacePrefix as stripNamespacePrefix6 } from "@signalwire/core"; // src/fabric/workers/fabricMemberWorker.ts import { fromSnakeToCamelCase, getLogger as getLogger15 } from "@signalwire/core"; var fabricMemberWorker = function* (options) { getLogger15().trace("fabricMemberWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; const memberId = payload.member.member_id; let memberInstance = get(memberId); if (!memberInstance && type !== "member.talking") { memberInstance = createFabricRoomSessionMemberObject({ store: roomSession.store, payload }); } if (memberInstance) { memberInstance.setPayload(payload); } set(memberId, memberInstance); if (type.startsWith("member.updated.")) { const clientType = fromSnakeToCamelCase(type); roomSession.emit(clientType, payload); } switch (type) { case "member.joined": roomSession.emit(type, payload); break; case "member.updated": roomSession.emit(type, payload); break; case "member.left": roomSession.emit(type, payload); remove(memberId); break; case "member.talking": roomSession.emit(type, payload); break; default: break; } getLogger15().trace("fabricMemberWorker ended"); }; // src/fabric/utils/helpers.ts var mapInternalFabricMemberToInternalVideoMemberEntity = (params) => { return { id: params.member_id, room_id: params.room_id, room_session_id: params.room_session_id, name: params.name, type: params.type, handraised: params.handraised, visible: params.visible, audio_muted: params.audio_muted, video_muted: params.video_muted, deaf: params.deaf, input_volume: params.input_volume, output_volume: params.output_volume, input_sensitivity: params.input_sensitivity, meta: params.meta, talking: params.talking, current_position: params.current_position, requested_position: params.requested_position, parent_id: params.parent_id }; }; var mapInternalFabricMemberToInternalVideoMemberUpdatedEntity = (params) => { return __spreadProps(__spreadValues({}, mapInternalFabricMemberToInternalVideoMemberEntity(params)), { updated: params.updated.map( (key) => key === "member_id" ? "id" : key ) }); }; var mapInternalFabricRoomToInternalVideoRoomEntity = (params) => { return { room_id: params.room_id, room_session_id: params.room_session_id, event_channel: params.event_channel, name: params.name, recording: params.recording, hide_video_muted: params.hide_video_muted, preview_url: params.preview_url, recordings: params.recordings, playbacks: params.playbacks, streams: params.streams, prioritize_handraise: params.prioritize_handraise }; }; var mapInternalFabricRoomToInternalVideoRoomSessionEntity = (params) => { return { id: params.id, display_name: params.display_name, room_id: params.room_id, room_session_id: params.room_session_id, event_channel: params.event_channel, name: params.name, recording: params.recording, recordings: params.recordings, playbacks: params.playbacks, hide_video_muted: params.hide_video_muted, preview_url: params.preview_url, layout_name: params.layout_name, locked: params.locked, meta: params.meta, members: params.members.map( mapInternalFabricMemberToInternalVideoMemberEntity ), streaming: params.streaming, streams: params.streams, prioritize_handraise: params.prioritize_handraise, updated: params.updated }; }; var mapCallJoinedToRoomSubscribedEventParams = (params) => { return { call_id: params.call_id, member_id: params.member_id, room: __spreadProps(__spreadValues({}, mapInternalFabricRoomToInternalVideoRoomEntity(params.room_session)), { members: params.room_session.members.map( mapInternalFabricMemberToInternalVideoMemberEntity ) }), room_session: __spreadProps(__spreadValues({}, mapInternalFabricRoomToInternalVideoRoomSessionEntity( params.room_session )), { members: params.room_session.members.map( mapInternalFabricMemberToInternalVideoMemberEntity ) }) }; }; var mapFabricMemberToVideoMemberJoinAndLeftEventParams = (params) => { return { room_session_id: params.room_session_id, room_id: params.room_id, member: mapInternalFabricMemberToInternalVideoMemberEntity(params.member) }; }; var mapFabricMemberActionToVideoMemberJoinAndLeftAction = (action) => { return { type: `video.${action.type}`, payload: mapFabricMemberToVideoMemberJoinAndLeftEventParams(action.payload) }; }; var mapFabricMemberEventToVideoMemberUpdatedEventParams = (params) => { return { room_session_id: params.room_session_id, room_id: params.room_id, member: mapInternalFabricMemberToInternalVideoMemberUpdatedEntity( params.member ) }; }; var mapFabricMemberActionToVideoMemberUpdatedAction = (action) => { return { type: `video.${action.type}`, payload: mapFabricMemberEventToVideoMemberUpdatedEventParams( action.payload ) }; }; var mapFabricLayoutActionToVideoLayoutAction = (action) => { return { type: `video.${action.type}`, payload: action.payload }; }; // src/fabric/utils/capabilitiesHelpers.ts var CapabilityOnOffState = class { constructor(_flags) { this._flags = _flags; } get on() { return this._flags.some((flag) => !flag.endsWith(".off")); } get off() { return this._flags.some((flag) => !flag.endsWith(".on")); } }; var MemberCapability = class { constructor(_flags, _memberType) { this._flags = _flags; this._memberType = _memberType; __publicField(this, "_muteAudio"); __publicField(this, "_muteVideo"); __publicField(this, "_deaf"); } get muteAudio() { var _a; this._muteAudio = (_a = this._muteAudio) != null ? _a : new CapabilityOnOffState( this._flags.filter( (flag) => flag === this._memberType || flag === `${this._memberType}.mute` || flag.startsWith(`${this._memberType}.mute.audio`) ) ); return this._muteAudio; } get muteVideo() { var _a; this._muteVideo = (_a = this._muteVideo) != null ? _a : new CapabilityOnOffState( this._flags.filter( (flag) => flag === this._memberType || flag === `${this._memberType}.mute` || flag.startsWith(`${this._memberType}.mute.video`) ) ); return this._muteVideo; } get microphoneVolume() { return this._flags.some( (flag) => flag === this._memberType || flag === `${this._memberType}.microphone` || flag.startsWith(`${this._memberType}.microphone.volume`) ); } get microphoneSensitivity() { return this._flags.some( (flag) => flag === this._memberType || flag === `${this._memberType}.microphone` || flag.startsWith(`${this._memberType}.microphone.sensitivity`) ); } get speakerVolume() { return this._flags.some( (flag) => flag === this._memberType || flag === `${this._memberType}.speaker` || flag.startsWith(`${this._memberType}.speaker.volume`) ); } get deaf() { var _a; this._deaf = (_a = this._deaf) != null ? _a : new CapabilityOnOffState