@whereby.com/browser-sdk 2.0.0-beta3 → 2.0.0-beta4

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.
@@ -1,6 +1,8 @@
1
- import React, { useRef, useEffect, useState, useReducer } from 'react';
2
- import adapter from 'webrtc-adapter';
1
+ import * as React from 'react';
2
+ import React__default, { useRef, useEffect, useState, useCallback } from 'react';
3
+ import { createListenerMiddleware, createSlice, createAsyncThunk, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
3
4
  import { io } from 'socket.io-client';
5
+ import adapter from 'webrtc-adapter';
4
6
  import SDPUtils from 'sdp';
5
7
  import * as sdpTransform from 'sdp-transform';
6
8
  import { v4 } from 'uuid';
@@ -116,9 +118,520 @@ var VideoView = (_a) => {
116
118
  videoEl.current.muted = Boolean(muted);
117
119
  }
118
120
  }, [muted, stream, videoEl]);
119
- return (React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
121
+ return (React__default.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
122
+ };
123
+
124
+ const listenerMiddleware = createListenerMiddleware();
125
+ const startAppListening = listenerMiddleware.startListening;
126
+ /**
127
+ * Creates a reactor that will be called whenever the provided selectors change.
128
+ * Every reactor needs to update a piece of state that it depends on, to avoid infinite loops.
129
+ * example:
130
+ * ```ts
131
+ * createReactor(
132
+ * [selectAppWantsToJoin, selectDeviceCredentialsRaw],
133
+ * ({ dispatch }, wantsToJoin, deviceCredentialsRaw) => {
134
+ * if (wantsToJoin && deviceCredentialsRaw.data) {
135
+ * dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
136
+ * }
137
+ * });
138
+ * ```
139
+ * @param selectors. The selectors to be used to check if the state has changed.
140
+ * @param callback. The callback to be called on every action. The first argument is the listenerApi, the second argument is the result of the selectors.
141
+ * @returns The unsubscribe function.
142
+ */
143
+ const createReactor = (selectors, callback) => {
144
+ return startAppListening({
145
+ predicate: (_, currentState, previousState) => {
146
+ const previousValues = selectors.map((selector) => selector(previousState));
147
+ const currentValues = selectors.map((selector) => selector(currentState));
148
+ return previousValues.some((previousValue, index) => previousValue !== currentValues[index]);
149
+ },
150
+ effect: (action, { dispatch, getState, extra }) => {
151
+ const selectorResults = selectors.map((selector) => selector(getState()));
152
+ callback({
153
+ dispatch,
154
+ getState,
155
+ extra,
156
+ }, ...selectorResults);
157
+ },
158
+ });
159
+ };
160
+
161
+ const initialState$g = {
162
+ wantsToJoin: false,
163
+ roomName: null,
164
+ roomKey: null,
165
+ roomUrl: null,
166
+ displayName: null,
167
+ sdkVersion: null,
168
+ externalId: null,
169
+ };
170
+ const appSlice = createSlice({
171
+ name: "app",
172
+ initialState: initialState$g,
173
+ reducers: {
174
+ doAppJoin: (state, action) => {
175
+ const url = new URL(action.payload.roomUrl);
176
+ return Object.assign(Object.assign(Object.assign({}, state), action.payload), { roomName: url.pathname, wantsToJoin: true });
177
+ },
178
+ appLeft: (state) => {
179
+ return Object.assign(Object.assign({}, state), { wantsToJoin: false });
180
+ },
181
+ setRoomKey: (state, action) => {
182
+ return Object.assign(Object.assign({}, state), { roomKey: action.payload });
183
+ },
184
+ },
185
+ });
186
+ /**
187
+ * Action creators
188
+ */
189
+ const { doAppJoin, appLeft, setRoomKey } = appSlice.actions;
190
+ const selectAppWantsToJoin = (state) => state.app.wantsToJoin;
191
+ const selectAppRoomName = (state) => state.app.roomName;
192
+ const selectAppRoomUrl = (state) => state.app.roomUrl;
193
+ const selectAppRoomKey = (state) => state.app.roomKey;
194
+ const selectAppDisplayName = (state) => state.app.displayName;
195
+ const selectAppSdkVersion = (state) => state.app.sdkVersion;
196
+ const selectAppExternalId = (state) => state.app.externalId;
197
+
198
+ function createAppAsyncThunk(typePrefix, payloadCreator) {
199
+ return createAsyncThunk(typePrefix, payloadCreator);
200
+ }
201
+ function createAppThunk(thunk) {
202
+ return thunk;
203
+ }
204
+
205
+ function createSignalEventAction(name) {
206
+ return createAction(`signalConnection/event/${name}`);
207
+ }
208
+ const signalEvents = {
209
+ audioEnabled: createSignalEventAction("audioEnabled"),
210
+ chatMessage: createSignalEventAction("chatMessage"),
211
+ clientLeft: createSignalEventAction("clientLeft"),
212
+ clientMetadataReceived: createSignalEventAction("clientMetadataReceived"),
213
+ cloudRecordingStarted: createSignalEventAction("cloudRecordingStarted"),
214
+ cloudRecordingStopped: createSignalEventAction("cloudRecordingStopped"),
215
+ disconnect: createSignalEventAction("disconnect"),
216
+ knockerLeft: createSignalEventAction("knockerLeft"),
217
+ knockHandled: createSignalEventAction("knockHandled"),
218
+ newClient: createSignalEventAction("newClient"),
219
+ roomJoined: createSignalEventAction("roomJoined"),
220
+ roomKnocked: createSignalEventAction("roomKnocked"),
221
+ screenshareStarted: createSignalEventAction("screenshareStarted"),
222
+ screenshareStopped: createSignalEventAction("screenshareStopped"),
223
+ streamingStopped: createSignalEventAction("streamingStopped"),
224
+ videoEnabled: createSignalEventAction("videoEnabled"),
120
225
  };
121
226
 
227
+ const initialState$f = {
228
+ isFetching: false,
229
+ data: null,
230
+ };
231
+ const deviceCredentialsSlice = createSlice({
232
+ name: "deviceCredentials",
233
+ initialState: initialState$f,
234
+ reducers: {},
235
+ extraReducers: (builder) => {
236
+ builder.addCase(doGetDeviceCredentials.pending, (state) => {
237
+ return Object.assign(Object.assign({}, state), { isFetching: true });
238
+ });
239
+ builder.addCase(doGetDeviceCredentials.fulfilled, (state, action) => {
240
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
241
+ });
242
+ builder.addCase(doGetDeviceCredentials.rejected, (state) => {
243
+ // not handled in the pwa either.
244
+ return Object.assign(Object.assign({}, state), { isFetching: true });
245
+ });
246
+ },
247
+ });
248
+ /**
249
+ * Action creators
250
+ */
251
+ const doGetDeviceCredentials = createAppAsyncThunk("deviceCredentials/doGetDeviceCredentials", (payload, { extra }) => __awaiter(void 0, void 0, void 0, function* () {
252
+ try {
253
+ const deviceCredentials = yield extra.services.credentialsService.getCredentials();
254
+ return deviceCredentials;
255
+ }
256
+ catch (error) {
257
+ console.error(error);
258
+ }
259
+ }));
260
+ /**
261
+ * Selectors
262
+ */
263
+ const selectDeviceCredentialsRaw = (state) => state.deviceCredentials;
264
+ const selectDeviceId = (state) => { var _a, _b; return (_b = (_a = state.deviceCredentials.data) === null || _a === void 0 ? void 0 : _a.credentials) === null || _b === void 0 ? void 0 : _b.uuid; };
265
+ /**
266
+ * Reactors
267
+ */
268
+ const selectShouldFetchDeviceCredentials = createSelector(selectAppWantsToJoin, selectDeviceCredentialsRaw, (wantsToJoin, deviceCredentials) => {
269
+ if (wantsToJoin && !deviceCredentials.isFetching && !deviceCredentials.data) {
270
+ return true;
271
+ }
272
+ return false;
273
+ });
274
+ createReactor([selectShouldFetchDeviceCredentials], ({ dispatch }, shouldFetchDeviceCredentials) => {
275
+ if (shouldFetchDeviceCredentials) {
276
+ dispatch(doGetDeviceCredentials());
277
+ }
278
+ });
279
+
280
+ const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
281
+
282
+ /**
283
+ * Wrapper class that extends the Socket.IO client library.
284
+ */
285
+ class ServerSocket {
286
+ constructor(hostName, optionsOverrides) {
287
+ this._socket = io(hostName, {
288
+ path: DEFAULT_SOCKET_PATH,
289
+ randomizationFactor: 0.5,
290
+ reconnectionDelay: 250,
291
+ reconnectionDelayMax: 5000,
292
+ timeout: 5000,
293
+ transports: ["websocket"],
294
+ withCredentials: true,
295
+ ...optionsOverrides,
296
+ });
297
+ this._socket.io.on("reconnect", () => {
298
+ this._socket.sendBuffer = [];
299
+ });
300
+ this._socket.io.on("reconnect_attempt", () => {
301
+ if (this._wasConnectedUsingWebsocket) {
302
+ this._socket.io.opts.transports = ["websocket"];
303
+ // only fallback to polling if not safari
304
+ // safari doesn't support cross doamin cookies making load-balancer stickiness not work
305
+ // and if socket.io reconnects to another signal instance with polling it will fail
306
+ // remove if we move signal to a whereby.com subdomain
307
+ if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
308
+ } else {
309
+ this._socket.io.opts.transports = ["websocket", "polling"];
310
+ }
311
+ });
312
+ this._socket.on("connect", () => {
313
+ const transport = this.getTransport();
314
+ if (transport === "websocket") {
315
+ this._wasConnectedUsingWebsocket = true;
316
+ }
317
+ });
318
+ }
319
+
320
+ connect() {
321
+ if (this.isConnected() || this.isConnecting()) {
322
+ return;
323
+ }
324
+ this._socket.open();
325
+ }
326
+
327
+ disconnect() {
328
+ this._socket.disconnect();
329
+ }
330
+
331
+ disconnectOnConnect() {
332
+ this._socket.once("connect", () => {
333
+ this._socket.disconnect();
334
+ });
335
+ }
336
+
337
+ emit() {
338
+ this._socket.emit.apply(this._socket, arguments);
339
+ }
340
+
341
+ emitIfConnected(eventName, data) {
342
+ if (!this.isConnected()) {
343
+ return;
344
+ }
345
+ this.emit(eventName, data);
346
+ }
347
+
348
+ getTransport() {
349
+ return (
350
+ this._socket &&
351
+ this._socket.io &&
352
+ this._socket.io.engine &&
353
+ this._socket.io.engine.transport &&
354
+ this._socket.io.engine.transport.name
355
+ );
356
+ }
357
+
358
+ getManager() {
359
+ return this._socket.io;
360
+ }
361
+
362
+ isConnecting() {
363
+ return this._socket && this._socket.connecting;
364
+ }
365
+
366
+ isConnected() {
367
+ return this._socket && this._socket.connected;
368
+ }
369
+
370
+ /**
371
+ * Register a new event handler.
372
+ *
373
+ * @param {string} eventName - Name of the event to listen for.
374
+ * @param {function} handler - The callback function that should be called for the event.
375
+ * @returns {function} Function to deregister the listener.
376
+ */
377
+ on(eventName, handler) {
378
+ this._socket.on(eventName, handler);
379
+
380
+ return () => {
381
+ this._socket.off(eventName, handler);
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Register a new event handler to be triggered only once.
387
+ *
388
+ * @param {string} eventName - Name of the event to listen for.
389
+ * @param {function} handler - The function that should be called for the event.
390
+ */
391
+ once(eventName, handler) {
392
+ this._socket.once(eventName, handler);
393
+ }
394
+
395
+ /**
396
+ * Deregister an event handler.
397
+ *
398
+ * @param {string} eventName - Name of the event the handler is registered for.
399
+ * @param {function} handler - The callback that will be deregistered.
400
+ */
401
+ off(eventName, handler) {
402
+ this._socket.off(eventName, handler);
403
+ }
404
+ }
405
+
406
+ function forwardSocketEvents(socket, dispatch) {
407
+ socket.on("room_joined", (payload) => dispatch(signalEvents.roomJoined(payload)));
408
+ socket.on("new_client", (payload) => dispatch(signalEvents.newClient(payload)));
409
+ socket.on("client_left", (payload) => dispatch(signalEvents.clientLeft(payload)));
410
+ socket.on("audio_enabled", (payload) => dispatch(signalEvents.audioEnabled(payload)));
411
+ socket.on("video_enabled", (payload) => dispatch(signalEvents.videoEnabled(payload)));
412
+ socket.on("client_metadata_received", (payload) => dispatch(signalEvents.clientMetadataReceived(payload)));
413
+ socket.on("chat_message", (payload) => dispatch(signalEvents.chatMessage(payload)));
414
+ socket.on("disconnect", () => dispatch(signalEvents.disconnect()));
415
+ socket.on("room_knocked", (payload) => dispatch(signalEvents.roomKnocked(payload)));
416
+ socket.on("knocker_left", (payload) => dispatch(signalEvents.knockerLeft(payload)));
417
+ socket.on("knock_handled", (payload) => dispatch(signalEvents.knockHandled(payload)));
418
+ socket.on("screenshare_started", (payload) => dispatch(signalEvents.screenshareStarted(payload)));
419
+ socket.on("screenshare_stopped", (payload) => dispatch(signalEvents.screenshareStopped(payload)));
420
+ socket.on("cloud_recording_started", (payload) => dispatch(signalEvents.cloudRecordingStarted(payload)));
421
+ socket.on("cloud_recording_stopped", () => dispatch(signalEvents.cloudRecordingStopped()));
422
+ socket.on("streaming_stopped", () => dispatch(signalEvents.streamingStopped()));
423
+ }
424
+ const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
425
+ function createSocket() {
426
+ const parsedUrl = new URL(SIGNAL_BASE_URL);
427
+ const socketHost = parsedUrl.origin;
428
+ const socketOverrides = {
429
+ autoConnect: false,
430
+ };
431
+ return new ServerSocket(socketHost, socketOverrides);
432
+ }
433
+ const initialState$e = {
434
+ deviceIdentified: false,
435
+ isIdentifyingDevice: false,
436
+ status: "",
437
+ socket: null,
438
+ };
439
+ const signalConnectionSlice = createSlice({
440
+ name: "signalConnection",
441
+ initialState: initialState$e,
442
+ reducers: {
443
+ socketConnecting: (state) => {
444
+ return Object.assign(Object.assign({}, state), { status: "connecting" });
445
+ },
446
+ socketConnected: (state, action) => {
447
+ return Object.assign(Object.assign({}, state), { socket: action.payload, status: "connected" });
448
+ },
449
+ socketDisconnected: (state) => {
450
+ return Object.assign(Object.assign({}, state), { status: "disconnected" });
451
+ },
452
+ socketReconnecting: (state) => {
453
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
454
+ },
455
+ deviceIdentifying: (state) => {
456
+ return Object.assign(Object.assign({}, state), { isIdentifyingDevice: true });
457
+ },
458
+ deviceIdentified: (state) => {
459
+ return Object.assign(Object.assign({}, state), { deviceIdentified: true, isIdentifyingDevice: false });
460
+ },
461
+ },
462
+ });
463
+ const { deviceIdentifying, deviceIdentified, socketConnected, socketConnecting, socketDisconnected } = signalConnectionSlice.actions;
464
+ /**
465
+ * Action creators
466
+ */
467
+ const doSignalSocketConnect = createAppThunk(() => {
468
+ return (dispatch, getState) => {
469
+ if (selectSignalConnectionSocket(getState())) {
470
+ return;
471
+ }
472
+ dispatch(socketConnecting());
473
+ const socket = createSocket();
474
+ socket.on("connect", () => dispatch(socketConnected(socket)));
475
+ socket.on("device_identified", () => dispatch(deviceIdentified()));
476
+ socket.getManager().on("reconnect", () => dispatch(doSignalReconnect()));
477
+ forwardSocketEvents(socket, dispatch);
478
+ socket.connect();
479
+ };
480
+ });
481
+ const doSignalIdentifyDevice = createAppThunk(({ deviceCredentials }) => (dispatch, getState) => {
482
+ const state = getState();
483
+ const signalSocket = selectSignalConnectionSocket(state);
484
+ if (!signalSocket) {
485
+ return;
486
+ }
487
+ signalSocket.emit("identify_device", { deviceCredentials });
488
+ dispatch(deviceIdentifying());
489
+ });
490
+ const doSignalDisconnect = createAppThunk(() => (dispatch, getState) => {
491
+ const socket = selectSignalConnectionRaw(getState()).socket;
492
+ socket === null || socket === void 0 ? void 0 : socket.emit("leave_room");
493
+ socket === null || socket === void 0 ? void 0 : socket.disconnect();
494
+ dispatch(socketDisconnected());
495
+ });
496
+ const doSignalReconnect = createAppThunk(() => (dispatch, getState) => {
497
+ const deviceCredentialsRaw = selectDeviceCredentialsRaw(getState());
498
+ dispatch(socketReconnecting());
499
+ if (deviceCredentialsRaw.data) {
500
+ dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
501
+ }
502
+ });
503
+ const { socketReconnecting } = signalConnectionSlice.actions;
504
+ /**
505
+ * Selectors
506
+ */
507
+ const selectSignalConnectionRaw = (state) => state.signalConnection;
508
+ const selectSignalIsIdentifyingDevice = (state) => state.signalConnection.isIdentifyingDevice;
509
+ const selectSignalConnectionDeviceIdentified = (state) => state.signalConnection.deviceIdentified;
510
+ const selectSignalStatus = (state) => state.signalConnection.status;
511
+ const selectSignalConnectionSocket = (state) => state.signalConnection.socket;
512
+ /**
513
+ * Reactors
514
+ */
515
+ startAppListening({
516
+ actionCreator: appLeft,
517
+ effect: (_, { dispatch }) => {
518
+ dispatch(doSignalDisconnect());
519
+ },
520
+ });
521
+ const selectShouldConnectSignal = createSelector(selectAppWantsToJoin, selectSignalStatus, (wantsToJoin, signalStatus) => {
522
+ if (wantsToJoin && ["", "reconnect"].includes(signalStatus)) {
523
+ return true;
524
+ }
525
+ return false;
526
+ });
527
+ createReactor([selectShouldConnectSignal], ({ dispatch }, shouldConnectSignal) => {
528
+ if (shouldConnectSignal) {
529
+ dispatch(doSignalSocketConnect());
530
+ }
531
+ });
532
+ const selectShouldIdentifyDevice = createSelector(selectDeviceCredentialsRaw, selectSignalStatus, selectSignalConnectionDeviceIdentified, selectSignalIsIdentifyingDevice, (deviceCredentialsRaw, signalStatus, deviceIdentified, isIdentifyingDevice) => {
533
+ if (deviceCredentialsRaw.data && signalStatus === "connected" && !deviceIdentified && !isIdentifyingDevice) {
534
+ return true;
535
+ }
536
+ return false;
537
+ });
538
+ createReactor([selectShouldIdentifyDevice, selectDeviceCredentialsRaw], ({ dispatch }, shouldIdentifyDevice, deviceCredentialsRaw) => {
539
+ if (shouldIdentifyDevice && deviceCredentialsRaw.data) {
540
+ dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
541
+ }
542
+ });
543
+
544
+ const initialState$d = {
545
+ chatMessages: [],
546
+ };
547
+ const chatSlice = createSlice({
548
+ name: "chat",
549
+ initialState: initialState$d,
550
+ reducers: {},
551
+ extraReducers(builder) {
552
+ builder.addCase(signalEvents.chatMessage, (state, action) => {
553
+ const message = {
554
+ senderId: action.payload.senderId,
555
+ timestamp: action.payload.timestamp,
556
+ text: action.payload.text,
557
+ };
558
+ return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, message] });
559
+ });
560
+ },
561
+ });
562
+ /**
563
+ * Action creators
564
+ */
565
+ const doSendChatMessage = createAppThunk((payload) => (_, getState) => {
566
+ const state = getState();
567
+ const socket = selectSignalConnectionRaw(state).socket;
568
+ socket === null || socket === void 0 ? void 0 : socket.emit("chat_message", { text: payload.text });
569
+ });
570
+ const selectChatMessages = (state) => state.chat.chatMessages;
571
+
572
+ const initialState$c = {
573
+ isRecording: false,
574
+ error: null,
575
+ startedAt: undefined,
576
+ };
577
+ const cloudRecordingSlice = createSlice({
578
+ name: "cloudRecording",
579
+ initialState: initialState$c,
580
+ reducers: {
581
+ recordingRequestStarted: (state) => {
582
+ return Object.assign(Object.assign({}, state), { status: "requested" });
583
+ },
584
+ },
585
+ extraReducers: (builder) => {
586
+ builder.addCase(signalEvents.cloudRecordingStopped, (state) => {
587
+ return Object.assign(Object.assign({}, state), { isRecording: false, status: undefined });
588
+ });
589
+ builder.addCase(signalEvents.cloudRecordingStarted, (state, action) => {
590
+ const { payload } = action;
591
+ if (!payload.error) {
592
+ return state;
593
+ }
594
+ return Object.assign(Object.assign({}, state), { isRecording: false, status: "error", error: payload.error });
595
+ });
596
+ builder.addCase(signalEvents.newClient, (state, { payload }) => {
597
+ var _a;
598
+ const { client } = payload;
599
+ if (((_a = client.role) === null || _a === void 0 ? void 0 : _a.roleName) === "recorder") {
600
+ return Object.assign(Object.assign({}, state), { isRecording: true, status: "recording", startedAt: client.startedCloudRecordingAt
601
+ ? new Date(client.startedCloudRecordingAt).getTime()
602
+ : new Date().getTime() });
603
+ }
604
+ return state;
605
+ });
606
+ },
607
+ });
608
+ /**
609
+ * Action creators
610
+ */
611
+ const { recordingRequestStarted } = cloudRecordingSlice.actions;
612
+ const doStartCloudRecording = createAppThunk(() => (dispatch, getState) => {
613
+ const state = getState();
614
+ const socket = selectSignalConnectionRaw(state).socket;
615
+ const status = selectCloudRecordingStatus(state);
616
+ if (status && ["recording", "requested"].includes(status)) {
617
+ return;
618
+ }
619
+ socket === null || socket === void 0 ? void 0 : socket.emit("start_recording", {
620
+ recording: "cloud",
621
+ });
622
+ dispatch(recordingRequestStarted());
623
+ });
624
+ const doStopCloudRecording = createAppThunk(() => (dispatch, getState) => {
625
+ const state = getState();
626
+ const socket = selectSignalConnectionRaw(state).socket;
627
+ socket === null || socket === void 0 ? void 0 : socket.emit("stop_recording");
628
+ });
629
+ /**
630
+ * Selectors
631
+ */
632
+ const selectCloudRecordingRaw = (state) => state.cloudRecording;
633
+ const selectCloudRecordingStatus = (state) => state.cloudRecording.status;
634
+
122
635
  const isSafari = adapter.browserDetails.browser === "safari";
123
636
 
124
637
  // Expects format 640x360@25, returns [width, height, fps]
@@ -318,6 +831,63 @@ function getUserMedia(constraints) {
318
831
  });
319
832
  }
320
833
 
834
+ function getSettingsFromTrack(kind, track, devices, lastUsedId) {
835
+ let settings = { deviceId: null };
836
+
837
+ if (!track) {
838
+ // In SFU V2 the track can be closed by the RtcManager, so check if the
839
+ // last used deviceId still is available
840
+ if (lastUsedId && devices) {
841
+ settings.deviceId = devices.find((d) => d.deviceId === lastUsedId && d.kind === kind)?.deviceId;
842
+ }
843
+ return settings;
844
+ }
845
+
846
+ settings.label = track.label;
847
+
848
+ // if MediaTrackSettings.deviceId is supported (not firefox android/esr)
849
+ // https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings#Browser_compatibility
850
+ if (track.getSettings) {
851
+ settings = { ...settings, ...track.getSettings() };
852
+ }
853
+ if (settings.deviceId) return settings;
854
+ // if MediaTrackCapabilities is supported (not by firefox or samsung internet in general)
855
+ // https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#Browser_compatibility
856
+ if (track.getCapabilities) {
857
+ settings.deviceId = track.getCapabilities().deviceId;
858
+ }
859
+ if (settings.deviceId) return settings;
860
+
861
+ // Firefox ESR (guessing), has no way of getting deviceId, but
862
+ // it probably gives us label, let's use that to find it!
863
+ if (track.label && devices) {
864
+ settings.deviceId = devices.find((d) => track.label === d.label && d.kind === kind)?.deviceId;
865
+ }
866
+ if (settings.deviceId) return settings;
867
+
868
+ // Okay. As if the above wasn't hacky enough (it was), this
869
+ // is even more, basically see what we sent before
870
+ // It's really sad if we get down to this point.
871
+ settings.deviceId = track.getConstraints()?.deviceId?.exact;
872
+ settings.broken = 1; // just a hint
873
+ return settings;
874
+ }
875
+
876
+ /**
877
+ * Gets audio and video device data from stream
878
+ *
879
+ * @returns {{video: {deviceId}, audio: {deviceId}}} - the ids are null if not found
880
+ */
881
+ function getDeviceData({ audioTrack, videoTrack, devices, stoppedVideoTrack, lastAudioId, lastVideoId }) {
882
+ const usable = (d) => (d?.readyState === "live" ? d : null);
883
+ videoTrack = usable(videoTrack) || stoppedVideoTrack;
884
+ audioTrack = usable(audioTrack);
885
+ const video = getSettingsFromTrack("videoinput", videoTrack, devices, lastVideoId);
886
+ const audio = getSettingsFromTrack("audioinput", audioTrack, devices, lastAudioId);
887
+
888
+ return { video, audio };
889
+ }
890
+
321
891
  /**
322
892
  * Stops all tracks in a media stream.
323
893
  */
