@whereby.com/core 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createAsyncThunk, createListenerMiddleware, addListener, createSlice, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
2
2
  import { ServerSocket, getDeviceData, getStream, getUpdatedDevices, RtcManagerDispatcher, assert, fromLocation } from '@whereby.com/media';
3
+ import { EventEmitter } from 'events';
3
4
  import { Chrome111 } from 'mediasoup-client/lib/handlers/Chrome111.js';
4
5
  import nodeBtoa from 'btoa';
5
6
  import axios from 'axios';
6
- import EventEmitter from 'events';
7
7
 
8
8
  function createAppAsyncThunk(typePrefix, payloadCreator) {
9
9
  return createAsyncThunk(typePrefix, payloadCreator);
@@ -43,9 +43,9 @@ const createReactor = (selectors, callback) => {
43
43
  });
44
44
  };
45
45
 
46
- const coreVersion = "0.15.1";
46
+ const coreVersion = "0.16.0";
47
47
 
48
- const initialState$e = {
48
+ const initialState$f = {
49
49
  isNodeSdk: false,
50
50
  isActive: false,
51
51
  roomName: null,
@@ -56,7 +56,7 @@ const initialState$e = {
56
56
  };
57
57
  const appSlice = createSlice({
58
58
  name: "app",
59
- initialState: initialState$e,
59
+ initialState: initialState$f,
60
60
  reducers: {
61
61
  doAppStart: (state, action) => {
62
62
  const url = new URL(action.payload.roomUrl);
@@ -101,6 +101,8 @@ const signalEvents = {
101
101
  roomSessionEnded: createSignalEventAction("roomSessionEnded"),
102
102
  screenshareStarted: createSignalEventAction("screenshareStarted"),
103
103
  screenshareStopped: createSignalEventAction("screenshareStopped"),
104
+ spotlightAdded: createSignalEventAction("spotlightAdded"),
105
+ spotlightRemoved: createSignalEventAction("spotlightRemoved"),
104
106
  streamingStopped: createSignalEventAction("streamingStopped"),
105
107
  videoEnabled: createSignalEventAction("videoEnabled"),
106
108
  };
@@ -110,14 +112,16 @@ const ROOM_ACTION_PERMISSIONS_BY_ROLE = {
110
112
  canRequestAudioEnable: ["host"],
111
113
  canKickClient: ["host"],
112
114
  canEndMeeting: ["host"],
115
+ canAskToSpeak: ["host"],
116
+ canSpotlight: ["host"],
113
117
  };
114
- const initialState$d = {
118
+ const initialState$e = {
115
119
  roomKey: null,
116
120
  roleName: "none",
117
121
  };
118
122
  const authorizationSlice = createSlice({
119
123
  name: "authorization",
120
- initialState: initialState$d,
124
+ initialState: initialState$e,
121
125
  reducers: {
122
126
  setRoomKey: (state, action) => {
123
127
  return Object.assign(Object.assign({}, state), { roomKey: action.payload });
@@ -141,6 +145,8 @@ const selectIsAuthorizedToLockRoom = createSelector(selectAuthorizationRoleName,
141
145
  const selectIsAuthorizedToRequestAudioEnable = createSelector(selectAuthorizationRoleName, (localParticipantRole) => ROOM_ACTION_PERMISSIONS_BY_ROLE.canRequestAudioEnable.includes(localParticipantRole));
142
146
  const selectIsAuthorizedToKickClient = createSelector(selectAuthorizationRoleName, (localParticipantRole) => ROOM_ACTION_PERMISSIONS_BY_ROLE.canKickClient.includes(localParticipantRole));
143
147
  const selectIsAuthorizedToEndMeeting = createSelector(selectAuthorizationRoleName, (localParticipantRole) => ROOM_ACTION_PERMISSIONS_BY_ROLE.canEndMeeting.includes(localParticipantRole));
148
+ const selectIsAuthorizedToAskToSpeak = createSelector(selectAuthorizationRoleName, (localParticipantRole) => ROOM_ACTION_PERMISSIONS_BY_ROLE.canAskToSpeak.includes(localParticipantRole));
149
+ const selectIsAuthorizedToSpotlight = createSelector(selectAuthorizationRoleName, (localParticipantRole) => ROOM_ACTION_PERMISSIONS_BY_ROLE.canSpotlight.includes(localParticipantRole));
144
150
 
145
151
  /******************************************************************************
146
152
  Copyright (c) Microsoft Corporation.
@@ -186,13 +192,13 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
186
192
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
187
193
  };
188
194
 
189
- const initialState$c = {
195
+ const initialState$d = {
190
196
  isFetching: false,
191
197
  data: null,
192
198
  };
193
199
  const deviceCredentialsSlice = createSlice({
194
200
  name: "deviceCredentials",
195
- initialState: initialState$c,
201
+ initialState: initialState$d,
196
202
  reducers: {},
197
203
  extraReducers: (builder) => {
198
204
  builder.addCase(doGetDeviceCredentials.pending, (state) => {
@@ -252,6 +258,8 @@ function forwardSocketEvents(socket, dispatch) {
252
258
  socket.on("cloud_recording_started", (payload) => dispatch(signalEvents.cloudRecordingStarted(payload)));
253
259
  socket.on("cloud_recording_stopped", () => dispatch(signalEvents.cloudRecordingStopped()));
254
260
  socket.on("streaming_stopped", () => dispatch(signalEvents.streamingStopped()));
261
+ socket.on("spotlight_added", (payload) => dispatch(signalEvents.spotlightAdded(payload)));
262
+ socket.on("spotlight_removed", (payload) => dispatch(signalEvents.spotlightRemoved(payload)));
255
263
  }
256
264
  const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
257
265
  function createSocket() {
@@ -262,7 +270,7 @@ function createSocket() {
262
270
  };
263
271
  return new ServerSocket(socketHost, socketOverrides);
264
272
  }
265
- const initialState$b = {
273
+ const initialState$c = {
266
274
  deviceIdentified: false,
267
275
  isIdentifyingDevice: false,
268
276
  status: "ready",
@@ -270,7 +278,7 @@ const initialState$b = {
270
278
  };
271
279
  const signalConnectionSlice = createSlice({
272
280
  name: "signalConnection",
273
- initialState: initialState$b,
281
+ initialState: initialState$c,
274
282
  reducers: {
275
283
  socketConnecting: (state) => {
276
284
  return Object.assign(Object.assign({}, state), { status: "connecting" });
@@ -374,12 +382,12 @@ startAppListening({
374
382
  },
375
383
  });
376
384
 
377
- const initialState$a = {
385
+ const initialState$b = {
378
386
  chatMessages: [],
379
387
  };
380
388
  const chatSlice = createSlice({
381
389
  name: "chat",
382
- initialState: initialState$a,
390
+ initialState: initialState$b,
383
391
  reducers: {},
384
392
  extraReducers(builder) {
385
393
  builder.addCase(signalEvents.chatMessage, (state, action) => {
@@ -591,9 +599,11 @@ function parseUnverifiedRoomKeyData(roomKey) {
591
599
  const initialLocalMediaState = {
592
600
  busyDeviceIds: [],
593
601
  cameraEnabled: false,
602
+ currentSpeakerDeviceId: "default",
594
603
  devices: [],
595
604
  isSettingCameraDevice: false,
596
605
  isSettingMicrophoneDevice: false,
606
+ isSettingSpeakerDevice: false,
597
607
  isTogglingCamera: false,
598
608
  lowDataMode: false,
599
609
  microphoneEnabled: false,
@@ -625,6 +635,10 @@ const localMediaSlice = createSlice({
625
635
  setCurrentMicrophoneDeviceId(state, action) {
626
636
  return Object.assign(Object.assign({}, state), { currentMicrophoneDeviceId: action.payload.deviceId });
627
637
  },
638
+ setCurrentSpeakerDeviceId(state, action) {
639
+ var _a;
640
+ return Object.assign(Object.assign({}, state), { currentSpeakerDeviceId: (_a = action.payload.deviceId) !== null && _a !== void 0 ? _a : "default" });
641
+ },
628
642
  toggleLowDataModeEnabled(state, action) {
629
643
  var _a;
630
644
  return Object.assign(Object.assign({}, state), { lowDataMode: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.lowDataMode });
@@ -707,7 +721,7 @@ const localMediaSlice = createSlice({
707
721
  });
708
722
  },
709
723
  });
710
- const { deviceBusy, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, toggleCameraEnabled, toggleMicrophoneEnabled, toggleLowDataModeEnabled, setLocalMediaOptions, setLocalMediaStream, localMediaStopped, localStreamMetadataUpdated, } = localMediaSlice.actions;
724
+ const { deviceBusy, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setCurrentSpeakerDeviceId, toggleCameraEnabled, toggleMicrophoneEnabled, toggleLowDataModeEnabled, setLocalMediaOptions, setLocalMediaStream, localMediaStopped, localStreamMetadataUpdated, } = localMediaSlice.actions;
711
725
  const doToggleCamera = createAppAsyncThunk("localMedia/doToggleCamera", (_, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
712
726
  const state = getState();
713
727
  const stream = selectLocalMediaStream(state);
@@ -897,6 +911,7 @@ const selectBusyDeviceIds = (state) => state.localMedia.busyDeviceIds;
897
911
  const selectCameraDeviceError = (state) => state.localMedia.cameraDeviceError;
898
912
  const selectCurrentCameraDeviceId = (state) => state.localMedia.currentCameraDeviceId;
899
913
  const selectCurrentMicrophoneDeviceId = (state) => state.localMedia.currentMicrophoneDeviceId;
914
+ const selectCurrentSpeakerDeviceId = (state) => state.localMedia.currentSpeakerDeviceId;
900
915
  const selectIsCameraEnabled = (state) => state.localMedia.cameraEnabled;
901
916
  const selectIsMicrophoneEnabled = (state) => state.localMedia.microphoneEnabled;
902
917
  const selectIsLowDataModeEnabled = (state) => state.localMedia.lowDataMode;
@@ -1033,7 +1048,7 @@ startAppListening({
1033
1048
  },
1034
1049
  });
1035
1050
 
1036
- const initialState$9 = {
1051
+ const initialState$a = {
1037
1052
  displayName: "",
1038
1053
  id: "",
1039
1054
  isAudioEnabled: true,
@@ -1043,34 +1058,14 @@ const initialState$9 = {
1043
1058
  isScreenSharing: false,
1044
1059
  roleName: "none",
1045
1060
  clientClaim: undefined,
1061
+ stickyReaction: undefined,
1046
1062
  };
1047
- const doEnableAudio = createAppAsyncThunk("localParticipant/doEnableAudio", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1048
- const state = getState();
1049
- const socket = selectSignalConnectionRaw(state).socket;
1050
- socket === null || socket === void 0 ? void 0 : socket.emit("enable_audio", { enabled: payload.enabled });
1051
- return payload.enabled;
1052
- }));
1053
- const doEnableVideo = createAppAsyncThunk("localParticipant/doEnableVideo", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1054
- const state = getState();
1055
- const socket = selectSignalConnectionRaw(state).socket;
1056
- socket === null || socket === void 0 ? void 0 : socket.emit("enable_video", { enabled: payload.enabled });
1057
- return payload.enabled;
1058
- }));
1059
- const doSetDisplayName = createAppAsyncThunk("localParticipant/doSetDisplayName", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1060
- const state = getState();
1061
- const socket = selectSignalConnectionRaw(state).socket;
1062
- socket === null || socket === void 0 ? void 0 : socket.emit("send_client_metadata", {
1063
- type: "UserData",
1064
- payload,
1065
- });
1066
- return payload.displayName;
1067
- }));
1068
1063
  const localParticipantSlice = createSlice({
1069
1064
  name: "localParticipant",
1070
- initialState: initialState$9,
1065
+ initialState: initialState$a,
1071
1066
  reducers: {
1072
- doSetLocalParticipant: (state, action) => {
1073
- return Object.assign(Object.assign({}, state), action.payload);
1067
+ doSetDisplayName: (state, action) => {
1068
+ return Object.assign(Object.assign({}, state), { displayName: action.payload.displayName });
1074
1069
  },
1075
1070
  },
1076
1071
  extraReducers: (builder) => {
@@ -1083,8 +1078,8 @@ const localParticipantSlice = createSlice({
1083
1078
  builder.addCase(doEnableVideo.fulfilled, (state, action) => {
1084
1079
  return Object.assign(Object.assign({}, state), { isVideoEnabled: action.payload });
1085
1080
  });
1086
- builder.addCase(doSetDisplayName.fulfilled, (state, action) => {
1087
- return Object.assign(Object.assign({}, state), { displayName: action.payload });
1081
+ builder.addCase(doSetLocalStickyReaction.fulfilled, (state, action) => {
1082
+ return Object.assign(Object.assign({}, state), { stickyReaction: action.payload });
1088
1083
  });
1089
1084
  builder.addCase(signalEvents.roomJoined, (state, action) => {
1090
1085
  var _a, _b;
@@ -1093,11 +1088,64 @@ const localParticipantSlice = createSlice({
1093
1088
  });
1094
1089
  },
1095
1090
  });
1096
- const { doSetLocalParticipant } = localParticipantSlice.actions;
1091
+ const { doSetDisplayName } = localParticipantSlice.actions;
1092
+ const doEnableAudio = createAppAsyncThunk("localParticipant/doEnableAudio", (payload, { dispatch, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1093
+ const state = getState();
1094
+ const socket = selectSignalConnectionRaw(state).socket;
1095
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_audio", { enabled: payload.enabled });
1096
+ if (payload.enabled) {
1097
+ dispatch(doSetLocalStickyReaction({ enabled: false }));
1098
+ }
1099
+ return payload.enabled;
1100
+ }));
1101
+ const doEnableVideo = createAppAsyncThunk("localParticipant/doEnableVideo", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1102
+ const state = getState();
1103
+ const socket = selectSignalConnectionRaw(state).socket;
1104
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_video", { enabled: payload.enabled });
1105
+ return payload.enabled;
1106
+ }));
1107
+ const doSetLocalStickyReaction = createAppAsyncThunk("localParticipant/doSetLocalStickyReaction", (payload, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1108
+ var _a;
1109
+ const state = getState();
1110
+ const currentStickyReaction = selectLocalParticipantStickyReaction(state);
1111
+ const stickyReactionCurrentlyEnabled = Boolean(currentStickyReaction);
1112
+ const enabled = (_a = payload.enabled) !== null && _a !== void 0 ? _a : !stickyReactionCurrentlyEnabled;
1113
+ if (enabled === stickyReactionCurrentlyEnabled) {
1114
+ return rejectWithValue(currentStickyReaction);
1115
+ }
1116
+ const stickyReaction = enabled ? { reaction: "✋", timestamp: new Date().toISOString() } : null;
1117
+ return stickyReaction;
1118
+ }));
1119
+ const doSendClientMetadata = createAppThunk(() => (_, getState) => {
1120
+ const state = getState();
1121
+ const socket = selectSignalConnectionRaw(state).socket;
1122
+ const payload = {
1123
+ displayName: selectLocalParticipantDisplayName(state),
1124
+ stickyReaction: selectLocalParticipantStickyReaction(state),
1125
+ };
1126
+ socket === null || socket === void 0 ? void 0 : socket.emit("send_client_metadata", {
1127
+ type: "UserData",
1128
+ payload,
1129
+ });
1130
+ });
1097
1131
  const selectLocalParticipantRaw = (state) => state.localParticipant;
1098
1132
  const selectSelfId = (state) => state.localParticipant.id;
1133
+ const selectLocalParticipantDisplayName = (state) => state.localParticipant.displayName;
1099
1134
  const selectLocalParticipantClientClaim = (state) => state.localParticipant.clientClaim;
1100
1135
  const selectLocalParticipantIsScreenSharing = (state) => state.localParticipant.isScreenSharing;
1136
+ const selectLocalParticipantStickyReaction = (state) => state.localParticipant.stickyReaction;
1137
+ const selectLocalParticipantView = createSelector(selectLocalParticipantRaw, selectLocalMediaStream, (participant, localStream) => {
1138
+ const clientView = {
1139
+ id: participant.id,
1140
+ clientId: participant.id,
1141
+ displayName: participant.displayName,
1142
+ stream: localStream,
1143
+ isLocalClient: true,
1144
+ isAudioEnabled: participant.isAudioEnabled,
1145
+ isVideoEnabled: participant.isVideoEnabled,
1146
+ };
1147
+ return clientView;
1148
+ });
1101
1149
  startAppListening({
1102
1150
  actionCreator: toggleCameraEnabled,
1103
1151
  effect: ({ payload }, { dispatch, getState }) => {
@@ -1114,15 +1162,18 @@ startAppListening({
1114
1162
  dispatch(doEnableAudio({ enabled: enabled || !isAudioEnabled }));
1115
1163
  },
1116
1164
  });
1165
+ createReactor([selectLocalParticipantDisplayName, selectLocalParticipantStickyReaction], ({ dispatch }) => {
1166
+ dispatch(doSendClientMetadata());
1167
+ });
1117
1168
 
1118
- const initialState$8 = {
1169
+ const initialState$9 = {
1119
1170
  status: "inactive",
1120
1171
  stream: null,
1121
1172
  error: null,
1122
1173
  };
1123
1174
  const localScreenshareSlice = createSlice({
1124
1175
  name: "localScreenshare",
1125
- initialState: initialState$8,
1176
+ initialState: initialState$9,
1126
1177
  reducers: {
1127
1178
  stopScreenshare(state, action) {
1128
1179
  return Object.assign(Object.assign({}, state), { status: "inactive", stream: null });
@@ -1191,60 +1242,6 @@ startAppListening({
1191
1242
  },
1192
1243
  });
1193
1244
 
1194
- const initialState$7 = {
1195
- data: null,
1196
- isFetching: false,
1197
- error: null,
1198
- };
1199
- const organizationSlice = createSlice({
1200
- initialState: initialState$7,
1201
- name: "organization",
1202
- reducers: {},
1203
- extraReducers: (builder) => {
1204
- builder.addCase(doOrganizationFetch.pending, (state) => {
1205
- return Object.assign(Object.assign({}, state), { isFetching: true });
1206
- });
1207
- builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
1208
- if (!action.payload)
1209
- return Object.assign(Object.assign({}, state), { isFetching: true });
1210
- return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
1211
- });
1212
- builder.addCase(doOrganizationFetch.rejected, (state) => {
1213
- return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
1214
- });
1215
- },
1216
- });
1217
- const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1218
- try {
1219
- const roomUrl = selectAppRoomUrl(getState());
1220
- const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
1221
- if (!organization) {
1222
- throw new Error("Invalid room url");
1223
- }
1224
- return organization;
1225
- }
1226
- catch (error) {
1227
- console.error(error);
1228
- }
1229
- }));
1230
- const selectOrganizationRaw = (state) => state.organization;
1231
- const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
1232
- const selectShouldFetchOrganization = createSelector(selectAppIsActive, selectOrganizationRaw, selectDeviceCredentialsRaw, (appIsActive, organization, deviceCredentials) => {
1233
- if (appIsActive &&
1234
- !organization.data &&
1235
- !organization.isFetching &&
1236
- !organization.error &&
1237
- !deviceCredentials.isFetching) {
1238
- return true;
1239
- }
1240
- return false;
1241
- });
1242
- createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
1243
- if (shouldFetchOrganization) {
1244
- dispatch(doOrganizationFetch());
1245
- }
1246
- });
1247
-
1248
1245
  function createRtcEventAction(name) {
1249
1246
  return createAction(`rtcConnection/event/${name}`);
1250
1247
  }
@@ -1346,12 +1343,12 @@ function addStream(state, payload) {
1346
1343
  presentationStream: stream,
1347
1344
  });
1348
1345
  }
1349
- const initialState$6 = {
1346
+ const initialState$8 = {
1350
1347
  remoteParticipants: [],
1351
1348
  };
1352
1349
  const remoteParticipantsSlice = createSlice({
1353
1350
  name: "remoteParticipants",
1354
- initialState: initialState$6,
1351
+ initialState: initialState$8,
1355
1352
  reducers: {
1356
1353
  streamStatusUpdated: (state, action) => {
1357
1354
  let newState = state;
@@ -1410,9 +1407,15 @@ const remoteParticipantsSlice = createSlice({
1410
1407
  });
1411
1408
  });
1412
1409
  builder.addCase(signalEvents.clientMetadataReceived, (state, action) => {
1413
- const { clientId, displayName } = action.payload.payload;
1410
+ const { error, payload } = action.payload;
1411
+ if (error || !payload) {
1412
+ console.warn(error || "Client metadata error received");
1413
+ return state;
1414
+ }
1415
+ const { clientId, displayName, stickyReaction } = payload;
1414
1416
  return updateParticipant(state, clientId, {
1415
1417
  displayName,
1418
+ stickyReaction,
1416
1419
  });
1417
1420
  });
1418
1421
  builder.addCase(signalEvents.screenshareStarted, (state, action) => {
@@ -1428,87 +1431,79 @@ const remoteParticipantsSlice = createSlice({
1428
1431
  const { participantStreamAdded, participantStreamIdAdded, streamStatusUpdated } = remoteParticipantsSlice.actions;
1429
1432
  const doRequestAudioEnable = createAppAuthorizedThunk((state) => selectIsAuthorizedToRequestAudioEnable(state), (payload) => (_, getState) => {
1430
1433
  const state = getState();
1434
+ const canEnableRemoteAudio = selectIsAuthorizedToAskToSpeak(state);
1435
+ if (payload.enable && !canEnableRemoteAudio) {
1436
+ console.warn("Not authorized to perform this action");
1437
+ return;
1438
+ }
1431
1439
  const socket = selectSignalConnectionRaw(state).socket;
1432
1440
  socket === null || socket === void 0 ? void 0 : socket.emit("request_audio_enable", payload);
1433
1441
  });
1434
1442
  const selectRemoteParticipantsRaw = (state) => state.remoteParticipants;
1435
1443
  const selectRemoteParticipants = (state) => state.remoteParticipants.remoteParticipants;
1436
- const selectScreenshares = createSelector(selectLocalScreenshareStream, selectRemoteParticipants, (localScreenshareStream, remoteParticipants) => {
1437
- const screenshares = [];
1438
- if (localScreenshareStream) {
1439
- screenshares.push({
1440
- id: localScreenshareStream.id || "local-screenshare",
1441
- participantId: "local",
1442
- hasAudioTrack: localScreenshareStream.getTracks().some((track) => track.kind === "audio"),
1443
- stream: localScreenshareStream,
1444
- isLocal: true,
1445
- });
1446
- }
1447
- for (const participant of remoteParticipants) {
1448
- if (participant.presentationStream) {
1449
- screenshares.push({
1450
- id: participant.presentationStream.id || `pres-${participant.id}`,
1451
- participantId: participant.id,
1452
- hasAudioTrack: participant.presentationStream.getTracks().some((track) => track.kind === "audio"),
1453
- stream: participant.presentationStream,
1454
- isLocal: false,
1455
- });
1456
- }
1457
- }
1458
- return screenshares;
1459
- });
1444
+ const selectNumParticipants = createSelector(selectRemoteParticipants, (clients) => clients.filter((c) => !NON_PERSON_ROLES.includes(c.roleName)).length + 1);
1460
1445
 
1461
- const initialState$5 = {
1462
- isLocked: false,
1446
+ const initialState$7 = {
1447
+ data: null,
1448
+ isFetching: false,
1449
+ error: null,
1463
1450
  };
1464
- const roomSlice = createSlice({
1465
- name: "room",
1466
- initialState: initialState$5,
1451
+ const organizationSlice = createSlice({
1452
+ initialState: initialState$7,
1453
+ name: "organization",
1467
1454
  reducers: {},
1468
1455
  extraReducers: (builder) => {
1469
- builder.addCase(signalEvents.roomJoined, (state, action) => {
1470
- const { error, isLocked } = action.payload;
1471
- if (error) {
1472
- return state;
1473
- }
1474
- return Object.assign(Object.assign({}, state), { isLocked: Boolean(isLocked) });
1456
+ builder.addCase(doOrganizationFetch.pending, (state) => {
1457
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1475
1458
  });
1476
- builder.addCase(signalEvents.roomLocked, (state, action) => {
1477
- const { isLocked } = action.payload;
1478
- return Object.assign(Object.assign({}, state), { isLocked: Boolean(isLocked) });
1459
+ builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
1460
+ if (!action.payload)
1461
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1462
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
1463
+ });
1464
+ builder.addCase(doOrganizationFetch.rejected, (state) => {
1465
+ return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
1479
1466
  });
1480
1467
  },
1481
1468
  });
1482
- const doLockRoom = createAppAuthorizedThunk((state) => selectIsAuthorizedToLockRoom(state), (payload) => (_, getState) => {
1483
- const state = getState();
1484
- const { socket } = selectSignalConnectionRaw(state);
1485
- socket === null || socket === void 0 ? void 0 : socket.emit("set_lock", { locked: payload.locked });
1486
- });
1487
- const doKickParticipant = createAppAuthorizedThunk((state) => selectIsAuthorizedToKickClient(state), (payload) => (_, getState) => {
1488
- const state = getState();
1489
- const { socket } = selectSignalConnectionRaw(state);
1490
- socket === null || socket === void 0 ? void 0 : socket.emit("kick_client", { clientId: payload.clientId, reasonId: "kick" });
1491
- });
1492
- const doEndMeeting = createAppAuthorizedThunk((state) => selectIsAuthorizedToEndMeeting(state), (payload) => (dispatch, getState) => {
1493
- const state = getState();
1494
- const clientsToKick = selectRemoteParticipants(state).map((c) => c.id);
1495
- if (clientsToKick.length) {
1496
- const { socket } = selectSignalConnectionRaw(state);
1497
- socket === null || socket === void 0 ? void 0 : socket.emit("kick_client", { clientIds: clientsToKick, reasonId: "end-meeting" });
1469
+ const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1470
+ try {
1471
+ const roomUrl = selectAppRoomUrl(getState());
1472
+ const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
1473
+ if (!organization) {
1474
+ throw new Error("Invalid room url");
1475
+ }
1476
+ return organization;
1498
1477
  }
1499
- if (!payload.stayBehind) {
1500
- dispatch(doAppStop());
1478
+ catch (error) {
1479
+ console.error(error);
1480
+ }
1481
+ }));
1482
+ const selectOrganizationRaw = (state) => state.organization;
1483
+ const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
1484
+ const selectShouldFetchOrganization = createSelector(selectAppIsActive, selectOrganizationRaw, selectDeviceCredentialsRaw, (appIsActive, organization, deviceCredentials) => {
1485
+ if (appIsActive &&
1486
+ !organization.data &&
1487
+ !organization.isFetching &&
1488
+ !organization.error &&
1489
+ !deviceCredentials.isFetching) {
1490
+ return true;
1491
+ }
1492
+ return false;
1493
+ });
1494
+ createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
1495
+ if (shouldFetchOrganization) {
1496
+ dispatch(doOrganizationFetch());
1501
1497
  }
1502
1498
  });
1503
- const selectRoomIsLocked = (state) => state.room.isLocked;
1504
1499
 
1505
- const initialState$4 = {
1500
+ const initialState$6 = {
1506
1501
  session: null,
1507
1502
  status: "ready",
1508
1503
  error: null,
1509
1504
  };
1510
1505
  const roomConnectionSlice = createSlice({
1511
- initialState: initialState$4,
1506
+ initialState: initialState$6,
1512
1507
  name: "roomConnection",
1513
1508
  reducers: {
1514
1509
  connectionStatusChanged: (state, action) => {
@@ -1667,6 +1662,249 @@ startAppListening({
1667
1662
  },
1668
1663
  });
1669
1664
 
1665
+ const emitter = new EventEmitter();
1666
+ function createNotificationEvent(payload) {
1667
+ const notificationEvent = Object.assign(Object.assign({}, payload), { timestamp: Date.now() });
1668
+ return notificationEvent;
1669
+ }
1670
+ const initialNotificationsState = {
1671
+ emitter,
1672
+ events: [],
1673
+ };
1674
+ const notificationsSlice = createSlice({
1675
+ name: "notifications",
1676
+ initialState: initialNotificationsState,
1677
+ reducers: {
1678
+ addNotification: (state, action) => {
1679
+ return Object.assign(Object.assign({}, state), { events: [...state.events, Object.assign({}, action.payload)] });
1680
+ },
1681
+ doClearNotifications: (state) => {
1682
+ return Object.assign(Object.assign({}, state), { events: [] });
1683
+ },
1684
+ },
1685
+ });
1686
+ const { doClearNotifications } = notificationsSlice.actions;
1687
+ const doSetNotification = createAppThunk((payload) => (dispatch, getState) => {
1688
+ dispatch(notificationsSlice.actions.addNotification(payload));
1689
+ const state = getState();
1690
+ const emitter = selectNotificationsEmitter(state);
1691
+ emitter.emit(payload.type, payload);
1692
+ emitter.emit("*", payload);
1693
+ });
1694
+ const selectNotificationsRaw = (state) => state.notifications;
1695
+ const selectNotificationsEvents = (state) => state.notifications.events;
1696
+ const selectNotificationsEmitter = (state) => state.notifications.emitter;
1697
+ startAppListening({
1698
+ actionCreator: signalEvents.chatMessage,
1699
+ effect: ({ payload }, { dispatch, getState }) => {
1700
+ const state = getState();
1701
+ const client = selectRemoteParticipants(state).find(({ id }) => id === payload.senderId);
1702
+ if (!client) {
1703
+ console.warn("Could not find remote client that sent chat message");
1704
+ return;
1705
+ }
1706
+ dispatch(doSetNotification(createNotificationEvent({
1707
+ type: "chatMessageReceived",
1708
+ message: `${client.displayName} says: ${payload.text}`,
1709
+ props: {
1710
+ client,
1711
+ chatMessage: {
1712
+ senderId: payload.senderId,
1713
+ timestamp: payload.timestamp,
1714
+ text: payload.text,
1715
+ },
1716
+ },
1717
+ })));
1718
+ },
1719
+ });
1720
+ startAppListening({
1721
+ actionCreator: signalEvents.audioEnableRequested,
1722
+ effect: ({ payload }, { dispatch, getState }) => {
1723
+ const { enable, requestedByClientId } = payload;
1724
+ const state = getState();
1725
+ const client = selectRemoteParticipants(state).find(({ id }) => id === requestedByClientId);
1726
+ if (!client) {
1727
+ console.warn("Could not find remote client that requested a local audio change");
1728
+ return;
1729
+ }
1730
+ dispatch(doSetNotification(createNotificationEvent({
1731
+ type: enable ? "requestAudioEnable" : "requestAudioDisable",
1732
+ message: enable
1733
+ ? `${client.displayName} has requested for you to speak`
1734
+ : `${client.displayName} has muted your microphone`,
1735
+ props: {
1736
+ client,
1737
+ enable,
1738
+ },
1739
+ })));
1740
+ },
1741
+ });
1742
+ startAppListening({
1743
+ actionCreator: signalEvents.clientMetadataReceived,
1744
+ effect: (action, { dispatch, getOriginalState, getState }) => {
1745
+ var _a;
1746
+ const { error, payload } = action.payload;
1747
+ if (error || !payload) {
1748
+ return;
1749
+ }
1750
+ const { clientId, stickyReaction } = payload;
1751
+ const state = getState();
1752
+ const canAskToSpeak = selectIsAuthorizedToAskToSpeak(state);
1753
+ if (!canAskToSpeak) {
1754
+ return;
1755
+ }
1756
+ const client = selectRemoteParticipants(state).find(({ id }) => id === clientId);
1757
+ if (!client) {
1758
+ console.warn("Could not find remote client that provided updated metadata");
1759
+ return;
1760
+ }
1761
+ const previousState = getOriginalState();
1762
+ const previousClient = selectRemoteParticipants(previousState).find(({ id }) => id === clientId);
1763
+ if ((!stickyReaction && !(previousClient === null || previousClient === void 0 ? void 0 : previousClient.stickyReaction)) ||
1764
+ (stickyReaction === null || stickyReaction === void 0 ? void 0 : stickyReaction.timestamp) === ((_a = previousClient === null || previousClient === void 0 ? void 0 : previousClient.stickyReaction) === null || _a === void 0 ? void 0 : _a.timestamp)) {
1765
+ return;
1766
+ }
1767
+ dispatch(doSetNotification(createNotificationEvent({
1768
+ type: stickyReaction ? "remoteHandRaised" : "remoteHandLowered",
1769
+ message: `${client.displayName} ${stickyReaction ? "raised" : "lowered"} their hand`,
1770
+ props: {
1771
+ client,
1772
+ stickyReaction,
1773
+ },
1774
+ })));
1775
+ },
1776
+ });
1777
+ createReactor([selectSignalStatus], ({ dispatch, getState }, signalStatus) => {
1778
+ const state = getState();
1779
+ const roomConnectionStatus = selectRoomConnectionStatus(state);
1780
+ if (["left", "kicked"].includes(roomConnectionStatus)) {
1781
+ return;
1782
+ }
1783
+ if (signalStatus === "disconnected") {
1784
+ dispatch(doSetNotification(createNotificationEvent({
1785
+ type: "signalTrouble",
1786
+ message: `Network connection lost. Trying to reconnect you...`,
1787
+ props: {},
1788
+ })));
1789
+ }
1790
+ else if (signalStatus === "connected") {
1791
+ dispatch(doSetNotification(createNotificationEvent({
1792
+ type: "signalOk",
1793
+ message: `Network connection available`,
1794
+ props: {},
1795
+ })));
1796
+ }
1797
+ });
1798
+
1799
+ function isStreamerClient(client) {
1800
+ return client.roleName === "streamer";
1801
+ }
1802
+ function isRecorderClient(client) {
1803
+ return client.roleName === "recorder";
1804
+ }
1805
+ const initialState$5 = {
1806
+ isLocked: false,
1807
+ };
1808
+ const roomSlice = createSlice({
1809
+ name: "room",
1810
+ initialState: initialState$5,
1811
+ reducers: {},
1812
+ extraReducers: (builder) => {
1813
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1814
+ const { error, isLocked } = action.payload;
1815
+ if (error) {
1816
+ return state;
1817
+ }
1818
+ return Object.assign(Object.assign({}, state), { isLocked: Boolean(isLocked) });
1819
+ });
1820
+ builder.addCase(signalEvents.roomLocked, (state, action) => {
1821
+ const { isLocked } = action.payload;
1822
+ return Object.assign(Object.assign({}, state), { isLocked: Boolean(isLocked) });
1823
+ });
1824
+ },
1825
+ });
1826
+ const doLockRoom = createAppAuthorizedThunk((state) => selectIsAuthorizedToLockRoom(state), (payload) => (_, getState) => {
1827
+ const state = getState();
1828
+ const { socket } = selectSignalConnectionRaw(state);
1829
+ socket === null || socket === void 0 ? void 0 : socket.emit("set_lock", { locked: payload.locked });
1830
+ });
1831
+ const doKickParticipant = createAppAuthorizedThunk((state) => selectIsAuthorizedToKickClient(state), (payload) => (_, getState) => {
1832
+ const state = getState();
1833
+ const { socket } = selectSignalConnectionRaw(state);
1834
+ socket === null || socket === void 0 ? void 0 : socket.emit("kick_client", { clientId: payload.clientId, reasonId: "kick" });
1835
+ });
1836
+ const doEndMeeting = createAppAuthorizedThunk((state) => selectIsAuthorizedToEndMeeting(state), (payload) => (dispatch, getState) => {
1837
+ const state = getState();
1838
+ const clientsToKick = selectRemoteParticipants(state).map((c) => c.id);
1839
+ if (clientsToKick.length) {
1840
+ const { socket } = selectSignalConnectionRaw(state);
1841
+ socket === null || socket === void 0 ? void 0 : socket.emit("kick_client", { clientIds: clientsToKick, reasonId: "end-meeting" });
1842
+ }
1843
+ if (!payload.stayBehind) {
1844
+ dispatch(doAppStop());
1845
+ }
1846
+ });
1847
+ const selectRoomIsLocked = (state) => state.room.isLocked;
1848
+ const selectScreenshares = createSelector(selectLocalScreenshareStream, selectRemoteParticipants, (localScreenshareStream, remoteParticipants) => {
1849
+ const screenshares = [];
1850
+ if (localScreenshareStream) {
1851
+ screenshares.push({
1852
+ id: localScreenshareStream.id || "local-screenshare",
1853
+ participantId: "local",
1854
+ hasAudioTrack: localScreenshareStream.getTracks().some((track) => track.kind === "audio"),
1855
+ stream: localScreenshareStream,
1856
+ isLocal: true,
1857
+ });
1858
+ }
1859
+ for (const participant of remoteParticipants) {
1860
+ if (participant.presentationStream) {
1861
+ screenshares.push({
1862
+ id: participant.presentationStream.id || `pres-${participant.id}`,
1863
+ participantId: participant.id,
1864
+ hasAudioTrack: participant.presentationStream.getTracks().some((track) => track.kind === "audio"),
1865
+ stream: participant.presentationStream,
1866
+ isLocal: false,
1867
+ });
1868
+ }
1869
+ }
1870
+ return screenshares;
1871
+ });
1872
+ const selectRemoteClientViews = createSelector(selectLocalScreenshareStream, selectLocalParticipantRaw, selectRemoteParticipants, (localScreenshareStream, localParticipant, remoteParticipants) => {
1873
+ const views = [];
1874
+ if (localScreenshareStream) {
1875
+ const isScreenshareAudioEnabled = !!localScreenshareStream.getAudioTracks().length;
1876
+ views.push({
1877
+ clientId: localParticipant.id,
1878
+ displayName: "Your screenshare",
1879
+ id: "local-screenshare",
1880
+ isAudioEnabled: isScreenshareAudioEnabled,
1881
+ isLocalClient: true,
1882
+ isPresentation: true,
1883
+ isVideoEnabled: true,
1884
+ stream: localScreenshareStream,
1885
+ });
1886
+ }
1887
+ for (const c of remoteParticipants) {
1888
+ if (isStreamerClient(c) || isRecorderClient(c)) {
1889
+ continue;
1890
+ }
1891
+ const { presentationStream } = c, clientView = __rest(c, ["presentationStream"]);
1892
+ const displayName = c.displayName || "Guest";
1893
+ const isPresentationActive = presentationStream && presentationStream.active;
1894
+ const presentationId = "pres-" + c.id;
1895
+ const isStreamActive = c.stream && c.stream.active;
1896
+ const isVideoEnabled = c.isVideoEnabled;
1897
+ views.push(Object.assign(Object.assign(Object.assign({}, clientView), { clientId: c.id, displayName, hasActivePresentation: !!isPresentationActive }), (c.isVideoEnabled ? { isVideoEnabled } : {})));
1898
+ if (isPresentationActive) {
1899
+ views.push(Object.assign(Object.assign(Object.assign({}, clientView), { clientId: c.id, stream: c.presentationStream, displayName: `Screenshare (${displayName})`, id: presentationId, isPresentation: true, isVideoEnabled: true }), (isStreamActive && { isRecording: null })));
1900
+ }
1901
+ }
1902
+ return views;
1903
+ });
1904
+ const selectAllClientViews = createSelector(selectLocalParticipantView, selectRemoteClientViews, (localParticipant, remoteParticipants) => {
1905
+ return [localParticipant, ...remoteParticipants];
1906
+ });
1907
+
1670
1908
  const createWebRtcEmitter = (dispatch) => {
1671
1909
  return {
1672
1910
  emit: (eventName, data) => {
@@ -1683,7 +1921,7 @@ const createWebRtcEmitter = (dispatch) => {
1683
1921
  },
1684
1922
  };
1685
1923
  };
1686
- const initialState$3 = {
1924
+ const initialState$4 = {
1687
1925
  dispatcherCreated: false,
1688
1926
  error: null,
1689
1927
  isCreatingDispatcher: false,
@@ -1696,7 +1934,7 @@ const initialState$3 = {
1696
1934
  };
1697
1935
  const rtcConnectionSlice = createSlice({
1698
1936
  name: "rtcConnection",
1699
- initialState: initialState$3,
1937
+ initialState: initialState$4,
1700
1938
  reducers: {
1701
1939
  isAcceptingStreams: (state, action) => {
1702
1940
  return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
@@ -1706,7 +1944,7 @@ const rtcConnectionSlice = createSlice({
1706
1944
  return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
1707
1945
  },
1708
1946
  rtcDisconnected: () => {
1709
- return Object.assign({}, initialState$3);
1947
+ return Object.assign({}, initialState$4);
1710
1948
  },
1711
1949
  rtcDispatcherCreated: (state, action) => {
1712
1950
  return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
@@ -2004,7 +2242,7 @@ const rtcAnalyticsCustomEvents = {
2004
2242
  getOutput: () => ({}),
2005
2243
  },
2006
2244
  displayName: {
2007
- actions: [doSetDisplayName.fulfilled],
2245
+ actions: [doSetDisplayName],
2008
2246
  rtcEventName: "displayName",
2009
2247
  getValue: (state) => selectAppDisplayName(state),
2010
2248
  getOutput: (value) => ({ displayName: value }),
@@ -2071,11 +2309,11 @@ const makeComparable = (value) => {
2071
2309
  return JSON.stringify(value);
2072
2310
  return value;
2073
2311
  };
2074
- const initialState$2 = {
2312
+ const initialState$3 = {
2075
2313
  reportedValues: {},
2076
2314
  };
2077
2315
  const rtcAnalyticsSlice = createSlice({
2078
- initialState: initialState$2,
2316
+ initialState: initialState$3,
2079
2317
  name: "rtcAnalytics",
2080
2318
  reducers: {
2081
2319
  updateReportedValues(state, action) {
@@ -2135,6 +2373,92 @@ createReactor([selectRtcManagerInitialized], ({ dispatch }, selectRtcManagerInit
2135
2373
  }
2136
2374
  });
2137
2375
 
2376
+ function streamIdForClient({ isPresentation, stream }) {
2377
+ var _a, _b;
2378
+ return isPresentation ? (_b = (_a = stream === null || stream === void 0 ? void 0 : stream.outboundId) !== null && _a !== void 0 ? _a : stream === null || stream === void 0 ? void 0 : stream.inboundId) !== null && _b !== void 0 ? _b : stream === null || stream === void 0 ? void 0 : stream.id : "0";
2379
+ }
2380
+ function isClientSpotlighted({ spotlights, isPresentation, clientId, stream, }) {
2381
+ return !!spotlights.find((s) => {
2382
+ const streamId = streamIdForClient({ isPresentation, stream });
2383
+ return s.clientId === clientId && s.streamId === streamId;
2384
+ });
2385
+ }
2386
+ function mergeSpotlight(spotlights, spotlight) {
2387
+ const found = spotlights.find((s) => s.clientId === spotlight.clientId && s.streamId === spotlight.streamId);
2388
+ if (found) {
2389
+ return spotlights;
2390
+ }
2391
+ return spotlights.concat(spotlight);
2392
+ }
2393
+ function mapSpotlightsToClientViews(spotlights, clientViews) {
2394
+ return spotlights.reduce((acc, s) => {
2395
+ const clientView = clientViews.find((c) => s.clientId === c.clientId && s.streamId === streamIdForClient(c));
2396
+ if (clientView && !acc.includes(clientView)) {
2397
+ acc.push(clientView);
2398
+ }
2399
+ return acc;
2400
+ }, []);
2401
+ }
2402
+ const initialState$2 = {
2403
+ sorted: [],
2404
+ };
2405
+ const spotlightsSlice = createSlice({
2406
+ name: "spotlights",
2407
+ initialState: initialState$2,
2408
+ reducers: {},
2409
+ extraReducers: (builder) => {
2410
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
2411
+ if (!action.payload.room) {
2412
+ return state;
2413
+ }
2414
+ const { spotlights } = action.payload.room;
2415
+ return Object.assign(Object.assign({}, state), { sorted: spotlights });
2416
+ });
2417
+ builder.addCase(signalEvents.spotlightAdded, (state, action) => {
2418
+ const { clientId, streamId } = action.payload;
2419
+ return Object.assign(Object.assign({}, state), { sorted: mergeSpotlight(state.sorted, { clientId, streamId }) });
2420
+ });
2421
+ builder.addCase(signalEvents.spotlightRemoved, (state, action) => {
2422
+ const { clientId, streamId } = action.payload;
2423
+ return Object.assign(Object.assign({}, state), { sorted: state.sorted.filter((s) => !(s.clientId === clientId && s.streamId === streamId)) });
2424
+ });
2425
+ builder.addMatcher(isAnyOf(signalEvents.clientKicked, signalEvents.clientLeft), (state, action) => {
2426
+ const { clientId } = action.payload;
2427
+ return Object.assign(Object.assign({}, state), { sorted: state.sorted.filter((s) => s.clientId !== clientId) });
2428
+ });
2429
+ },
2430
+ });
2431
+ const doSpotlightParticipant = createAppAuthorizedThunk((state) => selectIsAuthorizedToSpotlight(state), ({ id }) => (_, getState) => {
2432
+ const state = getState();
2433
+ const clientView = selectAllClientViews(state).find((c) => c.clientId === id);
2434
+ if (!clientView) {
2435
+ return;
2436
+ }
2437
+ const { socket } = selectSignalConnectionRaw(state);
2438
+ const streamId = streamIdForClient(clientView);
2439
+ const payload = { clientId: clientView.id, streamId: streamId !== null && streamId !== void 0 ? streamId : "0" };
2440
+ socket === null || socket === void 0 ? void 0 : socket.emit("add_spotlight", payload);
2441
+ });
2442
+ const doRemoveSpotlight = createAppAuthorizedThunk((state) => selectIsAuthorizedToSpotlight(state), ({ id }) => (_, getState) => {
2443
+ const state = getState();
2444
+ const clientView = selectAllClientViews(state).find((c) => c.clientId === id);
2445
+ if (!clientView) {
2446
+ return;
2447
+ }
2448
+ const { socket } = selectSignalConnectionRaw(state);
2449
+ const streamId = streamIdForClient(clientView);
2450
+ const payload = { clientId: clientView.id, streamId: streamId !== null && streamId !== void 0 ? streamId : "0" };
2451
+ socket === null || socket === void 0 ? void 0 : socket.emit("remove_spotlight", payload);
2452
+ });
2453
+ const selectSpotlightsRaw = (state) => state.spotlights;
2454
+ const selectSpotlights = (state) => state.spotlights.sorted;
2455
+ const selectIsLocalParticipantSpotlighted = createSelector(selectLocalParticipantRaw, selectSpotlights, (localParticipant, spotlights) => {
2456
+ return isClientSpotlighted({ clientId: localParticipant.id, stream: localParticipant.stream, spotlights });
2457
+ });
2458
+ const selectSpotlightedClientViews = createSelector(selectAllClientViews, selectSpotlights, (clientViews, spotlights) => {
2459
+ return mapSpotlightsToClientViews(spotlights, clientViews);
2460
+ });
2461
+
2138
2462
  const initialState$1 = {
2139
2463
  isStreaming: false,
2140
2464
  error: null,
@@ -2219,6 +2543,7 @@ const appReducer = combineReducers({
2219
2543
  localMedia: localMediaSlice.reducer,
2220
2544
  localParticipant: localParticipantSlice.reducer,
2221
2545
  localScreenshare: localScreenshareSlice.reducer,
2546
+ notifications: notificationsSlice.reducer,
2222
2547
  organization: organizationSlice.reducer,
2223
2548
  remoteParticipants: remoteParticipantsSlice.reducer,
2224
2549
  room: roomSlice.reducer,
@@ -2226,6 +2551,7 @@ const appReducer = combineReducers({
2226
2551
  rtcAnalytics: rtcAnalyticsSlice.reducer,
2227
2552
  rtcConnection: rtcConnectionSlice.reducer,
2228
2553
  signalConnection: signalConnectionSlice.reducer,
2554
+ spotlights: spotlightsSlice.reducer,
2229
2555
  streaming: streamingSlice.reducer,
2230
2556
  waitingParticipants: waitingParticipantsSlice.reducer,
2231
2557
  });
@@ -3233,18 +3559,19 @@ class RoomService {
3233
3559
  }
3234
3560
 
3235
3561
  class RoomParticipant {
3236
- constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
3562
+ constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled, stickyReaction }) {
3237
3563
  this.isLocalParticipant = false;
3238
3564
  this.displayName = displayName;
3239
3565
  this.id = id;
3240
3566
  this.stream = stream;
3241
3567
  this.isAudioEnabled = isAudioEnabled;
3242
3568
  this.isVideoEnabled = isVideoEnabled;
3569
+ this.stickyReaction = stickyReaction;
3243
3570
  }
3244
3571
  }
3245
3572
  class LocalParticipant extends RoomParticipant {
3246
- constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
3247
- super({ displayName, id, stream, isAudioEnabled, isVideoEnabled });
3573
+ constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled, stickyReaction }) {
3574
+ super({ displayName, id, stream, isAudioEnabled, isVideoEnabled, stickyReaction });
3248
3575
  this.isLocalParticipant = true;
3249
3576
  }
3250
3577
  }
@@ -3274,4 +3601,4 @@ function createServices() {
3274
3601
  };
3275
3602
  }
3276
3603
 
3277
- export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, appSlice, authorizationSlice, chatSlice, cloudRecordingSlice, createAppAsyncThunk, createAppAuthorizedThunk, createAppThunk, createReactor, createServices, createStore, createWebRtcEmitter, debounce, deviceBusy, deviceCredentialsSlice, deviceIdentified, deviceIdentifying, doAcceptWaitingParticipant, doAppStart, doAppStop, doConnectRoom, doConnectRtc, doDisconnectRtc, doEnableAudio, doEnableVideo, doEndMeeting, doGetDeviceCredentials, doHandleAcceptStreams, doHandleStreamingStarted, doHandleStreamingStopped, doKickParticipant, doKnockRoom, doLockRoom, doOrganizationFetch, doRejectWaitingParticipant, doRequestAudioEnable, doRtcAnalyticsCustomEventsInitialize, doRtcManagerCreated, doRtcManagerInitialize, doRtcReportStreamResolution, doSendChatMessage, doSetDevice, doSetDisplayName, doSetLocalParticipant, doSignalConnect, doSignalDisconnect, doSignalIdentifyDevice, doStartCloudRecording, doStartLocalMedia, doStartScreenshare, doStopCloudRecording, doStopLocalMedia, doStopScreenshare, doSwitchLocalStream, doToggleCamera, doToggleLowDataMode, doUpdateDeviceList, getAudioTrack, getFakeMediaStream, getVideoTrack, initialCloudRecordingState, initialLocalMediaState, isAcceptingStreams, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, observeStore, organizationSlice, parseRoomUrlAndSubdomain, parseUnverifiedRoomKeyData, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, resolutionReported, roomConnectionSlice, roomSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, selectAppDisplayName, selectAppExternalId, selectAppInitialConfig, selectAppIsActive, selectAppIsNodeSdk, selectAppRaw, selectAppRoomName, selectAppRoomUrl, selectAppUserAgent, selectAuthorizationRoleName, selectBusyDeviceIds, selectCameraDeviceError, selectCameraDevices, selectChatMessages, selectChatRaw, selectCloudRecordingError, selectCloudRecordingRaw, selectCloudRecordingStartedAt, selectCloudRecordingStatus, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectDeviceCredentialsRaw, selectDeviceId, selectHasFetchedDeviceCredentials, selectIsAcceptingStreams, selectIsAuthorizedToEndMeeting, selectIsAuthorizedToKickClient, selectIsAuthorizedToLockRoom, selectIsAuthorizedToRequestAudioEnable, selectIsCameraEnabled, selectIsCloudRecording, selectIsLocalMediaStarting, selectIsLowDataModeEnabled, selectIsMicrophoneEnabled, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsToggleCamera, selectLocalMediaConstraintsOptions, selectLocalMediaDevices, selectLocalMediaIsSwitchingStream, selectLocalMediaOptions, selectLocalMediaOwnsStream, selectLocalMediaRaw, selectLocalMediaShouldStartWithOptions, selectLocalMediaShouldStop, selectLocalMediaStartError, selectLocalMediaStatus, selectLocalMediaStream, selectLocalParticipantClientClaim, selectLocalParticipantIsScreenSharing, selectLocalParticipantRaw, selectLocalScreenshareRaw, selectLocalScreenshareStatus, selectLocalScreenshareStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectOrganizationId, selectOrganizationRaw, selectRemoteParticipants, selectRemoteParticipantsRaw, selectRoomConnectionError, selectRoomConnectionRaw, selectRoomConnectionSession, selectRoomConnectionSessionId, selectRoomConnectionStatus, selectRoomIsLocked, selectRoomKey, selectRtcConnectionRaw, selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectRtcManager, selectRtcManagerInitialized, selectRtcStatus, selectScreenshares, selectSelfId, selectShouldConnectRoom, selectShouldConnectRtc, selectShouldConnectSignal, selectShouldDisconnectRtc, selectShouldFetchDeviceCredentials, selectShouldFetchOrganization, selectShouldIdentifyDevice, selectShouldInitializeRtc, selectSignalConnectionDeviceIdentified, selectSignalConnectionRaw, selectSignalConnectionSocket, selectSignalIsIdentifyingDevice, selectSignalStatus, selectSpeakerDevices, selectStreamingRaw, selectStreamsToAccept, selectWaitingParticipants, selectWaitingParticipantsRaw, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setLocalMediaOptions, setLocalMediaStream, setRoomKey, signalConnectionSlice, signalEvents, socketConnected, socketConnecting, socketDisconnected, socketReconnecting, startAppListening, stopScreenshare, streamStatusUpdated, streamingSlice, toggleCameraEnabled, toggleLowDataModeEnabled, toggleMicrophoneEnabled, updateReportedValues, waitingParticipantsSlice };
3604
+ export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, appSlice, authorizationSlice, chatSlice, cloudRecordingSlice, createAppAsyncThunk, createAppAuthorizedThunk, createAppThunk, createReactor, createServices, createStore, createWebRtcEmitter, debounce, deviceBusy, deviceCredentialsSlice, deviceIdentified, deviceIdentifying, doAcceptWaitingParticipant, doAppStart, doAppStop, doClearNotifications, doConnectRoom, doConnectRtc, doDisconnectRtc, doEnableAudio, doEnableVideo, doEndMeeting, doGetDeviceCredentials, doHandleAcceptStreams, doHandleStreamingStarted, doHandleStreamingStopped, doKickParticipant, doKnockRoom, doLockRoom, doOrganizationFetch, doRejectWaitingParticipant, doRemoveSpotlight, doRequestAudioEnable, doRtcAnalyticsCustomEventsInitialize, doRtcManagerCreated, doRtcManagerInitialize, doRtcReportStreamResolution, doSendChatMessage, doSendClientMetadata, doSetDevice, doSetDisplayName, doSetLocalStickyReaction, doSetNotification, doSignalConnect, doSignalDisconnect, doSignalIdentifyDevice, doSpotlightParticipant, doStartCloudRecording, doStartLocalMedia, doStartScreenshare, doStopCloudRecording, doStopLocalMedia, doStopScreenshare, doSwitchLocalStream, doToggleCamera, doToggleLowDataMode, doUpdateDeviceList, getAudioTrack, getFakeMediaStream, getVideoTrack, hasValue, initialCloudRecordingState, initialLocalMediaState, initialNotificationsState, isAcceptingStreams, isClientSpotlighted, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, notificationsSlice, observeStore, organizationSlice, parseRoomUrlAndSubdomain, parseUnverifiedRoomKeyData, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, resolutionReported, roomConnectionSlice, roomSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, selectAllClientViews, selectAppDisplayName, selectAppExternalId, selectAppInitialConfig, selectAppIsActive, selectAppIsNodeSdk, selectAppRaw, selectAppRoomName, selectAppRoomUrl, selectAppUserAgent, selectAuthorizationRoleName, selectBusyDeviceIds, selectCameraDeviceError, selectCameraDevices, selectChatMessages, selectChatRaw, selectCloudRecordingError, selectCloudRecordingRaw, selectCloudRecordingStartedAt, selectCloudRecordingStatus, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectCurrentSpeakerDeviceId, selectDeviceCredentialsRaw, selectDeviceId, selectHasFetchedDeviceCredentials, selectIsAcceptingStreams, selectIsAuthorizedToAskToSpeak, selectIsAuthorizedToEndMeeting, selectIsAuthorizedToKickClient, selectIsAuthorizedToLockRoom, selectIsAuthorizedToRequestAudioEnable, selectIsAuthorizedToSpotlight, selectIsCameraEnabled, selectIsCloudRecording, selectIsLocalMediaStarting, selectIsLocalParticipantSpotlighted, selectIsLowDataModeEnabled, selectIsMicrophoneEnabled, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsToggleCamera, selectLocalMediaConstraintsOptions, selectLocalMediaDevices, selectLocalMediaIsSwitchingStream, selectLocalMediaOptions, selectLocalMediaOwnsStream, selectLocalMediaRaw, selectLocalMediaShouldStartWithOptions, selectLocalMediaShouldStop, selectLocalMediaStartError, selectLocalMediaStatus, selectLocalMediaStream, selectLocalParticipantClientClaim, selectLocalParticipantDisplayName, selectLocalParticipantIsScreenSharing, selectLocalParticipantRaw, selectLocalParticipantStickyReaction, selectLocalParticipantView, selectLocalScreenshareRaw, selectLocalScreenshareStatus, selectLocalScreenshareStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectNotificationsEmitter, selectNotificationsEvents, selectNotificationsRaw, selectNumParticipants, selectOrganizationId, selectOrganizationRaw, selectRemoteClientViews, selectRemoteParticipants, selectRemoteParticipantsRaw, selectRoomConnectionError, selectRoomConnectionRaw, selectRoomConnectionSession, selectRoomConnectionSessionId, selectRoomConnectionStatus, selectRoomIsLocked, selectRoomKey, selectRtcConnectionRaw, selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectRtcManager, selectRtcManagerInitialized, selectRtcStatus, selectScreenshares, selectSelfId, selectShouldConnectRoom, selectShouldConnectRtc, selectShouldConnectSignal, selectShouldDisconnectRtc, selectShouldFetchDeviceCredentials, selectShouldFetchOrganization, selectShouldIdentifyDevice, selectShouldInitializeRtc, selectSignalConnectionDeviceIdentified, selectSignalConnectionRaw, selectSignalConnectionSocket, selectSignalIsIdentifyingDevice, selectSignalStatus, selectSpeakerDevices, selectSpotlightedClientViews, selectSpotlights, selectSpotlightsRaw, selectStreamingRaw, selectStreamsToAccept, selectWaitingParticipants, selectWaitingParticipantsRaw, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setCurrentSpeakerDeviceId, setLocalMediaOptions, setLocalMediaStream, setRoomKey, signalConnectionSlice, signalEvents, socketConnected, socketConnecting, socketDisconnected, socketReconnecting, spotlightsSlice, startAppListening, stopScreenshare, streamIdForClient, streamStatusUpdated, streamingSlice, toggleCameraEnabled, toggleLowDataModeEnabled, toggleMicrophoneEnabled, updateReportedValues, waitingParticipantsSlice };