@whereby.com/core 0.25.0 → 0.26.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,7 +1,7 @@
1
1
  import { createAsyncThunk, createListenerMiddleware, addListener, createSlice, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
2
- import { ServerSocket, getDeviceData, getStream, getUpdatedDevices, RtcManagerDispatcher, assert, fromLocation } from '@whereby.com/media';
3
- import { EventEmitter } from 'events';
2
+ import { ServerSocket, getDeviceData, getStream, getUpdatedDevices, RtcManagerDispatcher, setClientProvider, subscribeIssues, assert, fromLocation } from '@whereby.com/media';
4
3
  import { Chrome111 } from 'mediasoup-client/lib/handlers/Chrome111.js';
4
+ import { EventEmitter } from 'events';
5
5
  import nodeBtoa from 'btoa';
6
6
  import axios from 'axios';
7
7
 
@@ -43,9 +43,9 @@ const createReactor = (selectors, callback) => {
43
43
  });
44
44
  };
45
45
 
46
- const coreVersion = "0.25.0";
46
+ const coreVersion = "0.26.0";
47
47
 
48
- const initialState$f = {
48
+ const initialState$g = {
49
49
  isNodeSdk: false,
50
50
  isActive: false,
51
51
  isDialIn: false,
@@ -57,7 +57,7 @@ const initialState$f = {
57
57
  };
58
58
  const appSlice = createSlice({
59
59
  name: "app",
60
- initialState: initialState$f,
60
+ initialState: initialState$g,
61
61
  reducers: {
62
62
  doAppStart: (state, action) => {
63
63
  const url = new URL(action.payload.roomUrl);
@@ -120,13 +120,13 @@ const ROOM_ACTION_PERMISSIONS_BY_ROLE = {
120
120
  canAskToSpeak: ["host"],
121
121
  canSpotlight: ["host"],
122
122
  };
123
- const initialState$e = {
123
+ const initialState$f = {
124
124
  roomKey: null,
125
125
  roleName: "none",
126
126
  };
127
127
  const authorizationSlice = createSlice({
128
128
  name: "authorization",
129
- initialState: initialState$e,
129
+ initialState: initialState$f,
130
130
  reducers: {
131
131
  setRoomKey: (state, action) => {
132
132
  return Object.assign(Object.assign({}, state), { roomKey: action.payload });
@@ -197,13 +197,13 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
197
197
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
198
198
  };
199
199
 
200
- const initialState$d = {
200
+ const initialState$e = {
201
201
  isFetching: false,
202
202
  data: null,
203
203
  };
204
204
  const deviceCredentialsSlice = createSlice({
205
205
  name: "deviceCredentials",
206
- initialState: initialState$d,
206
+ initialState: initialState$e,
207
207
  reducers: {},
208
208
  extraReducers: (builder) => {
209
209
  builder.addCase(doGetDeviceCredentials.pending, (state) => {
@@ -278,7 +278,7 @@ function createSocket() {
278
278
  };
279
279
  return new ServerSocket(socketHost, socketOverrides);
280
280
  }
281
- const initialState$c = {
281
+ const initialState$d = {
282
282
  deviceIdentified: false,
283
283
  isIdentifyingDevice: false,
284
284
  status: "ready",
@@ -286,7 +286,7 @@ const initialState$c = {
286
286
  };
287
287
  const signalConnectionSlice = createSlice({
288
288
  name: "signalConnection",
289
- initialState: initialState$c,
289
+ initialState: initialState$d,
290
290
  reducers: {
291
291
  socketConnecting: (state) => {
292
292
  return Object.assign(Object.assign({}, state), { status: "connecting" });
@@ -295,7 +295,7 @@ const signalConnectionSlice = createSlice({
295
295
  return Object.assign(Object.assign({}, state), { socket: action.payload, status: "connected" });
296
296
  },
297
297
  socketDisconnected: (state) => {
298
- return Object.assign(Object.assign({}, state), { deviceIdentified: false, status: "disconnected" });
298
+ return Object.assign(Object.assign({}, state), { socket: null, deviceIdentified: false, status: "disconnected" });
299
299
  },
300
300
  socketReconnecting: (state) => {
301
301
  return Object.assign(Object.assign({}, state), { status: "reconnecting" });
@@ -390,12 +390,12 @@ startAppListening({
390
390
  },
391
391
  });
392
392
 
393
- const initialState$b = {
393
+ const initialState$c = {
394
394
  chatMessages: [],
395
395
  };
396
396
  const chatSlice = createSlice({
397
397
  name: "chat",
398
- initialState: initialState$b,
398
+ initialState: initialState$c,
399
399
  reducers: {},
400
400
  extraReducers(builder) {
401
401
  builder.addCase(signalEvents.chatMessage, (state, action) => {
@@ -476,6 +476,60 @@ const selectCloudRecordingStartedAt = (state) => state.cloudRecording.startedAt;
476
476
  const selectCloudRecordingError = (state) => state.cloudRecording.error;
477
477
  const selectIsCloudRecording = (state) => state.cloudRecording.isRecording;
478
478
 
479
+ const initialState$b = {
480
+ data: null,
481
+ isFetching: false,
482
+ error: null,
483
+ };
484
+ const organizationSlice = createSlice({
485
+ initialState: initialState$b,
486
+ name: "organization",
487
+ reducers: {},
488
+ extraReducers: (builder) => {
489
+ builder.addCase(doOrganizationFetch.pending, (state) => {
490
+ return Object.assign(Object.assign({}, state), { isFetching: true });
491
+ });
492
+ builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
493
+ if (!action.payload)
494
+ return Object.assign(Object.assign({}, state), { isFetching: true });
495
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
496
+ });
497
+ builder.addCase(doOrganizationFetch.rejected, (state) => {
498
+ return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
499
+ });
500
+ },
501
+ });
502
+ const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
503
+ try {
504
+ const roomUrl = selectAppRoomUrl(getState());
505
+ const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
506
+ if (!organization) {
507
+ throw new Error("Invalid room url");
508
+ }
509
+ return organization;
510
+ }
511
+ catch (error) {
512
+ console.error(error);
513
+ }
514
+ }));
515
+ const selectOrganizationRaw = (state) => state.organization;
516
+ const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
517
+ const selectShouldFetchOrganization = createSelector(selectAppIsActive, selectOrganizationRaw, selectDeviceCredentialsRaw, (appIsActive, organization, deviceCredentials) => {
518
+ if (appIsActive &&
519
+ !organization.data &&
520
+ !organization.isFetching &&
521
+ !organization.error &&
522
+ !deviceCredentials.isFetching) {
523
+ return true;
524
+ }
525
+ return false;
526
+ });
527
+ createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
528
+ if (shouldFetchOrganization) {
529
+ dispatch(doOrganizationFetch());
530
+ }
531
+ });
532
+
479
533
  function fakeAudioStream() {
480
534
  const audioCtx = new AudioContext();
481
535
  const oscillator = audioCtx.createOscillator();
@@ -1181,82 +1235,175 @@ createReactor([selectLocalParticipantDisplayName, selectLocalParticipantStickyRe
1181
1235
  });
1182
1236
 
1183
1237
  const initialState$9 = {
1184
- status: "inactive",
1185
- stream: null,
1238
+ session: null,
1239
+ status: "ready",
1186
1240
  error: null,
1187
1241
  };
1188
- const localScreenshareSlice = createSlice({
1189
- name: "localScreenshare",
1242
+ const roomConnectionSlice = createSlice({
1190
1243
  initialState: initialState$9,
1244
+ name: "roomConnection",
1191
1245
  reducers: {
1192
- stopScreenshare(state, action) {
1193
- return Object.assign(Object.assign({}, state), { status: "inactive", stream: null });
1246
+ connectionStatusChanged: (state, action) => {
1247
+ return Object.assign(Object.assign({}, state), { status: action.payload });
1194
1248
  },
1195
1249
  },
1196
1250
  extraReducers: (builder) => {
1197
- builder.addCase(doStartScreenshare.pending, (state) => {
1198
- return Object.assign(Object.assign({}, state), { status: "starting" });
1251
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1252
+ var _a, _b;
1253
+ const { error, isLocked } = action.payload;
1254
+ if (error === "room_locked" && isLocked) {
1255
+ return Object.assign(Object.assign({}, state), { status: "room_locked" });
1256
+ }
1257
+ if (error) {
1258
+ return Object.assign(Object.assign({}, state), { status: "disconnected", error });
1259
+ }
1260
+ return Object.assign(Object.assign({}, state), { status: "connected", session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
1199
1261
  });
1200
- builder.addCase(doStartScreenshare.fulfilled, (state, { payload: { stream } }) => {
1201
- return Object.assign(Object.assign({}, state), { status: "active", stream });
1262
+ builder.addCase(signalEvents.disconnect, (state) => {
1263
+ if (["kicked", "left"].includes(state.status)) {
1264
+ return Object.assign({}, state);
1265
+ }
1266
+ return Object.assign(Object.assign({}, state), { status: "disconnected" });
1202
1267
  });
1203
- builder.addCase(doStartScreenshare.rejected, (state, { payload }) => {
1204
- return Object.assign(Object.assign({}, state), { error: payload, status: "inactive", stream: null });
1268
+ builder.addCase(signalEvents.newClient, (state, action) => {
1269
+ var _a, _b;
1270
+ return Object.assign(Object.assign({}, state), { session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
1271
+ });
1272
+ builder.addCase(signalEvents.roomSessionEnded, (state, action) => {
1273
+ var _a;
1274
+ if (((_a = state.session) === null || _a === void 0 ? void 0 : _a.id) !== action.payload.roomSessionId) {
1275
+ return state;
1276
+ }
1277
+ return Object.assign(Object.assign({}, state), { session: null });
1278
+ });
1279
+ builder.addCase(signalEvents.clientKicked, (state) => {
1280
+ return Object.assign(Object.assign({}, state), { status: "kicked" });
1281
+ });
1282
+ builder.addCase(signalEvents.roomLeft, (state) => {
1283
+ return Object.assign(Object.assign({}, state), { status: "left" });
1284
+ });
1285
+ builder.addCase(socketReconnecting, (state) => {
1286
+ return Object.assign(Object.assign({}, state), { status: "reconnecting" });
1205
1287
  });
1206
1288
  },
1207
1289
  });
1208
- const { stopScreenshare } = localScreenshareSlice.actions;
1209
- const doStartScreenshare = createAppAsyncThunk("localScreenshare/doStartScreenshare", (_, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1210
- var _a;
1211
- try {
1212
- const state = getState();
1213
- const screenshareStream = selectLocalScreenshareStream(state);
1214
- if (screenshareStream) {
1215
- return { stream: screenshareStream };
1216
- }
1217
- const stream = yield navigator.mediaDevices.getDisplayMedia();
1218
- const onEnded = () => {
1219
- dispatch(doStopScreenshare());
1220
- };
1221
- if ("oninactive" in stream) {
1222
- stream.addEventListener("inactive", onEnded);
1223
- }
1224
- else {
1225
- (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
1226
- }
1227
- return { stream };
1228
- }
1229
- catch (error) {
1230
- return rejectWithValue(error);
1231
- }
1232
- }));
1233
- const doStopScreenshare = createAppThunk(() => (dispatch, getState) => {
1290
+ const { connectionStatusChanged } = roomConnectionSlice.actions;
1291
+ const doKnockRoom = createAppThunk(() => (dispatch, getState) => {
1234
1292
  const state = getState();
1235
- const screenshareStream = selectLocalScreenshareStream(state);
1236
- if (!screenshareStream) {
1237
- return;
1293
+ const socket = selectSignalConnectionRaw(state).socket;
1294
+ const roomName = selectAppRoomName(state);
1295
+ const roomKey = selectRoomKey(state);
1296
+ const displayName = selectAppDisplayName(state);
1297
+ const isDialIn = selectAppIsDialIn(state);
1298
+ const userAgent = selectAppUserAgent(state);
1299
+ const externalId = selectAppExternalId(state);
1300
+ const organizationId = selectOrganizationId(state);
1301
+ socket === null || socket === void 0 ? void 0 : socket.emit("knock_room", {
1302
+ avatarUrl: null,
1303
+ config: {
1304
+ isAudioEnabled: true,
1305
+ isVideoEnabled: true,
1306
+ },
1307
+ deviceCapabilities: { canScreenshare: true },
1308
+ displayName,
1309
+ isCoLocated: false,
1310
+ isDialIn,
1311
+ isDevicePermissionDenied: false,
1312
+ kickFromOtherRooms: false,
1313
+ organizationId,
1314
+ roomKey,
1315
+ roomName,
1316
+ userAgent,
1317
+ externalId,
1318
+ });
1319
+ dispatch(connectionStatusChanged("knocking"));
1320
+ });
1321
+ const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
1322
+ const state = getState();
1323
+ const socket = selectSignalConnectionRaw(state).socket;
1324
+ const roomName = selectAppRoomName(state);
1325
+ const roomKey = selectRoomKey(state);
1326
+ const displayName = selectAppDisplayName(state);
1327
+ const userAgent = selectAppUserAgent(state);
1328
+ const externalId = selectAppExternalId(state);
1329
+ const isDialIn = selectAppIsDialIn(state);
1330
+ const organizationId = selectOrganizationId(state);
1331
+ const isCameraEnabled = selectIsCameraEnabled(getState());
1332
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
1333
+ const clientClaim = selectLocalParticipantClientClaim(getState());
1334
+ socket === null || socket === void 0 ? void 0 : socket.emit("join_room", Object.assign({ avatarUrl: null, config: {
1335
+ isAudioEnabled: isMicrophoneEnabled,
1336
+ isVideoEnabled: isCameraEnabled,
1337
+ }, deviceCapabilities: { canScreenshare: true }, displayName, isCoLocated: false, isDialIn, isDevicePermissionDenied: false, kickFromOtherRooms: false, organizationId,
1338
+ roomKey,
1339
+ roomName,
1340
+ userAgent,
1341
+ externalId }, (clientClaim && { clientClaim })));
1342
+ dispatch(connectionStatusChanged("connecting"));
1343
+ });
1344
+ const selectRoomConnectionRaw = (state) => state.roomConnection;
1345
+ const selectRoomConnectionSession = (state) => state.roomConnection.session;
1346
+ const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
1347
+ const selectRoomConnectionStatus = (state) => state.roomConnection.status;
1348
+ const selectRoomConnectionError = (state) => state.roomConnection.error;
1349
+ const selectShouldConnectRoom = createSelector([
1350
+ selectAppIsActive,
1351
+ selectOrganizationId,
1352
+ selectRoomConnectionStatus,
1353
+ selectSignalConnectionDeviceIdentified,
1354
+ selectLocalMediaStatus,
1355
+ selectRoomConnectionError,
1356
+ ], (appIsActive, hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus, roomConnectionError) => {
1357
+ if (appIsActive &&
1358
+ localMediaStatus === "started" &&
1359
+ signalConnectionDeviceIdentified &&
1360
+ !!hasOrganizationIdFetched &&
1361
+ ["ready", "reconnecting", "disconnected"].includes(roomConnectionStatus) &&
1362
+ !roomConnectionError) {
1363
+ return true;
1364
+ }
1365
+ return false;
1366
+ });
1367
+ createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
1368
+ if (shouldConnectRoom) {
1369
+ dispatch(doConnectRoom());
1238
1370
  }
1239
- screenshareStream.getTracks().forEach((track) => track.stop());
1240
- dispatch(stopScreenshare({ stream: screenshareStream }));
1241
1371
  });
1242
- const selectLocalScreenshareRaw = (state) => state.localScreenshare;
1243
- const selectLocalScreenshareStatus = (state) => state.localScreenshare.status;
1244
- const selectLocalScreenshareStream = (state) => state.localScreenshare.stream;
1245
1372
  startAppListening({
1246
- actionCreator: localMediaStopped,
1247
- effect: (_, { getState }) => {
1373
+ actionCreator: signalEvents.knockHandled,
1374
+ effect: ({ payload }, { dispatch, getState }) => {
1375
+ const { clientId, resolution } = payload;
1248
1376
  const state = getState();
1249
- const screenshareStream = selectLocalScreenshareStream(state);
1250
- if (!screenshareStream) {
1377
+ const selfId = selectSelfId(state);
1378
+ if (clientId !== selfId) {
1251
1379
  return;
1252
1380
  }
1253
- screenshareStream === null || screenshareStream === void 0 ? void 0 : screenshareStream.getTracks().forEach((track) => {
1254
- track.stop();
1255
- });
1256
- },
1257
- });
1258
-
1259
- function createRtcEventAction(name) {
1381
+ if (resolution === "accepted") {
1382
+ dispatch(setRoomKey(payload.metadata.roomKey));
1383
+ dispatch(doConnectRoom());
1384
+ }
1385
+ else if (resolution === "rejected") {
1386
+ dispatch(connectionStatusChanged("knock_rejected"));
1387
+ }
1388
+ },
1389
+ });
1390
+ startAppListening({
1391
+ actionCreator: doAppStop,
1392
+ effect: (_, { dispatch, getState }) => {
1393
+ const state = getState();
1394
+ const roomConnectionStatus = selectRoomConnectionStatus(state);
1395
+ if (roomConnectionStatus === "connected") {
1396
+ const socket = selectSignalConnectionRaw(state).socket;
1397
+ socket === null || socket === void 0 ? void 0 : socket.emit("leave_room");
1398
+ dispatch(connectionStatusChanged("leaving"));
1399
+ }
1400
+ else {
1401
+ doSignalDisconnect();
1402
+ }
1403
+ },
1404
+ });
1405
+
1406
+ function createRtcEventAction(name) {
1260
1407
  return createAction(`rtcConnection/event/${name}`);
1261
1408
  }
1262
1409
  const rtcEvents = {
@@ -1458,373 +1605,388 @@ const selectNumParticipants = createSelector(selectRemoteParticipants, selectLoc
1458
1605
  });
1459
1606
 
1460
1607
  const initialState$7 = {
1461
- data: null,
1462
- isFetching: false,
1608
+ status: "inactive",
1609
+ stream: null,
1463
1610
  error: null,
1464
1611
  };
1465
- const organizationSlice = createSlice({
1612
+ const localScreenshareSlice = createSlice({
1613
+ name: "localScreenshare",
1466
1614
  initialState: initialState$7,
1467
- name: "organization",
1468
- reducers: {},
1615
+ reducers: {
1616
+ stopScreenshare(state, action) {
1617
+ return Object.assign(Object.assign({}, state), { status: "inactive", stream: null });
1618
+ },
1619
+ },
1469
1620
  extraReducers: (builder) => {
1470
- builder.addCase(doOrganizationFetch.pending, (state) => {
1471
- return Object.assign(Object.assign({}, state), { isFetching: true });
1621
+ builder.addCase(doStartScreenshare.pending, (state) => {
1622
+ return Object.assign(Object.assign({}, state), { status: "starting" });
1472
1623
  });
1473
- builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
1474
- if (!action.payload)
1475
- return Object.assign(Object.assign({}, state), { isFetching: true });
1476
- return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
1624
+ builder.addCase(doStartScreenshare.fulfilled, (state, { payload: { stream } }) => {
1625
+ return Object.assign(Object.assign({}, state), { status: "active", stream });
1477
1626
  });
1478
- builder.addCase(doOrganizationFetch.rejected, (state) => {
1479
- return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
1627
+ builder.addCase(doStartScreenshare.rejected, (state, { payload }) => {
1628
+ return Object.assign(Object.assign({}, state), { error: payload, status: "inactive", stream: null });
1480
1629
  });
1481
1630
  },
1482
1631
  });
1483
- const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1632
+ const { stopScreenshare } = localScreenshareSlice.actions;
1633
+ const doStartScreenshare = createAppAsyncThunk("localScreenshare/doStartScreenshare", (_, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1634
+ var _a;
1484
1635
  try {
1485
- const roomUrl = selectAppRoomUrl(getState());
1486
- const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
1487
- if (!organization) {
1488
- throw new Error("Invalid room url");
1636
+ const state = getState();
1637
+ const screenshareStream = selectLocalScreenshareStream(state);
1638
+ if (screenshareStream) {
1639
+ return { stream: screenshareStream };
1489
1640
  }
1490
- return organization;
1641
+ const stream = yield navigator.mediaDevices.getDisplayMedia();
1642
+ const onEnded = () => {
1643
+ dispatch(doStopScreenshare());
1644
+ };
1645
+ if ("oninactive" in stream) {
1646
+ stream.addEventListener("inactive", onEnded);
1647
+ }
1648
+ else {
1649
+ (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
1650
+ }
1651
+ return { stream };
1491
1652
  }
1492
1653
  catch (error) {
1493
- console.error(error);
1654
+ return rejectWithValue(error);
1494
1655
  }
1495
1656
  }));
1496
- const selectOrganizationRaw = (state) => state.organization;
1497
- const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
1498
- const selectShouldFetchOrganization = createSelector(selectAppIsActive, selectOrganizationRaw, selectDeviceCredentialsRaw, (appIsActive, organization, deviceCredentials) => {
1499
- if (appIsActive &&
1500
- !organization.data &&
1501
- !organization.isFetching &&
1502
- !organization.error &&
1503
- !deviceCredentials.isFetching) {
1504
- return true;
1657
+ const doStopScreenshare = createAppThunk(() => (dispatch, getState) => {
1658
+ const state = getState();
1659
+ const screenshareStream = selectLocalScreenshareStream(state);
1660
+ if (!screenshareStream) {
1661
+ return;
1505
1662
  }
1506
- return false;
1663
+ screenshareStream.getTracks().forEach((track) => track.stop());
1664
+ dispatch(stopScreenshare({ stream: screenshareStream }));
1507
1665
  });
1508
- createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
1509
- if (shouldFetchOrganization) {
1510
- dispatch(doOrganizationFetch());
1511
- }
1666
+ const selectLocalScreenshareRaw = (state) => state.localScreenshare;
1667
+ const selectLocalScreenshareStatus = (state) => state.localScreenshare.status;
1668
+ const selectLocalScreenshareStream = (state) => state.localScreenshare.stream;
1669
+ startAppListening({
1670
+ actionCreator: localMediaStopped,
1671
+ effect: (_, { getState }) => {
1672
+ const state = getState();
1673
+ const screenshareStream = selectLocalScreenshareStream(state);
1674
+ if (!screenshareStream) {
1675
+ return;
1676
+ }
1677
+ screenshareStream === null || screenshareStream === void 0 ? void 0 : screenshareStream.getTracks().forEach((track) => {
1678
+ track.stop();
1679
+ });
1680
+ },
1512
1681
  });
1513
1682
 
1683
+ const createWebRtcEmitter = (dispatch) => {
1684
+ return {
1685
+ emit: (eventName, data) => {
1686
+ if (eventName === "rtc_manager_created") {
1687
+ dispatch(doRtcManagerCreated(data));
1688
+ }
1689
+ else if (eventName === "stream_added") {
1690
+ dispatch(rtcEvents.streamAdded(data));
1691
+ }
1692
+ else if (eventName === "rtc_manager_destroyed") {
1693
+ dispatch(rtcManagerDestroyed());
1694
+ }
1695
+ else ;
1696
+ },
1697
+ };
1698
+ };
1514
1699
  const initialState$6 = {
1515
- session: null,
1516
- status: "ready",
1700
+ dispatcherCreated: false,
1517
1701
  error: null,
1702
+ isCreatingDispatcher: false,
1703
+ reportedStreamResolutions: {},
1704
+ rtcManager: null,
1705
+ rtcManagerDispatcher: null,
1706
+ rtcManagerInitialized: false,
1707
+ status: "inactive",
1708
+ isAcceptingStreams: false,
1518
1709
  };
1519
- const roomConnectionSlice = createSlice({
1710
+ const rtcConnectionSlice = createSlice({
1711
+ name: "rtcConnection",
1520
1712
  initialState: initialState$6,
1521
- name: "roomConnection",
1522
1713
  reducers: {
1523
- connectionStatusChanged: (state, action) => {
1524
- return Object.assign(Object.assign({}, state), { status: action.payload });
1714
+ isAcceptingStreams: (state, action) => {
1715
+ return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
1716
+ },
1717
+ resolutionReported: (state, action) => {
1718
+ const { streamId, width, height } = action.payload;
1719
+ return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
1720
+ },
1721
+ rtcDisconnected: () => {
1722
+ return Object.assign({}, initialState$6);
1723
+ },
1724
+ rtcDispatcherCreated: (state, action) => {
1725
+ return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
1726
+ },
1727
+ rtcManagerCreated: (state, action) => {
1728
+ return Object.assign(Object.assign({}, state), { rtcManager: action.payload, status: "ready" });
1729
+ },
1730
+ rtcManagerDestroyed: (state) => {
1731
+ return Object.assign(Object.assign({}, state), { rtcManager: null });
1732
+ },
1733
+ rtcManagerInitialized: (state) => {
1734
+ return Object.assign(Object.assign({}, state), { rtcManagerInitialized: true });
1525
1735
  },
1526
1736
  },
1527
1737
  extraReducers: (builder) => {
1528
- builder.addCase(signalEvents.roomJoined, (state, action) => {
1529
- var _a, _b;
1530
- const { error, isLocked } = action.payload;
1531
- if (error === "room_locked" && isLocked) {
1532
- return Object.assign(Object.assign({}, state), { status: "room_locked" });
1533
- }
1534
- if (error) {
1535
- return Object.assign(Object.assign({}, state), { status: "disconnected", error });
1536
- }
1537
- return Object.assign(Object.assign({}, state), { status: "connected", session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
1538
- });
1539
- builder.addCase(signalEvents.disconnect, (state) => {
1540
- if (["kicked", "left"].includes(state.status)) {
1541
- return Object.assign({}, state);
1542
- }
1543
- return Object.assign(Object.assign({}, state), { status: "disconnected" });
1544
- });
1545
- builder.addCase(signalEvents.newClient, (state, action) => {
1546
- var _a, _b;
1547
- return Object.assign(Object.assign({}, state), { session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
1548
- });
1549
- builder.addCase(signalEvents.roomSessionEnded, (state, action) => {
1550
- var _a;
1551
- if (((_a = state.session) === null || _a === void 0 ? void 0 : _a.id) !== action.payload.roomSessionId) {
1552
- return state;
1553
- }
1554
- return Object.assign(Object.assign({}, state), { session: null });
1555
- });
1556
- builder.addCase(signalEvents.clientKicked, (state) => {
1557
- return Object.assign(Object.assign({}, state), { status: "kicked" });
1558
- });
1559
- builder.addCase(signalEvents.roomLeft, (state) => {
1560
- return Object.assign(Object.assign({}, state), { status: "left" });
1561
- });
1562
1738
  builder.addCase(socketReconnecting, (state) => {
1563
1739
  return Object.assign(Object.assign({}, state), { status: "reconnecting" });
1564
1740
  });
1741
+ builder.addCase(signalEvents.roomJoined, (state) => {
1742
+ return Object.assign(Object.assign({}, state), { status: state.status === "reconnecting" ? "ready" : state.status });
1743
+ });
1565
1744
  },
1566
1745
  });
1567
- const { connectionStatusChanged } = roomConnectionSlice.actions;
1568
- const doKnockRoom = createAppThunk(() => (dispatch, getState) => {
1746
+ const { resolutionReported, rtcDispatcherCreated, rtcDisconnected, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, isAcceptingStreams, } = rtcConnectionSlice.actions;
1747
+ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
1569
1748
  const state = getState();
1570
1749
  const socket = selectSignalConnectionRaw(state).socket;
1571
- const roomName = selectAppRoomName(state);
1572
- const roomKey = selectRoomKey(state);
1573
- const displayName = selectAppDisplayName(state);
1574
- const isDialIn = selectAppIsDialIn(state);
1575
- const userAgent = selectAppUserAgent(state);
1576
- const externalId = selectAppExternalId(state);
1577
- const organizationId = selectOrganizationId(state);
1578
- socket === null || socket === void 0 ? void 0 : socket.emit("knock_room", {
1579
- avatarUrl: null,
1580
- config: {
1581
- isAudioEnabled: true,
1582
- isVideoEnabled: true,
1750
+ const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
1751
+ const isCameraEnabled = selectIsCameraEnabled(state);
1752
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
1753
+ const isNodeSdk = selectAppIsNodeSdk(state);
1754
+ if (dispatcher || !socket) {
1755
+ return;
1756
+ }
1757
+ const webrtcProvider = {
1758
+ getMediaConstraints: () => ({
1759
+ audio: isMicrophoneEnabled,
1760
+ video: isCameraEnabled,
1761
+ }),
1762
+ deferrable(clientId) {
1763
+ return !clientId;
1583
1764
  },
1584
- deviceCapabilities: { canScreenshare: true },
1585
- displayName,
1586
- isCoLocated: false,
1587
- isDialIn,
1588
- isDevicePermissionDenied: false,
1589
- kickFromOtherRooms: false,
1590
- organizationId,
1591
- roomKey,
1592
- roomName,
1593
- userAgent,
1594
- externalId,
1595
- });
1596
- dispatch(connectionStatusChanged("knocking"));
1597
- });
1598
- const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
1599
- const state = getState();
1600
- const socket = selectSignalConnectionRaw(state).socket;
1601
- const roomName = selectAppRoomName(state);
1602
- const roomKey = selectRoomKey(state);
1603
- const displayName = selectAppDisplayName(state);
1604
- const userAgent = selectAppUserAgent(state);
1605
- const externalId = selectAppExternalId(state);
1606
- const isDialIn = selectAppIsDialIn(state);
1607
- const organizationId = selectOrganizationId(state);
1608
- const isCameraEnabled = selectIsCameraEnabled(getState());
1609
- const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
1610
- const clientClaim = selectLocalParticipantClientClaim(getState());
1611
- socket === null || socket === void 0 ? void 0 : socket.emit("join_room", Object.assign({ avatarUrl: null, config: {
1612
- isAudioEnabled: isMicrophoneEnabled,
1613
- isVideoEnabled: isCameraEnabled,
1614
- }, deviceCapabilities: { canScreenshare: true }, displayName, isCoLocated: false, isDialIn, isDevicePermissionDenied: false, kickFromOtherRooms: false, organizationId,
1615
- roomKey,
1616
- roomName,
1617
- userAgent,
1618
- externalId }, (clientClaim && { clientClaim })));
1619
- dispatch(connectionStatusChanged("connecting"));
1765
+ };
1766
+ const rtcManagerDispatcher = new RtcManagerDispatcher({
1767
+ emitter: createWebRtcEmitter(dispatch),
1768
+ serverSocket: socket,
1769
+ webrtcProvider,
1770
+ features: {
1771
+ isNodeSdk,
1772
+ lowDataModeEnabled: false,
1773
+ sfuServerOverrideHost: undefined,
1774
+ turnServerOverrideHost: undefined,
1775
+ useOnlyTURN: undefined,
1776
+ vp9On: false,
1777
+ h264On: false,
1778
+ simulcastScreenshareOn: false,
1779
+ deviceHandlerFactory: isNodeSdk ? Chrome111.createFactory() : undefined,
1780
+ },
1781
+ });
1782
+ dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
1620
1783
  });
1621
- const selectRoomConnectionRaw = (state) => state.roomConnection;
1622
- const selectRoomConnectionSession = (state) => state.roomConnection.session;
1623
- const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
1624
- const selectRoomConnectionStatus = (state) => state.roomConnection.status;
1625
- const selectRoomConnectionError = (state) => state.roomConnection.error;
1626
- const selectShouldConnectRoom = createSelector([
1627
- selectAppIsActive,
1628
- selectOrganizationId,
1629
- selectRoomConnectionStatus,
1630
- selectSignalConnectionDeviceIdentified,
1631
- selectLocalMediaStatus,
1632
- selectRoomConnectionError,
1633
- ], (appIsActive, hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus, roomConnectionError) => {
1634
- if (appIsActive &&
1635
- localMediaStatus === "started" &&
1636
- signalConnectionDeviceIdentified &&
1637
- !!hasOrganizationIdFetched &&
1638
- ["ready", "reconnecting", "disconnected"].includes(roomConnectionStatus) &&
1639
- !roomConnectionError) {
1640
- return true;
1784
+ const doDisconnectRtc = createAppThunk(() => (dispatch, getState) => {
1785
+ const { rtcManager } = selectRtcConnectionRaw(getState());
1786
+ if (rtcManager) {
1787
+ rtcManager.disconnectAll();
1641
1788
  }
1642
- return false;
1789
+ dispatch(rtcDisconnected());
1643
1790
  });
1644
- createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
1645
- if (shouldConnectRoom) {
1646
- dispatch(doConnectRoom());
1791
+ const doHandleAcceptStreams = createAppThunk((payload) => (dispatch, getState) => {
1792
+ var _a;
1793
+ dispatch(isAcceptingStreams(true));
1794
+ const state = getState();
1795
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
1796
+ const remoteClients = selectRemoteClients(state);
1797
+ if (!rtcManager) {
1798
+ throw new Error("No rtc manager");
1647
1799
  }
1648
- });
1649
- startAppListening({
1650
- actionCreator: signalEvents.knockHandled,
1651
- effect: ({ payload }, { dispatch, getState }) => {
1652
- const { clientId, resolution } = payload;
1653
- const state = getState();
1654
- const selfId = selectSelfId(state);
1655
- if (clientId !== selfId) {
1656
- return;
1800
+ const activeBreakout = false;
1801
+ const shouldAcceptNewClients = (_a = rtcManager.shouldAcceptStreamsFromBothSides) === null || _a === void 0 ? void 0 : _a.call(rtcManager);
1802
+ const updates = [];
1803
+ for (const { clientId, streamId, state } of payload) {
1804
+ const participant = remoteClients.find((p) => p.id === clientId);
1805
+ if (!participant)
1806
+ continue;
1807
+ if (state === "to_accept" ||
1808
+ (state === "new_accept" && shouldAcceptNewClients) ||
1809
+ (state === "old_accept" && !shouldAcceptNewClients)) {
1810
+ const enforceTurnProtocol = participant.isDialIn ? "onlytls" : undefined;
1811
+ rtcManager.acceptNewStream({
1812
+ streamId: streamId === "0" ? clientId : streamId,
1813
+ clientId,
1814
+ shouldAddLocalVideo: streamId === "0",
1815
+ activeBreakout,
1816
+ enforceTurnProtocol,
1817
+ });
1657
1818
  }
1658
- if (resolution === "accepted") {
1659
- dispatch(setRoomKey(payload.metadata.roomKey));
1660
- dispatch(doConnectRoom());
1819
+ else if (state === "new_accept" || state === "old_accept") ;
1820
+ else if (state === "to_unaccept") {
1821
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.disconnect(streamId === "0" ? clientId : streamId, activeBreakout);
1661
1822
  }
1662
- else if (resolution === "rejected") {
1663
- dispatch(connectionStatusChanged("knock_rejected"));
1823
+ else if (state !== "done_accept") {
1824
+ continue;
1664
1825
  }
1665
- },
1826
+ else ;
1827
+ updates.push({ clientId, streamId, state: state.replace(/to_|new_|old_/, "done_") });
1828
+ }
1829
+ dispatch(streamStatusUpdated(updates));
1830
+ dispatch(isAcceptingStreams(false));
1831
+ });
1832
+ const doRtcReportStreamResolution = createAppThunk(({ streamId, width, height }) => (dispatch, getState) => {
1833
+ const { reportedStreamResolutions, rtcManager } = selectRtcConnectionRaw(getState());
1834
+ const localStream = selectLocalMediaStream(getState());
1835
+ if (!rtcManager || (localStream === null || localStream === void 0 ? void 0 : localStream.id) === streamId) {
1836
+ return;
1837
+ }
1838
+ const old = reportedStreamResolutions[streamId];
1839
+ if (!old || old.width !== width || old.height !== height) {
1840
+ rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
1841
+ }
1842
+ dispatch(resolutionReported({ streamId, width, height }));
1843
+ });
1844
+ const doRtcManagerCreated = createAppThunk((payload) => (dispatch) => {
1845
+ const { rtcManager } = payload;
1846
+ dispatch(rtcManagerCreated(rtcManager));
1847
+ });
1848
+ const doRtcManagerInitialize = createAppThunk(() => (dispatch, getState) => {
1849
+ const localMediaStream = selectLocalMediaStream(getState());
1850
+ const rtcManager = selectRtcConnectionRaw(getState()).rtcManager;
1851
+ const isCameraEnabled = selectIsCameraEnabled(getState());
1852
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
1853
+ if (localMediaStream && rtcManager) {
1854
+ rtcManager.addNewStream("0", localMediaStream, !isMicrophoneEnabled, !isCameraEnabled);
1855
+ }
1856
+ dispatch(rtcManagerInitialized());
1666
1857
  });
1858
+ const selectRtcConnectionRaw = (state) => state.rtcConnection;
1859
+ const selectRtcManagerInitialized = (state) => state.rtcConnection.rtcManagerInitialized;
1860
+ const selectRtcManager = (state) => state.rtcConnection.rtcManager;
1861
+ const selectRtcDispatcherCreated = (state) => state.rtcConnection.dispatcherCreated;
1862
+ const selectRtcIsCreatingDispatcher = (state) => state.rtcConnection.isCreatingDispatcher;
1863
+ const selectRtcStatus = (state) => state.rtcConnection.status;
1864
+ const selectIsAcceptingStreams = (state) => state.rtcConnection.isAcceptingStreams;
1667
1865
  startAppListening({
1668
- actionCreator: doAppStop,
1669
- effect: (_, { dispatch, getState }) => {
1670
- const state = getState();
1671
- const roomConnectionStatus = selectRoomConnectionStatus(state);
1672
- if (roomConnectionStatus === "connected") {
1673
- const socket = selectSignalConnectionRaw(state).socket;
1674
- socket === null || socket === void 0 ? void 0 : socket.emit("leave_room");
1675
- dispatch(connectionStatusChanged("leaving"));
1676
- }
1677
- else {
1678
- doSignalDisconnect();
1679
- }
1866
+ actionCreator: doSetDevice.fulfilled,
1867
+ effect: ({ payload }, { getState }) => {
1868
+ const { replacedTracks } = payload;
1869
+ const { rtcManager } = selectRtcConnectionRaw(getState());
1870
+ const stream = selectLocalMediaStream(getState());
1871
+ const replace = (kind, oldTrack) => {
1872
+ const track = stream === null || stream === void 0 ? void 0 : stream.getTracks().find((t) => t.kind === kind);
1873
+ return track && (rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.replaceTrack(oldTrack, track));
1874
+ };
1875
+ replacedTracks === null || replacedTracks === void 0 ? void 0 : replacedTracks.forEach((t) => {
1876
+ replace(t.kind, t);
1877
+ });
1680
1878
  },
1681
1879
  });
1682
-
1683
- const emitter = new EventEmitter();
1684
- function createNotificationEvent(payload) {
1685
- const notificationEvent = Object.assign(Object.assign({}, payload), { timestamp: Date.now() });
1686
- return notificationEvent;
1687
- }
1688
- const initialNotificationsState = {
1689
- emitter,
1690
- events: [],
1691
- };
1692
- const notificationsSlice = createSlice({
1693
- name: "notifications",
1694
- initialState: initialNotificationsState,
1695
- reducers: {
1696
- addNotification: (state, action) => {
1697
- return Object.assign(Object.assign({}, state), { events: [...state.events, Object.assign({}, action.payload)] });
1698
- },
1699
- doClearNotifications: (state) => {
1700
- return Object.assign(Object.assign({}, state), { events: [] });
1701
- },
1880
+ startAppListening({
1881
+ actionCreator: doStartScreenshare.fulfilled,
1882
+ effect: ({ payload }, { getState }) => {
1883
+ const { stream } = payload;
1884
+ const { rtcManager } = selectRtcConnectionRaw(getState());
1885
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.addNewStream(stream.id, stream, false, true);
1702
1886
  },
1703
1887
  });
1704
- const { doClearNotifications } = notificationsSlice.actions;
1705
- const doSetNotification = createAppThunk((payload) => (dispatch, getState) => {
1706
- dispatch(notificationsSlice.actions.addNotification(payload));
1707
- const state = getState();
1708
- const emitter = selectNotificationsEmitter(state);
1709
- emitter.emit(payload.type, payload);
1710
- emitter.emit("*", payload);
1711
- });
1712
- const selectNotificationsRaw = (state) => state.notifications;
1713
- const selectNotificationsEvents = (state) => state.notifications.events;
1714
- const selectNotificationsEmitter = (state) => state.notifications.emitter;
1715
1888
  startAppListening({
1716
- actionCreator: signalEvents.chatMessage,
1717
- effect: ({ payload }, { dispatch, getState }) => {
1718
- const state = getState();
1719
- const client = selectRemoteParticipants(state).find(({ id }) => id === payload.senderId);
1720
- if (!client) {
1721
- console.warn("Could not find remote client that sent chat message");
1722
- return;
1723
- }
1724
- dispatch(doSetNotification(createNotificationEvent({
1725
- type: "chatMessageReceived",
1726
- message: `${client.displayName} says: ${payload.text}`,
1727
- props: {
1728
- client,
1729
- chatMessage: {
1730
- senderId: payload.senderId,
1731
- timestamp: payload.timestamp,
1732
- text: payload.text,
1733
- },
1734
- },
1735
- })));
1889
+ actionCreator: doAppStop,
1890
+ effect: (_, { getState }) => {
1891
+ const rtcManager = selectRtcManager(getState());
1892
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.rtcStatsDisconnect();
1736
1893
  },
1737
1894
  });
1738
1895
  startAppListening({
1739
- actionCreator: signalEvents.audioEnableRequested,
1740
- effect: ({ payload }, { dispatch, getState }) => {
1741
- const { enable, requestedByClientId } = payload;
1742
- const state = getState();
1743
- const client = selectRemoteParticipants(state).find(({ id }) => id === requestedByClientId);
1744
- if (!client) {
1745
- console.warn("Could not find remote client that requested a local audio change");
1746
- return;
1747
- }
1748
- dispatch(doSetNotification(createNotificationEvent({
1749
- type: enable ? "requestAudioEnable" : "requestAudioDisable",
1750
- message: enable
1751
- ? `${client.displayName} has requested for you to speak`
1752
- : `${client.displayName} has muted your microphone`,
1753
- props: {
1754
- client,
1755
- enable,
1756
- },
1757
- })));
1896
+ actionCreator: stopScreenshare,
1897
+ effect: ({ payload }, { getState }) => {
1898
+ const { stream } = payload;
1899
+ const { rtcManager } = selectRtcConnectionRaw(getState());
1900
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.removeStream(stream.id, stream, null);
1758
1901
  },
1759
1902
  });
1760
1903
  startAppListening({
1761
- actionCreator: signalEvents.clientMetadataReceived,
1762
- effect: (action, { dispatch, getOriginalState, getState }) => {
1904
+ actionCreator: doSwitchLocalStream.fulfilled,
1905
+ effect: ({ payload }, { getState }) => {
1763
1906
  var _a;
1764
- const { error, payload } = action.payload;
1765
- if (error || !payload) {
1766
- return;
1767
- }
1768
- const { clientId, stickyReaction } = payload;
1769
- const state = getState();
1770
- const canAskToSpeak = selectIsAuthorizedToAskToSpeak(state);
1771
- if (!canAskToSpeak) {
1772
- return;
1773
- }
1774
- const client = selectRemoteParticipants(state).find(({ id }) => id === clientId);
1775
- if (!client) {
1776
- console.warn("Could not find remote client that provided updated metadata");
1777
- return;
1778
- }
1779
- const previousState = getOriginalState();
1780
- const previousClient = selectRemoteParticipants(previousState).find(({ id }) => id === clientId);
1781
- if ((!stickyReaction && !(previousClient === null || previousClient === void 0 ? void 0 : previousClient.stickyReaction)) ||
1782
- (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)) {
1783
- return;
1907
+ const stream = selectLocalMediaStream(getState());
1908
+ const { rtcManager } = selectRtcConnectionRaw(getState());
1909
+ if (stream && rtcManager) {
1910
+ const replace = (kind, oldTrack) => {
1911
+ const track = stream.getTracks().find((t) => t.kind === kind);
1912
+ return track && rtcManager.replaceTrack(oldTrack, track);
1913
+ };
1914
+ (_a = payload === null || payload === void 0 ? void 0 : payload.replacedTracks) === null || _a === void 0 ? void 0 : _a.forEach((t) => {
1915
+ replace(t.kind, t);
1916
+ });
1784
1917
  }
1785
- dispatch(doSetNotification(createNotificationEvent({
1786
- type: stickyReaction ? "remoteHandRaised" : "remoteHandLowered",
1787
- message: `${client.displayName} ${stickyReaction ? "raised" : "lowered"} their hand`,
1788
- props: {
1789
- client,
1790
- stickyReaction,
1791
- },
1792
- })));
1793
1918
  },
1794
1919
  });
1795
- createReactor([selectSignalStatus], ({ dispatch, getState }, signalStatus) => {
1796
- const state = getState();
1797
- const roomConnectionStatus = selectRoomConnectionStatus(state);
1798
- if (["left", "kicked"].includes(roomConnectionStatus)) {
1799
- return;
1920
+ const selectShouldConnectRtc = createSelector(selectRtcStatus, selectAppIsActive, selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectSignalConnectionSocket, (rtcStatus, appIsActive, dispatcherCreated, isCreatingDispatcher, signalSocket) => {
1921
+ if (appIsActive && rtcStatus === "inactive" && !dispatcherCreated && !isCreatingDispatcher && signalSocket) {
1922
+ return true;
1800
1923
  }
1801
- if (signalStatus === "disconnected") {
1802
- dispatch(doSetNotification(createNotificationEvent({
1803
- type: "signalTrouble",
1804
- message: `Network connection lost. Trying to reconnect you...`,
1805
- props: {},
1806
- })));
1924
+ return false;
1925
+ });
1926
+ createReactor([selectShouldConnectRtc], ({ dispatch }, shouldConnectRtc) => {
1927
+ if (shouldConnectRtc) {
1928
+ dispatch(doConnectRtc());
1807
1929
  }
1808
- else if (signalStatus === "connected") {
1809
- dispatch(doSetNotification(createNotificationEvent({
1810
- type: "signalOk",
1811
- message: `Network connection available`,
1812
- props: {},
1813
- })));
1930
+ });
1931
+ const selectShouldInitializeRtc = createSelector(selectRtcManager, selectRtcManagerInitialized, selectLocalMediaStatus, (rtcManager, rtcManagerInitialized, localMediaStatus) => {
1932
+ if (localMediaStatus === "started" && rtcManager && !rtcManagerInitialized) {
1933
+ return true;
1814
1934
  }
1935
+ return false;
1815
1936
  });
1816
- startAppListening({
1817
- actionCreator: signalEvents.clientUnableToJoin,
1818
- effect: (action, { dispatch }) => {
1819
- var _a;
1820
- if (((_a = action.payload) === null || _a === void 0 ? void 0 : _a.error) === "room_full") {
1821
- dispatch(doSetNotification(createNotificationEvent({
1822
- type: "clientUnableToJoinFullRoom",
1823
- message: "Someone tried to join but the room is full and at capacity.",
1824
- props: {},
1825
- })));
1937
+ createReactor([selectShouldInitializeRtc], ({ dispatch }, shouldInitializeRtc) => {
1938
+ if (shouldInitializeRtc) {
1939
+ dispatch(doRtcManagerInitialize());
1940
+ }
1941
+ });
1942
+ const selectShouldDisconnectRtc = createSelector(selectRtcStatus, selectAppIsActive, (status, appIsActive) => {
1943
+ if (!appIsActive && !["inactive", "disconnected"].includes(status)) {
1944
+ return true;
1945
+ }
1946
+ return false;
1947
+ });
1948
+ createReactor([selectShouldDisconnectRtc], ({ dispatch }, shouldDisconnectRtc) => {
1949
+ if (shouldDisconnectRtc) {
1950
+ dispatch(doDisconnectRtc());
1951
+ }
1952
+ });
1953
+ const selectStreamsToAccept = createSelector(selectRtcStatus, selectRemoteClients, (rtcStatus, remoteParticipants) => {
1954
+ if (rtcStatus !== "ready") {
1955
+ return [];
1956
+ }
1957
+ const upd = [];
1958
+ for (const client of remoteParticipants) {
1959
+ const { streams, id: clientId, newJoiner } = client;
1960
+ for (let i = 0; i < streams.length; i++) {
1961
+ let streamId = streams[i].id;
1962
+ let state = streams[i].state;
1963
+ if ((streams === null || streams === void 0 ? void 0 : streams.length) > 1 && streams[1].id === "0") {
1964
+ if (i === 0) {
1965
+ streamId = streams[1].id;
1966
+ state = streams[1].state;
1967
+ }
1968
+ else if (i === 1) {
1969
+ streamId = streams[0].id;
1970
+ state = streams[0].state;
1971
+ }
1972
+ }
1973
+ {
1974
+ if (state === "done_accept")
1975
+ continue;
1976
+ upd.push({
1977
+ clientId,
1978
+ streamId,
1979
+ state: `${newJoiner && streamId === "0" ? "new" : "to"}_accept`,
1980
+ });
1981
+ }
1826
1982
  }
1827
- },
1983
+ }
1984
+ return upd;
1985
+ });
1986
+ createReactor([selectStreamsToAccept, selectIsAcceptingStreams], ({ dispatch }, streamsToAccept, isAcceptingStreams) => {
1987
+ if (0 < streamsToAccept.length && !isAcceptingStreams) {
1988
+ dispatch(doHandleAcceptStreams(streamsToAccept));
1989
+ }
1828
1990
  });
1829
1991
 
1830
1992
  function isStreamerClient(client) {
@@ -1936,313 +2098,256 @@ const selectAllClientViews = createSelector(selectLocalParticipantView, selectRe
1936
2098
  return [...(localParticipant ? [localParticipant] : []), ...remoteParticipants];
1937
2099
  });
1938
2100
 
1939
- const createWebRtcEmitter = (dispatch) => {
1940
- return {
1941
- emit: (eventName, data) => {
1942
- if (eventName === "rtc_manager_created") {
1943
- dispatch(doRtcManagerCreated(data));
1944
- }
1945
- else if (eventName === "stream_added") {
1946
- dispatch(rtcEvents.streamAdded(data));
1947
- }
1948
- else if (eventName === "rtc_manager_destroyed") {
1949
- dispatch(rtcManagerDestroyed());
1950
- }
1951
- else ;
1952
- },
1953
- };
1954
- };
1955
2101
  const initialState$4 = {
1956
- dispatcherCreated: false,
1957
- error: null,
1958
- isCreatingDispatcher: false,
1959
- reportedStreamResolutions: {},
1960
- rtcManager: null,
1961
- rtcManagerDispatcher: null,
1962
- rtcManagerInitialized: false,
1963
- status: "inactive",
1964
- isAcceptingStreams: false,
2102
+ running: false,
1965
2103
  };
1966
- const rtcConnectionSlice = createSlice({
1967
- name: "rtcConnection",
2104
+ const connectionMonitorSlice = createSlice({
2105
+ name: "connectionMonitor",
1968
2106
  initialState: initialState$4,
1969
2107
  reducers: {
1970
- isAcceptingStreams: (state, action) => {
1971
- return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
1972
- },
1973
- resolutionReported: (state, action) => {
1974
- const { streamId, width, height } = action.payload;
1975
- return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
2108
+ connectionMonitorStarted: (state, action) => {
2109
+ return Object.assign(Object.assign({}, state), { running: true, stopCallbackFunction: action.payload.stopIssueSubscription });
1976
2110
  },
1977
- rtcDisconnected: () => {
2111
+ connectionMonitorStopped: () => {
1978
2112
  return Object.assign({}, initialState$4);
1979
2113
  },
1980
- rtcDispatcherCreated: (state, action) => {
1981
- return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
1982
- },
1983
- rtcManagerCreated: (state, action) => {
1984
- return Object.assign(Object.assign({}, state), { rtcManager: action.payload, status: "ready" });
1985
- },
1986
- rtcManagerDestroyed: (state) => {
1987
- return Object.assign(Object.assign({}, state), { rtcManager: null });
1988
- },
1989
- rtcManagerInitialized: (state) => {
1990
- return Object.assign(Object.assign({}, state), { rtcManagerInitialized: true });
1991
- },
1992
- },
1993
- extraReducers: (builder) => {
1994
- builder.addCase(socketReconnecting, (state) => {
1995
- return Object.assign(Object.assign({}, state), { status: "reconnecting" });
1996
- });
1997
- builder.addCase(signalEvents.roomJoined, (state) => {
1998
- return Object.assign(Object.assign({}, state), { status: state.status === "reconnecting" ? "ready" : state.status });
1999
- });
2000
2114
  },
2001
2115
  });
2002
- const { resolutionReported, rtcDispatcherCreated, rtcDisconnected, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, isAcceptingStreams, } = rtcConnectionSlice.actions;
2003
- const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
2004
- const state = getState();
2005
- const socket = selectSignalConnectionRaw(state).socket;
2006
- const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
2007
- const isCameraEnabled = selectIsCameraEnabled(state);
2008
- const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
2009
- const isNodeSdk = selectAppIsNodeSdk(state);
2010
- if (dispatcher || !socket) {
2011
- return;
2012
- }
2013
- const webrtcProvider = {
2014
- getMediaConstraints: () => ({
2015
- audio: isMicrophoneEnabled,
2016
- video: isCameraEnabled,
2017
- }),
2018
- deferrable(clientId) {
2019
- return !clientId;
2020
- },
2021
- };
2022
- const rtcManagerDispatcher = new RtcManagerDispatcher({
2023
- emitter: createWebRtcEmitter(dispatch),
2024
- serverSocket: socket,
2025
- webrtcProvider,
2026
- features: {
2027
- isNodeSdk,
2028
- lowDataModeEnabled: false,
2029
- sfuServerOverrideHost: undefined,
2030
- turnServerOverrideHost: undefined,
2031
- useOnlyTURN: undefined,
2032
- vp9On: false,
2033
- h264On: false,
2034
- simulcastScreenshareOn: false,
2035
- deviceHandlerFactory: isNodeSdk ? Chrome111.createFactory() : undefined,
2116
+ const { connectionMonitorStarted, connectionMonitorStopped } = connectionMonitorSlice.actions;
2117
+ const doStartConnectionMonitor = createAppThunk(() => (dispatch, getState) => {
2118
+ setClientProvider(() => {
2119
+ const state = getState();
2120
+ const clientViews = selectAllClientViews(state).map((clientView) => {
2121
+ var _a, _b;
2122
+ return ({
2123
+ id: clientView.id,
2124
+ clientId: clientView.clientId,
2125
+ isLocalClient: clientView.isLocalClient,
2126
+ audio: {
2127
+ enabled: clientView.isAudioEnabled,
2128
+ track: (_a = clientView.stream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0],
2129
+ },
2130
+ video: {
2131
+ enabled: clientView.isVideoEnabled,
2132
+ track: (_b = clientView.stream) === null || _b === void 0 ? void 0 : _b.getVideoTracks()[0],
2133
+ },
2134
+ isPresentation: clientView.isPresentation,
2135
+ });
2136
+ });
2137
+ return clientViews;
2138
+ });
2139
+ const issueMonitorSubscription = subscribeIssues({
2140
+ onUpdatedIssues: (issuesAndMetricsByClients) => {
2141
+ var _a;
2142
+ const state = getState();
2143
+ const rtcManager = selectRtcManager(state);
2144
+ if (!rtcManager) {
2145
+ return;
2146
+ }
2147
+ let lossSend = 0;
2148
+ let lossReceive = 0;
2149
+ let bitrateSend = 0;
2150
+ let bitrateReceive = 0;
2151
+ Object.entries(issuesAndMetricsByClients.aggregated.metrics).forEach(([key, value]) => {
2152
+ if (/loc.*packetloss/.test(key))
2153
+ lossSend = Math.max(lossSend, value.curMax);
2154
+ if (/rem.*packetloss/.test(key))
2155
+ lossReceive = Math.max(lossReceive, value.curMax);
2156
+ if (/loc.*bitrate/.test(key))
2157
+ bitrateSend += value.curSum;
2158
+ if (/rem.*bitrate/.test(key))
2159
+ bitrateReceive += value.curSum;
2160
+ });
2161
+ rtcManager.sendStatsCustomEvent("insightsStats", {
2162
+ ls: Math.round(lossSend * 1000) / 1000,
2163
+ lr: Math.round(lossReceive * 1000) / 1000,
2164
+ bs: Math.round(bitrateSend),
2165
+ br: Math.round(bitrateReceive),
2166
+ cpu: (_a = issuesAndMetricsByClients.aggregated.metrics["global-cpu-pressure"]) === null || _a === void 0 ? void 0 : _a.curSum,
2167
+ _time: Date.now(),
2168
+ });
2036
2169
  },
2037
2170
  });
2038
- dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
2039
- });
2040
- const doDisconnectRtc = createAppThunk(() => (dispatch, getState) => {
2041
- const { rtcManager } = selectRtcConnectionRaw(getState());
2042
- if (rtcManager) {
2043
- rtcManager.disconnectAll();
2044
- }
2045
- dispatch(rtcDisconnected());
2171
+ dispatch(connectionMonitorStarted({ stopIssueSubscription: issueMonitorSubscription.stop }));
2046
2172
  });
2047
- const doHandleAcceptStreams = createAppThunk((payload) => (dispatch, getState) => {
2048
- var _a;
2049
- dispatch(isAcceptingStreams(true));
2173
+ const doStopConnectionMonitor = createAppThunk(() => (dispatch, getState) => {
2050
2174
  const state = getState();
2051
- const rtcManager = selectRtcConnectionRaw(state).rtcManager;
2052
- const remoteClients = selectRemoteClients(state);
2053
- if (!rtcManager) {
2054
- throw new Error("No rtc manager");
2055
- }
2056
- const activeBreakout = false;
2057
- const shouldAcceptNewClients = (_a = rtcManager.shouldAcceptStreamsFromBothSides) === null || _a === void 0 ? void 0 : _a.call(rtcManager);
2058
- const updates = [];
2059
- for (const { clientId, streamId, state } of payload) {
2060
- const participant = remoteClients.find((p) => p.id === clientId);
2061
- if (!participant)
2062
- continue;
2063
- if (state === "to_accept" ||
2064
- (state === "new_accept" && shouldAcceptNewClients) ||
2065
- (state === "old_accept" && !shouldAcceptNewClients)) {
2066
- const enforceTurnProtocol = participant.isDialIn ? "onlytls" : undefined;
2067
- rtcManager.acceptNewStream({
2068
- streamId: streamId === "0" ? clientId : streamId,
2069
- clientId,
2070
- shouldAddLocalVideo: streamId === "0",
2071
- activeBreakout,
2072
- enforceTurnProtocol,
2073
- });
2074
- }
2075
- else if (state === "new_accept" || state === "old_accept") ;
2076
- else if (state === "to_unaccept") {
2077
- rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.disconnect(streamId === "0" ? clientId : streamId, activeBreakout);
2078
- }
2079
- else if (state !== "done_accept") {
2080
- continue;
2081
- }
2082
- else ;
2083
- updates.push({ clientId, streamId, state: state.replace(/to_|new_|old_/, "done_") });
2175
+ const stopCallbackFn = selectStopCallbackFunction(state);
2176
+ if (stopCallbackFn) {
2177
+ stopCallbackFn();
2084
2178
  }
2085
- dispatch(streamStatusUpdated(updates));
2086
- dispatch(isAcceptingStreams(false));
2179
+ dispatch(connectionMonitorStopped());
2087
2180
  });
2088
- const doRtcReportStreamResolution = createAppThunk(({ streamId, width, height }) => (dispatch, getState) => {
2089
- const { reportedStreamResolutions, rtcManager } = selectRtcConnectionRaw(getState());
2090
- const localStream = selectLocalMediaStream(getState());
2091
- if (!rtcManager || (localStream === null || localStream === void 0 ? void 0 : localStream.id) === streamId) {
2092
- return;
2093
- }
2094
- const old = reportedStreamResolutions[streamId];
2095
- if (!old || old.width !== width || old.height !== height) {
2096
- rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
2181
+ const selectConnectionMonitorIsRunning = (state) => state.connectionMonitor.running;
2182
+ const selectStopCallbackFunction = (state) => state.connectionMonitor.stopCallbackFunction;
2183
+ const selectShouldStartConnectionMonitor = createSelector(selectRoomConnectionStatus, selectConnectionMonitorIsRunning, (roomConnectionStatus, isRunning) => {
2184
+ if (!isRunning && roomConnectionStatus === "connected") {
2185
+ return true;
2097
2186
  }
2098
- dispatch(resolutionReported({ streamId, width, height }));
2187
+ return false;
2099
2188
  });
2100
- const doRtcManagerCreated = createAppThunk((payload) => (dispatch) => {
2101
- const { rtcManager } = payload;
2102
- dispatch(rtcManagerCreated(rtcManager));
2189
+ const selectShouldStopConnectionMonitor = createSelector(selectRoomConnectionStatus, selectConnectionMonitorIsRunning, (roomConnectionStatus, isRunning) => {
2190
+ if (isRunning && ["kicked", "left"].includes(roomConnectionStatus)) {
2191
+ return true;
2192
+ }
2193
+ return false;
2103
2194
  });
2104
- const doRtcManagerInitialize = createAppThunk(() => (dispatch, getState) => {
2105
- const localMediaStream = selectLocalMediaStream(getState());
2106
- const rtcManager = selectRtcConnectionRaw(getState()).rtcManager;
2107
- const isCameraEnabled = selectIsCameraEnabled(getState());
2108
- const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
2109
- if (localMediaStream && rtcManager) {
2110
- rtcManager.addNewStream("0", localMediaStream, !isMicrophoneEnabled, !isCameraEnabled);
2195
+ createReactor([selectShouldStartConnectionMonitor], ({ dispatch }, shouldStartConnectionMonitor) => {
2196
+ if (shouldStartConnectionMonitor) {
2197
+ dispatch(doStartConnectionMonitor());
2111
2198
  }
2112
- dispatch(rtcManagerInitialized());
2113
2199
  });
2114
- const selectRtcConnectionRaw = (state) => state.rtcConnection;
2115
- const selectRtcManagerInitialized = (state) => state.rtcConnection.rtcManagerInitialized;
2116
- const selectRtcManager = (state) => state.rtcConnection.rtcManager;
2117
- const selectRtcDispatcherCreated = (state) => state.rtcConnection.dispatcherCreated;
2118
- const selectRtcIsCreatingDispatcher = (state) => state.rtcConnection.isCreatingDispatcher;
2119
- const selectRtcStatus = (state) => state.rtcConnection.status;
2120
- const selectIsAcceptingStreams = (state) => state.rtcConnection.isAcceptingStreams;
2121
- startAppListening({
2122
- actionCreator: doSetDevice.fulfilled,
2123
- effect: ({ payload }, { getState }) => {
2124
- const { replacedTracks } = payload;
2125
- const { rtcManager } = selectRtcConnectionRaw(getState());
2126
- const stream = selectLocalMediaStream(getState());
2127
- const replace = (kind, oldTrack) => {
2128
- const track = stream === null || stream === void 0 ? void 0 : stream.getTracks().find((t) => t.kind === kind);
2129
- return track && (rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.replaceTrack(oldTrack, track));
2130
- };
2131
- replacedTracks === null || replacedTracks === void 0 ? void 0 : replacedTracks.forEach((t) => {
2132
- replace(t.kind, t);
2133
- });
2134
- },
2200
+ createReactor([selectShouldStopConnectionMonitor], ({ dispatch }, shouldStartConnectionMonitor) => {
2201
+ if (shouldStartConnectionMonitor) {
2202
+ dispatch(doStopConnectionMonitor());
2203
+ }
2135
2204
  });
2136
- startAppListening({
2137
- actionCreator: doStartScreenshare.fulfilled,
2138
- effect: ({ payload }, { getState }) => {
2139
- const { stream } = payload;
2140
- const { rtcManager } = selectRtcConnectionRaw(getState());
2141
- rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.addNewStream(stream.id, stream, false, true);
2205
+
2206
+ const emitter = new EventEmitter();
2207
+ function createNotificationEvent(payload) {
2208
+ const notificationEvent = Object.assign(Object.assign({}, payload), { timestamp: Date.now() });
2209
+ return notificationEvent;
2210
+ }
2211
+ const initialNotificationsState = {
2212
+ emitter,
2213
+ events: [],
2214
+ };
2215
+ const notificationsSlice = createSlice({
2216
+ name: "notifications",
2217
+ initialState: initialNotificationsState,
2218
+ reducers: {
2219
+ addNotification: (state, action) => {
2220
+ return Object.assign(Object.assign({}, state), { events: [...state.events, Object.assign({}, action.payload)] });
2221
+ },
2222
+ doClearNotifications: (state) => {
2223
+ return Object.assign(Object.assign({}, state), { events: [] });
2224
+ },
2142
2225
  },
2143
2226
  });
2227
+ const { doClearNotifications } = notificationsSlice.actions;
2228
+ const doSetNotification = createAppThunk((payload) => (dispatch, getState) => {
2229
+ dispatch(notificationsSlice.actions.addNotification(payload));
2230
+ const state = getState();
2231
+ const emitter = selectNotificationsEmitter(state);
2232
+ emitter.emit(payload.type, payload);
2233
+ emitter.emit("*", payload);
2234
+ });
2235
+ const selectNotificationsRaw = (state) => state.notifications;
2236
+ const selectNotificationsEvents = (state) => state.notifications.events;
2237
+ const selectNotificationsEmitter = (state) => state.notifications.emitter;
2144
2238
  startAppListening({
2145
- actionCreator: doAppStop,
2146
- effect: (_, { getState }) => {
2147
- const rtcManager = selectRtcManager(getState());
2148
- rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.rtcStatsDisconnect();
2239
+ actionCreator: signalEvents.chatMessage,
2240
+ effect: ({ payload }, { dispatch, getState }) => {
2241
+ const state = getState();
2242
+ const client = selectRemoteParticipants(state).find(({ id }) => id === payload.senderId);
2243
+ if (!client) {
2244
+ console.warn("Could not find remote client that sent chat message");
2245
+ return;
2246
+ }
2247
+ dispatch(doSetNotification(createNotificationEvent({
2248
+ type: "chatMessageReceived",
2249
+ message: `${client.displayName} says: ${payload.text}`,
2250
+ props: {
2251
+ client,
2252
+ chatMessage: {
2253
+ senderId: payload.senderId,
2254
+ timestamp: payload.timestamp,
2255
+ text: payload.text,
2256
+ },
2257
+ },
2258
+ })));
2149
2259
  },
2150
2260
  });
2151
2261
  startAppListening({
2152
- actionCreator: stopScreenshare,
2153
- effect: ({ payload }, { getState }) => {
2154
- const { stream } = payload;
2155
- const { rtcManager } = selectRtcConnectionRaw(getState());
2156
- rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.removeStream(stream.id, stream, null);
2262
+ actionCreator: signalEvents.audioEnableRequested,
2263
+ effect: ({ payload }, { dispatch, getState }) => {
2264
+ const { enable, requestedByClientId } = payload;
2265
+ const state = getState();
2266
+ const client = selectRemoteParticipants(state).find(({ id }) => id === requestedByClientId);
2267
+ if (!client) {
2268
+ console.warn("Could not find remote client that requested a local audio change");
2269
+ return;
2270
+ }
2271
+ dispatch(doSetNotification(createNotificationEvent({
2272
+ type: enable ? "requestAudioEnable" : "requestAudioDisable",
2273
+ message: enable
2274
+ ? `${client.displayName} has requested for you to speak`
2275
+ : `${client.displayName} has muted your microphone`,
2276
+ props: {
2277
+ client,
2278
+ enable,
2279
+ },
2280
+ })));
2157
2281
  },
2158
2282
  });
2159
2283
  startAppListening({
2160
- actionCreator: doSwitchLocalStream.fulfilled,
2161
- effect: ({ payload }, { getState }) => {
2284
+ actionCreator: signalEvents.clientMetadataReceived,
2285
+ effect: (action, { dispatch, getOriginalState, getState }) => {
2162
2286
  var _a;
2163
- const stream = selectLocalMediaStream(getState());
2164
- const { rtcManager } = selectRtcConnectionRaw(getState());
2165
- if (stream && rtcManager) {
2166
- const replace = (kind, oldTrack) => {
2167
- const track = stream.getTracks().find((t) => t.kind === kind);
2168
- return track && rtcManager.replaceTrack(oldTrack, track);
2169
- };
2170
- (_a = payload === null || payload === void 0 ? void 0 : payload.replacedTracks) === null || _a === void 0 ? void 0 : _a.forEach((t) => {
2171
- replace(t.kind, t);
2172
- });
2287
+ const { error, payload } = action.payload;
2288
+ if (error || !payload) {
2289
+ return;
2290
+ }
2291
+ const { clientId, stickyReaction } = payload;
2292
+ const state = getState();
2293
+ const canAskToSpeak = selectIsAuthorizedToAskToSpeak(state);
2294
+ if (!canAskToSpeak) {
2295
+ return;
2296
+ }
2297
+ const client = selectRemoteParticipants(state).find(({ id }) => id === clientId);
2298
+ if (!client) {
2299
+ console.warn("Could not find remote client that provided updated metadata");
2300
+ return;
2301
+ }
2302
+ const previousState = getOriginalState();
2303
+ const previousClient = selectRemoteParticipants(previousState).find(({ id }) => id === clientId);
2304
+ if ((!stickyReaction && !(previousClient === null || previousClient === void 0 ? void 0 : previousClient.stickyReaction)) ||
2305
+ (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)) {
2306
+ return;
2173
2307
  }
2308
+ dispatch(doSetNotification(createNotificationEvent({
2309
+ type: stickyReaction ? "remoteHandRaised" : "remoteHandLowered",
2310
+ message: `${client.displayName} ${stickyReaction ? "raised" : "lowered"} their hand`,
2311
+ props: {
2312
+ client,
2313
+ stickyReaction,
2314
+ },
2315
+ })));
2174
2316
  },
2175
2317
  });
2176
- const selectShouldConnectRtc = createSelector(selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectSignalConnectionSocket, (dispatcherCreated, isCreatingDispatcher, signalSocket) => {
2177
- if (!dispatcherCreated && !isCreatingDispatcher && signalSocket) {
2178
- return true;
2179
- }
2180
- return false;
2181
- });
2182
- createReactor([selectShouldConnectRtc], ({ dispatch }, shouldConnectRtc) => {
2183
- if (shouldConnectRtc) {
2184
- dispatch(doConnectRtc());
2185
- }
2186
- });
2187
- const selectShouldInitializeRtc = createSelector(selectRtcManager, selectRtcManagerInitialized, selectLocalMediaStatus, (rtcManager, rtcManagerInitialized, localMediaStatus) => {
2188
- if (localMediaStatus === "started" && rtcManager && !rtcManagerInitialized) {
2189
- return true;
2190
- }
2191
- return false;
2192
- });
2193
- createReactor([selectShouldInitializeRtc], ({ dispatch }, shouldInitializeRtc) => {
2194
- if (shouldInitializeRtc) {
2195
- dispatch(doRtcManagerInitialize());
2318
+ createReactor([selectSignalStatus], ({ dispatch, getState }, signalStatus) => {
2319
+ const state = getState();
2320
+ const roomConnectionStatus = selectRoomConnectionStatus(state);
2321
+ if (["left", "kicked"].includes(roomConnectionStatus)) {
2322
+ return;
2196
2323
  }
2197
- });
2198
- const selectShouldDisconnectRtc = createSelector(selectRtcStatus, selectAppIsActive, (status, appIsActive) => {
2199
- if (!appIsActive && !["inactive", "disconnected"].includes(status)) {
2200
- return true;
2324
+ if (signalStatus === "disconnected") {
2325
+ dispatch(doSetNotification(createNotificationEvent({
2326
+ type: "signalTrouble",
2327
+ message: `Network connection lost. Trying to reconnect you...`,
2328
+ props: {},
2329
+ })));
2201
2330
  }
2202
- return false;
2203
- });
2204
- createReactor([selectShouldDisconnectRtc], ({ dispatch }, shouldDisconnectRtc) => {
2205
- if (shouldDisconnectRtc) {
2206
- dispatch(doDisconnectRtc());
2331
+ else if (signalStatus === "connected") {
2332
+ dispatch(doSetNotification(createNotificationEvent({
2333
+ type: "signalOk",
2334
+ message: `Network connection available`,
2335
+ props: {},
2336
+ })));
2207
2337
  }
2208
2338
  });
2209
- const selectStreamsToAccept = createSelector(selectRtcStatus, selectRemoteClients, (rtcStatus, remoteParticipants) => {
2210
- if (rtcStatus !== "ready") {
2211
- return [];
2212
- }
2213
- const upd = [];
2214
- for (const client of remoteParticipants) {
2215
- const { streams, id: clientId, newJoiner } = client;
2216
- for (let i = 0; i < streams.length; i++) {
2217
- let streamId = streams[i].id;
2218
- let state = streams[i].state;
2219
- if ((streams === null || streams === void 0 ? void 0 : streams.length) > 1 && streams[1].id === "0") {
2220
- if (i === 0) {
2221
- streamId = streams[1].id;
2222
- state = streams[1].state;
2223
- }
2224
- else if (i === 1) {
2225
- streamId = streams[0].id;
2226
- state = streams[0].state;
2227
- }
2228
- }
2229
- {
2230
- if (state === "done_accept")
2231
- continue;
2232
- upd.push({
2233
- clientId,
2234
- streamId,
2235
- state: `${newJoiner && streamId === "0" ? "new" : "to"}_accept`,
2236
- });
2237
- }
2339
+ startAppListening({
2340
+ actionCreator: signalEvents.clientUnableToJoin,
2341
+ effect: (action, { dispatch }) => {
2342
+ var _a;
2343
+ if (((_a = action.payload) === null || _a === void 0 ? void 0 : _a.error) === "room_full") {
2344
+ dispatch(doSetNotification(createNotificationEvent({
2345
+ type: "clientUnableToJoinFullRoom",
2346
+ message: "Someone tried to join but the room is full and at capacity.",
2347
+ props: {},
2348
+ })));
2238
2349
  }
2239
- }
2240
- return upd;
2241
- });
2242
- createReactor([selectStreamsToAccept, selectIsAcceptingStreams], ({ dispatch }, streamsToAccept, isAcceptingStreams) => {
2243
- if (0 < streamsToAccept.length && !isAcceptingStreams) {
2244
- dispatch(doHandleAcceptStreams(streamsToAccept));
2245
- }
2350
+ },
2246
2351
  });
2247
2352
 
2248
2353
  const rtcAnalyticsCustomEvents = {
@@ -2614,6 +2719,7 @@ const appReducer = combineReducers({
2614
2719
  authorization: authorizationSlice.reducer,
2615
2720
  chat: chatSlice.reducer,
2616
2721
  cloudRecording: cloudRecordingSlice.reducer,
2722
+ connectionMonitor: connectionMonitorSlice.reducer,
2617
2723
  deviceCredentials: deviceCredentialsSlice.reducer,
2618
2724
  localMedia: localMediaSlice.reducer,
2619
2725
  localParticipant: localParticipantSlice.reducer,
@@ -3677,4 +3783,4 @@ function createServices() {
3677
3783
  };
3678
3784
  }
3679
3785
 
3680
- export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, addSpotlight, 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, initialState$f as initialState, isAcceptingStreams, isClientSpotlighted, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, notificationsSlice, observeStore, organizationSlice, parseRoomUrlAndSubdomain, parseUnverifiedRoomKeyData, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, removeSpotlight, resolutionReported, roomConnectionSlice, roomSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, selectAllClientViews, selectAppDisplayName, selectAppExternalId, selectAppInitialConfig, selectAppIsActive, selectAppIsDialIn, 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, selectNumClients, selectNumParticipants, selectOrganizationId, selectOrganizationRaw, selectRemoteClientViews, selectRemoteClients, 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 };
3786
+ export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, addSpotlight, appSlice, authorizationSlice, chatSlice, cloudRecordingSlice, connectionMonitorSlice, connectionMonitorStarted, connectionMonitorStopped, 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, doStartConnectionMonitor, doStartLocalMedia, doStartScreenshare, doStopCloudRecording, doStopConnectionMonitor, doStopLocalMedia, doStopScreenshare, doSwitchLocalStream, doToggleCamera, doToggleLowDataMode, doUpdateDeviceList, getAudioTrack, getFakeMediaStream, getVideoTrack, hasValue, initialCloudRecordingState, initialLocalMediaState, initialNotificationsState, initialState$g as initialState, isAcceptingStreams, isClientSpotlighted, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, notificationsSlice, observeStore, organizationSlice, parseRoomUrlAndSubdomain, parseUnverifiedRoomKeyData, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, removeSpotlight, resolutionReported, roomConnectionSlice, roomSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, selectAllClientViews, selectAppDisplayName, selectAppExternalId, selectAppInitialConfig, selectAppIsActive, selectAppIsDialIn, selectAppIsNodeSdk, selectAppRaw, selectAppRoomName, selectAppRoomUrl, selectAppUserAgent, selectAuthorizationRoleName, selectBusyDeviceIds, selectCameraDeviceError, selectCameraDevices, selectChatMessages, selectChatRaw, selectCloudRecordingError, selectCloudRecordingRaw, selectCloudRecordingStartedAt, selectCloudRecordingStatus, selectConnectionMonitorIsRunning, 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, selectNumClients, selectNumParticipants, selectOrganizationId, selectOrganizationRaw, selectRemoteClientViews, selectRemoteClients, 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, selectShouldStartConnectionMonitor, selectShouldStopConnectionMonitor, selectSignalConnectionDeviceIdentified, selectSignalConnectionRaw, selectSignalConnectionSocket, selectSignalIsIdentifyingDevice, selectSignalStatus, selectSpeakerDevices, selectSpotlightedClientViews, selectSpotlights, selectSpotlightsRaw, selectStopCallbackFunction, 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 };