@@ -496,374 +1066,1152 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
496
1066
  return { error: error && addDetails(error), stream, replacedTracks };
497
1067
  }
498
1068
 
499
- class LocalMediaEvent extends CustomEvent {
500
- constructor(eventType, eventInitDict) {
501
- super(eventType, eventInitDict);
502
- }
503
- }
504
- const TypedLocalMediaEventTarget = EventTarget;
505
- class LocalMedia extends TypedLocalMediaEventTarget {
506
- constructor(optionsOrStream) {
507
- var _a, _b;
508
- super();
509
- this._options = null;
510
- this._devices = [];
511
- this._isTogglingCameraEnabled = false;
512
- if (optionsOrStream instanceof MediaStream) {
513
- this.stream = optionsOrStream;
514
- }
515
- else {
516
- this._options = optionsOrStream;
517
- this.stream = new MediaStream();
518
- }
519
- this._cameraEnabled = ((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled) || false;
520
- this._microphoneEnabled = ((_b = this.stream.getAudioTracks()[0]) === null || _b === void 0 ? void 0 : _b.enabled) || false;
521
- this._rtcManagers = [];
522
- this.screenshareStream = undefined;
523
- navigator.mediaDevices.addEventListener("devicechange", this._updateDeviceList.bind(this));
524
- }
525
- addRtcManager(rtcManager) {
526
- this._rtcManagers.push(rtcManager);
527
- }
528
- removeRtcManager(rtcManager) {
529
- this._rtcManagers = this._rtcManagers.filter((r) => r !== rtcManager);
530
- }
531
- getCameraDeviceId() {
532
- return this._currentCameraDeviceId;
533
- }
534
- getMicrophoneDeviceId() {
535
- return this._currentMicrophoneDeviceId;
536
- }
537
- isCameraEnabled() {
538
- return this._cameraEnabled;
539
- }
540
- isMicrophoneEnabled() {
541
- return this._microphoneEnabled;
542
- }
543
- toggleCameraEnabled(enabled) {
544
- return __awaiter(this, void 0, void 0, function* () {
545
- if (this._isTogglingCameraEnabled) {
546
- return;
547
- }
548
- let track = this.stream.getVideoTracks()[0];
549
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !(track === null || track === void 0 ? void 0 : track.enabled);
550
- if (this._cameraEnabled === newValue) {
551
- return;
1069
+ function compareLocalDevices(before, after) {
1070
+ const [beforeByKind, afterByKind] = [before, after].map((list) =>
1071
+ list
1072
+ .filter((device) => device.kind && device.deviceId)
1073
+ .reduce(
1074
+ (result, device) => ({
1075
+ ...result,
1076
+ [device.kind]: { ...result[device.kind], [device.deviceId]: device },
1077
+ }),
1078
+ {}
1079
+ )
1080
+ );
1081
+ const changesByKind = {};
1082
+ // find devices removed
1083
+ before.forEach((device) => {
1084
+ if (!device.kind || !device.deviceId) return;
1085
+ if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
1086
+ if (!afterByKind[device.kind] || !afterByKind[device.kind][device.deviceId]) {
1087
+ changesByKind[device.kind].removed[device.deviceId] = device;
1088
+ }
1089
+ });
1090
+ // find devices either added or changed
1091
+ after.forEach((device) => {
1092
+ if (!device.kind || !device.deviceId) return;
1093
+ if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
1094
+ if (!beforeByKind[device.kind] || !beforeByKind[device.kind][device.deviceId]) {
1095
+ changesByKind[device.kind].added[device.deviceId] = device;
1096
+ } else if (
1097
+ beforeByKind[device.kind][device.deviceId].label && // ignore when initially without label
1098
+ beforeByKind[device.kind][device.deviceId].label !== device.label
1099
+ ) {
1100
+ changesByKind[device.kind].changed[device.deviceId] = device;
1101
+ }
1102
+ });
1103
+ return changesByKind;
1104
+ }
1105
+
1106
+ function getUpdatedDevices({ oldDevices, newDevices, currentAudioId, currentVideoId, currentSpeakerId }) {
1107
+ const changesByKind = compareLocalDevices(oldDevices, newDevices);
1108
+ const changedDevices = {};
1109
+ const addedDevices = {};
1110
+ [
1111
+ ["audioinput", currentAudioId],
1112
+ ["videoinput", currentVideoId],
1113
+ ["audiooutput", currentSpeakerId],
1114
+ ].forEach(([kind, currentDeviceId]) => {
1115
+ const changes = changesByKind[kind];
1116
+ if (!changes) {
1117
+ return;
1118
+ }
1119
+ if (currentDeviceId) {
1120
+ // fall back to default if removed
1121
+ if (changes.removed[currentDeviceId]) {
1122
+ changedDevices[kind] = { deviceId: null }; // let browser decide
1123
+ if (kind === "audiooutput") {
1124
+ const fallbackSpeakerDevice = newDevices.find((d) => d.kind === "audiooutput");
1125
+ changedDevices[kind] = { deviceId: fallbackSpeakerDevice?.deviceId };
1126
+ }
1127
+ }
1128
+ // re-request if device has changed
1129
+ if (changes.changed[currentDeviceId]) {
1130
+ changedDevices[kind] = { deviceId: currentDeviceId };
1131
+ }
1132
+ }
1133
+ // request new device if added
1134
+ if (Object.keys(changes.added).length) {
1135
+ const [deviceAdded] = Object.keys(changes.added).slice(0, 1);
1136
+ const add = changes.added[deviceAdded];
1137
+ // device props are not enumerable (used in notificatio
1138
+ addedDevices[kind] = { deviceId: add.deviceId, label: add.label, kind: add.kind };
1139
+ }
1140
+ });
1141
+
1142
+ return { addedDevices, changedDevices };
1143
+ }
1144
+
1145
+ const initialState$b = {
1146
+ busyDeviceIds: [],
1147
+ cameraEnabled: false,
1148
+ devices: [],
1149
+ isSettingCameraDevice: false,
1150
+ isSettingMicrophoneDevice: false,
1151
+ isTogglingCamera: false,
1152
+ microphoneEnabled: false,
1153
+ status: "",
1154
+ isSwitchingStream: false,
1155
+ };
1156
+ const localMediaSlice = createSlice({
1157
+ name: "localMedia",
1158
+ initialState: initialState$b,
1159
+ reducers: {
1160
+ deviceBusy(state, action) {
1161
+ if (state.busyDeviceIds.includes(action.payload.deviceId)) {
1162
+ return state;
552
1163
  }
553
- this._cameraEnabled = newValue;
554
- this.dispatchEvent(new LocalMediaEvent("camera_enabled", { detail: { enabled: this._cameraEnabled } }));
555
- // Only stop tracks if we fully own the media stream
556
- const shouldStopTrack = !!this._options;
557
- this._isTogglingCameraEnabled = true;
558
- try {
559
- if (this._cameraEnabled) {
560
- if (track) {
561
- // We have existing video track, just enable it
562
- track.enabled = true;
563
- }
564
- else {
565
- // We dont have video track, get new one
566
- yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId: false, videoId: this._currentCameraDeviceId, type: "exact" }), { replaceStream: this.stream });
567
- track = this.stream.getVideoTracks()[0];
568
- }
569
- }
570
- else {
571
- if (!track) {
572
- return;
573
- }
574
- track.enabled = false;
575
- if (shouldStopTrack) {
576
- track.stop();
577
- this.stream.removeTrack(track);
578
- }
579
- }
580
- // Dispatch event on stream to allow RTC layer effects
581
- this.stream.dispatchEvent(new LocalMediaEvent("stopresumevideo", { detail: { track, enable: this._cameraEnabled } }));
1164
+ return Object.assign(Object.assign({}, state), { busyDeviceIds: [...state.busyDeviceIds, action.payload.deviceId] });
1165
+ },
1166
+ toggleCameraEnabled(state, action) {
1167
+ var _a;
1168
+ return Object.assign(Object.assign({}, state), { cameraEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.cameraEnabled });
1169
+ },
1170
+ setCurrentCameraDeviceId(state, action) {
1171
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.deviceId });
1172
+ },
1173
+ toggleMicrophoneEnabled(state, action) {
1174
+ var _a;
1175
+ return Object.assign(Object.assign({}, state), { microphoneEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.microphoneEnabled });
1176
+ },
1177
+ setCurrentMicrophoneDeviceId(state, action) {
1178
+ return Object.assign(Object.assign({}, state), { currentMicrophoneDeviceId: action.payload.deviceId });
1179
+ },
1180
+ setDevices(state, action) {
1181
+ return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
1182
+ },
1183
+ setLocalMediaStream(state, action) {
1184
+ return Object.assign(Object.assign({}, state), { stream: action.payload.stream });
1185
+ },
1186
+ setLocalMediaOptions(state, action) {
1187
+ return Object.assign(Object.assign({}, state), { options: action.payload.options });
1188
+ },
1189
+ localMediaStopped(state) {
1190
+ return Object.assign(Object.assign({}, state), { status: "stopped", stream: undefined });
1191
+ },
1192
+ localStreamMetadataUpdated(state, action) {
1193
+ const { audio, video } = action.payload;
1194
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: video.deviceId, currentMicrophoneDeviceId: audio.deviceId, busyDeviceIds: state.busyDeviceIds.filter((id) => id !== audio.deviceId && id !== video.deviceId) });
1195
+ },
1196
+ },
1197
+ extraReducers: (builder) => {
1198
+ builder.addCase(doAppJoin, (state, action) => {
1199
+ return Object.assign(Object.assign({}, state), { options: action.payload.localMediaOptions });
1200
+ });
1201
+ builder.addCase(doSetDevice.pending, (state, action) => {
1202
+ const { audio, video } = action.meta.arg;
1203
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video, isSettingMicrophoneDevice: audio });
1204
+ });
1205
+ builder.addCase(doSetDevice.fulfilled, (state, action) => {
1206
+ const { audio, video } = action.meta.arg;
1207
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video ? false : state.isSettingCameraDevice, isSettingMicrophoneDevice: audio ? false : state.isSettingMicrophoneDevice });
1208
+ });
1209
+ builder.addCase(doSetDevice.rejected, (state, action) => {
1210
+ const { audio, video } = action.meta.arg;
1211
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video ? false : state.isSettingCameraDevice, isSettingMicrophoneDevice: audio ? false : state.isSettingMicrophoneDevice, cameraDeviceError: video ? action.error : state.cameraDeviceError, microphoneDeviceError: audio ? action.error : state.microphoneDeviceError });
1212
+ });
1213
+ builder.addCase(doToggleCamera.pending, (state) => {
1214
+ return Object.assign(Object.assign({}, state), { isTogglingCamera: true });
1215
+ });
1216
+ builder.addCase(doToggleCamera.fulfilled, (state) => {
1217
+ return Object.assign(Object.assign({}, state), { isTogglingCamera: false });
1218
+ });
1219
+ builder.addCase(doUpdateDeviceList.fulfilled, (state, action) => {
1220
+ return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
1221
+ });
1222
+ builder.addCase(doStartLocalMedia.pending, (state) => {
1223
+ return Object.assign(Object.assign({}, state), { status: "starting" });
1224
+ });
1225
+ builder.addCase(doStartLocalMedia.fulfilled, (state, { payload: { stream, onDeviceChange } }) => {
1226
+ let cameraDeviceId = undefined;
1227
+ let cameraEnabled = false;
1228
+ let microphoneDeviceId = undefined;
1229
+ let microphoneEnabled = false;
1230
+ const audioTrack = stream.getAudioTracks()[0];
1231
+ const videoTrack = stream.getVideoTracks()[0];
1232
+ if (audioTrack) {
1233
+ microphoneDeviceId = audioTrack.getSettings().deviceId;
1234
+ microphoneEnabled = audioTrack.enabled;
582
1235
  }
583
- catch (error) {
584
- // TODO: Update error state
1236
+ if (videoTrack) {
1237
+ cameraEnabled = videoTrack.enabled;
1238
+ cameraDeviceId = videoTrack.getSettings().deviceId;
585
1239
  }
586
- this._isTogglingCameraEnabled = false;
1240
+ return Object.assign(Object.assign({}, state), { stream, status: "started", currentCameraDeviceId: cameraDeviceId, currentMicrophoneDeviceId: microphoneDeviceId, cameraEnabled,
1241
+ microphoneEnabled,
1242
+ onDeviceChange });
587
1243
  });
1244
+ builder.addCase(doStartLocalMedia.rejected, (state, action) => {
1245
+ return Object.assign(Object.assign({}, state), { status: "error", startError: action.error });
1246
+ });
1247
+ builder.addCase(doSwitchLocalStream.pending, (state) => {
1248
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: true });
1249
+ });
1250
+ builder.addCase(doSwitchLocalStream.fulfilled, (state) => {
1251
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
1252
+ });
1253
+ builder.addCase(doSwitchLocalStream.rejected, (state) => {
1254
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
1255
+ });
1256
+ },
1257
+ });
1258
+ /**
1259
+ * Action creators
1260
+ */
1261
+ const { deviceBusy, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, toggleCameraEnabled, toggleMicrophoneEnabled, setLocalMediaOptions, setLocalMediaStream, localMediaStopped, localStreamMetadataUpdated, } = localMediaSlice.actions;
1262
+ const doToggleCamera = createAppAsyncThunk("localMedia/doToggleCamera", (_, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1263
+ const state = getState();
1264
+ const stream = selectLocalMediaStream(state);
1265
+ if (!stream) {
1266
+ return;
588
1267
  }
589
- toggleMichrophoneEnabled(enabled) {
590
- const audioTrack = this.stream.getAudioTracks()[0];
591
- if (!audioTrack) {
592
- return;
1268
+ let track = stream.getVideoTracks()[0];
1269
+ const enabled = selectIsCameraEnabled(state);
1270
+ // Only stop tracks if we fully own the media stream
1271
+ const shouldStopTrack = selectLocalMediaOwnsStream(state);
1272
+ try {
1273
+ if (enabled) {
1274
+ if (track) {
1275
+ // We have existing video track, just enable it
1276
+ track.enabled = true;
1277
+ }
1278
+ else {
1279
+ // We dont have video track, get new one
1280
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1281
+ const cameraDeviceId = selectCurrentCameraDeviceId(state);
1282
+ yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: false, videoId: cameraDeviceId, type: "exact" }), { replaceStream: stream });
1283
+ track = stream.getVideoTracks()[0];
1284
+ }
593
1285
  }
594
- // Update internal state and dispatch event early
595
- this._microphoneEnabled = enabled !== null && enabled !== void 0 ? enabled : !audioTrack.enabled;
596
- this.dispatchEvent(new LocalMediaEvent("microphone_enabled", { detail: { enabled: this._microphoneEnabled } }));
597
- audioTrack.enabled = this._microphoneEnabled;
598
- }
599
- startScreenshare() {
600
- return __awaiter(this, void 0, void 0, function* () {
601
- if (this.screenshareStream) {
602
- return this.screenshareStream;
1286
+ else {
1287
+ if (!track) {
1288
+ return;
603
1289
  }
604
- const screenshareStream = yield navigator.mediaDevices.getDisplayMedia();
605
- this.screenshareStream = screenshareStream;
606
- return this.screenshareStream;
607
- });
608
- }
609
- stopScreenshare() {
610
- var _a;
611
- (_a = this.screenshareStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
612
- this.screenshareStream = undefined;
613
- }
614
- _getConstraintsOptions() {
615
- return {
616
- devices: this._devices,
617
- options: {
618
- disableAEC: false,
619
- disableAGC: false,
620
- hd: true,
621
- lax: false,
622
- lowDataMode: false,
623
- simulcast: true,
624
- widescreen: true,
625
- },
626
- };
627
- }
628
- _setDevice({ audioId, videoId }) {
629
- return __awaiter(this, void 0, void 0, function* () {
630
- const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId,
631
- videoId, type: "exact" }), { replaceStream: this.stream });
632
- if (replacedTracks) {
633
- replacedTracks.forEach((oldTrack) => {
634
- const newTrack = oldTrack.kind === "audio" ? this.stream.getAudioTracks()[0] : this.stream.getVideoTracks()[0];
635
- this._rtcManagers.forEach((rtcManager) => {
636
- rtcManager.replaceTrack(oldTrack, newTrack);
637
- });
638
- });
1290
+ track.enabled = false;
1291
+ if (shouldStopTrack) {
1292
+ track.stop();
1293
+ stream.removeTrack(track);
639
1294
  }
640
- this.dispatchEvent(new LocalMediaEvent("stream_updated", {
641
- detail: { stream: this.stream },
642
- }));
643
- });
1295
+ }
1296
+ // Dispatch event on stream to allow RTC layer effects
1297
+ stream.dispatchEvent(new CustomEvent("stopresumevideo", { detail: { track, enable: enabled } }));
1298
+ }
1299
+ catch (error) {
1300
+ return rejectWithValue(error);
1301
+ }
1302
+ }));
1303
+ const doToggleMicrophone = createAppAsyncThunk("localMedia/doToggleMicrophone", (_, { getState }) => {
1304
+ var _a;
1305
+ const state = getState();
1306
+ const stream = selectLocalMediaStream(state);
1307
+ if (!stream) {
1308
+ return;
644
1309
  }
645
- setCameraDevice(deviceId) {
646
- return __awaiter(this, void 0, void 0, function* () {
647
- this._currentCameraDeviceId = deviceId;
648
- yield this._setDevice({ videoId: this._currentCameraDeviceId, audioId: false });
649
- });
1310
+ const enabled = selectIsMicrophoneEnabled(state);
1311
+ const audioTrack = (_a = stream.getAudioTracks()) === null || _a === void 0 ? void 0 : _a[0];
1312
+ if (!audioTrack) {
1313
+ return;
650
1314
  }
651
- setMicrophoneDevice(deviceId) {
652
- return __awaiter(this, void 0, void 0, function* () {
653
- this._currentMicrophoneDeviceId = deviceId;
654
- yield this._setDevice({ audioId: this._currentMicrophoneDeviceId, videoId: false });
1315
+ audioTrack.enabled = enabled;
1316
+ });
1317
+ const doSetDevice = createAppAsyncThunk("localMedia/reactSetDevice", ({ audio, video }, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1318
+ try {
1319
+ const state = getState();
1320
+ const stream = selectLocalMediaStream(state);
1321
+ if (!stream) {
1322
+ throw new Error("No stream");
1323
+ }
1324
+ const audioId = audio ? selectCurrentMicrophoneDeviceId(state) : false;
1325
+ const videoId = video ? selectCurrentCameraDeviceId(state) : false;
1326
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1327
+ const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId,
1328
+ videoId, type: "exact" }), { replaceStream: stream });
1329
+ const isAudioEnabled = selectIsMicrophoneEnabled(state);
1330
+ stream.getAudioTracks().forEach((track) => (track.enabled = isAudioEnabled));
1331
+ const isVideoEnabled = selectIsCameraEnabled(state);
1332
+ stream.getVideoTracks().forEach((track) => (track.enabled = isVideoEnabled));
1333
+ return { replacedTracks };
1334
+ }
1335
+ catch (error) {
1336
+ return rejectWithValue(error);
1337
+ }
1338
+ }));
1339
+ const doUpdateDeviceList = createAppAsyncThunk("localMedia/doUpdateDeviceList", (_, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1340
+ var _a, _b;
1341
+ const state = getState();
1342
+ let newDevices = [];
1343
+ let oldDevices = [];
1344
+ const stream = selectLocalMediaStream(state);
1345
+ const busy = selectBusyDeviceIds(state);
1346
+ try {
1347
+ newDevices = yield navigator.mediaDevices.enumerateDevices();
1348
+ oldDevices = selectLocalMediaDevices(state);
1349
+ const shouldHandleDeviceUpdate = stream &&
1350
+ !selectLocalMediaIsSwitchingStream(state) &&
1351
+ newDevices &&
1352
+ oldDevices &&
1353
+ oldDevices.find((d) => d.deviceId);
1354
+ if (!shouldHandleDeviceUpdate) {
1355
+ return { devices: newDevices };
1356
+ }
1357
+ const { changedDevices } = getUpdatedDevices({
1358
+ oldDevices,
1359
+ newDevices,
1360
+ currentAudioId: selectCurrentMicrophoneDeviceId(state),
1361
+ currentVideoId: selectCurrentCameraDeviceId(state),
655
1362
  });
656
- }
657
- _updateDeviceList() {
658
- return __awaiter(this, void 0, void 0, function* () {
659
- try {
660
- const devices = yield navigator.mediaDevices.enumerateDevices();
661
- this.dispatchEvent(new LocalMediaEvent("device_list_updated", {
662
- detail: {
663
- cameraDevices: devices.filter((d) => d.kind === "videoinput"),
664
- microphoneDevices: devices.filter((d) => d.kind === "audioinput"),
665
- speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
666
- },
667
- }));
668
- this._devices = devices;
1363
+ let autoSwitchAudioId = (_a = changedDevices.audioinput) === null || _a === void 0 ? void 0 : _a.deviceId;
1364
+ let autoSwitchVideoId = (_b = changedDevices.videoinput) === null || _b === void 0 ? void 0 : _b.deviceId;
1365
+ // eslint-disable-next-line no-inner-declarations
1366
+ function nextId(devices, id) {
1367
+ const curIdx = id ? devices.findIndex((d) => d.deviceId === id) : 0;
1368
+ return (devices[(curIdx + 1) % devices.length] || {}).deviceId;
1369
+ }
1370
+ if (autoSwitchVideoId !== undefined) {
1371
+ const videoDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "videoinput");
1372
+ const videoId = selectCurrentCameraDeviceId(state);
1373
+ let nextVideoId = nextId(videoDevices.filter((d) => !busy.includes(d.deviceId)), videoId);
1374
+ if (!nextVideoId || videoId === nextVideoId) {
1375
+ nextVideoId = nextId(videoDevices, videoId);
669
1376
  }
670
- catch (error) {
671
- this.dispatchEvent(new LocalMediaEvent("device_list_update_error", {
672
- detail: {
673
- error,
674
- },
675
- }));
676
- throw error;
1377
+ if (videoId !== nextVideoId) {
1378
+ autoSwitchVideoId = nextVideoId;
677
1379
  }
678
- });
679
- }
680
- start() {
681
- return __awaiter(this, void 0, void 0, function* () {
682
- yield this._updateDeviceList();
683
- if (this._options) {
684
- yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId: this._options.audio, videoId: this._options.video }), { replaceStream: this.stream });
685
- const cameraTrack = this.stream.getVideoTracks()[0];
686
- if (cameraTrack) {
687
- this._cameraEnabled = cameraTrack.enabled;
688
- this._currentCameraDeviceId = cameraTrack.getSettings().deviceId;
689
- }
690
- const microphoneTrack = this.stream.getAudioTracks()[0];
691
- if (microphoneTrack) {
692
- this._microphoneEnabled = microphoneTrack.enabled;
693
- this._currentMicrophoneDeviceId = microphoneTrack.getSettings().deviceId;
694
- }
1380
+ }
1381
+ if (autoSwitchAudioId !== undefined) {
1382
+ const audioDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "audioinput");
1383
+ const audioId = selectCurrentMicrophoneDeviceId(state);
1384
+ let nextAudioId = nextId(audioDevices.filter((d) => !busy.includes(d.deviceId)), audioId);
1385
+ if (!nextAudioId || audioId === nextAudioId) {
1386
+ nextAudioId = nextId(audioDevices, audioId);
695
1387
  }
696
- this.dispatchEvent(new LocalMediaEvent("stream_updated", {
697
- detail: { stream: this.stream },
698
- }));
699
- return this.stream;
700
- });
1388
+ if (audioId !== nextAudioId) {
1389
+ autoSwitchAudioId = nextAudioId;
1390
+ }
1391
+ }
1392
+ if (autoSwitchAudioId !== undefined || autoSwitchVideoId !== undefined) {
1393
+ dispatch(doSwitchLocalStream({ audioId: autoSwitchAudioId, videoId: autoSwitchVideoId }));
1394
+ }
1395
+ return { devices: newDevices };
1396
+ }
1397
+ catch (error) {
1398
+ return rejectWithValue(error);
1399
+ }
1400
+ }));
1401
+ const doSwitchLocalStream = createAppAsyncThunk("localMedia/doSwitchLocalStream", ({ audioId, videoId }, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1402
+ const state = getState();
1403
+ const replaceStream = selectLocalMediaStream(state);
1404
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1405
+ const onlySwitchingOne = !!(videoId && !audioId) || !!(!videoId && audioId);
1406
+ if (!replaceStream) {
1407
+ // Switching no stream makes no sense
1408
+ return;
701
1409
  }
702
- stop() {
703
- var _a;
704
- if (this._options) {
705
- (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
706
- t.stop();
707
- });
1410
+ try {
1411
+ const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: audioId === undefined ? false : audioId, videoId: videoId === undefined ? false : videoId, type: "exact" }), { replaceStream });
1412
+ const deviceId = audioId || videoId;
1413
+ if (onlySwitchingOne && deviceId) {
1414
+ dispatch(deviceBusy({
1415
+ deviceId,
1416
+ }));
1417
+ }
1418
+ return { replacedTracks };
1419
+ }
1420
+ catch (error) {
1421
+ console.error(error);
1422
+ const deviceId = audioId || videoId;
1423
+ if (onlySwitchingOne && deviceId) {
1424
+ dispatch(deviceBusy({
1425
+ deviceId,
1426
+ }));
708
1427
  }
1428
+ return rejectWithValue(error);
1429
+ }
1430
+ }));
1431
+ const doStartLocalMedia = createAppAsyncThunk("localMedia/doStartLocalMedia", (payload, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1432
+ const onDeviceChange = debounce(() => {
1433
+ dispatch(doUpdateDeviceList());
1434
+ }, { delay: 500 });
1435
+ if (global.navigator.mediaDevices) {
1436
+ navigator.mediaDevices.addEventListener("devicechange", onDeviceChange);
1437
+ }
1438
+ // Resolve if existing stream is passed
1439
+ if ("getTracks" in payload) {
1440
+ return Promise.resolve({ stream: payload, onDeviceChange });
1441
+ }
1442
+ if (!(payload.audio || payload.video)) {
1443
+ return { stream: new MediaStream(), onDeviceChange };
1444
+ }
1445
+ else {
1446
+ dispatch(setLocalMediaOptions({ options: payload }));
1447
+ }
1448
+ try {
1449
+ // then update devices
1450
+ yield dispatch(doUpdateDeviceList());
1451
+ // then get new state
1452
+ const state = getState();
1453
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1454
+ const { stream } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: payload.audio, videoId: payload.video }));
1455
+ return { stream, onDeviceChange };
1456
+ }
1457
+ catch (error) {
1458
+ return rejectWithValue(error);
1459
+ }
1460
+ }));
1461
+ const doStopLocalMedia = createAppThunk(() => (dispatch, getState) => {
1462
+ const stream = selectLocalMediaStream(getState());
1463
+ const onDeviceChange = selectLocalMediaRaw(getState()).onDeviceChange;
1464
+ stream === null || stream === void 0 ? void 0 : stream.getTracks().forEach((track) => {
1465
+ track.stop();
1466
+ });
1467
+ if (global.navigator.mediaDevices && onDeviceChange) {
1468
+ navigator.mediaDevices.removeEventListener("devicechange", onDeviceChange);
709
1469
  }
710
- }
1470
+ dispatch(localMediaStopped());
1471
+ });
1472
+ /**
1473
+ * Selectors
1474
+ */
1475
+ const selectBusyDeviceIds = (state) => state.localMedia.busyDeviceIds;
1476
+ const selectCameraDeviceError = (state) => state.localMedia.cameraDeviceError;
1477
+ const selectCurrentCameraDeviceId = (state) => state.localMedia.currentCameraDeviceId;
1478
+ const selectCurrentMicrophoneDeviceId = (state) => state.localMedia.currentMicrophoneDeviceId;
1479
+ const selectIsCameraEnabled = (state) => state.localMedia.cameraEnabled;
1480
+ const selectIsMicrophoneEnabled = (state) => state.localMedia.microphoneEnabled;
1481
+ const selectIsSettingCameraDevice = (state) => state.localMedia.isSettingCameraDevice;
1482
+ const selectIsSettingMicrophoneDevice = (state) => state.localMedia.isSettingMicrophoneDevice;
1483
+ const selectIsToggleCamera = (state) => state.localMedia.isTogglingCamera;
1484
+ const selectLocalMediaDevices = (state) => state.localMedia.devices;
1485
+ const selectLocalMediaOptions = (state) => state.localMedia.options;
1486
+ const selectLocalMediaOwnsStream = createSelector(selectLocalMediaOptions, (options) => !!options);
1487
+ const selectLocalMediaRaw = (state) => state.localMedia;
1488
+ const selectLocalMediaStatus = (state) => state.localMedia.status;
1489
+ const selectLocalMediaStream = (state) => state.localMedia.stream;
1490
+ const selectMicrophoneDeviceError = (state) => state.localMedia.microphoneDeviceError;
1491
+ const selectLocalMediaStartError = (state) => state.localMedia.startError;
1492
+ const selectLocalMediaIsSwitchingStream = (state) => state.localMedia.isSwitchingStream;
1493
+ const selectLocalMediaConstraintsOptions = createSelector(selectLocalMediaDevices, (devices) => ({
1494
+ devices,
1495
+ options: {
1496
+ disableAEC: false,
1497
+ disableAGC: false,
1498
+ hd: true,
1499
+ lax: false,
1500
+ lowDataMode: false,
1501
+ simulcast: true,
1502
+ widescreen: true,
1503
+ },
1504
+ }));
1505
+ const selectIsLocalMediaStarting = createSelector(selectLocalMediaStatus, (status) => status === "starting");
1506
+ const selectCameraDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "videoinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1507
+ const selectMicrophoneDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "audioinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1508
+ const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) => devices.filter((d) => d.kind === "audiooutput"));
1509
+ /**
1510
+ * Reactors
1511
+ */
1512
+ // Start localMedia unless started when roomConnection is wanted
1513
+ const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
1514
+ if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
1515
+ return localMediaOptions;
1516
+ }
1517
+ });
1518
+ createReactor([selectLocalMediaShouldStartWithOptions], ({ dispatch }, options) => {
1519
+ if (options) {
1520
+ dispatch(doStartLocalMedia(options));
1521
+ }
1522
+ });
1523
+ // Stop localMedia when roomConnection is no longer wanted and media was started when joining
1524
+ const selectLocalMediaShouldStop = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
1525
+ return !appWantsToJoin && localMediaStatus !== "" && !!localMediaOptions;
1526
+ });
1527
+ createReactor([selectLocalMediaShouldStop], ({ dispatch }, localMediaShouldStop) => {
1528
+ if (localMediaShouldStop) {
1529
+ dispatch(doStopLocalMedia());
1530
+ }
1531
+ });
1532
+ startAppListening({
1533
+ predicate: (_action, currentState, previousState) => {
1534
+ const oldValue = selectIsMicrophoneEnabled(previousState);
1535
+ const newValue = selectIsMicrophoneEnabled(currentState);
1536
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1537
+ return isReady && oldValue !== newValue;
1538
+ },
1539
+ effect: (_, { dispatch }) => {
1540
+ dispatch(doToggleMicrophone());
1541
+ },
1542
+ });
1543
+ startAppListening({
1544
+ predicate: (_action, currentState, previousState) => {
1545
+ const isToggling = selectIsToggleCamera(currentState);
1546
+ if (isToggling) {
1547
+ return false;
1548
+ }
1549
+ const oldValue = selectIsCameraEnabled(previousState);
1550
+ const newValue = selectIsCameraEnabled(currentState);
1551
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1552
+ return isReady && oldValue !== newValue;
1553
+ },
1554
+ effect: (_action, { dispatch }) => {
1555
+ dispatch(doToggleCamera());
1556
+ },
1557
+ });
1558
+ startAppListening({
1559
+ predicate: (_action, currentState, previousState) => {
1560
+ const oldValue = selectCurrentCameraDeviceId(previousState);
1561
+ const newValue = selectCurrentCameraDeviceId(currentState);
1562
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1563
+ return isReady && oldValue !== newValue;
1564
+ },
1565
+ effect: (_action, { dispatch }) => {
1566
+ dispatch(doSetDevice({ audio: false, video: true }));
1567
+ },
1568
+ });
1569
+ startAppListening({
1570
+ predicate: (_action, currentState, previousState) => {
1571
+ const oldValue = selectCurrentMicrophoneDeviceId(previousState);
1572
+ const newValue = selectCurrentMicrophoneDeviceId(currentState);
1573
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1574
+ return isReady && oldValue !== newValue;
1575
+ },
1576
+ effect: (_action, { dispatch }) => {
1577
+ dispatch(doSetDevice({ audio: true, video: false }));
1578
+ },
1579
+ });
1580
+ startAppListening({
1581
+ matcher: isAnyOf(doStartLocalMedia.fulfilled, doUpdateDeviceList.fulfilled, doSwitchLocalStream.fulfilled, doSwitchLocalStream.rejected),
1582
+ effect: (_action, { dispatch, getState }) => {
1583
+ const state = getState();
1584
+ const stream = selectLocalMediaStream(state);
1585
+ const devices = selectLocalMediaDevices(state);
1586
+ if (!stream)
1587
+ return;
1588
+ const deviceData = getDeviceData({
1589
+ audioTrack: stream.getAudioTracks()[0],
1590
+ videoTrack: stream.getVideoTracks()[0],
1591
+ devices,
1592
+ });
1593
+ dispatch(localStreamMetadataUpdated(deviceData));
1594
+ },
1595
+ });
711
1596
 
