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