UNPKG

@signalwire/js

Version:
1,557 lines (1,513 loc) 151 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __reflectGet = Reflect.get; 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 __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var index_exports = {}; __export(index_exports, { Chat: () => chat_exports, Fabric: () => fabric_exports, LocalVideoOverlay: () => LocalVideoOverlay, PubSub: () => pubSub_exports, SignalWire: () => SignalWire, UserOverlay: () => UserOverlay, Video: () => video_exports, WebRTC: () => webrtc_exports, buildVideoElement: () => buildVideoElement }); module.exports = __toCommonJS(index_exports); // src/chat/index.ts var chat_exports = {}; __export(chat_exports, { ChatMember: () => import_core35.ChatMember, ChatMessage: () => import_core35.ChatMessage, Client: () => Client2 }); // src/createClient.ts var import_core34 = require("@signalwire/core"); // src/Client.ts var import_core31 = require("@signalwire/core"); // src/features/mediaElements/mediaElementsSagas.ts var import_core2 = require("@signalwire/core"); var import_webrtc = require("@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 var import_core = require("@signalwire/core"); var audioSetSpeakerAction = import_core.actions.createAction( "swJs/audioSetSpeakerAction" ); // src/features/mediaElements/mediaElementsSagas.ts var makeAudioElementSaga = ({ speakerId }) => { return function* audioElementSaga({ instance: room, runSaga }) { if (typeof Audio === "undefined") { (0, import_core2.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) { (0, import_core2.getLogger)().error("audioElementSaga", error); } }; }; function* audioElementActionsWatcher({ element, room }) { const setSpeakerActionType = import_core2.actions.getCustomSagaActionType( room.__uuid, audioSetSpeakerAction ); while (true) { const action = yield import_core2.sagaEffects.take([setSpeakerActionType]); try { switch (action.type) { case setSpeakerActionType: const response = yield import_core2.sagaEffects.call( import_webrtc.setMediaElementSinkId, element, action.payload ); room.emit( // @ts-expect-error `${import_core2.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" }); (0, import_core2.getLogger)().error(error); } } } function* audioElementSetupWorker({ track, element, speakerId, room }) { setAudioMediaTrack({ track, element }); if (speakerId) { (0, import_webrtc.setMediaElementSinkId)(element, speakerId).catch(() => { }); } yield import_core2.sagaEffects.fork(audioElementActionsWatcher, { element, room }); } // src/cantina/VideoManager.ts var import_core6 = require("@signalwire/core"); // src/cantina/workers/videoManagerWorker.ts var import_core5 = require("@signalwire/core"); // src/cantina/workers/videoManagerRoomsWorker.ts var import_core3 = require("@signalwire/core"); var videoManagerRoomsWorker = function* (options) { (0, import_core3.getLogger)().trace("videoManagerRoomsWorker started"); const { instance: client, action: { type, payload } } = options; const modPayload = { rooms: payload.rooms.map((row) => (0, import_core3.toExternalJSON)(row)) }; client.emit( (0, import_core3.stripNamespacePrefix)(type), // @ts-expect-error modPayload ); (0, import_core3.getLogger)().trace("videoManagerRoomsWorker ended"); }; // src/cantina/workers/videoManagerRoomWorker.ts var import_core4 = require("@signalwire/core"); var videoManagerRoomWorker = function* (options) { (0, import_core4.getLogger)().trace("videoManagerRoomWorker started"); const { instance: client, action: { type, payload } } = options; client.emit( (0, import_core4.stripNamespacePrefix)(type), (0, import_core4.toExternalJSON)(payload) ); (0, import_core4.getLogger)().trace("videoManagerRoomWorker ended"); }; // src/cantina/workers/videoManagerWorker.ts var videoManagerWorker = function* (options) { (0, import_core5.getLogger)().trace("videoManagerWorker started"); const { channels: { swEventChannel } } = options; function* worker(action) { const { type } = action; switch (type) { case "video-manager.rooms.subscribed": yield import_core5.sagaEffects.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 import_core5.sagaEffects.fork(videoManagerRoomWorker, __spreadValues({ action }, options)); break; default: (0, import_core5.getLogger)().warn(`Unknown video-manager event: "${type}"`); break; } } while (true) { const action = yield import_core5.sagaEffects.take( swEventChannel, (action2) => { return action2.type.startsWith("video-manager."); } ); yield import_core5.sagaEffects.fork(worker, action); } (0, import_core5.getLogger)().trace("videoManagerWorker ended"); }; // src/cantina/VideoManager.ts var VideoManagerAPI = class extends import_core6.BaseConsumer { constructor(options) { super(options); this.runWorker("videoManagerWorker", { worker: videoManagerWorker }); } /** @internal */ getSubscriptions() { const eventNamesWithPrefix = this.eventNames().map( (event) => `video-manager.${event}` ); return (0, import_core6.validateEventsToSubscribe)(eventNamesWithPrefix); } }; var createVideoManagerObject = (params) => { const manager = (0, import_core6.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 var import_core30 = require("@signalwire/core"); // src/utils/videoElement.ts var import_core29 = require("@signalwire/core"); // src/VideoOverlays.ts var import_core28 = require("@signalwire/core"); // src/video/VideoRoomSession.ts var import_core17 = require("@signalwire/core"); // src/BaseRoomSession.ts var import_core15 = require("@signalwire/core"); var import_webrtc3 = require("@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 var import_core7 = require("@signalwire/core"); var getJoinMediaParams = (options) => { const { authState, audio = true, video = true, sendAudio, sendVideo, receiveAudio, receiveVideo } = options; (0, import_core7.getLogger)().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) { (0, import_core7.getLogger)().info( "Not allowed to send audio on this room. Default values will be used." ); } if (!canSendVideo && reqToSendVideo) { (0, import_core7.getLogger)().info( "Not allowed to send video on this room. Default values will be used." ); } if (!canReceiveAudio && reqToReceiveAudio) { (0, import_core7.getLogger)().info( "Not allowed to receive video from the room. Default values will be used." ); } if (!canReceiveVideo && reqToReceiveVideo) { (0, import_core7.getLogger)().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 var import_core8 = require("@signalwire/core"); var import_webrtc2 = require("@signalwire/webrtc"); var RoomSessionScreenShareConnection = class extends import_webrtc2.BaseConnection { join() { return super.invite(); } leave() { return super.hangup(); } }; var RoomSessionScreenShareAPI = (0, import_core8.extendComponent)(RoomSessionScreenShareConnection, { audioMute: import_core8.Rooms.audioMuteMember, audioUnmute: import_core8.Rooms.audioUnmuteMember, videoMute: import_core8.Rooms.videoMuteMember, videoUnmute: import_core8.Rooms.videoUnmuteMember, setMicrophoneVolume: import_core8.Rooms.setInputVolumeMember, setInputVolume: import_core8.Rooms.setInputVolumeMember, setInputSensitivity: import_core8.Rooms.setInputSensitivityMember }); // src/video/workers/memberListUpdatedWorker.ts var import_core9 = require("@signalwire/core"); var noop = () => { }; var EXTERNAL_MEMBER_LIST_UPDATED_EVENT = "memberList.updated"; var INTERNAL_MEMBER_LIST_UPDATED_EVENT = (0, import_core9.toInternalEventName)({ event: EXTERNAL_MEMBER_LIST_UPDATED_EVENT }); var SYNTHETIC_MEMBER_LIST_UPDATED_EVENT = (0, import_core9.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 (0, import_core9.validateEventsToSubscribe)(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 import_core9.sagaEffects.take( swEventChannel, ({ type }) => { return isMemberListEvent(type); } ); yield import_core9.sagaEffects.fork(worker, pubSubAction); } } var memberListUpdatedWorker = function* membersChangedWorker({ channels: { swEventChannel }, instance }) { const subscriptions = instance.getSubscriptions(); if (!shouldHandleMemberList(subscriptions)) { return; } const { cleanup } = initMemberListSubscriptions(instance, subscriptions); yield import_core9.sagaEffects.fork(membersListUpdatedWatcher, { swEventChannel, instance }); instance.once("destroy", () => { cleanup(); }); }; // src/video/workers/childMemberJoinedWorker.ts var import_core10 = require("@signalwire/core"); var childMemberJoinedWorker = function* (options) { (0, import_core10.getLogger)().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 import_core10.sagaEffects.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 import_core10.sagaEffects.select( import_core10.componentSelectors.getComponentsById ); const parent = Object.values(byId).find((row) => { return "memberId" in row && row.memberId === member.parent_id; }); if (parent) { yield import_core10.sagaEffects.put( import_core10.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") }); } } (0, import_core10.getLogger)().trace("childMemberJoinedWorker ended"); }; // src/video/workers/videoWorker.ts var import_core14 = require("@signalwire/core"); // src/video/workers/videoStreamWorker.ts var import_core11 = require("@signalwire/core"); var videoStreamWorker = function* (options) { (0, import_core11.getLogger)().trace("videoStreamWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let streamInstance = get(payload.stream.id); if (!streamInstance) { streamInstance = import_core11.Rooms.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: (0, import_core11.getLogger)().warn(`Unknown video.stream event: "${type}"`); break; } (0, import_core11.getLogger)().trace("videoStreamWorker ended"); }; // src/video/workers/videoRecordWorker.ts var import_core12 = require("@signalwire/core"); var videoRecordWorker = function* (options) { (0, import_core12.getLogger)().trace("videoRecordWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let recordingInstance = get(payload.recording.id); if (!recordingInstance) { recordingInstance = import_core12.Rooms.createRoomSessionRecordingObject({ store: roomSession.store, payload }); } else { recordingInstance.setPayload(payload); } set(payload.recording.id, recordingInstance); const event = (0, import_core12.stripNamespacePrefix)(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: (0, import_core12.getLogger)().warn(`Unknown video.stream event: "${type}"`); break; } (0, import_core12.getLogger)().trace("videoRecordWorker ended"); }; // src/video/workers/videoPlaybackWorker.ts var import_core13 = require("@signalwire/core"); var videoPlaybackWorker = function* (options) { (0, import_core13.getLogger)().trace("videoPlaybackWorker started"); const { instance: roomSession, action: { type, payload }, instanceMap: { get, set, remove } } = options; let playbackInstance = get(payload.playback.id); if (!playbackInstance) { playbackInstance = import_core13.Rooms.createRoomSessionPlaybackObject({ store: roomSession.store, payload }); } else { playbackInstance.setPayload(payload); } set(payload.playback.id, playbackInstance); const event = (0, import_core13.stripNamespacePrefix)(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: (0, import_core13.getLogger)().warn(`Unknown video.stream event: "${type}"`); break; } (0, import_core13.getLogger)().trace("videoPlaybackWorker ended"); }; // src/video/workers/videoWorker.ts var videoWorker = function* (options) { (0, import_core14.getLogger)().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 import_core14.sagaEffects.fork(import_core14.MemberPosition.memberPositionWorker, __spreadProps(__spreadValues({}, options), { instance: roomSession, initialState: payload })); return; case "video.playback.started": case "video.playback.updated": case "video.playback.ended": yield import_core14.sagaEffects.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 import_core14.sagaEffects.fork(videoRecordWorker, __spreadValues({ action }, options)); return; case "video.stream.ended": case "video.stream.started": yield import_core14.sagaEffects.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 = (0, import_core14.stripNamespacePrefix)(type, "video"); roomSession.emit(event, payload); } const isVideoEvent = (action) => { return action.type.startsWith("video."); }; while (true) { const action = yield import_core14.sagaEffects.take( swEventChannel, isVideoEvent ); yield import_core14.sagaEffects.fork(worker, action); } (0, import_core14.getLogger)().trace("videoWorker ended"); }; // src/BaseRoomSession.ts var BaseRoomSessionConnection = class _BaseRoomSessionConnection extends import_webrtc3.BaseConnection { 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 (!(0, import_webrtc3.supportsMediaOutput)()) return; (0, import_webrtc3.createSpeakerDeviceWatcher)().then((deviceWatcher) => { deviceWatcher.on("removed", (data) => __async(this, null, function* () { 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 }); yield (_b = (_a = this._audioEl).setSinkId) == null ? void 0 : _b.call(_a, ""); const defaultSpeakers = yield (0, import_webrtc3.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 */ hangup(id) { return __async(this, null, function* () { this._screenShareList.forEach((screenShare) => { screenShare.leave(); }); return __superGet(_BaseRoomSessionConnection.prototype, this, "hangup").call(this, 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. */ startScreenShare() { return __async(this, arguments, function* (opts = {}) { return new Promise((resolve, reject) => __async(this, null, function* () { var _a; const { autoJoin = true, audio = false, video = true, layout, positions } = opts; try { const displayStream = yield (0, import_webrtc3.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 = (0, import_core15.connect)({ 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 yield 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 `${import_core15.LOCAL_EVENT_PREFIX}.speaker.updated`, (newId) => __async(this, null, function* () { const prevSpeaker = yield (0, import_webrtc3.getSpeakerById)(prevId); const newSpeaker = yield (0, import_webrtc3.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 var import_core16 = require("@signalwire/core"); var import_webrtc4 = require("@signalwire/webrtc"); var RoomSessionDeviceConnection = class extends import_webrtc4.BaseConnection { join() { return super.invite(); } leave() { return super.hangup(); } }; var RoomSessionDeviceAPI = (0, import_core16.extendComponent)(RoomSessionDeviceConnection, { audioMute: import_core16.Rooms.audioMuteMember, audioUnmute: import_core16.Rooms.audioUnmuteMember, videoMute: import_core16.Rooms.videoMuteMember, videoUnmute: import_core16.Rooms.videoUnmuteMember, setInputVolume: import_core16.Rooms.setInputVolumeMember, setMicrophoneVolume: import_core16.Rooms.setInputVolumeMember, setInputSensitivity: import_core16.Rooms.setInputSensitivityMember }); // src/video/VideoRoomSession.ts var VideoRoomSessionConnection = class _VideoRoomSessionConnection 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 (0, import_core17.validateEventsToSubscribe)( eventNamesWithPrefix ); } /** @internal */ _finalize() { this._deviceList.clear(); super._finalize(); } /** @internal */ hangup(id) { return __async(this, null, function* () { this._deviceList.forEach((device) => { device.leave(); }); return __superGet(_VideoRoomSessionConnection.prototype, this, "hangup").call(this, 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. */ createScreenShareObject() { return __async(this, arguments, function* (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. */ addDevice() { return __async(this, arguments, function* (opts = {}) { return new Promise((resolve, reject) => __async(this, null, function* () { 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 = (0, import_core17.connect)({ 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 yield roomDevice.join(); } return resolve(roomDevice); } catch (error) { this.logger.error("RoomDevice Error", error); reject(error); } })); }); } }; var VideoRoomSessionAPI = (0, import_core17.extendComponent)(VideoRoomSessionConnection, { audioMute: import_core17.Rooms.audioMuteMember, audioUnmute: import_core17.Rooms.audioUnmuteMember, videoMute: import_core17.Rooms.videoMuteMember, videoUnmute: import_core17.Rooms.videoUnmuteMember, deaf: import_core17.Rooms.deafMember, undeaf: import_core17.Rooms.undeafMember, setInputVolume: import_core17.Rooms.setInputVolumeMember, setOutputVolume: import_core17.Rooms.setOutputVolumeMember, setMicrophoneVolume: import_core17.Rooms.setInputVolumeMember, setSpeakerVolume: import_core17.Rooms.setOutputVolumeMember, setInputSensitivity: import_core17.Rooms.setInputSensitivityMember, removeMember: import_core17.Rooms.removeMember, removeAllMembers: import_core17.Rooms.removeAllMembers, getMembers: import_core17.Rooms.getMembers, getLayouts: import_core17.Rooms.getLayouts, setLayout: import_core17.Rooms.setLayout, setPositions: import_core17.Rooms.setPositions, setMemberPosition: import_core17.Rooms.setMemberPosition, hideVideoMuted: import_core17.Rooms.hideVideoMuted, showVideoMuted: import_core17.Rooms.showVideoMuted, getRecordings: import_core17.Rooms.getRecordings, startRecording: import_core17.Rooms.startRecording, getPlaybacks: import_core17.Rooms.getPlaybacks, play: import_core17.Rooms.play, setHideVideoMuted: import_core17.Rooms.setHideVideoMuted, getMeta: import_core17.Rooms.getMeta, setMeta: import_core17.Rooms.setMeta, updateMeta: import_core17.Rooms.updateMeta, deleteMeta: import_core17.Rooms.deleteMeta, getMemberMeta: import_core17.Rooms.getMemberMeta, setMemberMeta: import_core17.Rooms.setMemberMeta, updateMemberMeta: import_core17.Rooms.updateMemberMeta, deleteMemberMeta: import_core17.Rooms.deleteMemberMeta, promote: import_core17.Rooms.promote, demote: import_core17.Rooms.demote, getStreams: import_core17.Rooms.getStreams, startStream: import_core17.Rooms.startStream, lock: import_core17.Rooms.lock, unlock: import_core17.Rooms.unlock, setRaisedHand: import_core17.Rooms.setRaisedHand, setPrioritizeHandraise: import_core17.Rooms.setPrioritizeHandraise }); var isVideoRoomSession = (room) => { return room instanceof VideoRoomSessionConnection; }; var createVideoRoomSessionObject = (params) => { const room = (0, import_core17.connect)({ store: params.store, customSagas: params.customSagas, Component: VideoRoomSessionAPI })(params); return room; }; // src/fabric/FabricRoomSession.ts var import_core27 = require("@signalwire/core"); // src/utils/storage.ts var import_jwt_decode = __toESM(require("jwt-decode")); var import_core18 = require("@signalwire/core"); var getStorage = () => { if (window && window.sessionStorage) { return window.sessionStorage; } return void 0; }; var sessionStorageManager = (token) => { var _a; let roomName = ""; try { const jwtPayload = (0, import_jwt_decode.default)(token); roomName = (_a = jwtPayload == null ? void 0 : jwtPayload.r) != null ? _a : ""; } catch (e) { try { const jwtPayload = (0, import_jwt_decode.default)(token, { header: true }); roomName = jwtPayload.typ || ""; } catch (e2) { if (process.env.NODE_ENV !== "production") { (0, import_core18.getLogger)().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 var import_core19 = require("@signalwire/core"); var wsClientWorker = function* (options) { (0, import_core19.getLogger)().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 import_core19.sagaEffects.take( swEventChannel, () => true ); yield import_core19.sagaEffects.fork(fireHoseWorker, action); if (isVertoInvite(action)) { (0, import_core19.getLogger)().debug("Receiving a call over WebSocket", action); yield import_core19.sagaEffects.fork( vertoInviteWorker, action ); } } } finally { (0, import_core19.getLogger)().trace("wsClientWorker ended"); } }; // src/fabric/workers/conversationWorker.ts var import_core20 = require("@signalwire/core"); var conversationWorker = function* (options) { (0, import_core20.getLogger)().debug("conversationWorker started"); const { channels: { swEventChannel }, initialState } = options; const { conversation } = initialState; const isConversationEvent = (action) => { return action.type.startsWith("conversation."); }; while (true) { const action = yield import_core20.sagaEffects.take( swEventChannel, isConversationEvent ); conversation.handleEvent(action.payload); } (0, import_core20.getLogger)().trace("conversationWorker ended"); }; // src/fabric/workers/fabricWorker.ts var import_core26 = require("@signalwire/core"); // src/fabric/FabricRoomSessionMember.ts var import_core21 = require("@signalwire/core"); var FabricRoomSessionMemberAPI = class extends import_core21.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 = (0, import_core21.connect)({ store: params.store, Component: FabricRoomSessionMemberAPI })(params); return member; }; // src/fabric/workers/callSegmentWorker.ts var import_core25 = require("@signalwire/core"); // src/fabric/workers/callLeftWorker.ts var import_core22 = require("@signalwire/core"); var callLeftWorker = function* (options) { (0, import_core22.getLogger)().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); (0, import_core22.getLogger)().trace("callLeftWorker ended"); }; // src/fabric/workers/callJoinWorker.ts var import_core24 = require("@signalwire/core"); // src/fabric/workers/fabricMemberWorker.ts var import_core23 = require("@signalwire/core"); var fabricMemberWorker = function* (options) { (0, import_core23.getLogger)().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 = (0, import_core23.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; } (0, import_core23.getLogger)().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_handrais