712
- const initialState$1 = {
713
- cameraDeviceError: null,
714
- cameraDevices: [],
715
- isSettingCameraDevice: false,
716
- isSettingMicrophoneDevice: false,
717
- isStarting: false,
718
- microphoneDeviceError: null,
719
- microphoneDevices: [],
720
- speakerDevices: [],
721
- startError: null,
1597
+ const initialState$a = {
1598
+ displayName: "",
1599
+ id: "",
1600
+ isAudioEnabled: true,
1601
+ isVideoEnabled: true,
1602
+ isLocalParticipant: true,
1603
+ stream: undefined,
1604
+ isScreenSharing: false,
1605
+ roleName: "",
722
1606
  };
723
- function reducer$1(state, action) {
724
- switch (action.type) {
725
- case "DEVICE_LIST_UPDATED":
1607
+ const doEnableAudio = createAppAsyncThunk("localParticipant/doEnableAudio", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1608
+ const state = getState();
1609
+ const socket = selectSignalConnectionRaw(state).socket;
1610
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_audio", { enabled: payload.enabled });
1611
+ return payload.enabled;
1612
+ }));
1613
+ const doEnableVideo = createAppAsyncThunk("localParticipant/doEnableVideo", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1614
+ const state = getState();
1615
+ const socket = selectSignalConnectionRaw(state).socket;
1616
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_video", { enabled: payload.enabled });
1617
+ return payload.enabled;
1618
+ }));
1619
+ const doSetDisplayName = createAppAsyncThunk("localParticipant/doSetDisplayName", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1620
+ const state = getState();
1621
+ const socket = selectSignalConnectionRaw(state).socket;
1622
+ socket === null || socket === void 0 ? void 0 : socket.emit("send_client_metadata", {
1623
+ type: "UserData",
1624
+ payload,
1625
+ });
1626
+ return payload.displayName;
1627
+ }));
1628
+ const localParticipantSlice = createSlice({
1629
+ name: "localParticipant",
1630
+ initialState: initialState$a,
1631
+ reducers: {
1632
+ doSetLocalParticipant: (state, action) => {
726
1633
  return Object.assign(Object.assign({}, state), action.payload);
727
- case "LOCAL_STREAM_UPDATED":
728
- return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.currentCameraDeviceId, currentMicrophoneDeviceId: action.payload.currentMicrophoneDeviceId, localStream: action.payload.stream });
729
- case "SET_CAMERA_DEVICE":
730
- return Object.assign(Object.assign({}, state), { cameraDeviceError: null, isSettingCameraDevice: true });
731
- case "SET_CAMERA_DEVICE_COMPLETE":
732
- return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
733
- case "SET_CAMERA_DEVICE_ERROR":
734
- return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
735
- case "SET_MICROPHONE_DEVICE":
736
- return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
737
- case "SET_MICROPHONE_DEVICE_COMPLETE":
738
- return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
739
- case "SET_MICROPHONE_DEVICE_ERROR":
740
- return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
741
- case "START":
742
- return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
743
- case "START_COMPLETE":
744
- return Object.assign(Object.assign({}, state), { isStarting: false });
745
- case "START_ERROR":
746
- return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
747
- default:
748
- return state;
749
- }
750
- }
751
- function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
752
- const [localMedia] = useState(() => new LocalMedia(optionsOrStream));
753
- const [state, dispatch] = useReducer(reducer$1, initialState$1);
754
- useEffect(() => {
755
- localMedia.addEventListener("device_list_updated", (e) => {
756
- const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
757
- dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
1634
+ },
1635
+ },
1636
+ extraReducers: (builder) => {
1637
+ builder.addCase(doAppJoin, (state, action) => {
1638
+ return Object.assign(Object.assign({}, state), { displayName: action.payload.displayName });
758
1639
  });
759
- localMedia.addEventListener("stream_updated", (e) => {
760
- const { stream } = e.detail;
761
- dispatch({
762
- type: "LOCAL_STREAM_UPDATED",
763
- payload: {
764
- stream,
765
- currentCameraDeviceId: localMedia.getCameraDeviceId(),
766
- currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
767
- },
768
- });
1640
+ builder.addCase(doEnableAudio.fulfilled, (state, action) => {
1641
+ return Object.assign(Object.assign({}, state), { isAudioEnabled: action.payload });
769
1642
  });
770
- const start = () => __awaiter(this, void 0, void 0, function* () {
771
- dispatch({ type: "START" });
772
- try {
773
- yield localMedia.start();
774
- dispatch({ type: "START_COMPLETE" });
775
- }
776
- catch (error) {
777
- dispatch({ type: "START_ERROR", payload: error });
778
- }
1643
+ builder.addCase(doEnableVideo.fulfilled, (state, action) => {
1644
+ return Object.assign(Object.assign({}, state), { isVideoEnabled: action.payload });
779
1645
  });
780
- start();
781
- // Perform cleanup on unmount
782
- return () => {
783
- localMedia.stop();
784
- };
785
- }, []);
786
- return {
787
- state,
788
- actions: {
789
- setCameraDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
790
- dispatch({ type: "SET_CAMERA_DEVICE" });
791
- try {
792
- yield localMedia.setCameraDevice(...args);
793
- dispatch({ type: "SET_CAMERA_DEVICE_COMPLETE" });
794
- }
795
- catch (error) {
796
- dispatch({ type: "SET_CAMERA_DEVICE_ERROR", payload: error });
797
- }
798
- }),
799
- setMicrophoneDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
800
- dispatch({ type: "SET_MICROPHONE_DEVICE" });
801
- try {
802
- yield localMedia.setMicrophoneDevice(...args);
803
- dispatch({ type: "SET_MICROPHONE_DEVICE_COMPLETE" });
804
- }
805
- catch (error) {
806
- dispatch({ type: "SET_MICROPHONE_DEVICE_ERROR", payload: error });
807
- }
808
- }),
809
- toggleCameraEnabled: (...args) => {
810
- return localMedia.toggleCameraEnabled(...args);
811
- },
812
- toggleMicrophoneEnabled: (...args) => {
813
- return localMedia.toggleMichrophoneEnabled(...args);
814
- },
815
- },
816
- _ref: localMedia,
817
- };
818
- }
819
-
820
- const EVENTS = {
821
- CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
822
- STREAM_ADDED: "stream_added",
823
- RTC_MANAGER_CREATED: "rtc_manager_created",
824
- RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
825
- LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
826
- LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
827
- REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
828
- REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
829
- };
1646
+ builder.addCase(doSetDisplayName.fulfilled, (state, action) => {
1647
+ return Object.assign(Object.assign({}, state), { displayName: action.payload });
1648
+ });
1649
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1650
+ var _a, _b;
1651
+ const client = (_b = (_a = action.payload) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.clients.find((c) => { var _a; return c.id === ((_a = action.payload) === null || _a === void 0 ? void 0 : _a.selfId); });
1652
+ return Object.assign(Object.assign({}, state), { id: action.payload.selfId, roleName: (client === null || client === void 0 ? void 0 : client.role.roleName) || "" });
1653
+ });
1654
+ },
1655
+ });
1656
+ localParticipantSlice.actions;
1657
+ const selectLocalParticipantRaw = (state) => state.localParticipant;
1658
+ const selectSelfId = (state) => state.localParticipant.id;
1659
+ const selectLocalParticipantRole = (state) => state.localParticipant.roleName;
1660
+ startAppListening({
1661
+ actionCreator: toggleCameraEnabled,
1662
+ effect: ({ payload }, { dispatch, getState }) => {
1663
+ const { enabled } = payload;
1664
+ const { isVideoEnabled } = selectLocalParticipantRaw(getState());
1665
+ dispatch(doEnableVideo({ enabled: enabled || !isVideoEnabled }));
1666
+ },
1667
+ });
1668
+ startAppListening({
1669
+ actionCreator: toggleMicrophoneEnabled,
1670
+ effect: ({ payload }, { dispatch, getState }) => {
1671
+ const { enabled } = payload;
1672
+ const { isAudioEnabled } = selectLocalParticipantRaw(getState());
1673
+ dispatch(doEnableAudio({ enabled: enabled || !isAudioEnabled }));
1674
+ },
1675
+ });
830
1676
 
831
- const TYPES = {
832
- CONNECTING: "connecting",
833
- CONNECTION_FAILED: "connection_failed",
834
- CONNECTION_SUCCESSFUL: "connection_successful",
835
- CONNECTION_DISCONNECTED: "connection_disconnected",
836
- };
1677
+ const initialState$9 = {
1678
+ status: "",
1679
+ stream: null,
1680
+ error: null,
1681
+ };
1682
+ /**
1683
+ * Reducer
1684
+ */
1685
+ const localScreenshareSlice = createSlice({
1686
+ name: "localScreenshare",
1687
+ initialState: initialState$9,
1688
+ reducers: {
1689
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1690
+ stopScreenshare(state, action) {
1691
+ return Object.assign(Object.assign({}, state), { status: "", stream: null });
1692
+ },
1693
+ },
1694
+ extraReducers: (builder) => {
1695
+ builder.addCase(doStartScreenshare.pending, (state) => {
1696
+ return Object.assign(Object.assign({}, state), { status: "starting" });
1697
+ });
1698
+ builder.addCase(doStartScreenshare.fulfilled, (state, { payload: { stream } }) => {
1699
+ return Object.assign(Object.assign({}, state), { status: "active", stream });
1700
+ });
1701
+ builder.addCase(doStartScreenshare.rejected, (state, { payload }) => {
1702
+ return Object.assign(Object.assign({}, state), { error: payload, status: "", stream: null });
1703
+ });
1704
+ },
1705
+ });
1706
+ /**
1707
+ * Action creators
1708
+ */
1709
+ const { stopScreenshare } = localScreenshareSlice.actions;
1710
+ const doStartScreenshare = createAppAsyncThunk("localScreenshare/doStartScreenshare", (_, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1711
+ var _a;
1712
+ try {
1713
+ const state = getState();
1714
+ const screenshareStream = selectLocalScreenshareStream(state);
1715
+ if (screenshareStream) {
1716
+ return { stream: screenshareStream };
1717
+ }
1718
+ const stream = yield navigator.mediaDevices.getDisplayMedia();
1719
+ const onEnded = () => {
1720
+ dispatch(doStopScreenshare());
1721
+ };
1722
+ if ("oninactive" in stream) {
1723
+ // Chrome
1724
+ stream.addEventListener("inactive", onEnded);
1725
+ }
1726
+ else {
1727
+ // Firefox
1728
+ (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
1729
+ }
1730
+ return { stream };
1731
+ }
1732
+ catch (error) {
1733
+ return rejectWithValue(error);
1734
+ }
1735
+ }));
1736
+ const doStopScreenshare = createAppThunk(() => (dispatch, getState) => {
1737
+ const state = getState();
1738
+ const screenshareStream = selectLocalScreenshareStream(state);
1739
+ if (!screenshareStream) {
1740
+ return;
1741
+ }
1742
+ screenshareStream.getTracks().forEach((track) => track.stop());
1743
+ dispatch(stopScreenshare({ stream: screenshareStream }));
1744
+ });
1745
+ const selectLocalScreenshareStream = (state) => state.localScreenshare.stream;
1746
+ /**
1747
+ * Reactors
1748
+ */
1749
+ startAppListening({
1750
+ actionCreator: localMediaStopped,
1751
+ effect: (_, { getState }) => {
1752
+ const state = getState();
1753
+ const screenshareStream = selectLocalScreenshareStream(state);
1754
+ if (!screenshareStream) {
1755
+ return;
1756
+ }
1757
+ screenshareStream === null || screenshareStream === void 0 ? void 0 : screenshareStream.getTracks().forEach((track) => {
1758
+ track.stop();
1759
+ });
1760
+ },
1761
+ });
837
1762
 
838
- // Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
1763
+ const initialState$8 = {
1764
+ data: null,
1765
+ isFetching: false,
1766
+ error: null,
1767
+ };
1768
+ const organizationSlice = createSlice({
1769
+ initialState: initialState$8,
1770
+ name: "organization",
1771
+ reducers: {},
1772
+ extraReducers: (builder) => {
1773
+ builder.addCase(doOrganizationFetch.pending, (state) => {
1774
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1775
+ });
1776
+ builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
1777
+ if (!action.payload)
1778
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1779
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
1780
+ });
1781
+ builder.addCase(doOrganizationFetch.rejected, (state) => {
1782
+ return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
1783
+ });
1784
+ },
1785
+ });
1786
+ /**
1787
+ * Action creators
1788
+ */
1789
+ const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1790
+ try {
1791
+ const roomUrl = selectAppRoomUrl(getState());
1792
+ const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
1793
+ if (!organization) {
1794
+ throw new Error("Invalid room url");
1795
+ }
1796
+ return organization;
1797
+ }
1798
+ catch (error) {
1799
+ console.error(error);
1800
+ }
1801
+ }));
1802
+ /**
1803
+ * Selectors
1804
+ */
1805
+ const selectOrganizationRaw = (state) => state.organization;
1806
+ const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
1807
+ /**
1808
+ * Reducers
1809
+ */
1810
+ const selectShouldFetchOrganization = createSelector(selectAppWantsToJoin, selectOrganizationRaw, selectDeviceCredentialsRaw, (wantsToJoin, organization, deviceCredentials) => {
1811
+ if (wantsToJoin &&
1812
+ !organization.data &&
1813
+ !organization.isFetching &&
1814
+ !organization.error &&
1815
+ !deviceCredentials.isFetching) {
1816
+ return true;
1817
+ }
1818
+ return false;
1819
+ });
1820
+ createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
1821
+ if (shouldFetchOrganization) {
1822
+ dispatch(doOrganizationFetch());
1823
+ }
1824
+ });
839
1825
 
840
- // Requests: messages from the client to the server
841
- const PROTOCOL_REQUESTS = {
842
- BLOCK_CLIENT: "block_client",
843
- CLAIM_ROOM: "claim_room",
844
- CLEAR_CHAT_HISTORY: "clear_chat_history",
845
- ENABLE_AUDIO: "enable_audio",
846
- ENABLE_VIDEO: "enable_video",
847
- END_STREAM: "end_stream",
848
- FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
849
- HANDLE_KNOCK: "handle_knock",
850
- IDENTIFY_DEVICE: "identify_device",
851
- INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
852
- JOIN_ROOM: "join_room",
853
- KICK_CLIENT: "kick_client",
854
- KNOCK_ROOM: "knock_room",
855
- LEAVE_ROOM: "leave_room",
856
- SEND_CLIENT_METADATA: "send_client_metadata",
857
- SET_LOCK: "set_lock",
858
- SHARE_MEDIA: "share_media",
859
- START_NEW_STREAM: "start_new_stream",
860
- START_SCREENSHARE: "start_screenshare",
861
- STOP_SCREENSHARE: "stop_screenshare",
862
- START_URL_EMBED: "start_url_embed",
863
- STOP_URL_EMBED: "stop_url_embed",
864
- START_RECORDING: "start_recording",
865
- STOP_RECORDING: "stop_recording",
866
- SFU_TOKEN: "sfu_token",
1826
+ function createRtcEventAction(name) {
1827
+ return createAction(`rtcConnection/event/${name}`);
1828
+ }
1829
+ const rtcEvents = {
1830
+ rtcManagerCreated: createRtcEventAction("rtcManagerCreated"),
1831
+ rtcManagerDestroyed: createRtcEventAction("rtcManagerDestroyed"),
1832
+ streamAdded: createRtcEventAction("streamAdded"),
1833
+ };
1834
+
1835
+ const NON_PERSON_ROLES = ["recorder", "streamer"];
1836
+ /**
1837
+ * State mapping utils
1838
+ */
1839
+ function createParticipant(client, newJoiner = false) {
1840
+ const { streams } = client, rest = __rest(client, ["streams"]);
1841
+ return Object.assign(Object.assign({}, rest), { stream: null, streams: streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" })), isLocalParticipant: false, presentationStream: null, newJoiner });
1842
+ }
1843
+ function findParticipant(state, participantId) {
1844
+ const index = state.remoteParticipants.findIndex((c) => c.id === participantId);
1845
+ return { index, participant: state.remoteParticipants[index] };
1846
+ }
1847
+ function updateParticipant(state, participantId, updates) {
1848
+ const { participant, index } = findParticipant(state, participantId);
1849
+ if (!participant) {
1850
+ console.error(`Did not find client for update ${participantId}`);
1851
+ return state;
1852
+ }
1853
+ return Object.assign(Object.assign({}, state), { remoteParticipants: [
1854
+ ...state.remoteParticipants.slice(0, index),
1855
+ Object.assign(Object.assign({}, participant), updates),
1856
+ ...state.remoteParticipants.slice(index + 1),
1857
+ ] });
1858
+ }
1859
+ function addParticipant(state, participant) {
1860
+ const { participant: foundParticipant } = findParticipant(state, participant.id);
1861
+ if (foundParticipant) {
1862
+ console.warn(`Client already existing ${participant.id}. Ignoring`);
1863
+ return state;
1864
+ }
1865
+ return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants, participant] });
1866
+ }
1867
+ function updateStreamState(state, participantId, streamId, state_) {
1868
+ const { participant } = findParticipant(state, participantId);
1869
+ if (!participant) {
1870
+ console.error(`No client ${participantId} found to update stream ${streamId} ${state_}`);
1871
+ return state;
1872
+ }
1873
+ const idIdx = participant.streams.findIndex((s) => s.id === streamId);
1874
+ const streams = [...participant.streams];
1875
+ streams[idIdx] = Object.assign(Object.assign({}, streams[idIdx]), { state: state_ });
1876
+ return updateParticipant(state, participantId, { streams });
1877
+ }
1878
+ function removeClient(state, participantId) {
1879
+ return Object.assign(Object.assign({}, state), { remoteParticipants: state.remoteParticipants.filter((c) => c.id !== participantId) });
1880
+ }
1881
+ function addStreamId(state, participantId, streamId) {
1882
+ const { participant } = findParticipant(state, participantId);
1883
+ if (!participant || participant.streams.find((s) => s.id === streamId)) {
1884
+ console.warn(`No participant ${participantId} or stream ${streamId} already exists`);
1885
+ return state;
1886
+ }
1887
+ return updateParticipant(state, participantId, {
1888
+ streams: [...participant.streams, { id: streamId, state: "to_accept" }],
1889
+ });
1890
+ }
1891
+ function removeStreamId(state, participantId, streamId) {
1892
+ var _a, _b;
1893
+ const { participant } = findParticipant(state, participantId);
1894
+ if (!participant) {
1895
+ console.error(`No participant ${participantId} found to remove stream ${streamId}`);
1896
+ return state;
1897
+ }
1898
+ const currentStreamId = participant.stream && participant.stream.id;
1899
+ const presentationId = ((_a = participant.presentationStream) === null || _a === void 0 ? void 0 : _a.inboundId) || ((_b = participant.presentationStream) === null || _b === void 0 ? void 0 : _b.id);
1900
+ const idIdx = participant.streams.findIndex((s) => s.id === streamId);
1901
+ return updateParticipant(state, participantId, Object.assign(Object.assign({ streams: participant.streams.filter((_, i) => i !== idIdx) }, (currentStreamId === streamId && { stream: null })), (presentationId === streamId && { presentationStream: null })));
1902
+ }
1903
+ function addStream(state, payload) {
1904
+ const { clientId, stream, streamType } = payload;
1905
+ let { streamId } = payload;
1906
+ const { participant } = findParticipant(state, clientId);
1907
+ if (!participant) {
1908
+ console.error(`Did not find client ${clientId} for adding stream`);
1909
+ return state;
1910
+ }
1911
+ const remoteParticipants = state.remoteParticipants;
1912
+ if (!streamId) {
1913
+ streamId = stream.id;
1914
+ }
1915
+ const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
1916
+ if (!remoteParticipant) {
1917
+ return state;
1918
+ }
1919
+ const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId);
1920
+ if ((remoteParticipant.stream &&
1921
+ (remoteParticipant.stream.id === streamId || remoteParticipant.stream.inboundId === streamId)) ||
1922
+ (!remoteParticipant.stream && streamType === "webcam") ||
1923
+ (!remoteParticipant.stream && !streamType && !remoteParticipantStream)) {
1924
+ return updateParticipant(state, clientId, { stream });
1925
+ }
1926
+ // screen share
1927
+ return updateParticipant(state, clientId, {
1928
+ presentationStream: stream,
1929
+ });
1930
+ }
1931
+ const initialState$7 = {
1932
+ remoteParticipants: [],
1933
+ };
1934
+ const remoteParticipantsSlice = createSlice({
1935
+ name: "remoteParticipants",
1936
+ initialState: initialState$7,
1937
+ reducers: {
1938
+ streamStatusUpdated: (state, action) => {
1939
+ let newState = state;
1940
+ for (const { clientId, streamId, state } of action.payload) {
1941
+ newState = updateStreamState(newState, clientId, streamId, state);
1942
+ }
1943
+ return newState;
1944
+ },
1945
+ participantStreamAdded: (state, action) => {
1946
+ const { clientId, stream } = action.payload;
1947
+ return updateParticipant(state, clientId, {
1948
+ stream,
1949
+ });
1950
+ },
1951
+ participantStreamIdAdded: (state, action) => {
1952
+ const { clientId, streamId } = action.payload;
1953
+ return addStreamId(state, clientId, streamId);
1954
+ },
1955
+ },
1956
+ extraReducers: (builder) => {
1957
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1958
+ var _a;
1959
+ if (!((_a = action.payload) === null || _a === void 0 ? void 0 : _a.room))
1960
+ return state;
1961
+ const selfId = action.payload.selfId;
1962
+ const { clients } = action.payload.room;
1963
+ return Object.assign(Object.assign({}, state), { remoteParticipants: clients
1964
+ .filter((c) => c.id !== selfId)
1965
+ .filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
1966
+ .map((c) => createParticipant(c)) });
1967
+ });
1968
+ builder.addCase(rtcEvents.streamAdded, (state, action) => {
1969
+ return addStream(state, action.payload);
1970
+ });
1971
+ builder.addCase(signalEvents.newClient, (state, action) => {
1972
+ const { client } = action.payload;
1973
+ if (NON_PERSON_ROLES.includes(client.role.roleName)) {
1974
+ return state;
1975
+ }
1976
+ return addParticipant(state, createParticipant(client, true));
1977
+ });
1978
+ builder.addCase(signalEvents.clientLeft, (state, action) => {
1979
+ const { clientId } = action.payload;
1980
+ return removeClient(state, clientId);
1981
+ });
1982
+ builder.addCase(signalEvents.audioEnabled, (state, action) => {
1983
+ const { clientId, isAudioEnabled } = action.payload;
1984
+ return updateParticipant(state, clientId, {
1985
+ isAudioEnabled,
1986
+ });
1987
+ });
1988
+ builder.addCase(signalEvents.videoEnabled, (state, action) => {
1989
+ const { clientId, isVideoEnabled } = action.payload;
1990
+ return updateParticipant(state, clientId, {
1991
+ isVideoEnabled,
1992
+ });
1993
+ });
1994
+ builder.addCase(signalEvents.clientMetadataReceived, (state, action) => {
1995
+ const { clientId, displayName } = action.payload.payload;
1996
+ return updateParticipant(state, clientId, {
1997
+ displayName,
1998
+ });
1999
+ });
2000
+ builder.addCase(signalEvents.screenshareStarted, (state, action) => {
2001
+ const { clientId, streamId } = action.payload;
2002
+ return addStreamId(state, clientId, streamId);
2003
+ });
2004
+ builder.addCase(signalEvents.screenshareStopped, (state, action) => {
2005
+ const { clientId, streamId } = action.payload;
2006
+ return removeStreamId(state, clientId, streamId);
2007
+ });
2008
+ },
2009
+ });
2010
+ /**
2011
+ * Action creators
2012
+ */
2013
+ const { participantStreamAdded, participantStreamIdAdded, streamStatusUpdated } = remoteParticipantsSlice.actions;
2014
+ const selectRemoteParticipants = (state) => state.remoteParticipants.remoteParticipants;
2015
+ const selectScreenshares = createSelector(selectLocalScreenshareStream, selectRemoteParticipants, (localScreenshareStream, remoteParticipants) => {
2016
+ const screenshares = [];
2017
+ if (localScreenshareStream) {
2018
+ screenshares.push({
2019
+ id: localScreenshareStream.id,
2020
+ participantId: "local",
2021
+ hasAudioTrack: localScreenshareStream.getAudioTracks().length > 0,
2022
+ stream: localScreenshareStream,
2023
+ isLocal: true,
2024
+ });
2025
+ }
2026
+ for (const participant of remoteParticipants) {
2027
+ if (participant.presentationStream) {
2028
+ screenshares.push({
2029
+ id: participant.presentationStream.id,
2030
+ participantId: participant.id,
2031
+ hasAudioTrack: participant.presentationStream.getAudioTracks().length > 0,
2032
+ stream: participant.presentationStream,
2033
+ isLocal: false,
2034
+ });
2035
+ }
2036
+ }
2037
+ return screenshares;
2038
+ });
2039
+
2040
+ const initialState$6 = {
2041
+ status: "initializing",
2042
+ error: null,
2043
+ };
2044
+ const roomConnectionSlice = createSlice({
2045
+ initialState: initialState$6,
2046
+ name: "roomConnection",
2047
+ reducers: {
2048
+ connectionStatusChanged: (state, action) => {
2049
+ return Object.assign(Object.assign({}, state), { status: action.payload });
2050
+ },
2051
+ },
2052
+ extraReducers: (builder) => {
2053
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
2054
+ //TODO: Handle error
2055
+ const { error, isLocked } = action.payload;
2056
+ if (error === "room_locked" && isLocked) {
2057
+ return Object.assign(Object.assign({}, state), { status: "room_locked" });
2058
+ }
2059
+ return Object.assign(Object.assign({}, state), { status: "connected" });
2060
+ });
2061
+ builder.addCase(socketReconnecting, (state) => {
2062
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
2063
+ });
2064
+ },
2065
+ });
2066
+ /**
2067
+ * Action creators
2068
+ */
2069
+ const { connectionStatusChanged } = roomConnectionSlice.actions;
2070
+ const doKnockRoom = createAppThunk(() => (dispatch, getState) => {
2071
+ const state = getState();
2072
+ const socket = selectSignalConnectionRaw(state).socket;
2073
+ const roomName = selectAppRoomName(state);
2074
+ const roomKey = selectAppRoomKey(state);
2075
+ const displayName = selectAppDisplayName(state);
2076
+ const sdkVersion = selectAppSdkVersion(state);
2077
+ const externalId = selectAppExternalId(state);
2078
+ const organizationId = selectOrganizationId(state);
2079
+ socket === null || socket === void 0 ? void 0 : socket.emit("knock_room", {
2080
+ avatarUrl: null,
2081
+ config: {
2082
+ isAudioEnabled: true,
2083
+ isVideoEnabled: true,
2084
+ },
2085
+ deviceCapabilities: { canScreenshare: true },
2086
+ displayName,
2087
+ isCoLocated: false,
2088
+ isDevicePermissionDenied: false,
2089
+ kickFromOtherRooms: false,
2090
+ organizationId,
2091
+ roomKey,
2092
+ roomName,
2093
+ selfId: "",
2094
+ userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
2095
+ externalId,
2096
+ });
2097
+ dispatch(connectionStatusChanged("knocking"));
2098
+ });
2099
+ const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
2100
+ const state = getState();
2101
+ const socket = selectSignalConnectionRaw(state).socket;
2102
+ const roomName = selectAppRoomName(state);
2103
+ const roomKey = selectAppRoomKey(state);
2104
+ const displayName = selectAppDisplayName(state);
2105
+ const sdkVersion = selectAppSdkVersion(state);
2106
+ const externalId = selectAppExternalId(state);
2107
+ const organizationId = selectOrganizationId(state);
2108
+ const isCameraEnabled = selectIsCameraEnabled(getState());
2109
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
2110
+ const selfId = selectSelfId(getState());
2111
+ socket === null || socket === void 0 ? void 0 : socket.emit("join_room", {
2112
+ avatarUrl: null,
2113
+ config: {
2114
+ isAudioEnabled: isMicrophoneEnabled,
2115
+ isVideoEnabled: isCameraEnabled,
2116
+ },
2117
+ deviceCapabilities: { canScreenshare: true },
2118
+ displayName,
2119
+ isCoLocated: false,
2120
+ isDevicePermissionDenied: false,
2121
+ kickFromOtherRooms: false,
2122
+ organizationId,
2123
+ roomKey,
2124
+ roomName,
2125
+ selfId,
2126
+ userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
2127
+ externalId,
2128
+ });
2129
+ dispatch(connectionStatusChanged("connecting"));
2130
+ });
2131
+ const selectRoomConnectionStatus = (state) => state.roomConnection.status;
2132
+ /**
2133
+ * Reactors
2134
+ */
2135
+ const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
2136
+ if (localMediaStatus === "started" &&
2137
+ signalConnectionDeviceIdentified &&
2138
+ !!hasOrganizationIdFetched &&
2139
+ ["initializing", "reconnect"].includes(roomConnectionStatus)) {
2140
+ return true;
2141
+ }
2142
+ return false;
2143
+ });
2144
+ createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
2145
+ if (shouldConnectRoom) {
2146
+ dispatch(doConnectRoom());
2147
+ }
2148
+ });
2149
+ startAppListening({
2150
+ actionCreator: signalEvents.knockHandled,
2151
+ effect: ({ payload }, { dispatch, getState }) => {
2152
+ const { clientId, resolution } = payload;
2153
+ const state = getState();
2154
+ const selfId = selectSelfId(state);
2155
+ if (clientId !== selfId) {
2156
+ return;
2157
+ }
2158
+ if (resolution === "accepted") {
2159
+ dispatch(setRoomKey(payload.metadata.roomKey));
2160
+ dispatch(doConnectRoom());
2161
+ }
2162
+ else if (resolution === "rejected") {
2163
+ dispatch(connectionStatusChanged("knock_rejected"));
2164
+ }
2165
+ },
2166
+ });
2167
+
2168
+ const EVENTS = {
2169
+ CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
2170
+ STREAM_ADDED: "stream_added",
2171
+ RTC_MANAGER_CREATED: "rtc_manager_created",
2172
+ RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
2173
+ LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
2174
+ LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
2175
+ REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
2176
+ REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
2177
+ };
2178
+
2179
+ const TYPES = {
2180
+ CONNECTING: "connecting",
2181
+ CONNECTION_FAILED: "connection_failed",
2182
+ CONNECTION_SUCCESSFUL: "connection_successful",
2183
+ CONNECTION_DISCONNECTED: "connection_disconnected",
2184
+ };
2185
+
2186
+ // Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
2187
+
2188
+ // Requests: messages from the client to the server
2189
+ const PROTOCOL_REQUESTS = {
2190
+ BLOCK_CLIENT: "block_client",
2191
+ CLAIM_ROOM: "claim_room",
2192
+ CLEAR_CHAT_HISTORY: "clear_chat_history",
2193
+ ENABLE_AUDIO: "enable_audio",
2194
+ ENABLE_VIDEO: "enable_video",
2195
+ END_STREAM: "end_stream",
2196
+ FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
2197
+ HANDLE_KNOCK: "handle_knock",
2198
+ IDENTIFY_DEVICE: "identify_device",
2199
+ INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
2200
+ JOIN_ROOM: "join_room",
2201
+ KICK_CLIENT: "kick_client",
2202
+ KNOCK_ROOM: "knock_room",
2203
+ LEAVE_ROOM: "leave_room",
2204
+ SEND_CLIENT_METADATA: "send_client_metadata",
2205
+ SET_LOCK: "set_lock",
2206
+ SHARE_MEDIA: "share_media",
2207
+ START_NEW_STREAM: "start_new_stream",
2208
+ START_SCREENSHARE: "start_screenshare",
2209
+ STOP_SCREENSHARE: "stop_screenshare",
2210
+ START_URL_EMBED: "start_url_embed",
2211
+ STOP_URL_EMBED: "stop_url_embed",
2212
+ START_RECORDING: "start_recording",
2213
+ STOP_RECORDING: "stop_recording",
2214
+ SFU_TOKEN: "sfu_token",
867
2215
  };
868
2216
 
869
2217
  // Responses: messages from the server to the client, in response to requests
@@ -1000,187 +2348,61 @@ class RtcStream {
1000
2348
  }
1001
2349
  }
1002
2350
 
1003
- const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
1004
-
1005
2351
  /**
1006
- * Wrapper class that extends the Socket.IO client library.
2352
+ * Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
2353
+ * frequently. A browser restart fixes this.
2354
+ *
2355
+ * Should be called after the connection has been up for a while.
2356
+ *
2357
+ * @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
1007
2358
  */
1008
- class ServerSocket {
1009
- constructor(hostName, optionsOverrides) {
1010
- this._socket = io(hostName, {
1011
- path: DEFAULT_SOCKET_PATH,
1012
- randomizationFactor: 0.5,
1013
- reconnectionDelay: 250,
1014
- reconnectionDelayMax: 5000,
1015
- timeout: 5000,
1016
- transports: ["websocket"],
1017
- withCredentials: true,
1018
- ...optionsOverrides,
1019
- });
1020
- this._socket.io.on("reconnect", () => {
1021
- this._socket.sendBuffer = [];
1022
- });
1023
- this._socket.io.on("reconnect_attempt", () => {
1024
- if (this._wasConnectedUsingWebsocket) {
1025
- this._socket.io.opts.transports = ["websocket"];
1026
- // only fallback to polling if not safari
1027
- // safari doesn't support cross doamin cookies making load-balancer stickiness not work
1028
- // and if socket.io reconnects to another signal instance with polling it will fail
1029
- // remove if we move signal to a whereby.com subdomain
1030
- if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
1031
- } else {
1032
- this._socket.io.opts.transports = ["websocket", "polling"];
1033
- }
1034
- });
1035
- this._socket.on("connect", () => {
1036
- const transport = this.getTransport();
1037
- if (transport === "websocket") {
1038
- this._wasConnectedUsingWebsocket = true;
1039
- }
1040
- });
1041
- }
1042
-
1043
- connect() {
1044
- if (this.isConnected() || this.isConnecting()) {
1045
- return;
1046
- }
1047
- this._socket.open();
1048
- }
1049
-
1050
- disconnect() {
1051
- this._socket.disconnect();
2359
+ function detectMicrophoneNotWorking(pc) {
2360
+ if (
2361
+ adapter.browserDetails.browser !== "chrome" ||
2362
+ adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
2363
+ pc.signalingState === "closed"
2364
+ ) {
2365
+ return Promise.resolve(false);
1052
2366
  }
1053
-
1054
- disconnectOnConnect() {
1055
- this._socket.once("connect", () => {
1056
- this._socket.disconnect();
2367
+ const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
2368
+ const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
2369
+ return pc.getStats(null).then((result) => {
2370
+ let microphoneFailed = false;
2371
+ result.forEach((report) => {
2372
+ if (
2373
+ report.type === "outbound-rtp" &&
2374
+ (report.kind === "audio" || report.mediaType === "audio") &&
2375
+ sendingAudio
2376
+ ) {
2377
+ if (report.bytesSent === 0) {
2378
+ microphoneFailed = "outbound";
2379
+ }
2380
+ } else if (
2381
+ report.type === "inbound-rtp" &&
2382
+ (report.kind === "audio" || report.mediaType === "audio") &&
2383
+ receivingAudio
2384
+ ) {
2385
+ if (report.bytesReceived === 0) {
2386
+ microphoneFailed = "inbound";
2387
+ }
2388
+ }
1057
2389
  });
1058
- }
1059
-
1060
- emit() {
1061
- this._socket.emit.apply(this._socket, arguments);
1062
- }
1063
-
1064
- emitIfConnected(eventName, data) {
1065
- if (!this.isConnected()) {
1066
- return;
1067
- }
1068
- this.emit(eventName, data);
1069
- }
2390
+ return microphoneFailed;
2391
+ });
2392
+ }
1070
2393
 
1071
- getTransport() {
1072
- return (
1073
- this._socket &&
1074
- this._socket.io &&
1075
- this._socket.io.engine &&
1076
- this._socket.io.engine.transport &&
1077
- this._socket.io.engine.transport.name
1078
- );
1079
- }
2394
+ var rtcManagerEvents = {
2395
+ CAMERA_NOT_WORKING: "camera_not_working",
2396
+ CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
2397
+ MICROPHONE_NOT_WORKING: "microphone_not_working",
2398
+ MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
2399
+ SFU_CONNECTION_CLOSED: "sfu_connection_closed",
2400
+ COLOCATION_SPEAKER: "colocation_speaker",
2401
+ DOMINANT_SPEAKER: "dominant_speaker",
2402
+ };
1080
2403
 
1081
- getManager() {
1082
- return this._socket.io;
1083
- }
1084
-
1085
- isConnecting() {
1086
- return this._socket && this._socket.connecting;
1087
- }
1088
-
1089
- isConnected() {
1090
- return this._socket && this._socket.connected;
1091
- }
1092
-
1093
- /**
1094
- * Register a new event handler.
1095
- *
1096
- * @param {string} eventName - Name of the event to listen for.
1097
- * @param {function} handler - The callback function that should be called for the event.
1098
- * @returns {function} Function to deregister the listener.
1099
- */
1100
- on(eventName, handler) {
1101
- this._socket.on(eventName, handler);
1102
-
1103
- return () => {
1104
- this._socket.off(eventName, handler);
1105
- };
1106
- }
1107
-
1108
- /**
1109
- * Register a new event handler to be triggered only once.
1110
- *
1111
- * @param {string} eventName - Name of the event to listen for.
1112
- * @param {function} handler - The function that should be called for the event.
1113
- */
1114
- once(eventName, handler) {
1115
- this._socket.once(eventName, handler);
1116
- }
1117
-
1118
- /**
1119
- * Deregister an event handler.
1120
- *
1121
- * @param {string} eventName - Name of the event the handler is registered for.
1122
- * @param {function} handler - The callback that will be deregistered.
1123
- */
1124
- off(eventName, handler) {
1125
- this._socket.off(eventName, handler);
1126
- }
1127
- }
1128
-
1129
- /**
1130
- * Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
1131
- * frequently. A browser restart fixes this.
1132
- *
1133
- * Should be called after the connection has been up for a while.
1134
- *
1135
- * @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
1136
- */
1137
- function detectMicrophoneNotWorking(pc) {
1138
- if (
1139
- adapter.browserDetails.browser !== "chrome" ||
1140
- adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
1141
- pc.signalingState === "closed"
1142
- ) {
1143
- return Promise.resolve(false);
1144
- }
1145
- const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
1146
- const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
1147
- return pc.getStats(null).then((result) => {
1148
- let microphoneFailed = false;
1149
- result.forEach((report) => {
1150
- if (
1151
- report.type === "outbound-rtp" &&
1152
- (report.kind === "audio" || report.mediaType === "audio") &&
1153
- sendingAudio
1154
- ) {
1155
- if (report.bytesSent === 0) {
1156
- microphoneFailed = "outbound";
1157
- }
1158
- } else if (
1159
- report.type === "inbound-rtp" &&
1160
- (report.kind === "audio" || report.mediaType === "audio") &&
1161
- receivingAudio
1162
- ) {
1163
- if (report.bytesReceived === 0) {
1164
- microphoneFailed = "inbound";
1165
- }
1166
- }
1167
- });
1168
- return microphoneFailed;
1169
- });
1170
- }
1171
-
1172
- var rtcManagerEvents = {
1173
- CAMERA_NOT_WORKING: "camera_not_working",
1174
- CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
1175
- MICROPHONE_NOT_WORKING: "microphone_not_working",
1176
- MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
1177
- SFU_CONNECTION_CLOSED: "sfu_connection_closed",
1178
- COLOCATION_SPEAKER: "colocation_speaker",
1179
- DOMINANT_SPEAKER: "dominant_speaker",
1180
- };
1181
-
1182
- const browserName$3 = adapter.browserDetails.browser;
1183
- const browserVersion$1 = adapter.browserDetails.version;
2404
+ const browserName$3 = adapter.browserDetails.browser;
2405
+ const browserVersion$1 = adapter.browserDetails.version;
1184
2406
 
1185
2407
  function setCodecPreferenceSDP(sdp, vp9On, redOn) {
1186
2408
  try {
@@ -5803,6 +7025,580 @@ class RtcManagerDispatcher {
5803
7025
  }
5804
7026
  }
5805
7027
 
7028
+ const createWebRtcEmitter = (dispatch) => {
7029
+ return {
7030
+ emit: (eventName, data) => {
7031
+ if (eventName === "rtc_manager_created") {
7032
+ dispatch(doRtcManagerCreated(data));
7033
+ }
7034
+ else if (eventName === "stream_added") {
7035
+ dispatch(rtcEvents.streamAdded(data));
7036
+ }
7037
+ else if (eventName === "rtc_manager_destroyed") {
7038
+ dispatch(rtcManagerDestroyed());
7039
+ }
7040
+ else ;
7041
+ },
7042
+ };
7043
+ };
7044
+ const initialState$5 = {
7045
+ dispatcherCreated: false,
7046
+ error: null,
7047
+ isCreatingDispatcher: false,
7048
+ reportedStreamResolutions: {},
7049
+ rtcManager: null,
7050
+ rtcManagerDispatcher: null,
7051
+ rtcManagerInitialized: false,
7052
+ status: "",
7053
+ isAcceptingStreams: false,
7054
+ };
7055
+ const rtcConnectionSlice = createSlice({
7056
+ name: "rtcConnection",
7057
+ initialState: initialState$5,
7058
+ reducers: {
7059
+ isAcceptingStreams: (state, action) => {
7060
+ return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
7061
+ },
7062
+ resolutionReported: (state, action) => {
7063
+ const { streamId, width, height } = action.payload;
7064
+ return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
7065
+ },
7066
+ rtcDisconnected: () => {
7067
+ return Object.assign({}, initialState$5);
7068
+ },
7069
+ rtcDispatcherCreated: (state, action) => {
7070
+ return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
7071
+ },
7072
+ rtcManagerCreated: (state, action) => {
7073
+ return Object.assign(Object.assign({}, state), { rtcManager: action.payload, status: "ready" });
7074
+ },
7075
+ rtcManagerDestroyed: (state) => {
7076
+ return Object.assign(Object.assign({}, state), { rtcManager: null });
7077
+ },
7078
+ rtcManagerInitialized: (state) => {
7079
+ return Object.assign(Object.assign({}, state), { rtcManagerInitialized: true });
7080
+ },
7081
+ },
7082
+ extraReducers: (builder) => {
7083
+ builder.addCase(socketReconnecting, (state) => {
7084
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
7085
+ });
7086
+ builder.addCase(signalEvents.roomJoined, (state) => {
7087
+ return Object.assign(Object.assign({}, state), { status: state.status === "reconnect" ? "ready" : state.status });
7088
+ });
7089
+ },
7090
+ });
7091
+ /**
7092
+ * Action creators
7093
+ */
7094
+ const { resolutionReported, rtcDispatcherCreated, rtcDisconnected, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, isAcceptingStreams, } = rtcConnectionSlice.actions;
7095
+ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
7096
+ const state = getState();
7097
+ const socket = selectSignalConnectionRaw(state).socket;
7098
+ const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
7099
+ const isCameraEnabled = selectIsCameraEnabled(state);
7100
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
7101
+ if (dispatcher) {
7102
+ return;
7103
+ }
7104
+ const webrtcProvider = {
7105
+ getMediaConstraints: () => ({
7106
+ audio: isMicrophoneEnabled,
7107
+ video: isCameraEnabled,
7108
+ }),
7109
+ deferrable(clientId) {
7110
+ return !clientId;
7111
+ },
7112
+ };
7113
+ const rtcManagerDispatcher = new RtcManagerDispatcher({
7114
+ emitter: createWebRtcEmitter(dispatch),
7115
+ serverSocket: socket,
7116
+ logger: console,
7117
+ webrtcProvider,
7118
+ features: {
7119
+ lowDataModeEnabled: false,
7120
+ sfuServerOverrideHost: undefined,
7121
+ turnServerOverrideHost: undefined,
7122
+ useOnlyTURN: undefined,
7123
+ vp9On: false,
7124
+ h264On: false,
7125
+ simulcastScreenshareOn: false,
7126
+ },
7127
+ });
7128
+ dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
7129
+ });
7130
+ const doDisconnectRtc = createAppThunk(() => (dispatch, getState) => {
7131
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7132
+ if (rtcManager) {
7133
+ rtcManager.disconnectAll();
7134
+ }
7135
+ dispatch(rtcDisconnected());
7136
+ });
7137
+ const doHandleAcceptStreams = createAppThunk((payload) => (dispatch, getState) => {
7138
+ var _a;
7139
+ dispatch(isAcceptingStreams(true));
7140
+ const state = getState();
7141
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7142
+ const remoteParticipants = selectRemoteParticipants(state);
7143
+ if (!rtcManager) {
7144
+ throw new Error("No rtc manager");
7145
+ }
7146
+ const activeBreakout = false;
7147
+ const shouldAcceptNewClients = (_a = rtcManager.shouldAcceptStreamsFromBothSides) === null || _a === void 0 ? void 0 : _a.call(rtcManager);
7148
+ const updates = [];
7149
+ for (const { clientId, streamId, state } of payload) {
7150
+ const participant = remoteParticipants.find((p) => p.id === clientId);
7151
+ if (!participant)
7152
+ continue;
7153
+ if (state === "to_accept" ||
7154
+ (state === "new_accept" && shouldAcceptNewClients) ||
7155
+ (state === "old_accept" && !shouldAcceptNewClients) // these are done to enable broadcast in legacy/p2p
7156
+ ) {
7157
+ rtcManager.acceptNewStream({
7158
+ streamId: streamId === "0" ? clientId : streamId,
7159
+ clientId,
7160
+ shouldAddLocalVideo: streamId === "0",
7161
+ activeBreakout,
7162
+ });
7163
+ }
7164
+ else if (state === "new_accept" || state === "old_accept") ;
7165
+ else if (state === "to_unaccept") {
7166
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.disconnect(streamId === "0" ? clientId : streamId, activeBreakout);
7167
+ }
7168
+ else if (state !== "done_accept") {
7169
+ continue;
7170
+ // console.warn(`Stream state not handled: ${state} for ${clientId}-${streamId}`);
7171
+ }
7172
+ else ;
7173
+ updates.push({ clientId, streamId, state: state.replace(/to_|new_|old_/, "done_") });
7174
+ }
7175
+ dispatch(streamStatusUpdated(updates));
7176
+ dispatch(isAcceptingStreams(false));
7177
+ });
7178
+ const doRtcReportStreamResolution = createAppThunk(({ streamId, width, height }) => (dispatch, getState) => {
7179
+ const { reportedStreamResolutions, rtcManager } = selectRtcConnectionRaw(getState());
7180
+ const localStream = selectLocalMediaStream(getState());
7181
+ if (!rtcManager || (localStream === null || localStream === void 0 ? void 0 : localStream.id) === streamId) {
7182
+ return;
7183
+ }
7184
+ const old = reportedStreamResolutions[streamId];
7185
+ if (!old || old.width !== width || old.height !== height) {
7186
+ rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
7187
+ }
7188
+ dispatch(resolutionReported({ streamId, width, height }));
7189
+ });
7190
+ const doRtcManagerCreated = createAppThunk((payload) => (dispatch) => {
7191
+ const { rtcManager } = payload;
7192
+ dispatch(rtcManagerCreated(rtcManager));
7193
+ });
7194
+ const doRtcManagerInitialize = createAppThunk(() => (dispatch, getState) => {
7195
+ const localMediaStream = selectLocalMediaStream(getState());
7196
+ const rtcManager = selectRtcConnectionRaw(getState()).rtcManager;
7197
+ const isCameraEnabled = selectIsCameraEnabled(getState());
7198
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
7199
+ if (localMediaStream && rtcManager) {
7200
+ rtcManager.addNewStream("0", localMediaStream, !isMicrophoneEnabled, !isCameraEnabled);
7201
+ }
7202
+ dispatch(rtcManagerInitialized());
7203
+ });
7204
+ /**
7205
+ * Selectors
7206
+ */
7207
+ const selectRtcConnectionRaw = (state) => state.rtcConnection;
7208
+ const selectRtcManagerInitialized = (state) => state.rtcConnection.rtcManagerInitialized;
7209
+ const selectRtcManager = (state) => state.rtcConnection.rtcManager;
7210
+ const selectRtcDispatcherCreated = (state) => state.rtcConnection.dispatcherCreated;
7211
+ const selectRtcIsCreatingDispatcher = (state) => state.rtcConnection.isCreatingDispatcher;
7212
+ const selectRtcStatus = (state) => state.rtcConnection.status;
7213
+ const selectIsAcceptingStreams = (state) => state.rtcConnection.isAcceptingStreams;
7214
+ /**
7215
+ * Reactors
7216
+ */
7217
+ startAppListening({
7218
+ actionCreator: doSetDevice.fulfilled,
7219
+ effect: ({ payload }, { getState }) => {
7220
+ const { replacedTracks } = payload;
7221
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7222
+ const stream = selectLocalMediaStream(getState());
7223
+ const replace = (kind, oldTrack) => {
7224
+ const track = stream === null || stream === void 0 ? void 0 : stream.getTracks().find((t) => t.kind === kind);
7225
+ return track && (rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.replaceTrack(oldTrack, track));
7226
+ };
7227
+ replacedTracks === null || replacedTracks === void 0 ? void 0 : replacedTracks.forEach((t) => {
7228
+ replace(t.kind, t);
7229
+ });
7230
+ },
7231
+ });
7232
+ startAppListening({
7233
+ actionCreator: doStartScreenshare.fulfilled,
7234
+ effect: ({ payload }, { getState }) => {
7235
+ const { stream } = payload;
7236
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7237
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.addNewStream(stream.id, stream, false, true);
7238
+ },
7239
+ });
7240
+ startAppListening({
7241
+ actionCreator: stopScreenshare,
7242
+ effect: ({ payload }, { getState }) => {
7243
+ const { stream } = payload;
7244
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7245
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.removeStream(stream.id, stream, null);
7246
+ },
7247
+ });
7248
+ const selectShouldConnectRtc = createSelector(selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectSignalConnectionSocket, (dispatcherCreated, isCreatingDispatcher, signalSocket) => {
7249
+ if (!dispatcherCreated && !isCreatingDispatcher && signalSocket) {
7250
+ return true;
7251
+ }
7252
+ return false;
7253
+ });
7254
+ createReactor([selectShouldConnectRtc], ({ dispatch }, shouldConnectRtc) => {
7255
+ if (shouldConnectRtc) {
7256
+ dispatch(doConnectRtc());
7257
+ }
7258
+ });
7259
+ const selectShouldInitializeRtc = createSelector(selectRtcManager, selectRtcManagerInitialized, selectLocalMediaStatus, (rtcManager, rtcManagerInitialized, localMediaStatus) => {
7260
+ if (localMediaStatus === "started" && rtcManager && !rtcManagerInitialized) {
7261
+ return true;
7262
+ }
7263
+ return false;
7264
+ });
7265
+ createReactor([selectShouldInitializeRtc], ({ dispatch }, shouldInitializeRtc) => {
7266
+ if (shouldInitializeRtc) {
7267
+ dispatch(doRtcManagerInitialize());
7268
+ }
7269
+ });
7270
+ // Disonnect and clean up
7271
+ const selectShouldDisconnectRtc = createSelector(selectRtcStatus, selectAppWantsToJoin, (status, wantsToJoin) => {
7272
+ if (!wantsToJoin && !["", "disconnected"].includes(status)) {
7273
+ return true;
7274
+ }
7275
+ return false;
7276
+ });
7277
+ createReactor([selectShouldDisconnectRtc], ({ dispatch }, shouldDisconnectRtc) => {
7278
+ if (shouldDisconnectRtc) {
7279
+ dispatch(doDisconnectRtc());
7280
+ }
7281
+ });
7282
+ // react accept streams
7283
+ const selectStreamsToAccept = createSelector(selectRtcStatus, selectRemoteParticipants, (rtcStatus, remoteParticipants) => {
7284
+ if (rtcStatus !== "ready") {
7285
+ return [];
7286
+ }
7287
+ const upd = [];
7288
+ // This should actually use remoteClientViews for its handling
7289
+ for (const client of remoteParticipants) {
7290
+ const { streams, id: clientId, newJoiner } = client;
7291
+ for (let i = 0; i < streams.length; i++) {
7292
+ const streamId = streams[i].id;
7293
+ const state = streams[i].state;
7294
+ {
7295
+ // Already connected
7296
+ if (state === "done_accept")
7297
+ continue;
7298
+ upd.push({
7299
+ clientId,
7300
+ streamId,
7301
+ state: `${newJoiner && streamId === "0" ? "new" : "to"}_accept`,
7302
+ });
7303
+ }
7304
+ }
7305
+ }
7306
+ return upd;
7307
+ });
7308
+ createReactor([selectStreamsToAccept, selectIsAcceptingStreams], ({ dispatch }, streamsToAccept, isAcceptingStreams) => {
7309
+ if (0 < streamsToAccept.length && !isAcceptingStreams) {
7310
+ dispatch(doHandleAcceptStreams(streamsToAccept));
7311
+ }
7312
+ });
7313
+
7314
+ const rtcAnalyticsCustomEvents = {
7315
+ audioEnabled: {
7316
+ action: doEnableAudio.fulfilled,
7317
+ rtcEventName: "audioEnabled",
7318
+ getValue: (state) => selectIsMicrophoneEnabled(state),
7319
+ getOutput: (value) => ({ enabled: value }),
7320
+ },
7321
+ videoEnabled: {
7322
+ action: doEnableVideo.fulfilled,
7323
+ rtcEventName: "videoEnabled",
7324
+ getValue: (state) => selectIsCameraEnabled(state),
7325
+ getOutput: (value) => ({ enabled: value }),
7326
+ },
7327
+ localStream: {
7328
+ action: doSetDevice.fulfilled,
7329
+ rtcEventName: "localStream",
7330
+ getValue: (state) => {
7331
+ var _a;
7332
+ return (_a = selectLocalMediaStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
7333
+ },
7334
+ getOutput: (value) => ({ stream: value }),
7335
+ },
7336
+ localScreenshareStream: {
7337
+ action: doStartScreenshare.fulfilled,
7338
+ rtcEventName: "localScreenshareStream",
7339
+ getValue: (state) => {
7340
+ var _a;
7341
+ return (_a = selectLocalScreenshareStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
7342
+ },
7343
+ getOutput: (value) => ({ tracks: value }),
7344
+ },
7345
+ localScreenshareStreamStopped: {
7346
+ action: stopScreenshare,
7347
+ rtcEventName: "localScreenshareStream",
7348
+ getValue: () => () => null,
7349
+ getOutput: () => ({}),
7350
+ },
7351
+ displayName: {
7352
+ action: doSetDisplayName.fulfilled,
7353
+ rtcEventName: "displayName",
7354
+ getValue: (state) => selectAppDisplayName(state),
7355
+ getOutput: (value) => ({ displayName: value }),
7356
+ },
7357
+ clientId: {
7358
+ action: null,
7359
+ rtcEventName: "clientId",
7360
+ getValue: (state) => selectSelfId(state),
7361
+ getOutput: (value) => ({ clientId: value }),
7362
+ },
7363
+ deviceId: {
7364
+ action: null,
7365
+ rtcEventName: "deviceId",
7366
+ getValue: (state) => selectDeviceId(state),
7367
+ getOutput: (value) => ({ deviceId: value }),
7368
+ },
7369
+ externalId: {
7370
+ action: null,
7371
+ rtcEventName: "externalId",
7372
+ getValue: (state) => selectAppExternalId(state),
7373
+ getOutput: (value) => ({ externalId: value }),
7374
+ },
7375
+ organizationId: {
7376
+ action: null,
7377
+ rtcEventName: "organizationId",
7378
+ getValue: (state) => selectOrganizationId(state),
7379
+ getOutput: (value) => ({ organizationId: value }),
7380
+ },
7381
+ signalConnectionStatus: {
7382
+ action: null,
7383
+ rtcEventName: "signalConnectionStatus",
7384
+ getValue: (state) => selectSignalStatus(state),
7385
+ getOutput: (value) => ({ status: value }),
7386
+ },
7387
+ rtcConnectionStatus: {
7388
+ action: null,
7389
+ rtcEventName: "rtcConnectionStatus",
7390
+ getValue: (state) => selectRtcStatus(state),
7391
+ getOutput: (value) => ({ status: value }),
7392
+ },
7393
+ userRole: {
7394
+ action: null,
7395
+ rtcEventName: "userRole",
7396
+ getValue: (state) => selectLocalParticipantRole(state),
7397
+ getOutput: (value) => ({ userRole: value }),
7398
+ },
7399
+ };
7400
+ const rtcCustomEventActions = Object.values(rtcAnalyticsCustomEvents)
7401
+ .map(({ action }) => action)
7402
+ .filter((action) => action !== null);
7403
+ const makeComparable = (value) => {
7404
+ if (typeof value === "object")
7405
+ return JSON.stringify(value);
7406
+ return value;
7407
+ };
7408
+ const initialState$4 = {
7409
+ reportedValues: {},
7410
+ };
7411
+ const rtcAnalyticsSlice = createSlice({
7412
+ initialState: initialState$4,
7413
+ name: "rtcAnalytics",
7414
+ reducers: {
7415
+ updateReportedValues(state, action) {
7416
+ return Object.assign(Object.assign({}, state), { reportedValues: Object.assign(Object.assign({}, state.reportedValues), { [action.payload.rtcEventName]: action.payload.value }) });
7417
+ },
7418
+ },
7419
+ });
7420
+ const doRtcAnalyticsCustomEventsInitialize = createAppThunk(() => (dispatch, getState) => {
7421
+ const state = getState();
7422
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7423
+ if (!rtcManager)
7424
+ return;
7425
+ Object.values(rtcAnalyticsCustomEvents).forEach(({ rtcEventName, getValue, getOutput }) => {
7426
+ var _a;
7427
+ const value = getValue(state);
7428
+ const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
7429
+ const comparableValue = makeComparable(value);
7430
+ if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
7431
+ rtcManager.sendStatsCustomEvent(rtcEventName, output);
7432
+ dispatch(updateReportedValues({ rtcEventName, value }));
7433
+ }
7434
+ });
7435
+ });
7436
+ /**
7437
+ * Action creators
7438
+ */
7439
+ const { updateReportedValues } = rtcAnalyticsSlice.actions;
7440
+ startAppListening({
7441
+ matcher: isAnyOf(...rtcCustomEventActions),
7442
+ effect: ({ type }, { getState, dispatch }) => {
7443
+ var _a;
7444
+ const state = getState();
7445
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7446
+ if (!rtcManager)
7447
+ return;
7448
+ const rtcCustomEvent = Object.values(rtcAnalyticsCustomEvents).find(({ action }) => (action === null || action === void 0 ? void 0 : action.type) === type);
7449
+ if (!rtcCustomEvent)
7450
+ return;
7451
+ const { getValue, getOutput, rtcEventName } = rtcCustomEvent;
7452
+ const value = getValue(state);
7453
+ const comparableValue = makeComparable(value);
7454
+ const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
7455
+ if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
7456
+ rtcManager.sendStatsCustomEvent(rtcEventName, output);
7457
+ dispatch(updateReportedValues({ rtcEventName, value }));
7458
+ }
7459
+ },
7460
+ });
7461
+ /**
7462
+ * Reactors
7463
+ */
7464
+ createReactor([selectRtcManagerInitialized], ({ dispatch }, selectRtcManagerInitialized) => {
7465
+ if (selectRtcManagerInitialized) {
7466
+ dispatch(doRtcAnalyticsCustomEventsInitialize());
7467
+ }
7468
+ });
7469
+
7470
+ const initialState$3 = {
7471
+ isStreaming: false,
7472
+ error: null,
7473
+ startedAt: undefined,
7474
+ };
7475
+ const streamingSlice = createSlice({
7476
+ name: "streaming",
7477
+ initialState: initialState$3,
7478
+ reducers: {
7479
+ doHandleStreamingStarted: (state) => {
7480
+ return Object.assign(Object.assign({}, state), { isStreaming: true, error: null,
7481
+ // We don't have the streaming start time stored on the
7482
+ // server, so we use the current time instead. This gives
7483
+ // an invalid timestamp for "Client B" if "Client A" has
7484
+ // been streaming for a while before "Client B" joins.
7485
+ startedAt: new Date().getTime() });
7486
+ },
7487
+ doHandleStreamingStopped: (state) => {
7488
+ return Object.assign(Object.assign({}, state), { isStreaming: false });
7489
+ },
7490
+ },
7491
+ });
7492
+ /**
7493
+ * Action creators
7494
+ */
7495
+ streamingSlice.actions;
7496
+ /**
7497
+ * Selectors
7498
+ */
7499
+ const selectStreamingRaw = (state) => state.streaming;
7500
+
7501
+ const initialState$2 = {
7502
+ waitingParticipants: [],
7503
+ };
7504
+ const waitingParticipantsSlice = createSlice({
7505
+ name: "waitingParticipants",
7506
+ initialState: initialState$2,
7507
+ reducers: {},
7508
+ extraReducers: (builder) => {
7509
+ builder.addCase(signalEvents.roomJoined, (state, { payload }) => {
7510
+ var _a;
7511
+ if ((_a = payload.room) === null || _a === void 0 ? void 0 : _a.knockers.length) {
7512
+ return Object.assign(Object.assign({}, state), { waitingParticipants: payload.room.knockers.map((knocker) => ({
7513
+ id: knocker.clientId,
7514
+ displayName: knocker.displayName,
7515
+ })) });
7516
+ }
7517
+ else {
7518
+ return state;
7519
+ }
7520
+ });
7521
+ builder.addCase(signalEvents.roomKnocked, (state, action) => {
7522
+ const { clientId, displayName } = action.payload;
7523
+ return Object.assign(Object.assign({}, state), { waitingParticipants: [...state.waitingParticipants, { id: clientId, displayName }] });
7524
+ });
7525
+ builder.addCase(signalEvents.knockerLeft, (state, action) => {
7526
+ const { clientId } = action.payload;
7527
+ return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((p) => p.id !== clientId) });
7528
+ });
7529
+ },
7530
+ });
7531
+ /**
7532
+ * Action creators
7533
+ */
7534
+ const doAcceptWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
7535
+ const { participantId } = payload;
7536
+ const state = getState();
7537
+ const socket = selectSignalConnectionSocket(state);
7538
+ socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
7539
+ action: "accept",
7540
+ clientId: participantId,
7541
+ response: {},
7542
+ });
7543
+ });
7544
+ const doRejectWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
7545
+ const { participantId } = payload;
7546
+ const state = getState();
7547
+ const socket = selectSignalConnectionSocket(state);
7548
+ socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
7549
+ action: "reject",
7550
+ clientId: participantId,
7551
+ response: {},
7552
+ });
7553
+ });
7554
+ const selectWaitingParticipants = (state) => state.waitingParticipants.waitingParticipants;
7555
+
7556
+ var _a;
7557
+ const IS_DEV = (_a = process.env.REACT_APP_IS_DEV === "true") !== null && _a !== void 0 ? _a : false;
7558
+ const rootReducer = combineReducers({
7559
+ app: appSlice.reducer,
7560
+ chat: chatSlice.reducer,
7561
+ cloudRecording: cloudRecordingSlice.reducer,
7562
+ deviceCredentials: deviceCredentialsSlice.reducer,
7563
+ localMedia: localMediaSlice.reducer,
7564
+ localParticipant: localParticipantSlice.reducer,
7565
+ localScreenshare: localScreenshareSlice.reducer,
7566
+ organization: organizationSlice.reducer,
7567
+ remoteParticipants: remoteParticipantsSlice.reducer,
7568
+ roomConnection: roomConnectionSlice.reducer,
7569
+ rtcAnalytics: rtcAnalyticsSlice.reducer,
7570
+ rtcConnection: rtcConnectionSlice.reducer,
7571
+ signalConnection: signalConnectionSlice.reducer,
7572
+ streaming: streamingSlice.reducer,
7573
+ waitingParticipants: waitingParticipantsSlice.reducer,
7574
+ });
7575
+ const createStore = ({ preloadedState, injectServices, }) => {
7576
+ return configureStore({
7577
+ devTools: IS_DEV,
7578
+ reducer: rootReducer,
7579
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({
7580
+ thunk: {
7581
+ extraArgument: { services: injectServices },
7582
+ },
7583
+ serializableCheck: false,
7584
+ }).prepend(listenerMiddleware.middleware),
7585
+ preloadedState,
7586
+ });
7587
+ };
7588
+ const observeStore = (store, select, onChange) => {
7589
+ let currentState;
7590
+ function handleChange() {
7591
+ const nextState = select(store.getState());
7592
+ if (nextState !== currentState) {
7593
+ currentState = nextState;
7594
+ onChange(currentState);
7595
+ }
7596
+ }
7597
+ const unsubscribe = store.subscribe(handleChange);
7598
+ handleChange();
7599
+ return unsubscribe;
7600
+ };
7601
+
5806
7602
  const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
5807
7603
  const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
5808
7604
  const localhostPattern = /^(?:([^.]+)[.])?(localhost:?\d*)/;
@@ -5910,17 +7706,6 @@ function assertInstanceOf(value, type, parameterName) {
5910
7706
  assert$1.ok(value instanceof type, `${resolvedParameterName}<${type.name}> is required`);
5911
7707
  return value;
5912
7708
  }
5913
- /**
5914
- * Asserts that the provided room name is a valid roomName.
5915
- *
5916
- * @param roomName - The roomName to check.
5917
- * @param {string} [parameterName="roomName"] - The name of the parameter.
5918
- */
5919
- function assertRoomName(roomName, parameterName = "roomName") {
5920
- assertString(roomName, parameterName);
5921
- assert$1.ok(typeof roomName === "string" && roomName[0] === "/", `${parameterName} must begin with a '/'`);
5922
- return roomName;
5923
- }
5924
7709
  /**
5925
7710
  * Asserts that the provided array is a valid array.
5926
7711
  *
@@ -5931,22 +7716,6 @@ function assertArray(array, parameterName) {
5931
7716
  assert$1.ok(Array.isArray(array), `${parameterName}<array> is required`);
5932
7717
  return array;
5933
7718
  }
5934
- /**
5935
- * Asserts that value is one of the values provided in an array
5936
- *
5937
- * @param value - The value to check.
5938
- * @param allowedValues - An array of allowed values
5939
- * @param {string} parameterName - The name of the parameter.
5940
- */
5941
- function assertOneOf(value, allowedValues, parameterName) {
5942
- assertTruthy(value, "value");
5943
- assertArray(allowedValues, "allowedValues");
5944
- const isAllowed = allowedValues.includes(value);
5945
- if (!isAllowed) {
5946
- throw new Error(`${parameterName}<string> must be one of the following: ${allowedValues.join(", ")}`);
5947
- }
5948
- return value;
5949
- }
5950
7719
  /**
5951
7720
  * Asserts that the provided reference is a record.
5952
7721
  *
@@ -6190,22 +7959,6 @@ function extractString(data, propertyName) {
6190
7959
  return assertString(record[propertyName], propertyName);
6191
7960
  }
6192
7961
  const extractNullOrString = nullOrExtract(extractString);
6193
- /**
6194
- * Extract a Date from the given Json object.
6195
- * If the value is not a valid Date, an error is thrown.
6196
- *
6197
- * @param data - the object to extract the value from
6198
- * @param propertyName - the name of the parameter to extract
6199
- * @returns the extracted value
6200
- */
6201
- function extractDate(data, propertyName) {
6202
- const dateString = extractString(data, propertyName);
6203
- const d = new Date(dateString);
6204
- if (isNaN(d.getTime())) {
6205
- throw new Error(`Invalid date for ${dateString}`);
6206
- }
6207
- return d;
6208
- }
6209
7962
  /**
6210
7963
  * Extract an Array from the given Json object.
6211
7964
  * If the value is not a valid Array, an error is thrown.
@@ -6522,43 +8275,6 @@ class CredentialsService extends EventEmitter$1 {
6522
8275
  }
6523
8276
  }
6524
8277
 
6525
- const noOrganization = () => Promise.resolve(undefined);
6526
- /**
6527
- * Class used for all Whereby organization API calls.
6528
- */
6529
- class OrganizationApiClient {
6530
- /**
6531
- * Create an OrganizationApiClient instance.
6532
- *
6533
- * @param {Object} options - The options for the OrganizationApiClient.
6534
- * @param {ApiClient} [options.apiClient] - The apiClient to use.
6535
- * @param {Function} [options.fetchOrganization] - function that returns a promise with the organization.
6536
- */
6537
- constructor({ apiClient, fetchOrganization = noOrganization, }) {
6538
- this._apiClient = apiClient;
6539
- this._fetchOrganization = fetchOrganization;
6540
- this._apiClient = apiClient;
6541
- }
6542
- _callRequestMethod(method, url, options) {
6543
- assertString(url, "url");
6544
- assert$1.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
6545
- assert$1.ok(options, "options are required");
6546
- return this._fetchOrganization().then((organization) => {
6547
- if (!organization) {
6548
- return this._apiClient[method](url, options);
6549
- }
6550
- const { organizationId } = organization;
6551
- return this._apiClient[method](`/organizations/${encodeURIComponent(organizationId)}${url}`, options);
6552
- });
6553
- }
6554
- request(url, options) {
6555
- return this._callRequestMethod("request", url, options);
6556
- }
6557
- requestMultipart(url, options) {
6558
- return this._callRequestMethod("requestMultipart", url, options);
6559
- }
6560
- }
6561
-
6562
8278
  class EmbeddedFreeTierStatus {
6563
8279
  constructor({ isExhausted, renewsAt, totalMinutesLimit, totalMinutesUsed, }) {
6564
8280
  this.isExhausted = isExhausted;
@@ -6758,1279 +8474,192 @@ class OrganizationService {
6758
8474
  method: "GET",
6759
8475
  })
6760
8476
  .then(({ data }) => {
6761
- return Organization.fromJson(data);
6762
- })
6763
- .catch((res) => {
6764
- if (res instanceof Response) {
6765
- if (res.status === 404) {
6766
- return null;
6767
- }
6768
- throw new Error(res.statusText);
6769
- }
6770
- throw res;
6771
- });
6772
- }
6773
- /**
6774
- * Retrieves the organizations that contain a user
6775
- * matching provided the email+code or phoneNumber+code
6776
- * combination.
6777
- */
6778
- getOrganizationsByContactPoint(options) {
6779
- const { code } = options;
6780
- const email = "email" in options ? options.email : null;
6781
- const phoneNumber = "phoneNumber" in options ? options.phoneNumber : null;
6782
- assert$1.ok((email || phoneNumber) && !(email && phoneNumber), "either email or phoneNumber is required");
6783
- assertString(code, "code");
6784
- const contactPoint = email ? { type: "email", value: email } : { type: "phoneNumber", value: phoneNumber };
6785
- return this._apiClient
6786
- .request("/organization-queries", {
6787
- method: "POST",
6788
- data: {
6789
- contactPoint,
6790
- code,
6791
- },
6792
- })
6793
- .then(({ data }) => {
6794
- return extractArray(data, "organizations", (organization) => Organization.fromJson(organization));
6795
- });
6796
- }
6797
- /**
6798
- * Retrieves the organizations that contain a user
6799
- * matching provided the idToken
6800
- */
6801
- getOrganizationsByIdToken({ idToken }) {
6802
- assertString(idToken, "idToken");
6803
- return this._apiClient
6804
- .request("/organization-queries", {
6805
- method: "POST",
6806
- data: {
6807
- idToken,
6808
- },
6809
- })
6810
- .then(({ data }) => {
6811
- return extractArray(data, "organizations", (organization) => {
6812
- return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(organization, "organization")));
6813
- });
6814
- });
6815
- }
6816
- /**
6817
- * Retrieves the organizations containing a user
6818
- * with either the email or phoneNumber matching the logged in user.
6819
- *
6820
- * This is useful for showing the possible organization that the current
6821
- * user could log in to.
6822
- */
6823
- getOrganizationsByLoggedInUser() {
6824
- return this._apiClient
6825
- .request("/user/organizations", {
6826
- method: "GET",
6827
- })
6828
- .then(({ data }) => {
6829
- return extractArray(data, "organizations", (o) => {
6830
- return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(o, "organization")));
6831
- });
6832
- });
6833
- }
6834
- /**
6835
- * Checks if a subdomain is available and verifies its format.
6836
- */
6837
- getSubdomainAvailability(subdomain) {
6838
- assertString(subdomain, "subdomain");
6839
- return this._apiClient
6840
- .request(`/organization-subdomains/${encodeURIComponent(subdomain)}/availability`, {
6841
- method: "GET",
6842
- })
6843
- .then(({ data }) => {
6844
- assertInstanceOf(data, Object, "data");
6845
- return {
6846
- status: extractString(data, "status"),
6847
- };
6848
- });
6849
- }
6850
- /**
6851
- * Updates preferences of the organization.
6852
- */
6853
- updatePreferences({ organizationId, preferences, }) {
6854
- assertTruthy(organizationId, "organizationId");
6855
- assertTruthy(preferences, "preferences");
6856
- return this._apiClient
6857
- .request(`/organizations/${encodeURIComponent(organizationId)}/preferences`, {
6858
- method: "PATCH",
6859
- data: preferences,
6860
- })
6861
- .then(() => undefined);
6862
- }
6863
- /**
6864
- * Delete organization
6865
- */
6866
- deleteOrganization({ organizationId }) {
6867
- assertTruthy(organizationId, "organizationId");
6868
- return this._apiClient
6869
- .request(`/organizations/${encodeURIComponent(organizationId)}`, {
6870
- method: "DELETE",
6871
- })
6872
- .then(() => undefined);
6873
- }
6874
- }
6875
-
6876
- class OrganizationServiceCache {
6877
- constructor({ organizationService, subdomain }) {
6878
- this._organizationService = organizationService;
6879
- this._subdomain = subdomain;
6880
- this._organizationPromise = null;
6881
- }
6882
- initOrganization() {
6883
- return this.fetchOrganization().then(() => undefined);
6884
- }
6885
- fetchOrganization() {
6886
- if (!this._organizationPromise) {
6887
- this._organizationPromise = this._organizationService.getOrganizationBySubdomain(this._subdomain);
6888
- }
6889
- return this._organizationPromise;
6890
- }
6891
- }
6892
-
6893
- // @ts-nocheck
6894
- class Room {
6895
- constructor(properties = {}) {
6896
- assert$1.ok(properties instanceof Object, "properties<object> must be empty or an object");
6897
- this.isClaimed = false;
6898
- this.isBanned = false;
6899
- this.isLocked = false;
6900
- this.knockPage = {
6901
- backgroundImageUrl: null,
6902
- backgroundThumbnailUrl: null,
6903
- };
6904
- this.logoUrl = null;
6905
- this.backgroundImageUrl = null;
6906
- this.backgroundThumbnailUrl = null;
6907
- this.type = null;
6908
- this.legacyRoomType = null;
6909
- this.mode = null;
6910
- this.product = null;
6911
- this.roomName = null;
6912
- this.theme = null;
6913
- this.preferences = {};
6914
- this.protectedPreferences = {};
6915
- this.publicProfile = null;
6916
- // Only allow existing property names to be modified
6917
- const validProperties = {};
6918
- Object.getOwnPropertyNames(properties).forEach((prop) => {
6919
- if (Object.getOwnPropertyNames(this).indexOf(prop) !== -1) {
6920
- validProperties[prop] = properties[prop];
6921
- }
6922
- });
6923
- if (properties.ownerId !== undefined) {
6924
- this.ownerId = properties.ownerId;
6925
- }
6926
- if (properties.meeting !== undefined) {
6927
- this.meeting = properties.meeting;
6928
- }
6929
- Object.assign(this, validProperties);
6930
- }
6931
- }
6932
-
6933
- class Meeting {
6934
- constructor({ meetingId, roomName, roomUrl, startDate, endDate, hostRoomUrl, viewerRoomUrl }) {
6935
- assertString(meetingId, "meetingId");
6936
- assertString(roomName, "roomName");
6937
- assertString(roomUrl, "roomUrl");
6938
- assertInstanceOf(startDate, Date, "startDate");
6939
- assertInstanceOf(endDate, Date, "endDate");
6940
- this.meetingId = meetingId;
6941
- this.roomName = roomName;
6942
- this.roomUrl = roomUrl;
6943
- this.startDate = startDate;
6944
- this.endDate = endDate;
6945
- this.hostRoomUrl = hostRoomUrl;
6946
- this.viewerRoomUrl = viewerRoomUrl;
6947
- }
6948
- static fromJson(data) {
6949
- return new Meeting({
6950
- meetingId: extractString(data, "meetingId"),
6951
- roomName: extractString(data, "roomName"),
6952
- roomUrl: extractString(data, "roomUrl"),
6953
- startDate: extractDate(data, "startDate"),
6954
- endDate: extractDate(data, "endDate"),
6955
- hostRoomUrl: extractNullOrString(data, "hostRoomUrl"),
6956
- viewerRoomUrl: extractNullOrString(data, "viewerRoomUrl"),
6957
- });
6958
- }
6959
- }
6960
-
6961
- // @ts-nocheck
6962
- function createRoomUrl(roomName, path = "") {
6963
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
6964
- return `/room/${encodedDisplayName}${path}`;
6965
- }
6966
- /**
6967
- * Service for talking to the Room related APIs
6968
- */
6969
- class RoomService {
6970
- /**
6971
- * @param {object} organizationApiClient (required)
6972
- */
6973
- constructor({ organizationApiClient }) {
6974
- this._organizationApiClient = assertInstanceOf(organizationApiClient, OrganizationApiClient);
6975
- }
6976
- /**
6977
- * Gets the list of visited rooms
6978
- *
6979
- * @param {Object} args
6980
- * @param {Array<string>} [types=["team"]] - The type of rooms that should be fetched.
6981
- * @param {Array<string>} [fields=["meeting"]] - The fields of room that should be fetched.
6982
- * @returns {Promise<array>} - It will resolve with an array.
6983
- */
6984
- getRooms({ types, fields = [] } = {}) {
6985
- assertArray(types, "types");
6986
- assertArray(fields, "fields");
6987
- return this._organizationApiClient
6988
- .request("/room", {
6989
- method: "GET",
6990
- params: { types: types.join(","), fields: fields.join(","), includeOnlyLegacyRoomType: "false" },
6991
- })
6992
- .then(({ data }) => data.rooms.map((room) => new Room(room)));
6993
- }
6994
- /**
6995
- * Gets the specified room.
6996
- *
6997
- * Currently information is implicitly alluded to via the servers
6998
- * `/room/roomName` response. This method patches the data
6999
- * tempoarily, until the day it comes back from the server.
7000
- *
7001
- * @returns {Promise<Room>} - It will resolve with the Room.
7002
- */
7003
- getRoom({ roomName, fields }) {
7004
- assertRoomName(roomName);
7005
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7006
- return this._organizationApiClient
7007
- .request(`/rooms/${encodedDisplayName}`, {
7008
- method: "GET",
7009
- params: Object.assign({ includeOnlyLegacyRoomType: "false" }, (fields && { fields: fields.join(",") })),
7010
- })
7011
- .then(({ data }) => new Room(Object.assign({}, data, Object.assign({ roomName }, (data.meeting && { meeting: Meeting.fromJson(data.meeting) })))))
7012
- .catch((response) => {
7013
- if (response.status === 404) {
7014
- return new Room({
7015
- roomName,
7016
- isClaimed: false,
7017
- mode: "normal",
7018
- product: {
7019
- categoryName: "personal_free",
7020
- },
7021
- type: "personal",
7022
- legacyRoomType: "free",
7023
- });
7024
- }
7025
- if (response.status === 400 && response.data.error === "Banned room") {
7026
- return new Room({ roomName, isBanned: true });
7027
- }
7028
- // Either server error or something else.
7029
- throw new Error(response.data ? response.data.error : "Could not fetch room information");
7030
- });
7031
- }
7032
- /**
7033
- * Claims the specified room.
7034
- *
7035
- * @param {Object} args
7036
- * @param {String} args.roomName - The roomName to claim
7037
- * @param {String} args.type - The type of room to claim
7038
- * @param {String} args.mode - The optional mode of room to claim
7039
- * @param {[Boolean]} args.isLocked - The optional lock status of room to claim
7040
- * @returns {Promise} - It will resolve with undefined.
7041
- */
7042
- claimRoom({ roomName, type, mode, isLocked }) {
7043
- assertRoomName(roomName);
7044
- assertString(type, "type");
7045
- return this._organizationApiClient
7046
- .request("/room/claim", {
7047
- method: "POST",
7048
- data: Object.assign(Object.assign({ roomName,
7049
- type }, (typeof mode === "string" && { mode })), (typeof isLocked === "boolean" && { isLocked })),
7050
- })
7051
- .then(() => undefined)
7052
- .catch((response) => {
7053
- throw new Error(response.data.error || "Failed to claim room");
7054
- });
7055
- }
7056
- /**
7057
- * Unclaims the specified room.
7058
- *
7059
- * @param {string} roomName - the room name to unclaim.
7060
- * @returns {Promise.<undefined>} - It will resolve with undefined.
7061
- */
7062
- unclaimRoom(roomName) {
7063
- assertRoomName(roomName);
7064
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7065
- return this._organizationApiClient
7066
- .request(`/room/${encodedDisplayName}`, {
7067
- method: "DELETE",
7068
- })
7069
- .then(() => undefined);
7070
- }
7071
- /**
7072
- * Changes the name of the room
7073
- *
7074
- * @param {Object} args
7075
- * @param {string} args.roomName - The name of the room to rename
7076
- * @param {string} args.newRoomName - The new name
7077
- * @returns {Promise<undefined>} - It will resolve if the room was renamed, reject for all other cases
7078
- */
7079
- renameRoom({ roomName, newRoomName }) {
7080
- assertRoomName(roomName);
7081
- assertString(newRoomName, "newRoomName");
7082
- const encodedRoomName = encodeURIComponent(roomName.substring(1));
7083
- return this._organizationApiClient
7084
- .request(`/room/${encodedRoomName}/roomName`, {
7085
- method: "PUT",
7086
- data: { newRoomName },
7087
- })
7088
- .then(() => undefined);
7089
- }
7090
- /**
7091
- * Changes the room mode (experimental)
7092
- *
7093
- * @param {string} roomName - The name of the room to change mode of
7094
- * @param {string} mode - The name of mode to set, currently only "group" is supported
7095
- * @returns {Promise<undefined>} - It will resolve if mode was changed, rejects for all other cases
7096
- */
7097
- changeMode({ roomName, mode }) {
7098
- assertRoomName(roomName);
7099
- assertString(mode, "mode");
7100
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7101
- return this._organizationApiClient
7102
- .request(`/room/${encodedDisplayName}/mode`, {
7103
- method: "PUT",
7104
- data: { mode },
7105
- })
7106
- .then(() => undefined);
7107
- }
7108
- /**
7109
- * Updates the room prefs
7110
- *
7111
- * @param {string} roomName - The name of the room to change mode of
7112
- * @param {object} preferences - The prefs you want to update and their values
7113
- * @returns {Promise<undefined>} - It will resolve if updated, rejects for all other cases
7114
- */
7115
- updatePreferences({ roomName, preferences }) {
7116
- assertRoomName(roomName);
7117
- assertInstanceOf(preferences, Object, "preferences");
7118
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7119
- return this._organizationApiClient
7120
- .request(`/room/${encodedDisplayName}/preferences`, {
7121
- method: "PATCH",
7122
- data: preferences,
7123
- })
7124
- .then(() => undefined);
7125
- }
7126
- /**
7127
- * Updates the protected room prefs
7128
- *
7129
- * @param {string} roomName - The name of the room to change mode of
7130
- * @param {object} preferences - The protected prefs you want to update and their values
7131
- * @returns {Promise<undefined>} - It will resolve if updated, rejects for all other cases
7132
- */
7133
- updateProtectedPreferences({ roomName, preferences }) {
7134
- assertRoomName(roomName);
7135
- assertInstanceOf(preferences, Object, "preferences");
7136
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7137
- return this._organizationApiClient
7138
- .request(`/room/${encodedDisplayName}/protected-preferences`, {
7139
- method: "PATCH",
7140
- data: preferences,
7141
- })
7142
- .then(() => undefined);
7143
- }
7144
- getRoomPermissions(roomName, { roomKey } = {}) {
7145
- assertRoomName(roomName);
7146
- return this._organizationApiClient
7147
- .request(createRoomUrl(roomName, "/permissions"), Object.assign({ method: "GET" }, (roomKey && { headers: { "X-Whereby-Room-Key": roomKey } })))
7148
- .then((response) => {
7149
- const { permissions, limits } = response.data;
7150
- return {
7151
- permissions,
7152
- limits,
7153
- };
7154
- });
7155
- }
7156
- /**
7157
- * Gets the specified room metrics
7158
- *
7159
- * @param {Object} args
7160
- * @param {string} args.roomName - The name of the room to get metrics from.
7161
- * @param {string} args.metrics - Comma-separated list of metrics to include.
7162
- * @param {string} args.from (optional) - Start time (inclusive) to count from in
7163
- * ISO format. Defaults to counting from the start of time.
7164
- * @param {string} args.to (optional) - End time (exclusive) to count up to in
7165
- * ISO format. Defaults to counting up to the current time.
7166
- * @returns {Promise<Object>} - It will resolve with the requested metrics.
7167
- */
7168
- getRoomMetrics({ roomName, metrics, from, to }) {
7169
- assertRoomName(roomName);
7170
- assertString(metrics, "metrics");
7171
- return this._organizationApiClient
7172
- .request(createRoomUrl(roomName, "/metrics"), {
7173
- method: "GET",
7174
- params: { metrics, from, to },
7175
- })
7176
- .then((response) => response.data);
7177
- }
7178
- /**
7179
- * Changes the room type
7180
- *
7181
- * @param {Object} args
7182
- * @param {string} args.roomName - The name of the room to change mode of
7183
- * @param {"personal" | "personal_xl"} args.type - Room type that should be set
7184
- * @returns {Promise<undefined>} - It will resolve if type was changed, rejects for all other cases
7185
- */
7186
- changeType({ roomName, type }) {
7187
- assertRoomName(roomName);
7188
- assertOneOf(type, ["personal", "personal_xl"], "type");
7189
- const encodedDisplayName = encodeURIComponent(roomName.substring(1));
7190
- return this._organizationApiClient
7191
- .request(`/room/${encodedDisplayName}/type`, {
7192
- method: "PUT",
7193
- data: { type },
7194
- })
7195
- .then(() => undefined);
7196
- }
7197
- /** Gets a Forest campaign social image
7198
- *
7199
- * @param {Object} args
7200
- * @param {string} args.roomName - The name of the room to get metrics from.
7201
- * @param {number} args.count - Number to be displayed in the image as tree count.
7202
- * @returns {Promise<string>} - It will resolve with the image url.
7203
- */
7204
- getForestSocialImage({ roomName, count }) {
7205
- assertRoomName(roomName);
7206
- assertNumber(count, "count");
7207
- return this._organizationApiClient
7208
- .request(createRoomUrl(roomName, `/forest-social-image/${count}`), {
7209
- method: "GET",
7210
- })
7211
- .then((response) => response.data.imageUrl);
7212
- }
7213
- }
7214
-
7215
- class RoomParticipant {
7216
- constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
7217
- this.isLocalParticipant = false;
7218
- this.displayName = displayName;
7219
- this.id = id;
7220
- this.stream = stream;
7221
- this.isAudioEnabled = isAudioEnabled;
7222
- this.isVideoEnabled = isVideoEnabled;
7223
- }
7224
- }
7225
- class RemoteParticipant extends RoomParticipant {
7226
- constructor({ displayName, id, newJoiner, streams, isAudioEnabled, isVideoEnabled, }) {
7227
- super({ displayName, id, isAudioEnabled, isVideoEnabled });
7228
- this.newJoiner = newJoiner;
7229
- this.streams = streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" }));
7230
- }
7231
- addStream(streamId, state) {
7232
- this.streams.push({ id: streamId, state });
7233
- }
7234
- removeStream(streamId) {
7235
- const index = this.streams.findIndex((s) => s.id === streamId);
7236
- if (index !== -1) {
7237
- this.streams.splice(index, 1);
7238
- }
7239
- }
7240
- updateStreamState(streamId, state) {
7241
- const stream = this.streams.find((s) => s.id === streamId);
7242
- if (stream) {
7243
- stream.state = state;
7244
- }
7245
- }
7246
- }
7247
- class LocalParticipant extends RoomParticipant {
7248
- constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
7249
- super({ displayName, id, stream, isAudioEnabled, isVideoEnabled });
7250
- this.isLocalParticipant = true;
7251
- }
7252
- }
7253
-
7254
- const sdkVersion = "2.0.0-beta3";
7255
-
7256
- class RoomConnectionEvent extends CustomEvent {
7257
- constructor(eventType, eventInitDict) {
7258
- super(eventType, eventInitDict);
7259
- }
7260
- }
7261
- const API_BASE_URL = "https://api.whereby.dev" ;
7262
- const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
7263
- const NON_PERSON_ROLES = ["recorder", "streamer"];
7264
- // cache last reported stream resolutions
7265
- const reportedStreamResolutions = new Map();
7266
- function createSocket() {
7267
- const parsedUrl = new URL(SIGNAL_BASE_URL);
7268
- const socketHost = parsedUrl.origin;
7269
- const socketOverrides = {
7270
- autoConnect: false,
7271
- };
7272
- return new ServerSocket(socketHost, socketOverrides);
7273
- }
7274
- function handleStreamAdded(remoteParticipants, { clientId, stream, streamId, streamType }) {
7275
- if (!streamId) {
7276
- streamId = stream.id;
7277
- }
7278
- const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
7279
- if (!remoteParticipant) {
7280
- return;
7281
- }
7282
- const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId) || remoteParticipant.streams[0];
7283
- if ((remoteParticipant.stream && remoteParticipant.stream.id === streamId) ||
7284
- (!remoteParticipant.stream && streamType === "webcam") ||
7285
- (!remoteParticipant.stream && !streamType && remoteParticipant.streams.indexOf(remoteParticipantStream) < 1)) {
7286
- return new RoomConnectionEvent("participant_stream_added", {
7287
- detail: { participantId: clientId, stream, streamId },
7288
- });
7289
- }
7290
- // screenshare
7291
- return new RoomConnectionEvent("screenshare_started", {
7292
- detail: {
7293
- participantId: clientId,
7294
- stream,
7295
- id: streamId,
7296
- isLocal: false,
7297
- hasAudioTrack: stream.getAudioTracks().length > 0,
7298
- },
7299
- });
7300
- }
7301
- const noop = () => {
7302
- return;
7303
- };
7304
- const TypedEventTarget = EventTarget;
7305
- class RoomConnection extends TypedEventTarget {
7306
- constructor(roomUrl, { displayName, localMedia, localMediaOptions: localMediaConstraints, logger, roomKey, externalId, }) {
7307
- super();
7308
- this.remoteParticipants = [];
7309
- this.screenshares = [];
7310
- this._deviceCredentials = null;
7311
- this._ownsLocalMedia = false;
7312
- this.organizationId = "";
7313
- this.connectionStatus = "initializing";
7314
- this.selfId = null;
7315
- this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
7316
- const searchParams = new URLSearchParams(this.roomUrl.search);
7317
- this._roomKey = roomKey || searchParams.get("roomKey");
7318
- this.roomName = this.roomUrl.pathname;
7319
- this.logger = logger || {
7320
- debug: noop,
7321
- error: noop,
7322
- info: noop,
7323
- log: noop,
7324
- warn: noop,
7325
- };
7326
- this.displayName = displayName;
7327
- this.externalId = externalId;
7328
- this.localMediaConstraints = localMediaConstraints;
7329
- const urls = fromLocation({ host: this.roomUrl.host });
7330
- // Set up local media
7331
- if (localMedia) {
7332
- this.localMedia = localMedia;
7333
- }
7334
- else if (localMediaConstraints) {
7335
- this.localMedia = new LocalMedia(localMediaConstraints);
7336
- this._ownsLocalMedia = true;
7337
- }
7338
- else {
7339
- throw new Error("Missing constraints");
7340
- }
7341
- // Set up services
7342
- this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
7343
- this.apiClient = new ApiClient({
7344
- fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
7345
- baseUrl: API_BASE_URL,
7346
- });
7347
- this.organizationService = new OrganizationService({ apiClient: this.apiClient });
7348
- this.organizationServiceCache = new OrganizationServiceCache({
7349
- organizationService: this.organizationService,
7350
- subdomain: urls.subdomain,
7351
- });
7352
- this.organizationApiClient = new OrganizationApiClient({
7353
- apiClient: this.apiClient,
7354
- fetchOrganization: () => __awaiter(this, void 0, void 0, function* () {
7355
- const organization = yield this.organizationServiceCache.fetchOrganization();
7356
- return organization || undefined;
7357
- }),
7358
- });
7359
- this.roomService = new RoomService({ organizationApiClient: this.organizationApiClient });
7360
- // Create signal socket and set up event listeners
7361
- this.signalSocket = createSocket();
7362
- this.signalSocket.on("new_client", this._handleNewClient.bind(this));
7363
- this.signalSocket.on("chat_message", this._handleNewChatMessage.bind(this));
7364
- this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
7365
- this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
7366
- this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
7367
- this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
7368
- this.signalSocket.on("knock_handled", this._handleKnockHandled.bind(this));
7369
- this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
7370
- this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
7371
- this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
7372
- this.signalSocket.on("cloud_recording_started", this._handleCloudRecordingStarted.bind(this));
7373
- this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
7374
- this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
7375
- this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
7376
- this.signalSocket.on("streaming_stopped", this._handleStreamingStopped.bind(this));
7377
- this.signalSocket.on("disconnect", this._handleDisconnect.bind(this));
7378
- this.signalSocket.on("connect_error", this._handleDisconnect.bind(this));
7379
- this.signalSocketManager = this.signalSocket.getManager();
7380
- this.signalSocketManager.on("reconnect", this._handleReconnect.bind(this));
7381
- // Set up local media listeners
7382
- this.localMedia.addEventListener("camera_enabled", (e) => {
7383
- const { enabled } = e.detail;
7384
- this.signalSocket.emit("enable_video", { enabled });
7385
- this.dispatchEvent(new RoomConnectionEvent("local_camera_enabled", { detail: { enabled } }));
7386
- });
7387
- this.localMedia.addEventListener("microphone_enabled", (e) => {
7388
- const { enabled } = e.detail;
7389
- this.signalSocket.emit("enable_audio", { enabled });
7390
- this.dispatchEvent(new RoomConnectionEvent("local_microphone_enabled", { detail: { enabled } }));
7391
- });
7392
- const webrtcProvider = {
7393
- getMediaConstraints: () => ({
7394
- audio: this.localMedia.isMicrophoneEnabled(),
7395
- video: this.localMedia.isCameraEnabled(),
7396
- }),
7397
- deferrable(clientId) {
7398
- return !clientId;
7399
- },
7400
- };
7401
- this.rtcManagerDispatcher = new RtcManagerDispatcher({
7402
- emitter: {
7403
- emit: this._handleRtcEvent.bind(this),
7404
- },
7405
- serverSocket: this.signalSocket,
7406
- webrtcProvider,
7407
- features: {
7408
- lowDataModeEnabled: false,
7409
- sfuServerOverrideHost: undefined,
7410
- turnServerOverrideHost: undefined,
7411
- useOnlyTURN: undefined,
7412
- vp9On: false,
7413
- h264On: false,
7414
- simulcastScreenshareOn: false,
7415
- },
7416
- logger: this.logger,
7417
- });
7418
- }
7419
- get roomKey() {
7420
- return this._roomKey;
7421
- }
7422
- _handleNewChatMessage(message) {
7423
- this.dispatchEvent(new RoomConnectionEvent("chat_message", { detail: message }));
7424
- }
7425
- _handleCloudRecordingStarted(event) {
7426
- // Only handle the start failure event here. The recording is
7427
- // considered started when the recorder client joins.
7428
- if (event.error) {
7429
- this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started_error", {
7430
- detail: { error: event.error, status: "error" },
7431
- }));
7432
- }
7433
- }
7434
- _handleRecorderClientJoined({ client }) {
7435
- this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started", {
7436
- detail: {
7437
- status: "recording",
7438
- startedAt: client.startedCloudRecordingAt
7439
- ? new Date(client.startedCloudRecordingAt).getTime()
7440
- : new Date().getTime(),
7441
- },
7442
- }));
7443
- }
7444
- _handleStreamingStarted() {
7445
- this.dispatchEvent(new RoomConnectionEvent("streaming_started", {
7446
- detail: {
7447
- status: "streaming",
7448
- // We don't have the streaming start time stored on the
7449
- // server, so we use the current time instead. This gives
7450
- // an invalid timestamp for "Client B" if "Client A" has
7451
- // been streaming for a while before "Client B" joins.
7452
- startedAt: new Date().getTime(),
7453
- },
7454
- }));
7455
- }
7456
- _handleNewClient({ client }) {
7457
- if (client.role.roleName === "recorder") {
7458
- this._handleRecorderClientJoined({ client });
7459
- }
7460
- if (client.role.roleName === "streamer") {
7461
- this._handleStreamingStarted();
7462
- }
7463
- if (NON_PERSON_ROLES.includes(client.role.roleName)) {
7464
- return;
7465
- }
7466
- const remoteParticipant = new RemoteParticipant(Object.assign(Object.assign({}, client), { newJoiner: true }));
7467
- this.remoteParticipants = [...this.remoteParticipants, remoteParticipant];
7468
- this._handleAcceptStreams([remoteParticipant]);
7469
- this.dispatchEvent(new RoomConnectionEvent("participant_joined", {
7470
- detail: { remoteParticipant },
7471
- }));
7472
- }
7473
- _handleClientLeft({ clientId }) {
7474
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
7475
- this.remoteParticipants = this.remoteParticipants.filter((p) => p.id !== clientId);
7476
- if (!remoteParticipant) {
7477
- return;
7478
- }
7479
- this.dispatchEvent(new RoomConnectionEvent("participant_left", { detail: { participantId: remoteParticipant.id } }));
7480
- }
7481
- _handleClientAudioEnabled({ clientId, isAudioEnabled }) {
7482
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
7483
- if (!remoteParticipant) {
7484
- return;
7485
- }
7486
- this.dispatchEvent(new RoomConnectionEvent("participant_audio_enabled", {
7487
- detail: { participantId: remoteParticipant.id, isAudioEnabled },
7488
- }));
7489
- }
7490
- _handleClientVideoEnabled({ clientId, isVideoEnabled }) {
7491
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
7492
- if (!remoteParticipant) {
7493
- return;
7494
- }
7495
- this.dispatchEvent(new RoomConnectionEvent("participant_video_enabled", {
7496
- detail: { participantId: remoteParticipant.id, isVideoEnabled },
7497
- }));
7498
- }
7499
- _handleClientMetadataReceived({ payload: { clientId, displayName } }) {
7500
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
7501
- if (!remoteParticipant) {
7502
- return;
7503
- }
7504
- this.dispatchEvent(new RoomConnectionEvent("participant_metadata_changed", {
7505
- detail: { participantId: remoteParticipant.id, displayName },
7506
- }));
7507
- }
7508
- _handleKnockHandled(payload) {
7509
- const { clientId, resolution } = payload;
7510
- // If the knocker is not the local participant, ignore the event
7511
- if (clientId !== this.selfId) {
7512
- return;
7513
- }
7514
- if (resolution === "accepted") {
7515
- this._roomKey = payload.metadata.roomKey;
7516
- this._joinRoom();
7517
- }
7518
- else if (resolution === "rejected") {
7519
- this.connectionStatus = "knock_rejected";
7520
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7521
- detail: {
7522
- connectionStatus: this.connectionStatus,
7523
- },
7524
- }));
7525
- }
7526
- }
7527
- _handleKnockerLeft(payload) {
7528
- const { clientId } = payload;
7529
- this.dispatchEvent(new RoomConnectionEvent("waiting_participant_left", {
7530
- detail: { participantId: clientId },
7531
- }));
7532
- }
7533
- _handleRoomJoined(event) {
7534
- const { error, isLocked, room, selfId } = event;
7535
- this.selfId = selfId;
7536
- if (error === "room_locked" && isLocked) {
7537
- this.connectionStatus = "room_locked";
7538
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7539
- detail: {
7540
- connectionStatus: this.connectionStatus,
7541
- },
7542
- }));
7543
- return;
7544
- }
7545
- // Check if we have an error
7546
- // Check if it is a room joined error
7547
- // Set state to connect_failed_locked
7548
- // Set state to connect_failed_no_host
7549
- if (room) {
7550
- const { clients, knockers } = room;
7551
- const localClient = clients.find((c) => c.id === selfId);
7552
- if (!localClient)
7553
- throw new Error("Missing local client");
7554
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
7555
- const recorderClient = clients.find((c) => c.role.roleName === "recorder");
7556
- if (recorderClient) {
7557
- this._handleRecorderClientJoined({ client: recorderClient });
7558
- }
7559
- const streamerClient = clients.find((c) => c.role.roleName === "streamer");
7560
- if (streamerClient) {
7561
- this._handleStreamingStarted();
7562
- }
7563
- this.remoteParticipants = clients
7564
- .filter((c) => c.id !== selfId)
7565
- .filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
7566
- .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
7567
- this.connectionStatus = "connected";
7568
- this.dispatchEvent(new RoomConnectionEvent("room_joined", {
7569
- detail: {
7570
- localParticipant: this.localParticipant,
7571
- remoteParticipants: this.remoteParticipants,
7572
- waitingParticipants: knockers.map((knocker) => {
7573
- return { id: knocker.clientId, displayName: knocker.displayName };
7574
- }),
7575
- },
7576
- }));
7577
- }
7578
- }
7579
- _handleRoomKnocked(event) {
7580
- const { clientId, displayName } = event;
7581
- this.dispatchEvent(new RoomConnectionEvent("waiting_participant_joined", {
7582
- detail: { participantId: clientId, displayName },
7583
- }));
7584
- }
7585
- _handleReconnect() {
7586
- this.logger.log("Reconnected to signal socket");
7587
- this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
7588
- this.signalSocket.once("device_identified", () => {
7589
- this._joinRoom();
7590
- });
7591
- }
7592
- _handleDisconnect() {
7593
- this.connectionStatus = "disconnected";
7594
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7595
- detail: {
7596
- connectionStatus: this.connectionStatus,
7597
- },
7598
- }));
7599
- }
7600
- _handleCloudRecordingStopped() {
7601
- this.dispatchEvent(new RoomConnectionEvent("cloud_recording_stopped"));
7602
- }
7603
- _handleStreamingStopped() {
7604
- this.dispatchEvent(new RoomConnectionEvent("streaming_stopped"));
7605
- }
7606
- _handleScreenshareStarted(screenshare) {
7607
- const { clientId: participantId, streamId: id, hasAudioTrack } = screenshare;
7608
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
7609
- if (!remoteParticipant) {
7610
- this.logger.log("WARN: Could not find participant for screenshare");
7611
- return;
7612
- }
7613
- const foundScreenshare = this.screenshares.find((s) => s.id === id);
7614
- if (foundScreenshare) {
7615
- this.logger.log("WARN: Screenshare already exists");
7616
- return;
7617
- }
7618
- remoteParticipant.addStream(id, "to_accept");
7619
- this._handleAcceptStreams([remoteParticipant]);
7620
- this.screenshares = [
7621
- ...this.screenshares,
7622
- { participantId, id, hasAudioTrack, stream: undefined, isLocal: false },
7623
- ];
7624
- }
7625
- _handleScreenshareStopped(screenshare) {
7626
- const { clientId: participantId, streamId: id } = screenshare;
7627
- const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
7628
- if (!remoteParticipant) {
7629
- this.logger.log("WARN: Could not find participant for screenshare");
7630
- return;
7631
- }
7632
- remoteParticipant.removeStream(id);
7633
- this.screenshares = this.screenshares.filter((s) => !(s.participantId === participantId && s.id === id));
7634
- this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId, id } }));
7635
- }
7636
- _handleRtcEvent(eventName, data) {
7637
- if (eventName === "rtc_manager_created") {
7638
- return this._handleRtcManagerCreated(data);
7639
- }
7640
- else if (eventName === "stream_added") {
7641
- return this._handleStreamAdded(data);
7642
- }
7643
- else if (eventName === "rtc_manager_destroyed") {
7644
- return this._handleRtcManagerDestroyed();
7645
- }
7646
- else {
7647
- this.logger.log(`Unhandled RTC event ${eventName}`);
7648
- }
7649
- }
7650
- _handleRtcManagerCreated({ rtcManager }) {
7651
- var _a;
7652
- this.rtcManager = rtcManager;
7653
- this.localMedia.addRtcManager(rtcManager);
7654
- if (this.localMedia.stream) {
7655
- (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
7656
- }
7657
- if (this.remoteParticipants.length) {
7658
- this._handleAcceptStreams(this.remoteParticipants);
7659
- }
7660
- }
7661
- _handleRtcManagerDestroyed() {
7662
- this.rtcManager = undefined;
7663
- }
7664
- _handleAcceptStreams(remoteParticipants) {
7665
- var _a, _b;
7666
- if (!this.rtcManager) {
7667
- this.logger.log("Unable to accept streams, no rtc manager");
7668
- return;
7669
- }
7670
- const shouldAcceptNewClients = (_b = (_a = this.rtcManager).shouldAcceptStreamsFromBothSides) === null || _b === void 0 ? void 0 : _b.call(_a);
7671
- const activeBreakout = false; // TODO: Remove this once breakout is implemented
7672
- remoteParticipants.forEach((participant) => {
7673
- const { id: participantId, streams, newJoiner } = participant;
7674
- streams.forEach((stream) => {
7675
- var _a, _b;
7676
- const { id: streamId, state: streamState } = stream;
7677
- let newState = undefined;
7678
- {
7679
- if (streamState !== "done_accept") {
7680
- newState = `${newJoiner && streamId === "0" ? "new" : "to"}_accept`;
7681
- }
7682
- }
7683
- if (!newState) {
7684
- return;
7685
- }
7686
- if (newState === "to_accept" ||
7687
- (newState === "new_accept" && shouldAcceptNewClients) ||
7688
- (newState === "old_accept" && !shouldAcceptNewClients)) {
7689
- this.logger.log(`Accepting stream ${streamId} from ${participantId}`);
7690
- (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.acceptNewStream({
7691
- streamId: streamId === "0" ? participantId : streamId,
7692
- clientId: participantId,
7693
- shouldAddLocalVideo: streamId === "0",
7694
- activeBreakout,
7695
- });
7696
- }
7697
- else if (newState === "new_accept" || newState === "old_accept") ;
7698
- else if (newState === "to_unaccept") {
7699
- this.logger.log(`Disconnecting stream ${streamId} from ${participantId}`);
7700
- (_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.disconnect(streamId === "0" ? participantId : streamId, activeBreakout);
7701
- }
7702
- else if (newState !== "done_accept") {
7703
- this.logger.warn(`Stream state not handled: ${newState} for ${participantId}-${streamId}`);
7704
- return;
8477
+ return Organization.fromJson(data);
8478
+ })
8479
+ .catch((res) => {
8480
+ if (res instanceof Response) {
8481
+ if (res.status === 404) {
8482
+ return null;
7705
8483
  }
7706
- else ;
7707
- // Update stream state
7708
- participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
7709
- });
8484
+ throw new Error(res.statusText);
8485
+ }
8486
+ throw res;
7710
8487
  });
7711
8488
  }
7712
- _handleStreamAdded(args) {
7713
- const streamAddedEvent = handleStreamAdded(this.remoteParticipants, args);
7714
- if (streamAddedEvent) {
7715
- this.dispatchEvent(streamAddedEvent);
7716
- }
7717
- }
7718
- _joinRoom() {
7719
- this.signalSocket.emit("join_room", {
7720
- avatarUrl: null,
7721
- config: {
7722
- isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
7723
- isVideoEnabled: this.localMedia.isCameraEnabled(),
8489
+ /**
8490
+ * Retrieves the organizations that contain a user
8491
+ * matching provided the email+code or phoneNumber+code
8492
+ * combination.
8493
+ */
8494
+ getOrganizationsByContactPoint(options) {
8495
+ const { code } = options;
8496
+ const email = "email" in options ? options.email : null;
8497
+ const phoneNumber = "phoneNumber" in options ? options.phoneNumber : null;
8498
+ assert$1.ok((email || phoneNumber) && !(email && phoneNumber), "either email or phoneNumber is required");
8499
+ assertString(code, "code");
8500
+ const contactPoint = email ? { type: "email", value: email } : { type: "phoneNumber", value: phoneNumber };
8501
+ return this._apiClient
8502
+ .request("/organization-queries", {
8503
+ method: "POST",
8504
+ data: {
8505
+ contactPoint,
8506
+ code,
7724
8507
  },
7725
- deviceCapabilities: { canScreenshare: true },
7726
- displayName: this.displayName,
7727
- isCoLocated: false,
7728
- isDevicePermissionDenied: false,
7729
- kickFromOtherRooms: false,
7730
- organizationId: this.organizationId,
7731
- roomKey: this.roomKey,
7732
- roomName: this.roomName,
7733
- selfId: "",
7734
- userAgent: `browser-sdk:${sdkVersion }`,
7735
- externalId: this.externalId,
7736
- });
7737
- }
7738
- join() {
7739
- return __awaiter(this, void 0, void 0, function* () {
7740
- if (["connected", "connecting"].includes(this.connectionStatus)) {
7741
- console.warn(`Trying to join when room state is already ${this.connectionStatus}`);
7742
- return;
7743
- }
7744
- this.signalSocket.connect();
7745
- this.connectionStatus = "connecting";
7746
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7747
- detail: {
7748
- connectionStatus: this.connectionStatus,
7749
- },
7750
- }));
7751
- const organization = yield this.organizationServiceCache.fetchOrganization();
7752
- if (!organization) {
7753
- throw new Error("Invalid room url");
7754
- }
7755
- this.organizationId = organization.organizationId;
7756
- if (this._ownsLocalMedia) {
7757
- yield this.localMedia.start();
7758
- }
7759
- // Identify device on signal connection
7760
- this._deviceCredentials = yield this.credentialsService.getCredentials();
7761
- this.logger.log("Connected to signal socket");
7762
- this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
7763
- this.signalSocket.once("device_identified", () => {
7764
- this._joinRoom();
7765
- });
8508
+ })
8509
+ .then(({ data }) => {
8510
+ return extractArray(data, "organizations", (organization) => Organization.fromJson(organization));
7766
8511
  });
7767
8512
  }
7768
- knock() {
7769
- this.connectionStatus = "knocking";
7770
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7771
- detail: {
7772
- connectionStatus: this.connectionStatus,
8513
+ /**
8514
+ * Retrieves the organizations that contain a user
8515
+ * matching provided the idToken
8516
+ */
8517
+ getOrganizationsByIdToken({ idToken }) {
8518
+ assertString(idToken, "idToken");
8519
+ return this._apiClient
8520
+ .request("/organization-queries", {
8521
+ method: "POST",
8522
+ data: {
8523
+ idToken,
7773
8524
  },
7774
- }));
7775
- this.signalSocket.emit("knock_room", {
7776
- displayName: this.displayName,
7777
- imageUrl: null,
7778
- kickFromOtherRooms: true,
7779
- liveVideo: false,
7780
- organizationId: this.organizationId,
7781
- roomKey: this._roomKey,
7782
- roomName: this.roomName,
7783
- externalId: this.externalId,
8525
+ })
8526
+ .then(({ data }) => {
8527
+ return extractArray(data, "organizations", (organization) => {
8528
+ return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(organization, "organization")));
8529
+ });
7784
8530
  });
7785
8531
  }
7786
- leave() {
7787
- this.connectionStatus = "disconnecting";
7788
- if (this._ownsLocalMedia) {
7789
- this.localMedia.stop();
7790
- }
7791
- if (this.rtcManager) {
7792
- this.localMedia.removeRtcManager(this.rtcManager);
7793
- this.rtcManager.disconnectAll();
7794
- this.rtcManager = undefined;
7795
- }
7796
- if (!this.signalSocket) {
7797
- return;
7798
- }
7799
- this.signalSocket.emit("leave_room");
7800
- this.signalSocket.disconnect();
7801
- this.connectionStatus = "disconnected";
7802
- }
7803
- sendChatMessage(text) {
7804
- this.signalSocket.emit("chat_message", {
7805
- text,
8532
+ /**
8533
+ * Retrieves the organizations containing a user
8534
+ * with either the email or phoneNumber matching the logged in user.
8535
+ *
8536
+ * This is useful for showing the possible organization that the current
8537
+ * user could log in to.
8538
+ */
8539
+ getOrganizationsByLoggedInUser() {
8540
+ return this._apiClient
8541
+ .request("/user/organizations", {
8542
+ method: "GET",
8543
+ })
8544
+ .then(({ data }) => {
8545
+ return extractArray(data, "organizations", (o) => {
8546
+ return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(o, "organization")));
8547
+ });
7806
8548
  });
7807
8549
  }
7808
- setDisplayName(displayName) {
7809
- this.signalSocket.emit("send_client_metadata", {
7810
- type: "UserData",
7811
- payload: {
7812
- displayName,
7813
- },
8550
+ /**
8551
+ * Checks if a subdomain is available and verifies its format.
8552
+ */
8553
+ getSubdomainAvailability(subdomain) {
8554
+ assertString(subdomain, "subdomain");
8555
+ return this._apiClient
8556
+ .request(`/organization-subdomains/${encodeURIComponent(subdomain)}/availability`, {
8557
+ method: "GET",
8558
+ })
8559
+ .then(({ data }) => {
8560
+ assertInstanceOf(data, Object, "data");
8561
+ return {
8562
+ status: extractString(data, "status"),
8563
+ };
7814
8564
  });
7815
8565
  }
7816
- acceptWaitingParticipant(participantId) {
7817
- this.signalSocket.emit("handle_knock", {
7818
- action: "accept",
7819
- clientId: participantId,
7820
- response: {},
7821
- });
8566
+ /**
8567
+ * Updates preferences of the organization.
8568
+ */
8569
+ updatePreferences({ organizationId, preferences, }) {
8570
+ assertTruthy(organizationId, "organizationId");
8571
+ assertTruthy(preferences, "preferences");
8572
+ return this._apiClient
8573
+ .request(`/organizations/${encodeURIComponent(organizationId)}/preferences`, {
8574
+ method: "PATCH",
8575
+ data: preferences,
8576
+ })
8577
+ .then(() => undefined);
7822
8578
  }
7823
- rejectWaitingParticipant(participantId) {
7824
- this.signalSocket.emit("handle_knock", {
7825
- action: "reject",
7826
- clientId: participantId,
7827
- response: {},
7828
- });
8579
+ /**
8580
+ * Delete organization
8581
+ */
8582
+ deleteOrganization({ organizationId }) {
8583
+ assertTruthy(organizationId, "organizationId");
8584
+ return this._apiClient
8585
+ .request(`/organizations/${encodeURIComponent(organizationId)}`, {
8586
+ method: "DELETE",
8587
+ })
8588
+ .then(() => undefined);
7829
8589
  }
7830
- updateStreamResolution({ streamId, width, height }) {
7831
- var _a, _b;
7832
- if (!streamId || !this.rtcManager) {
7833
- return;
7834
- }
7835
- // no need to report resolution for local participant
7836
- if (((_b = (_a = this.localParticipant) === null || _a === void 0 ? void 0 : _a.stream) === null || _b === void 0 ? void 0 : _b.id) === streamId) {
7837
- return;
7838
- }
7839
- const old = reportedStreamResolutions.get(streamId);
7840
- if (!old || old.width !== width || old.height !== height) {
7841
- this.rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
7842
- }
7843
- reportedStreamResolutions.set(streamId, { width, height });
8590
+ }
8591
+
8592
+ class OrganizationServiceCache {
8593
+ constructor({ organizationService, subdomain }) {
8594
+ this._organizationService = organizationService;
8595
+ this._subdomain = subdomain;
8596
+ this._organizationPromise = null;
7844
8597
  }
7845
- startScreenshare() {
7846
- var _a, _b;
7847
- return __awaiter(this, void 0, void 0, function* () {
7848
- const screenshareStream = this.localMedia.screenshareStream || (yield this.localMedia.startScreenshare());
7849
- const onEnded = () => {
7850
- this.stopScreenshare();
7851
- };
7852
- if ("oninactive" in screenshareStream) {
7853
- // Chrome
7854
- screenshareStream.addEventListener("inactive", onEnded);
7855
- }
7856
- else {
7857
- // FF
7858
- (_a = screenshareStream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
7859
- }
7860
- (_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.addNewStream(screenshareStream.id, screenshareStream, false, true);
7861
- this.screenshares = [
7862
- ...this.screenshares,
7863
- {
7864
- participantId: this.selfId || "",
7865
- id: screenshareStream.id,
7866
- hasAudioTrack: false,
7867
- stream: screenshareStream,
7868
- isLocal: true,
7869
- },
7870
- ];
7871
- this.dispatchEvent(new RoomConnectionEvent("screenshare_started", {
7872
- detail: {
7873
- participantId: this.selfId || "",
7874
- id: screenshareStream.id,
7875
- hasAudioTrack: false,
7876
- stream: screenshareStream,
7877
- isLocal: true,
7878
- },
7879
- }));
7880
- });
8598
+ initOrganization() {
8599
+ return this.fetchOrganization().then(() => undefined);
7881
8600
  }
7882
- stopScreenshare() {
7883
- var _a;
7884
- if (this.localMedia.screenshareStream) {
7885
- const { id } = this.localMedia.screenshareStream;
7886
- (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.removeStream(id, this.localMedia.screenshareStream, null);
7887
- this.screenshares = this.screenshares.filter((s) => s.id !== id);
7888
- this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId: this.selfId || "", id } }));
7889
- this.localMedia.stopScreenshare();
8601
+ fetchOrganization() {
8602
+ if (!this._organizationPromise) {
8603
+ this._organizationPromise = this._organizationService.getOrganizationBySubdomain(this._subdomain);
7890
8604
  }
8605
+ return this._organizationPromise;
7891
8606
  }
7892
- startCloudRecording() {
7893
- this.signalSocket.emit("start_recording", {
7894
- recording: "cloud",
8607
+ }
8608
+
8609
+ const API_BASE_URL = "https://api.whereby.dev" ;
8610
+ function createServices() {
8611
+ const credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
8612
+ const apiClient = new ApiClient({
8613
+ fetchDeviceCredentials: credentialsService.getCredentials.bind(credentialsService),
8614
+ baseUrl: API_BASE_URL,
8615
+ });
8616
+ const organizationService = new OrganizationService({ apiClient });
8617
+ const fetchOrganizationFromRoomUrl = (roomUrl) => {
8618
+ const roomUrlObj = new URL(roomUrl);
8619
+ const urls = fromLocation({ host: roomUrlObj.host });
8620
+ const organizationServiceCache = new OrganizationServiceCache({
8621
+ organizationService,
8622
+ subdomain: urls.subdomain,
7895
8623
  });
7896
- this.dispatchEvent(new RoomConnectionEvent("cloud_recording_request_started"));
7897
- }
7898
- stopCloudRecording() {
7899
- this.signalSocket.emit("stop_recording");
7900
- }
8624
+ return organizationServiceCache.fetchOrganization();
8625
+ };
8626
+ return {
8627
+ credentialsService,
8628
+ apiClient,
8629
+ organizationService,
8630
+ fetchOrganizationFromRoomUrl,
8631
+ };
7901
8632
  }
7902
8633
 
7903
- const initialState = {
8634
+ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants) => {
8635
+ const state = {
8636
+ chatMessages,
8637
+ cloudRecording: cloudRecording.isRecording ? { status: "recording" } : undefined,
8638
+ localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
8639
+ localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
8640
+ remoteParticipants,
8641
+ screenshares,
8642
+ connectionStatus,
8643
+ liveStream: streaming.isStreaming
8644
+ ? {
8645
+ status: "streaming",
8646
+ startedAt: streaming.startedAt,
8647
+ }
8648
+ : undefined,
8649
+ waitingParticipants,
8650
+ };
8651
+ return state;
8652
+ });
8653
+
8654
+ const sdkVersion = "2.0.0-beta4";
8655
+
8656
+ const initialState$1 = {
7904
8657
  chatMessages: [],
7905
8658
  remoteParticipants: [],
7906
8659
  connectionStatus: "initializing",
7907
8660
  screenshares: [],
7908
8661
  waitingParticipants: [],
7909
8662
  };
7910
- function updateParticipant(remoteParticipants, participantId, updates) {
7911
- const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
7912
- if (!existingParticipant) {
7913
- return remoteParticipants;
7914
- }
7915
- const index = remoteParticipants.indexOf(existingParticipant);
7916
- return [
7917
- ...remoteParticipants.slice(0, index),
7918
- Object.assign(Object.assign({}, existingParticipant), updates),
7919
- ...remoteParticipants.slice(index + 1),
7920
- ];
7921
- }
7922
- // omit the internal props
7923
- function convertRemoteParticipantToRemoteParticipantState(p) {
7924
- return {
7925
- displayName: p.displayName,
7926
- id: p.id,
7927
- isAudioEnabled: p.isAudioEnabled,
7928
- isLocalParticipant: p.isLocalParticipant,
7929
- isVideoEnabled: p.isVideoEnabled,
7930
- stream: p.stream,
7931
- };
7932
- }
7933
- function addScreenshare(screenshares, screenshare) {
7934
- const existingScreenshare = screenshares.find((ss) => ss.id === screenshare.id);
7935
- if (existingScreenshare) {
7936
- return screenshares;
7937
- }
7938
- return [...screenshares, screenshare];
7939
- }
7940
- function reducer(state, action) {
7941
- switch (action.type) {
7942
- case "CHAT_MESSAGE":
7943
- return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload] });
7944
- case "CLOUD_RECORDING_REQUEST_STARTED":
7945
- return Object.assign(Object.assign({}, state), { cloudRecording: {
7946
- status: "requested",
7947
- } });
7948
- case "CLOUD_RECORDING_STARTED":
7949
- return Object.assign(Object.assign({}, state), { cloudRecording: {
7950
- status: action.payload.status,
7951
- startedAt: action.payload.startedAt,
7952
- } });
7953
- case "CLOUD_RECORDING_STARTED_ERROR":
7954
- return Object.assign(Object.assign({}, state), { cloudRecording: {
7955
- status: action.payload.status,
7956
- error: action.payload.error,
7957
- } });
7958
- case "CLOUD_RECORDING_STOPPED":
7959
- delete state.cloudRecording;
7960
- return Object.assign({}, state);
7961
- case "ROOM_JOINED":
7962
- return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, waitingParticipants: action.payload.waitingParticipants, connectionStatus: "connected" });
7963
- case "CONNECTION_STATUS_CHANGED":
7964
- return Object.assign(Object.assign({}, state), { connectionStatus: action.payload.connectionStatus });
7965
- case "PARTICIPANT_AUDIO_ENABLED":
7966
- return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
7967
- isAudioEnabled: action.payload.isAudioEnabled,
7968
- }) });
7969
- case "PARTICIPANT_JOINED":
7970
- return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants, action.payload.paritipant] });
7971
- case "PARTICIPANT_LEFT":
7972
- return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants.filter((p) => p.id !== action.payload.participantId)] });
7973
- case "PARTICIPANT_STREAM_ADDED":
7974
- return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
7975
- stream: action.payload.stream,
7976
- }) });
7977
- case "PARTICIPANT_VIDEO_ENABLED":
7978
- return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
7979
- isVideoEnabled: action.payload.isVideoEnabled,
7980
- }) });
7981
- case "PARTICIPANT_METADATA_CHANGED":
7982
- return Object.assign(Object.assign({}, state), { remoteParticipants: [
7983
- ...state.remoteParticipants.map((p) => p.id === action.payload.participantId ? Object.assign(Object.assign({}, p), { displayName: action.payload.displayName }) : p),
7984
- ] });
7985
- case "LOCAL_CLIENT_DISPLAY_NAME_CHANGED":
7986
- if (!state.localParticipant)
7987
- return state;
7988
- return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { displayName: action.payload.displayName }) });
7989
- case "SCREENSHARE_STARTED":
7990
- return Object.assign(Object.assign({}, state), { screenshares: addScreenshare(state.screenshares, {
7991
- participantId: action.payload.participantId,
7992
- id: action.payload.id,
7993
- hasAudioTrack: action.payload.hasAudioTrack,
7994
- stream: action.payload.stream,
7995
- isLocal: action.payload.isLocal,
7996
- }) });
7997
- case "SCREENSHARE_STOPPED":
7998
- return Object.assign(Object.assign({}, state), { screenshares: state.screenshares.filter((ss) => ss.id !== action.payload.id) });
7999
- case "LOCAL_SCREENSHARE_START_ERROR":
8000
- return Object.assign(Object.assign({}, state), { localScreenshareStatus: undefined });
8001
- case "LOCAL_SCREENSHARE_STARTING":
8002
- return Object.assign(Object.assign({}, state), { localScreenshareStatus: "starting" });
8003
- case "LOCAL_SCREENSHARE_STARTED":
8004
- return Object.assign(Object.assign({}, state), { localScreenshareStatus: "active" });
8005
- case "LOCAL_SCREENSHARE_STOPPED":
8006
- return Object.assign(Object.assign({}, state), { localScreenshareStatus: undefined, screenshares: state.screenshares.filter((ss) => !ss.isLocal) });
8007
- case "LOCAL_CAMERA_ENABLED":
8008
- if (!state.localParticipant)
8009
- return state;
8010
- return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { isVideoEnabled: action.payload }) });
8011
- case "LOCAL_MICROPHONE_ENABLED":
8012
- if (!state.localParticipant)
8013
- return state;
8014
- return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { isAudioEnabled: action.payload }) });
8015
- case "STREAMING_STARTED":
8016
- return Object.assign(Object.assign({}, state), { liveStream: {
8017
- status: action.payload.status,
8018
- startedAt: action.payload.startedAt,
8019
- } });
8020
- case "STREAMING_STOPPED":
8021
- delete state.liveStream;
8022
- return Object.assign({}, state);
8023
- case "WAITING_PARTICIPANT_JOINED":
8024
- return Object.assign(Object.assign({}, state), { waitingParticipants: [
8025
- ...state.waitingParticipants,
8026
- { id: action.payload.participantId, displayName: action.payload.displayName },
8027
- ] });
8028
- case "WAITING_PARTICIPANT_LEFT":
8029
- return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((wp) => wp.id !== action.payload.participantId) });
8030
- default:
8031
- throw state;
8032
- }
8033
- }
8034
8663
  const defaultRoomConnectionOptions = {
8035
8664
  localMediaOptions: {
8036
8665
  audio: true,
@@ -8038,207 +8667,139 @@ const defaultRoomConnectionOptions = {
8038
8667
  },
8039
8668
  };
8040
8669
  function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectionOptions) {
8041
- const [roomConnection] = useState(() => {
8042
- var _a;
8043
- return new RoomConnection(roomUrl, Object.assign(Object.assign({}, roomConnectionOptions), { localMedia: ((_a = roomConnectionOptions === null || roomConnectionOptions === void 0 ? void 0 : roomConnectionOptions.localMedia) === null || _a === void 0 ? void 0 : _a._ref) || undefined }));
8670
+ const [store] = React.useState(() => {
8671
+ if (roomConnectionOptions.localMedia) {
8672
+ return roomConnectionOptions.localMedia.store;
8673
+ }
8674
+ const services = createServices();
8675
+ return createStore({ injectServices: services });
8044
8676
  });
8045
- const [state, dispatch] = useReducer(reducer, initialState);
8046
- function createEventListener(eventName, listener, options) {
8047
- return {
8048
- eventName,
8049
- listener,
8050
- options,
8677
+ const [boundVideoView, setBoundVideoView] = React.useState();
8678
+ const [roomConnectionState, setRoomConnectionState] = React.useState(initialState$1);
8679
+ React.useEffect(() => {
8680
+ const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
8681
+ const url = new URL(roomUrl); // Throw if invalid Whereby room url
8682
+ const searchParams = new URLSearchParams(url.search);
8683
+ const roomKey = searchParams.get("roomKey");
8684
+ store.dispatch(doAppJoin({
8685
+ displayName: roomConnectionOptions.displayName || "Guest",
8686
+ localMediaOptions: roomConnectionOptions.localMedia
8687
+ ? undefined
8688
+ : roomConnectionOptions.localMediaOptions,
8689
+ roomKey,
8690
+ roomUrl,
8691
+ sdkVersion: sdkVersion,
8692
+ externalId: roomConnectionOptions.externalId || null,
8693
+ }));
8694
+ return () => {
8695
+ unsubscribe();
8696
+ store.dispatch(appLeft());
8051
8697
  };
8052
- }
8053
- const eventListeners = React.useMemo(() => [
8054
- createEventListener("chat_message", (e) => {
8055
- dispatch({ type: "CHAT_MESSAGE", payload: e.detail });
8056
- }),
8057
- createEventListener("cloud_recording_request_started", () => {
8058
- dispatch({ type: "CLOUD_RECORDING_REQUEST_STARTED" });
8059
- }),
8060
- createEventListener("cloud_recording_started", (e) => {
8061
- const { status, startedAt } = e.detail;
8062
- dispatch({ type: "CLOUD_RECORDING_STARTED", payload: { status, startedAt } });
8063
- }),
8064
- createEventListener("cloud_recording_started_error", (e) => {
8065
- dispatch({ type: "CLOUD_RECORDING_STARTED_ERROR", payload: e.detail });
8066
- }),
8067
- createEventListener("cloud_recording_stopped", () => {
8068
- dispatch({ type: "CLOUD_RECORDING_STOPPED" });
8069
- }),
8070
- createEventListener("local_camera_enabled", (e) => {
8071
- const { enabled } = e.detail;
8072
- dispatch({ type: "LOCAL_CAMERA_ENABLED", payload: enabled });
8073
- }),
8074
- createEventListener("local_microphone_enabled", (e) => {
8075
- const { enabled } = e.detail;
8076
- dispatch({ type: "LOCAL_MICROPHONE_ENABLED", payload: enabled });
8077
- }),
8078
- createEventListener("participant_audio_enabled", (e) => {
8079
- const { participantId, isAudioEnabled } = e.detail;
8080
- dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
8081
- }),
8082
- createEventListener("participant_joined", (e) => {
8083
- const { remoteParticipant } = e.detail;
8084
- dispatch({
8085
- type: "PARTICIPANT_JOINED",
8086
- payload: {
8087
- paritipant: convertRemoteParticipantToRemoteParticipantState(remoteParticipant),
8088
- },
8089
- });
8090
- }),
8091
- createEventListener("participant_left", (e) => {
8092
- const { participantId } = e.detail;
8093
- dispatch({ type: "PARTICIPANT_LEFT", payload: { participantId } });
8094
- }),
8095
- createEventListener("participant_metadata_changed", (e) => {
8096
- const { participantId, displayName } = e.detail;
8097
- dispatch({ type: "PARTICIPANT_METADATA_CHANGED", payload: { participantId, displayName } });
8098
- }),
8099
- createEventListener("participant_stream_added", (e) => {
8100
- const { participantId, stream } = e.detail;
8101
- dispatch({ type: "PARTICIPANT_STREAM_ADDED", payload: { participantId, stream } });
8102
- }),
8103
- createEventListener("participant_video_enabled", (e) => {
8104
- const { participantId, isVideoEnabled } = e.detail;
8105
- dispatch({ type: "PARTICIPANT_VIDEO_ENABLED", payload: { participantId, isVideoEnabled } });
8106
- }),
8107
- createEventListener("connection_status_changed", (e) => {
8108
- const { connectionStatus } = e.detail;
8109
- dispatch({
8110
- type: "CONNECTION_STATUS_CHANGED",
8111
- payload: { connectionStatus },
8112
- });
8113
- }),
8114
- createEventListener("room_joined", (e) => {
8115
- const { localParticipant, remoteParticipants, waitingParticipants } = e.detail;
8116
- dispatch({
8117
- type: "ROOM_JOINED",
8118
- payload: {
8119
- localParticipant,
8120
- remoteParticipants: remoteParticipants.map(convertRemoteParticipantToRemoteParticipantState),
8121
- waitingParticipants,
8122
- },
8123
- });
8124
- }),
8125
- createEventListener("screenshare_started", (e) => {
8126
- const { participantId, stream, id, hasAudioTrack, isLocal } = e.detail;
8127
- dispatch({
8128
- type: "SCREENSHARE_STARTED",
8129
- payload: { participantId, stream, id, hasAudioTrack, isLocal },
8130
- });
8131
- }),
8132
- createEventListener("screenshare_stopped", (e) => {
8133
- var _a;
8134
- const { participantId, id } = e.detail;
8135
- dispatch({
8136
- type: "SCREENSHARE_STOPPED",
8137
- payload: { participantId, id },
8138
- });
8139
- // dispach LOCAL_SCREENSHARE_STOPPED here because the exposed
8140
- // stopScreenshare method is not called when the screenshare is
8141
- // stopped by the browser's native stop screenshare button
8142
- if (participantId === ((_a = state.localParticipant) === null || _a === void 0 ? void 0 : _a.id)) {
8143
- dispatch({ type: "LOCAL_SCREENSHARE_STOPPED" });
8144
- }
8145
- }),
8146
- createEventListener("streaming_started", (e) => {
8147
- const { status, startedAt } = e.detail;
8148
- dispatch({ type: "STREAMING_STARTED", payload: { status, startedAt } });
8149
- }),
8150
- createEventListener("streaming_stopped", () => {
8151
- dispatch({ type: "STREAMING_STOPPED" });
8152
- }),
8153
- createEventListener("waiting_participant_joined", (e) => {
8154
- const { participantId, displayName } = e.detail;
8155
- dispatch({
8156
- type: "WAITING_PARTICIPANT_JOINED",
8157
- payload: { participantId, displayName },
8158
- });
8159
- }),
8160
- createEventListener("waiting_participant_left", (e) => {
8161
- const { participantId } = e.detail;
8162
- dispatch({
8163
- type: "WAITING_PARTICIPANT_LEFT",
8164
- payload: { participantId },
8698
+ }, []);
8699
+ React.useEffect(() => {
8700
+ if (store && !boundVideoView) {
8701
+ setBoundVideoView(() => (props) => {
8702
+ return React.createElement(VideoView, Object.assign({}, props, {
8703
+ onResize: ({ stream, width, height, }) => {
8704
+ store.dispatch(doRtcReportStreamResolution({
8705
+ streamId: stream.id,
8706
+ width,
8707
+ height,
8708
+ }));
8709
+ },
8710
+ }));
8165
8711
  });
8166
- }),
8167
- ], []);
8712
+ }
8713
+ }, [store, boundVideoView]);
8714
+ const sendChatMessage = React.useCallback((text) => store.dispatch(doSendChatMessage({ text })), [store]);
8715
+ const knock = React.useCallback(() => store.dispatch(doKnockRoom()), [store]);
8716
+ const setDisplayName = React.useCallback((displayName) => store.dispatch(doSetDisplayName({ displayName })), [store]);
8717
+ const toggleCamera = React.useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
8718
+ const toggleMicrophone = React.useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
8719
+ const acceptWaitingParticipant = React.useCallback((participantId) => store.dispatch(doAcceptWaitingParticipant({ participantId })), [store]);
8720
+ const rejectWaitingParticipant = React.useCallback((participantId) => store.dispatch(doRejectWaitingParticipant({ participantId })), [store]);
8721
+ const startCloudRecording = React.useCallback(() => store.dispatch(doStartCloudRecording()), [store]);
8722
+ const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
8723
+ const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
8724
+ const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
8725
+ return {
8726
+ state: roomConnectionState,
8727
+ actions: {
8728
+ sendChatMessage,
8729
+ knock,
8730
+ setDisplayName,
8731
+ toggleCamera,
8732
+ toggleMicrophone,
8733
+ acceptWaitingParticipant,
8734
+ rejectWaitingParticipant,
8735
+ startCloudRecording,
8736
+ startScreenshare,
8737
+ stopCloudRecording,
8738
+ stopScreenshare,
8739
+ },
8740
+ components: {
8741
+ VideoView: boundVideoView || VideoView,
8742
+ },
8743
+ _ref: store,
8744
+ };
8745
+ }
8746
+
8747
+ const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectLocalMediaStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, (cameraDeviceError, cameraDevices, currentCameraDeviceId, currentMicrophoneDeviceId, isSettingCameraDevice, isSettingMicrophoneDevice, isStarting, localStream, microphoneDeviceError, microphoneDevices, speakerDevices, startError) => {
8748
+ const state = {
8749
+ cameraDeviceError,
8750
+ cameraDevices,
8751
+ currentCameraDeviceId,
8752
+ currentMicrophoneDeviceId,
8753
+ isSettingCameraDevice,
8754
+ isSettingMicrophoneDevice,
8755
+ isStarting,
8756
+ localStream,
8757
+ microphoneDeviceError,
8758
+ microphoneDevices,
8759
+ speakerDevices,
8760
+ startError,
8761
+ };
8762
+ return state;
8763
+ });
8764
+
8765
+ const initialState = {
8766
+ cameraDeviceError: null,
8767
+ cameraDevices: [],
8768
+ isSettingCameraDevice: false,
8769
+ isSettingMicrophoneDevice: false,
8770
+ isStarting: false,
8771
+ microphoneDeviceError: null,
8772
+ microphoneDevices: [],
8773
+ speakerDevices: [],
8774
+ startError: null,
8775
+ };
8776
+ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
8777
+ const [store] = useState(() => {
8778
+ const services = createServices();
8779
+ return createStore({ injectServices: services });
8780
+ });
8781
+ const [localMediaState, setLocalMediaState] = useState(initialState);
8168
8782
  useEffect(() => {
8169
- eventListeners.forEach(({ eventName, listener }) => {
8170
- roomConnection.addEventListener(eventName, listener);
8171
- });
8172
- roomConnection.join();
8783
+ const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
8784
+ store.dispatch(doStartLocalMedia(optionsOrStream));
8173
8785
  return () => {
8174
- eventListeners.forEach(({ eventName, listener }) => {
8175
- roomConnection.removeEventListener(eventName, listener);
8176
- });
8177
- roomConnection.leave();
8786
+ unsubscribe();
8787
+ store.dispatch(doStopLocalMedia());
8178
8788
  };
8179
8789
  }, []);
8790
+ const setCameraDevice = useCallback((deviceId) => store.dispatch(setCurrentCameraDeviceId({ deviceId })), [store]);
8791
+ const setMicrophoneDevice = useCallback((deviceId) => store.dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [store]);
8792
+ const toggleCamera = useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
8793
+ const toggleMicrophone = useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
8180
8794
  return {
8181
- state,
8795
+ state: localMediaState,
8182
8796
  actions: {
8183
- knock: () => {
8184
- roomConnection.knock();
8185
- },
8186
- sendChatMessage: (text) => {
8187
- roomConnection.sendChatMessage(text);
8188
- },
8189
- setDisplayName: (displayName) => {
8190
- roomConnection.setDisplayName(displayName);
8191
- dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
8192
- },
8193
- toggleCamera: (enabled) => {
8194
- roomConnection.localMedia.toggleCameraEnabled(enabled);
8195
- },
8196
- toggleMicrophone: (enabled) => {
8197
- roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
8198
- },
8199
- acceptWaitingParticipant: (participantId) => {
8200
- roomConnection.acceptWaitingParticipant(participantId);
8201
- },
8202
- rejectWaitingParticipant: (participantId) => {
8203
- roomConnection.rejectWaitingParticipant(participantId);
8204
- },
8205
- startCloudRecording: () => {
8206
- var _a;
8207
- // don't start recording if it's already started or requested
8208
- if (state.cloudRecording && ["recording", "requested"].includes((_a = state.cloudRecording) === null || _a === void 0 ? void 0 : _a.status)) {
8209
- return;
8210
- }
8211
- roomConnection.startCloudRecording();
8212
- },
8213
- stopCloudRecording: () => {
8214
- roomConnection.stopCloudRecording();
8215
- },
8216
- startScreenshare: () => __awaiter(this, void 0, void 0, function* () {
8217
- dispatch({ type: "LOCAL_SCREENSHARE_STARTING" });
8218
- try {
8219
- yield roomConnection.startScreenshare();
8220
- dispatch({ type: "LOCAL_SCREENSHARE_STARTED" });
8221
- }
8222
- catch (error) {
8223
- dispatch({ type: "LOCAL_SCREENSHARE_START_ERROR", payload: error });
8224
- }
8225
- }),
8226
- stopScreenshare: () => {
8227
- roomConnection.stopScreenshare();
8228
- },
8229
- },
8230
- components: {
8231
- VideoView: (props) => React.createElement(VideoView, Object.assign({}, props, {
8232
- onResize: ({ stream, width, height, }) => {
8233
- roomConnection.updateStreamResolution({
8234
- streamId: stream.id,
8235
- width,
8236
- height,
8237
- });
8238
- },
8239
- })),
8797
+ setCameraDevice,
8798
+ setMicrophoneDevice,
8799
+ toggleCameraEnabled: toggleCamera,
8800
+ toggleMicrophoneEnabled: toggleMicrophone,
8240
8801
  },
8241
- _ref: roomConnection,
8802
+ store,
8242
8803
  };
8243
8804
  }
8244
8805