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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,522 @@ 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
+ roomSessionEnded: createSignalEventAction("roomSessionEnded"),
222
+ screenshareStarted: createSignalEventAction("screenshareStarted"),
223
+ screenshareStopped: createSignalEventAction("screenshareStopped"),
224
+ streamingStopped: createSignalEventAction("streamingStopped"),
225
+ videoEnabled: createSignalEventAction("videoEnabled"),
120
226
  };
121
227
 
228
+ const initialState$f = {
229
+ isFetching: false,
230
+ data: null,
231
+ };
232
+ const deviceCredentialsSlice = createSlice({
233
+ name: "deviceCredentials",
234
+ initialState: initialState$f,
235
+ reducers: {},
236
+ extraReducers: (builder) => {
237
+ builder.addCase(doGetDeviceCredentials.pending, (state) => {
238
+ return Object.assign(Object.assign({}, state), { isFetching: true });
239
+ });
240
+ builder.addCase(doGetDeviceCredentials.fulfilled, (state, action) => {
241
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
242
+ });
243
+ builder.addCase(doGetDeviceCredentials.rejected, (state) => {
244
+ // not handled in the pwa either.
245
+ return Object.assign(Object.assign({}, state), { isFetching: true });
246
+ });
247
+ },
248
+ });
249
+ /**
250
+ * Action creators
251
+ */
252
+ const doGetDeviceCredentials = createAppAsyncThunk("deviceCredentials/doGetDeviceCredentials", (payload, { extra }) => __awaiter(void 0, void 0, void 0, function* () {
253
+ try {
254
+ const deviceCredentials = yield extra.services.credentialsService.getCredentials();
255
+ return deviceCredentials;
256
+ }
257
+ catch (error) {
258
+ console.error(error);
259
+ }
260
+ }));
261
+ /**
262
+ * Selectors
263
+ */
264
+ const selectDeviceCredentialsRaw = (state) => state.deviceCredentials;
265
+ 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; };
266
+ /**
267
+ * Reactors
268
+ */
269
+ const selectShouldFetchDeviceCredentials = createSelector(selectAppWantsToJoin, selectDeviceCredentialsRaw, (wantsToJoin, deviceCredentials) => {
270
+ if (wantsToJoin && !deviceCredentials.isFetching && !deviceCredentials.data) {
271
+ return true;
272
+ }
273
+ return false;
274
+ });
275
+ createReactor([selectShouldFetchDeviceCredentials], ({ dispatch }, shouldFetchDeviceCredentials) => {
276
+ if (shouldFetchDeviceCredentials) {
277
+ dispatch(doGetDeviceCredentials());
278
+ }
279
+ });
280
+
281
+ const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
282
+
283
+ /**
284
+ * Wrapper class that extends the Socket.IO client library.
285
+ */
286
+ class ServerSocket {
287
+ constructor(hostName, optionsOverrides) {
288
+ this._socket = io(hostName, {
289
+ path: DEFAULT_SOCKET_PATH,
290
+ randomizationFactor: 0.5,
291
+ reconnectionDelay: 250,
292
+ reconnectionDelayMax: 5000,
293
+ timeout: 5000,
294
+ transports: ["websocket"],
295
+ withCredentials: true,
296
+ ...optionsOverrides,
297
+ });
298
+ this._socket.io.on("reconnect", () => {
299
+ this._socket.sendBuffer = [];
300
+ });
301
+ this._socket.io.on("reconnect_attempt", () => {
302
+ if (this._wasConnectedUsingWebsocket) {
303
+ this._socket.io.opts.transports = ["websocket"];
304
+ // only fallback to polling if not safari
305
+ // safari doesn't support cross doamin cookies making load-balancer stickiness not work
306
+ // and if socket.io reconnects to another signal instance with polling it will fail
307
+ // remove if we move signal to a whereby.com subdomain
308
+ if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
309
+ } else {
310
+ this._socket.io.opts.transports = ["websocket", "polling"];
311
+ }
312
+ });
313
+ this._socket.on("connect", () => {
314
+ const transport = this.getTransport();
315
+ if (transport === "websocket") {
316
+ this._wasConnectedUsingWebsocket = true;
317
+ }
318
+ });
319
+ }
320
+
321
+ connect() {
322
+ if (this.isConnected() || this.isConnecting()) {
323
+ return;
324
+ }
325
+ this._socket.open();
326
+ }
327
+
328
+ disconnect() {
329
+ this._socket.disconnect();
330
+ }
331
+
332
+ disconnectOnConnect() {
333
+ this._socket.once("connect", () => {
334
+ this._socket.disconnect();
335
+ });
336
+ }
337
+
338
+ emit() {
339
+ this._socket.emit.apply(this._socket, arguments);
340
+ }
341
+
342
+ emitIfConnected(eventName, data) {
343
+ if (!this.isConnected()) {
344
+ return;
345
+ }
346
+ this.emit(eventName, data);
347
+ }
348
+
349
+ getTransport() {
350
+ return (
351
+ this._socket &&
352
+ this._socket.io &&
353
+ this._socket.io.engine &&
354
+ this._socket.io.engine.transport &&
355
+ this._socket.io.engine.transport.name
356
+ );
357
+ }
358
+
359
+ getManager() {
360
+ return this._socket.io;
361
+ }
362
+
363
+ isConnecting() {
364
+ return this._socket && this._socket.connecting;
365
+ }
366
+
367
+ isConnected() {
368
+ return this._socket && this._socket.connected;
369
+ }
370
+
371
+ /**
372
+ * Register a new event handler.
373
+ *
374
+ * @param {string} eventName - Name of the event to listen for.
375
+ * @param {function} handler - The callback function that should be called for the event.
376
+ * @returns {function} Function to deregister the listener.
377
+ */
378
+ on(eventName, handler) {
379
+ this._socket.on(eventName, handler);
380
+
381
+ return () => {
382
+ this._socket.off(eventName, handler);
383
+ };
384
+ }
385
+
386
+ /**
387
+ * Register a new event handler to be triggered only once.
388
+ *
389
+ * @param {string} eventName - Name of the event to listen for.
390
+ * @param {function} handler - The function that should be called for the event.
391
+ */
392
+ once(eventName, handler) {
393
+ this._socket.once(eventName, handler);
394
+ }
395
+
396
+ /**
397
+ * Deregister an event handler.
398
+ *
399
+ * @param {string} eventName - Name of the event the handler is registered for.
400
+ * @param {function} handler - The callback that will be deregistered.
401
+ */
402
+ off(eventName, handler) {
403
+ this._socket.off(eventName, handler);
404
+ }
405
+ }
406
+
407
+ function forwardSocketEvents(socket, dispatch) {
408
+ socket.on("room_joined", (payload) => dispatch(signalEvents.roomJoined(payload)));
409
+ socket.on("new_client", (payload) => dispatch(signalEvents.newClient(payload)));
410
+ socket.on("client_left", (payload) => dispatch(signalEvents.clientLeft(payload)));
411
+ socket.on("audio_enabled", (payload) => dispatch(signalEvents.audioEnabled(payload)));
412
+ socket.on("video_enabled", (payload) => dispatch(signalEvents.videoEnabled(payload)));
413
+ socket.on("client_metadata_received", (payload) => dispatch(signalEvents.clientMetadataReceived(payload)));
414
+ socket.on("chat_message", (payload) => dispatch(signalEvents.chatMessage(payload)));
415
+ socket.on("disconnect", () => dispatch(signalEvents.disconnect()));
416
+ socket.on("room_knocked", (payload) => dispatch(signalEvents.roomKnocked(payload)));
417
+ socket.on("room_session_ended", (payload) => dispatch(signalEvents.roomSessionEnded(payload)));
418
+ socket.on("knocker_left", (payload) => dispatch(signalEvents.knockerLeft(payload)));
419
+ socket.on("knock_handled", (payload) => dispatch(signalEvents.knockHandled(payload)));
420
+ socket.on("screenshare_started", (payload) => dispatch(signalEvents.screenshareStarted(payload)));
421
+ socket.on("screenshare_stopped", (payload) => dispatch(signalEvents.screenshareStopped(payload)));
422
+ socket.on("cloud_recording_started", (payload) => dispatch(signalEvents.cloudRecordingStarted(payload)));
423
+ socket.on("cloud_recording_stopped", () => dispatch(signalEvents.cloudRecordingStopped()));
424
+ socket.on("streaming_stopped", () => dispatch(signalEvents.streamingStopped()));
425
+ }
426
+ const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
427
+ function createSocket() {
428
+ const parsedUrl = new URL(SIGNAL_BASE_URL);
429
+ const socketHost = parsedUrl.origin;
430
+ const socketOverrides = {
431
+ autoConnect: false,
432
+ };
433
+ return new ServerSocket(socketHost, socketOverrides);
434
+ }
435
+ const initialState$e = {
436
+ deviceIdentified: false,
437
+ isIdentifyingDevice: false,
438
+ status: "",
439
+ socket: null,
440
+ };
441
+ const signalConnectionSlice = createSlice({
442
+ name: "signalConnection",
443
+ initialState: initialState$e,
444
+ reducers: {
445
+ socketConnecting: (state) => {
446
+ return Object.assign(Object.assign({}, state), { status: "connecting" });
447
+ },
448
+ socketConnected: (state, action) => {
449
+ return Object.assign(Object.assign({}, state), { socket: action.payload, status: "connected" });
450
+ },
451
+ socketDisconnected: (state) => {
452
+ return Object.assign(Object.assign({}, state), { status: "disconnected" });
453
+ },
454
+ socketReconnecting: (state) => {
455
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
456
+ },
457
+ deviceIdentifying: (state) => {
458
+ return Object.assign(Object.assign({}, state), { isIdentifyingDevice: true });
459
+ },
460
+ deviceIdentified: (state) => {
461
+ return Object.assign(Object.assign({}, state), { deviceIdentified: true, isIdentifyingDevice: false });
462
+ },
463
+ },
464
+ });
465
+ const { deviceIdentifying, deviceIdentified, socketConnected, socketConnecting, socketDisconnected } = signalConnectionSlice.actions;
466
+ /**
467
+ * Action creators
468
+ */
469
+ const doSignalSocketConnect = createAppThunk(() => {
470
+ return (dispatch, getState) => {
471
+ if (selectSignalConnectionSocket(getState())) {
472
+ return;
473
+ }
474
+ dispatch(socketConnecting());
475
+ const socket = createSocket();
476
+ socket.on("connect", () => dispatch(socketConnected(socket)));
477
+ socket.on("device_identified", () => dispatch(deviceIdentified()));
478
+ socket.getManager().on("reconnect", () => dispatch(doSignalReconnect()));
479
+ forwardSocketEvents(socket, dispatch);
480
+ socket.connect();
481
+ };
482
+ });
483
+ const doSignalIdentifyDevice = createAppThunk(({ deviceCredentials }) => (dispatch, getState) => {
484
+ const state = getState();
485
+ const signalSocket = selectSignalConnectionSocket(state);
486
+ if (!signalSocket) {
487
+ return;
488
+ }
489
+ signalSocket.emit("identify_device", { deviceCredentials });
490
+ dispatch(deviceIdentifying());
491
+ });
492
+ const doSignalDisconnect = createAppThunk(() => (dispatch, getState) => {
493
+ const socket = selectSignalConnectionRaw(getState()).socket;
494
+ socket === null || socket === void 0 ? void 0 : socket.emit("leave_room");
495
+ socket === null || socket === void 0 ? void 0 : socket.disconnect();
496
+ dispatch(socketDisconnected());
497
+ });
498
+ const doSignalReconnect = createAppThunk(() => (dispatch, getState) => {
499
+ const deviceCredentialsRaw = selectDeviceCredentialsRaw(getState());
500
+ dispatch(socketReconnecting());
501
+ if (deviceCredentialsRaw.data) {
502
+ dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
503
+ }
504
+ });
505
+ const { socketReconnecting } = signalConnectionSlice.actions;
506
+ /**
507
+ * Selectors
508
+ */
509
+ const selectSignalConnectionRaw = (state) => state.signalConnection;
510
+ const selectSignalIsIdentifyingDevice = (state) => state.signalConnection.isIdentifyingDevice;
511
+ const selectSignalConnectionDeviceIdentified = (state) => state.signalConnection.deviceIdentified;
512
+ const selectSignalStatus = (state) => state.signalConnection.status;
513
+ const selectSignalConnectionSocket = (state) => state.signalConnection.socket;
514
+ /**
515
+ * Reactors
516
+ */
517
+ startAppListening({
518
+ actionCreator: appLeft,
519
+ effect: (_, { dispatch }) => {
520
+ dispatch(doSignalDisconnect());
521
+ },
522
+ });
523
+ const selectShouldConnectSignal = createSelector(selectAppWantsToJoin, selectSignalStatus, (wantsToJoin, signalStatus) => {
524
+ if (wantsToJoin && ["", "reconnect"].includes(signalStatus)) {
525
+ return true;
526
+ }
527
+ return false;
528
+ });
529
+ createReactor([selectShouldConnectSignal], ({ dispatch }, shouldConnectSignal) => {
530
+ if (shouldConnectSignal) {
531
+ dispatch(doSignalSocketConnect());
532
+ }
533
+ });
534
+ const selectShouldIdentifyDevice = createSelector(selectDeviceCredentialsRaw, selectSignalStatus, selectSignalConnectionDeviceIdentified, selectSignalIsIdentifyingDevice, (deviceCredentialsRaw, signalStatus, deviceIdentified, isIdentifyingDevice) => {
535
+ if (deviceCredentialsRaw.data && signalStatus === "connected" && !deviceIdentified && !isIdentifyingDevice) {
536
+ return true;
537
+ }
538
+ return false;
539
+ });
540
+ createReactor([selectShouldIdentifyDevice, selectDeviceCredentialsRaw], ({ dispatch }, shouldIdentifyDevice, deviceCredentialsRaw) => {
541
+ if (shouldIdentifyDevice && deviceCredentialsRaw.data) {
542
+ dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
543
+ }
544
+ });
545
+
546
+ const initialState$d = {
547
+ chatMessages: [],
548
+ };
549
+ const chatSlice = createSlice({
550
+ name: "chat",
551
+ initialState: initialState$d,
552
+ reducers: {},
553
+ extraReducers(builder) {
554
+ builder.addCase(signalEvents.chatMessage, (state, action) => {
555
+ const message = {
556
+ senderId: action.payload.senderId,
557
+ timestamp: action.payload.timestamp,
558
+ text: action.payload.text,
559
+ };
560
+ return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, message] });
561
+ });
562
+ },
563
+ });
564
+ /**
565
+ * Action creators
566
+ */
567
+ const doSendChatMessage = createAppThunk((payload) => (_, getState) => {
568
+ const state = getState();
569
+ const socket = selectSignalConnectionRaw(state).socket;
570
+ socket === null || socket === void 0 ? void 0 : socket.emit("chat_message", { text: payload.text });
571
+ });
572
+ const selectChatMessages = (state) => state.chat.chatMessages;
573
+
574
+ const initialState$c = {
575
+ isRecording: false,
576
+ error: null,
577
+ startedAt: undefined,
578
+ };
579
+ const cloudRecordingSlice = createSlice({
580
+ name: "cloudRecording",
581
+ initialState: initialState$c,
582
+ reducers: {
583
+ recordingRequestStarted: (state) => {
584
+ return Object.assign(Object.assign({}, state), { status: "requested" });
585
+ },
586
+ },
587
+ extraReducers: (builder) => {
588
+ builder.addCase(signalEvents.cloudRecordingStopped, (state) => {
589
+ return Object.assign(Object.assign({}, state), { isRecording: false, status: undefined });
590
+ });
591
+ builder.addCase(signalEvents.cloudRecordingStarted, (state, action) => {
592
+ const { payload } = action;
593
+ if (!payload.error) {
594
+ return state;
595
+ }
596
+ return Object.assign(Object.assign({}, state), { isRecording: false, status: "error", error: payload.error });
597
+ });
598
+ builder.addCase(signalEvents.newClient, (state, { payload }) => {
599
+ var _a;
600
+ const { client } = payload;
601
+ if (((_a = client.role) === null || _a === void 0 ? void 0 : _a.roleName) === "recorder") {
602
+ return Object.assign(Object.assign({}, state), { isRecording: true, status: "recording", startedAt: client.startedCloudRecordingAt
603
+ ? new Date(client.startedCloudRecordingAt).getTime()
604
+ : new Date().getTime() });
605
+ }
606
+ return state;
607
+ });
608
+ },
609
+ });
610
+ /**
611
+ * Action creators
612
+ */
613
+ const { recordingRequestStarted } = cloudRecordingSlice.actions;
614
+ const doStartCloudRecording = createAppThunk(() => (dispatch, getState) => {
615
+ const state = getState();
616
+ const socket = selectSignalConnectionRaw(state).socket;
617
+ const status = selectCloudRecordingStatus(state);
618
+ if (status && ["recording", "requested"].includes(status)) {
619
+ return;
620
+ }
621
+ socket === null || socket === void 0 ? void 0 : socket.emit("start_recording", {
622
+ recording: "cloud",
623
+ });
624
+ dispatch(recordingRequestStarted());
625
+ });
626
+ const doStopCloudRecording = createAppThunk(() => (dispatch, getState) => {
627
+ const state = getState();
628
+ const socket = selectSignalConnectionRaw(state).socket;
629
+ socket === null || socket === void 0 ? void 0 : socket.emit("stop_recording");
630
+ });
631
+ /**
632
+ * Selectors
633
+ */
634
+ const selectCloudRecordingRaw = (state) => state.cloudRecording;
635
+ const selectCloudRecordingStatus = (state) => state.cloudRecording.status;
636
+
122
637
  const isSafari = adapter.browserDetails.browser === "safari";
123
638
 
124
639
  // Expects format 640x360@25, returns [width, height, fps]
@@ -318,6 +833,63 @@ function getUserMedia(constraints) {
318
833
  });
319
834
  }
320
835
 
836
+ function getSettingsFromTrack(kind, track, devices, lastUsedId) {
837
+ let settings = { deviceId: null };
838
+
839
+ if (!track) {
840
+ // In SFU V2 the track can be closed by the RtcManager, so check if the
841
+ // last used deviceId still is available
842
+ if (lastUsedId && devices) {
843
+ settings.deviceId = devices.find((d) => d.deviceId === lastUsedId && d.kind === kind)?.deviceId;
844
+ }
845
+ return settings;
846
+ }
847
+
848
+ settings.label = track.label;
849
+
850
+ // if MediaTrackSettings.deviceId is supported (not firefox android/esr)
851
+ // https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings#Browser_compatibility
852
+ if (track.getSettings) {
853
+ settings = { ...settings, ...track.getSettings() };
854
+ }
855
+ if (settings.deviceId) return settings;
856
+ // if MediaTrackCapabilities is supported (not by firefox or samsung internet in general)
857
+ // https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#Browser_compatibility
858
+ if (track.getCapabilities) {
859
+ settings.deviceId = track.getCapabilities().deviceId;
860
+ }
861
+ if (settings.deviceId) return settings;
862
+
863
+ // Firefox ESR (guessing), has no way of getting deviceId, but
864
+ // it probably gives us label, let's use that to find it!
865
+ if (track.label && devices) {
866
+ settings.deviceId = devices.find((d) => track.label === d.label && d.kind === kind)?.deviceId;
867
+ }
868
+ if (settings.deviceId) return settings;
869
+
870
+ // Okay. As if the above wasn't hacky enough (it was), this
871
+ // is even more, basically see what we sent before
872
+ // It's really sad if we get down to this point.
873
+ settings.deviceId = track.getConstraints()?.deviceId?.exact;
874
+ settings.broken = 1; // just a hint
875
+ return settings;
876
+ }
877
+
878
+ /**
879
+ * Gets audio and video device data from stream
880
+ *
881
+ * @returns {{video: {deviceId}, audio: {deviceId}}} - the ids are null if not found
882
+ */
883
+ function getDeviceData({ audioTrack, videoTrack, devices, stoppedVideoTrack, lastAudioId, lastVideoId }) {
884
+ const usable = (d) => (d?.readyState === "live" ? d : null);
885
+ videoTrack = usable(videoTrack) || stoppedVideoTrack;
886
+ audioTrack = usable(audioTrack);
887
+ const video = getSettingsFromTrack("videoinput", videoTrack, devices, lastVideoId);
888
+ const audio = getSettingsFromTrack("audioinput", audioTrack, devices, lastAudioId);
889
+
890
+ return { video, audio };
891
+ }
892
+
321
893
  /**
322
894
  * Stops all tracks in a media stream.
323
895
  */
@@ -496,326 +1068,1118 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
496
1068
  return { error: error && addDetails(error), stream, replacedTracks };
497
1069
  }
498
1070
 
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;
1071
+ function compareLocalDevices(before, after) {
1072
+ const [beforeByKind, afterByKind] = [before, after].map((list) =>
1073
+ list
1074
+ .filter((device) => device.kind && device.deviceId)
1075
+ .reduce(
1076
+ (result, device) => ({
1077
+ ...result,
1078
+ [device.kind]: { ...result[device.kind], [device.deviceId]: device },
1079
+ }),
1080
+ {}
1081
+ )
1082
+ );
1083
+ const changesByKind = {};
1084
+ // find devices removed
1085
+ before.forEach((device) => {
1086
+ if (!device.kind || !device.deviceId) return;
1087
+ if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
1088
+ if (!afterByKind[device.kind] || !afterByKind[device.kind][device.deviceId]) {
1089
+ changesByKind[device.kind].removed[device.deviceId] = device;
1090
+ }
1091
+ });
1092
+ // find devices either added or changed
1093
+ after.forEach((device) => {
1094
+ if (!device.kind || !device.deviceId) return;
1095
+ if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
1096
+ if (!beforeByKind[device.kind] || !beforeByKind[device.kind][device.deviceId]) {
1097
+ changesByKind[device.kind].added[device.deviceId] = device;
1098
+ } else if (
1099
+ beforeByKind[device.kind][device.deviceId].label && // ignore when initially without label
1100
+ beforeByKind[device.kind][device.deviceId].label !== device.label
1101
+ ) {
1102
+ changesByKind[device.kind].changed[device.deviceId] = device;
1103
+ }
1104
+ });
1105
+ return changesByKind;
1106
+ }
1107
+
1108
+ function getUpdatedDevices({ oldDevices, newDevices, currentAudioId, currentVideoId, currentSpeakerId }) {
1109
+ const changesByKind = compareLocalDevices(oldDevices, newDevices);
1110
+ const changedDevices = {};
1111
+ const addedDevices = {};
1112
+ [
1113
+ ["audioinput", currentAudioId],
1114
+ ["videoinput", currentVideoId],
1115
+ ["audiooutput", currentSpeakerId],
1116
+ ].forEach(([kind, currentDeviceId]) => {
1117
+ const changes = changesByKind[kind];
1118
+ if (!changes) {
1119
+ return;
1120
+ }
1121
+ if (currentDeviceId) {
1122
+ // fall back to default if removed
1123
+ if (changes.removed[currentDeviceId]) {
1124
+ changedDevices[kind] = { deviceId: null }; // let browser decide
1125
+ if (kind === "audiooutput") {
1126
+ const fallbackSpeakerDevice = newDevices.find((d) => d.kind === "audiooutput");
1127
+ changedDevices[kind] = { deviceId: fallbackSpeakerDevice?.deviceId };
1128
+ }
1129
+ }
1130
+ // re-request if device has changed
1131
+ if (changes.changed[currentDeviceId]) {
1132
+ changedDevices[kind] = { deviceId: currentDeviceId };
1133
+ }
1134
+ }
1135
+ // request new device if added
1136
+ if (Object.keys(changes.added).length) {
1137
+ const [deviceAdded] = Object.keys(changes.added).slice(0, 1);
1138
+ const add = changes.added[deviceAdded];
1139
+ // device props are not enumerable (used in notificatio
1140
+ addedDevices[kind] = { deviceId: add.deviceId, label: add.label, kind: add.kind };
1141
+ }
1142
+ });
1143
+
1144
+ return { addedDevices, changedDevices };
1145
+ }
1146
+
1147
+ const initialState$b = {
1148
+ busyDeviceIds: [],
1149
+ cameraEnabled: false,
1150
+ devices: [],
1151
+ isSettingCameraDevice: false,
1152
+ isSettingMicrophoneDevice: false,
1153
+ isTogglingCamera: false,
1154
+ microphoneEnabled: false,
1155
+ status: "",
1156
+ isSwitchingStream: false,
1157
+ };
1158
+ const localMediaSlice = createSlice({
1159
+ name: "localMedia",
1160
+ initialState: initialState$b,
1161
+ reducers: {
1162
+ deviceBusy(state, action) {
1163
+ if (state.busyDeviceIds.includes(action.payload.deviceId)) {
1164
+ return state;
552
1165
  }
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 } }));
1166
+ return Object.assign(Object.assign({}, state), { busyDeviceIds: [...state.busyDeviceIds, action.payload.deviceId] });
1167
+ },
1168
+ toggleCameraEnabled(state, action) {
1169
+ var _a;
1170
+ return Object.assign(Object.assign({}, state), { cameraEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.cameraEnabled });
1171
+ },
1172
+ setCurrentCameraDeviceId(state, action) {
1173
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.deviceId });
1174
+ },
1175
+ toggleMicrophoneEnabled(state, action) {
1176
+ var _a;
1177
+ return Object.assign(Object.assign({}, state), { microphoneEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.microphoneEnabled });
1178
+ },
1179
+ setCurrentMicrophoneDeviceId(state, action) {
1180
+ return Object.assign(Object.assign({}, state), { currentMicrophoneDeviceId: action.payload.deviceId });
1181
+ },
1182
+ setDevices(state, action) {
1183
+ return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
1184
+ },
1185
+ setLocalMediaStream(state, action) {
1186
+ return Object.assign(Object.assign({}, state), { stream: action.payload.stream });
1187
+ },
1188
+ setLocalMediaOptions(state, action) {
1189
+ return Object.assign(Object.assign({}, state), { options: action.payload.options });
1190
+ },
1191
+ localMediaStopped(state) {
1192
+ return Object.assign(Object.assign({}, state), { status: "stopped", stream: undefined });
1193
+ },
1194
+ localStreamMetadataUpdated(state, action) {
1195
+ const { audio, video } = action.payload;
1196
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: video.deviceId, currentMicrophoneDeviceId: audio.deviceId, busyDeviceIds: state.busyDeviceIds.filter((id) => id !== audio.deviceId && id !== video.deviceId) });
1197
+ },
1198
+ },
1199
+ extraReducers: (builder) => {
1200
+ builder.addCase(doAppJoin, (state, action) => {
1201
+ return Object.assign(Object.assign({}, state), { options: action.payload.localMediaOptions });
1202
+ });
1203
+ builder.addCase(doSetDevice.pending, (state, action) => {
1204
+ const { audio, video } = action.meta.arg;
1205
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video, isSettingMicrophoneDevice: audio });
1206
+ });
1207
+ builder.addCase(doSetDevice.fulfilled, (state, action) => {
1208
+ const { audio, video } = action.meta.arg;
1209
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video ? false : state.isSettingCameraDevice, isSettingMicrophoneDevice: audio ? false : state.isSettingMicrophoneDevice });
1210
+ });
1211
+ builder.addCase(doSetDevice.rejected, (state, action) => {
1212
+ const { audio, video } = action.meta.arg;
1213
+ 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 });
1214
+ });
1215
+ builder.addCase(doToggleCamera.pending, (state) => {
1216
+ return Object.assign(Object.assign({}, state), { isTogglingCamera: true });
1217
+ });
1218
+ builder.addCase(doToggleCamera.fulfilled, (state) => {
1219
+ return Object.assign(Object.assign({}, state), { isTogglingCamera: false });
1220
+ });
1221
+ builder.addCase(doUpdateDeviceList.fulfilled, (state, action) => {
1222
+ return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
1223
+ });
1224
+ builder.addCase(doStartLocalMedia.pending, (state) => {
1225
+ return Object.assign(Object.assign({}, state), { status: "starting" });
1226
+ });
1227
+ builder.addCase(doStartLocalMedia.fulfilled, (state, { payload: { stream, onDeviceChange } }) => {
1228
+ let cameraDeviceId = undefined;
1229
+ let cameraEnabled = false;
1230
+ let microphoneDeviceId = undefined;
1231
+ let microphoneEnabled = false;
1232
+ const audioTrack = stream.getAudioTracks()[0];
1233
+ const videoTrack = stream.getVideoTracks()[0];
1234
+ if (audioTrack) {
1235
+ microphoneDeviceId = audioTrack.getSettings().deviceId;
1236
+ microphoneEnabled = audioTrack.enabled;
582
1237
  }
583
- catch (error) {
584
- // TODO: Update error state
1238
+ if (videoTrack) {
1239
+ cameraEnabled = videoTrack.enabled;
1240
+ cameraDeviceId = videoTrack.getSettings().deviceId;
585
1241
  }
586
- this._isTogglingCameraEnabled = false;
1242
+ return Object.assign(Object.assign({}, state), { stream, status: "started", currentCameraDeviceId: cameraDeviceId, currentMicrophoneDeviceId: microphoneDeviceId, cameraEnabled,
1243
+ microphoneEnabled,
1244
+ onDeviceChange });
587
1245
  });
1246
+ builder.addCase(doStartLocalMedia.rejected, (state, action) => {
1247
+ return Object.assign(Object.assign({}, state), { status: "error", startError: action.error });
1248
+ });
1249
+ builder.addCase(doSwitchLocalStream.pending, (state) => {
1250
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: true });
1251
+ });
1252
+ builder.addCase(doSwitchLocalStream.fulfilled, (state) => {
1253
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
1254
+ });
1255
+ builder.addCase(doSwitchLocalStream.rejected, (state) => {
1256
+ return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
1257
+ });
1258
+ },
1259
+ });
1260
+ /**
1261
+ * Action creators
1262
+ */
1263
+ const { deviceBusy, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, toggleCameraEnabled, toggleMicrophoneEnabled, setLocalMediaOptions, setLocalMediaStream, localMediaStopped, localStreamMetadataUpdated, } = localMediaSlice.actions;
1264
+ const doToggleCamera = createAppAsyncThunk("localMedia/doToggleCamera", (_, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1265
+ const state = getState();
1266
+ const stream = selectLocalMediaStream(state);
1267
+ if (!stream) {
1268
+ return;
588
1269
  }
589
- toggleMichrophoneEnabled(enabled) {
590
- const audioTrack = this.stream.getAudioTracks()[0];
591
- if (!audioTrack) {
592
- return;
1270
+ let track = stream.getVideoTracks()[0];
1271
+ const enabled = selectIsCameraEnabled(state);
1272
+ // Only stop tracks if we fully own the media stream
1273
+ const shouldStopTrack = selectLocalMediaOwnsStream(state);
1274
+ try {
1275
+ if (enabled) {
1276
+ if (track) {
1277
+ // We have existing video track, just enable it
1278
+ track.enabled = true;
1279
+ }
1280
+ else {
1281
+ // We dont have video track, get new one
1282
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1283
+ const cameraDeviceId = selectCurrentCameraDeviceId(state);
1284
+ yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: false, videoId: cameraDeviceId, type: "exact" }), { replaceStream: stream });
1285
+ track = stream.getVideoTracks()[0];
1286
+ }
593
1287
  }
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;
1288
+ else {
1289
+ if (!track) {
1290
+ return;
603
1291
  }
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;
1292
+ track.enabled = false;
1293
+ if (shouldStopTrack) {
1294
+ track.stop();
1295
+ stream.removeTrack(track);
1296
+ }
1297
+ }
1298
+ // Dispatch event on stream to allow RTC layer effects
1299
+ stream.dispatchEvent(new CustomEvent("stopresumevideo", { detail: { track, enable: enabled } }));
1300
+ }
1301
+ catch (error) {
1302
+ return rejectWithValue(error);
1303
+ }
1304
+ }));
1305
+ const doToggleMicrophone = createAppAsyncThunk("localMedia/doToggleMicrophone", (_, { getState }) => {
1306
+ var _a;
1307
+ const state = getState();
1308
+ const stream = selectLocalMediaStream(state);
1309
+ if (!stream) {
1310
+ return;
613
1311
  }
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
- };
1312
+ const enabled = selectIsMicrophoneEnabled(state);
1313
+ const audioTrack = (_a = stream.getAudioTracks()) === null || _a === void 0 ? void 0 : _a[0];
1314
+ if (!audioTrack) {
1315
+ return;
627
1316
  }
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
- });
1317
+ audioTrack.enabled = enabled;
1318
+ });
1319
+ const doSetDevice = createAppAsyncThunk("localMedia/reactSetDevice", ({ audio, video }, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1320
+ try {
1321
+ const state = getState();
1322
+ const stream = selectLocalMediaStream(state);
1323
+ if (!stream) {
1324
+ throw new Error("No stream");
1325
+ }
1326
+ const audioId = audio ? selectCurrentMicrophoneDeviceId(state) : false;
1327
+ const videoId = video ? selectCurrentCameraDeviceId(state) : false;
1328
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1329
+ const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId,
1330
+ videoId, type: "exact" }), { replaceStream: stream });
1331
+ const isAudioEnabled = selectIsMicrophoneEnabled(state);
1332
+ stream.getAudioTracks().forEach((track) => (track.enabled = isAudioEnabled));
1333
+ const isVideoEnabled = selectIsCameraEnabled(state);
1334
+ stream.getVideoTracks().forEach((track) => (track.enabled = isVideoEnabled));
1335
+ return { replacedTracks };
1336
+ }
1337
+ catch (error) {
1338
+ return rejectWithValue(error);
1339
+ }
1340
+ }));
1341
+ const doUpdateDeviceList = createAppAsyncThunk("localMedia/doUpdateDeviceList", (_, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1342
+ var _a, _b;
1343
+ const state = getState();
1344
+ let newDevices = [];
1345
+ let oldDevices = [];
1346
+ const stream = selectLocalMediaStream(state);
1347
+ const busy = selectBusyDeviceIds(state);
1348
+ try {
1349
+ newDevices = yield navigator.mediaDevices.enumerateDevices();
1350
+ oldDevices = selectLocalMediaDevices(state);
1351
+ const shouldHandleDeviceUpdate = stream &&
1352
+ !selectLocalMediaIsSwitchingStream(state) &&
1353
+ newDevices &&
1354
+ oldDevices &&
1355
+ oldDevices.find((d) => d.deviceId);
1356
+ if (!shouldHandleDeviceUpdate) {
1357
+ return { devices: newDevices };
1358
+ }
1359
+ const { changedDevices } = getUpdatedDevices({
1360
+ oldDevices,
1361
+ newDevices,
1362
+ currentAudioId: selectCurrentMicrophoneDeviceId(state),
1363
+ currentVideoId: selectCurrentCameraDeviceId(state),
1364
+ });
1365
+ let autoSwitchAudioId = (_a = changedDevices.audioinput) === null || _a === void 0 ? void 0 : _a.deviceId;
1366
+ let autoSwitchVideoId = (_b = changedDevices.videoinput) === null || _b === void 0 ? void 0 : _b.deviceId;
1367
+ // eslint-disable-next-line no-inner-declarations
1368
+ function nextId(devices, id) {
1369
+ const curIdx = id ? devices.findIndex((d) => d.deviceId === id) : 0;
1370
+ return (devices[(curIdx + 1) % devices.length] || {}).deviceId;
1371
+ }
1372
+ if (autoSwitchVideoId !== undefined) {
1373
+ const videoDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "videoinput");
1374
+ const videoId = selectCurrentCameraDeviceId(state);
1375
+ let nextVideoId = nextId(videoDevices.filter((d) => !busy.includes(d.deviceId)), videoId);
1376
+ if (!nextVideoId || videoId === nextVideoId) {
1377
+ nextVideoId = nextId(videoDevices, videoId);
1378
+ }
1379
+ if (videoId !== nextVideoId) {
1380
+ autoSwitchVideoId = nextVideoId;
1381
+ }
1382
+ }
1383
+ if (autoSwitchAudioId !== undefined) {
1384
+ const audioDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "audioinput");
1385
+ const audioId = selectCurrentMicrophoneDeviceId(state);
1386
+ let nextAudioId = nextId(audioDevices.filter((d) => !busy.includes(d.deviceId)), audioId);
1387
+ if (!nextAudioId || audioId === nextAudioId) {
1388
+ nextAudioId = nextId(audioDevices, audioId);
639
1389
  }
640
- this.dispatchEvent(new LocalMediaEvent("stream_updated", {
641
- detail: { stream: this.stream },
1390
+ if (audioId !== nextAudioId) {
1391
+ autoSwitchAudioId = nextAudioId;
1392
+ }
1393
+ }
1394
+ if (autoSwitchAudioId !== undefined || autoSwitchVideoId !== undefined) {
1395
+ dispatch(doSwitchLocalStream({ audioId: autoSwitchAudioId, videoId: autoSwitchVideoId }));
1396
+ }
1397
+ return { devices: newDevices };
1398
+ }
1399
+ catch (error) {
1400
+ return rejectWithValue(error);
1401
+ }
1402
+ }));
1403
+ const doSwitchLocalStream = createAppAsyncThunk("localMedia/doSwitchLocalStream", ({ audioId, videoId }, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1404
+ const state = getState();
1405
+ const replaceStream = selectLocalMediaStream(state);
1406
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1407
+ const onlySwitchingOne = !!(videoId && !audioId) || !!(!videoId && audioId);
1408
+ if (!replaceStream) {
1409
+ // Switching no stream makes no sense
1410
+ return;
1411
+ }
1412
+ try {
1413
+ const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: audioId === undefined ? false : audioId, videoId: videoId === undefined ? false : videoId, type: "exact" }), { replaceStream });
1414
+ const deviceId = audioId || videoId;
1415
+ if (onlySwitchingOne && deviceId) {
1416
+ dispatch(deviceBusy({
1417
+ deviceId,
642
1418
  }));
643
- });
1419
+ }
1420
+ return { replacedTracks };
1421
+ }
1422
+ catch (error) {
1423
+ console.error(error);
1424
+ const deviceId = audioId || videoId;
1425
+ if (onlySwitchingOne && deviceId) {
1426
+ dispatch(deviceBusy({
1427
+ deviceId,
1428
+ }));
1429
+ }
1430
+ return rejectWithValue(error);
1431
+ }
1432
+ }));
1433
+ const doStartLocalMedia = createAppAsyncThunk("localMedia/doStartLocalMedia", (payload, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1434
+ const onDeviceChange = debounce(() => {
1435
+ dispatch(doUpdateDeviceList());
1436
+ }, { delay: 500 });
1437
+ if (global.navigator.mediaDevices) {
1438
+ navigator.mediaDevices.addEventListener("devicechange", onDeviceChange);
1439
+ }
1440
+ // Resolve if existing stream is passed
1441
+ if ("getTracks" in payload) {
1442
+ return Promise.resolve({ stream: payload, onDeviceChange });
1443
+ }
1444
+ if (!(payload.audio || payload.video)) {
1445
+ return { stream: new MediaStream(), onDeviceChange };
1446
+ }
1447
+ else {
1448
+ dispatch(setLocalMediaOptions({ options: payload }));
1449
+ }
1450
+ try {
1451
+ // then update devices
1452
+ yield dispatch(doUpdateDeviceList());
1453
+ // then get new state
1454
+ const state = getState();
1455
+ const constraintsOptions = selectLocalMediaConstraintsOptions(state);
1456
+ const { stream } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: payload.audio, videoId: payload.video }));
1457
+ return { stream, onDeviceChange };
1458
+ }
1459
+ catch (error) {
1460
+ return rejectWithValue(error);
1461
+ }
1462
+ }));
1463
+ const doStopLocalMedia = createAppThunk(() => (dispatch, getState) => {
1464
+ const stream = selectLocalMediaStream(getState());
1465
+ const onDeviceChange = selectLocalMediaRaw(getState()).onDeviceChange;
1466
+ stream === null || stream === void 0 ? void 0 : stream.getTracks().forEach((track) => {
1467
+ track.stop();
1468
+ });
1469
+ if (global.navigator.mediaDevices && onDeviceChange) {
1470
+ navigator.mediaDevices.removeEventListener("devicechange", onDeviceChange);
644
1471
  }
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 });
1472
+ dispatch(localMediaStopped());
1473
+ });
1474
+ /**
1475
+ * Selectors
1476
+ */
1477
+ const selectBusyDeviceIds = (state) => state.localMedia.busyDeviceIds;
1478
+ const selectCameraDeviceError = (state) => state.localMedia.cameraDeviceError;
1479
+ const selectCurrentCameraDeviceId = (state) => state.localMedia.currentCameraDeviceId;
1480
+ const selectCurrentMicrophoneDeviceId = (state) => state.localMedia.currentMicrophoneDeviceId;
1481
+ const selectIsCameraEnabled = (state) => state.localMedia.cameraEnabled;
1482
+ const selectIsMicrophoneEnabled = (state) => state.localMedia.microphoneEnabled;
1483
+ const selectIsSettingCameraDevice = (state) => state.localMedia.isSettingCameraDevice;
1484
+ const selectIsSettingMicrophoneDevice = (state) => state.localMedia.isSettingMicrophoneDevice;
1485
+ const selectIsToggleCamera = (state) => state.localMedia.isTogglingCamera;
1486
+ const selectLocalMediaDevices = (state) => state.localMedia.devices;
1487
+ const selectLocalMediaOptions = (state) => state.localMedia.options;
1488
+ const selectLocalMediaOwnsStream = createSelector(selectLocalMediaOptions, (options) => !!options);
1489
+ const selectLocalMediaRaw = (state) => state.localMedia;
1490
+ const selectLocalMediaStatus = (state) => state.localMedia.status;
1491
+ const selectLocalMediaStream = (state) => state.localMedia.stream;
1492
+ const selectMicrophoneDeviceError = (state) => state.localMedia.microphoneDeviceError;
1493
+ const selectLocalMediaStartError = (state) => state.localMedia.startError;
1494
+ const selectLocalMediaIsSwitchingStream = (state) => state.localMedia.isSwitchingStream;
1495
+ const selectLocalMediaConstraintsOptions = createSelector(selectLocalMediaDevices, (devices) => ({
1496
+ devices,
1497
+ options: {
1498
+ disableAEC: false,
1499
+ disableAGC: false,
1500
+ hd: true,
1501
+ lax: false,
1502
+ lowDataMode: false,
1503
+ simulcast: true,
1504
+ widescreen: true,
1505
+ },
1506
+ }));
1507
+ const selectIsLocalMediaStarting = createSelector(selectLocalMediaStatus, (status) => status === "starting");
1508
+ const selectCameraDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "videoinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1509
+ const selectMicrophoneDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "audioinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1510
+ const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) => devices.filter((d) => d.kind === "audiooutput"));
1511
+ /**
1512
+ * Reactors
1513
+ */
1514
+ // Start localMedia unless started when roomConnection is wanted
1515
+ const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
1516
+ if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
1517
+ return localMediaOptions;
1518
+ }
1519
+ });
1520
+ createReactor([selectLocalMediaShouldStartWithOptions], ({ dispatch }, options) => {
1521
+ if (options) {
1522
+ dispatch(doStartLocalMedia(options));
1523
+ }
1524
+ });
1525
+ // Stop localMedia when roomConnection is no longer wanted and media was started when joining
1526
+ const selectLocalMediaShouldStop = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
1527
+ return !appWantsToJoin && localMediaStatus !== "" && !!localMediaOptions;
1528
+ });
1529
+ createReactor([selectLocalMediaShouldStop], ({ dispatch }, localMediaShouldStop) => {
1530
+ if (localMediaShouldStop) {
1531
+ dispatch(doStopLocalMedia());
1532
+ }
1533
+ });
1534
+ startAppListening({
1535
+ predicate: (_action, currentState, previousState) => {
1536
+ const oldValue = selectIsMicrophoneEnabled(previousState);
1537
+ const newValue = selectIsMicrophoneEnabled(currentState);
1538
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1539
+ return isReady && oldValue !== newValue;
1540
+ },
1541
+ effect: (_, { dispatch }) => {
1542
+ dispatch(doToggleMicrophone());
1543
+ },
1544
+ });
1545
+ startAppListening({
1546
+ predicate: (_action, currentState, previousState) => {
1547
+ const isToggling = selectIsToggleCamera(currentState);
1548
+ if (isToggling) {
1549
+ return false;
1550
+ }
1551
+ const oldValue = selectIsCameraEnabled(previousState);
1552
+ const newValue = selectIsCameraEnabled(currentState);
1553
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1554
+ return isReady && oldValue !== newValue;
1555
+ },
1556
+ effect: (_action, { dispatch }) => {
1557
+ dispatch(doToggleCamera());
1558
+ },
1559
+ });
1560
+ startAppListening({
1561
+ predicate: (_action, currentState, previousState) => {
1562
+ const oldValue = selectCurrentCameraDeviceId(previousState);
1563
+ const newValue = selectCurrentCameraDeviceId(currentState);
1564
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1565
+ return isReady && oldValue !== newValue;
1566
+ },
1567
+ effect: (_action, { dispatch }) => {
1568
+ dispatch(doSetDevice({ audio: false, video: true }));
1569
+ },
1570
+ });
1571
+ startAppListening({
1572
+ predicate: (_action, currentState, previousState) => {
1573
+ const oldValue = selectCurrentMicrophoneDeviceId(previousState);
1574
+ const newValue = selectCurrentMicrophoneDeviceId(currentState);
1575
+ const isReady = selectLocalMediaStatus(previousState) === "started";
1576
+ return isReady && oldValue !== newValue;
1577
+ },
1578
+ effect: (_action, { dispatch }) => {
1579
+ dispatch(doSetDevice({ audio: true, video: false }));
1580
+ },
1581
+ });
1582
+ startAppListening({
1583
+ matcher: isAnyOf(doStartLocalMedia.fulfilled, doUpdateDeviceList.fulfilled, doSwitchLocalStream.fulfilled, doSwitchLocalStream.rejected),
1584
+ effect: (_action, { dispatch, getState }) => {
1585
+ const state = getState();
1586
+ const stream = selectLocalMediaStream(state);
1587
+ const devices = selectLocalMediaDevices(state);
1588
+ if (!stream)
1589
+ return;
1590
+ const deviceData = getDeviceData({
1591
+ audioTrack: stream.getAudioTracks()[0],
1592
+ videoTrack: stream.getVideoTracks()[0],
1593
+ devices,
1594
+ });
1595
+ dispatch(localStreamMetadataUpdated(deviceData));
1596
+ },
1597
+ });
1598
+
1599
+ const initialState$a = {
1600
+ displayName: "",
1601
+ id: "",
1602
+ isAudioEnabled: true,
1603
+ isVideoEnabled: true,
1604
+ isLocalParticipant: true,
1605
+ stream: undefined,
1606
+ isScreenSharing: false,
1607
+ roleName: "",
1608
+ };
1609
+ const doEnableAudio = createAppAsyncThunk("localParticipant/doEnableAudio", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1610
+ const state = getState();
1611
+ const socket = selectSignalConnectionRaw(state).socket;
1612
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_audio", { enabled: payload.enabled });
1613
+ return payload.enabled;
1614
+ }));
1615
+ const doEnableVideo = createAppAsyncThunk("localParticipant/doEnableVideo", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1616
+ const state = getState();
1617
+ const socket = selectSignalConnectionRaw(state).socket;
1618
+ socket === null || socket === void 0 ? void 0 : socket.emit("enable_video", { enabled: payload.enabled });
1619
+ return payload.enabled;
1620
+ }));
1621
+ const doSetDisplayName = createAppAsyncThunk("localParticipant/doSetDisplayName", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
1622
+ const state = getState();
1623
+ const socket = selectSignalConnectionRaw(state).socket;
1624
+ socket === null || socket === void 0 ? void 0 : socket.emit("send_client_metadata", {
1625
+ type: "UserData",
1626
+ payload,
1627
+ });
1628
+ return payload.displayName;
1629
+ }));
1630
+ const localParticipantSlice = createSlice({
1631
+ name: "localParticipant",
1632
+ initialState: initialState$a,
1633
+ reducers: {
1634
+ doSetLocalParticipant: (state, action) => {
1635
+ return Object.assign(Object.assign({}, state), action.payload);
1636
+ },
1637
+ },
1638
+ extraReducers: (builder) => {
1639
+ builder.addCase(doAppJoin, (state, action) => {
1640
+ return Object.assign(Object.assign({}, state), { displayName: action.payload.displayName });
1641
+ });
1642
+ builder.addCase(doEnableAudio.fulfilled, (state, action) => {
1643
+ return Object.assign(Object.assign({}, state), { isAudioEnabled: action.payload });
1644
+ });
1645
+ builder.addCase(doEnableVideo.fulfilled, (state, action) => {
1646
+ return Object.assign(Object.assign({}, state), { isVideoEnabled: action.payload });
1647
+ });
1648
+ builder.addCase(doSetDisplayName.fulfilled, (state, action) => {
1649
+ return Object.assign(Object.assign({}, state), { displayName: action.payload });
1650
+ });
1651
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1652
+ var _a, _b;
1653
+ 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); });
1654
+ return Object.assign(Object.assign({}, state), { id: action.payload.selfId, roleName: (client === null || client === void 0 ? void 0 : client.role.roleName) || "" });
1655
+ });
1656
+ },
1657
+ });
1658
+ localParticipantSlice.actions;
1659
+ const selectLocalParticipantRaw = (state) => state.localParticipant;
1660
+ const selectSelfId = (state) => state.localParticipant.id;
1661
+ const selectLocalParticipantRole = (state) => state.localParticipant.roleName;
1662
+ startAppListening({
1663
+ actionCreator: toggleCameraEnabled,
1664
+ effect: ({ payload }, { dispatch, getState }) => {
1665
+ const { enabled } = payload;
1666
+ const { isVideoEnabled } = selectLocalParticipantRaw(getState());
1667
+ dispatch(doEnableVideo({ enabled: enabled || !isVideoEnabled }));
1668
+ },
1669
+ });
1670
+ startAppListening({
1671
+ actionCreator: toggleMicrophoneEnabled,
1672
+ effect: ({ payload }, { dispatch, getState }) => {
1673
+ const { enabled } = payload;
1674
+ const { isAudioEnabled } = selectLocalParticipantRaw(getState());
1675
+ dispatch(doEnableAudio({ enabled: enabled || !isAudioEnabled }));
1676
+ },
1677
+ });
1678
+
1679
+ const initialState$9 = {
1680
+ status: "",
1681
+ stream: null,
1682
+ error: null,
1683
+ };
1684
+ /**
1685
+ * Reducer
1686
+ */
1687
+ const localScreenshareSlice = createSlice({
1688
+ name: "localScreenshare",
1689
+ initialState: initialState$9,
1690
+ reducers: {
1691
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1692
+ stopScreenshare(state, action) {
1693
+ return Object.assign(Object.assign({}, state), { status: "", stream: null });
1694
+ },
1695
+ },
1696
+ extraReducers: (builder) => {
1697
+ builder.addCase(doStartScreenshare.pending, (state) => {
1698
+ return Object.assign(Object.assign({}, state), { status: "starting" });
1699
+ });
1700
+ builder.addCase(doStartScreenshare.fulfilled, (state, { payload: { stream } }) => {
1701
+ return Object.assign(Object.assign({}, state), { status: "active", stream });
649
1702
  });
1703
+ builder.addCase(doStartScreenshare.rejected, (state, { payload }) => {
1704
+ return Object.assign(Object.assign({}, state), { error: payload, status: "", stream: null });
1705
+ });
1706
+ },
1707
+ });
1708
+ /**
1709
+ * Action creators
1710
+ */
1711
+ const { stopScreenshare } = localScreenshareSlice.actions;
1712
+ const doStartScreenshare = createAppAsyncThunk("localScreenshare/doStartScreenshare", (_, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
1713
+ var _a;
1714
+ try {
1715
+ const state = getState();
1716
+ const screenshareStream = selectLocalScreenshareStream(state);
1717
+ if (screenshareStream) {
1718
+ return { stream: screenshareStream };
1719
+ }
1720
+ const stream = yield navigator.mediaDevices.getDisplayMedia();
1721
+ const onEnded = () => {
1722
+ dispatch(doStopScreenshare());
1723
+ };
1724
+ if ("oninactive" in stream) {
1725
+ // Chrome
1726
+ stream.addEventListener("inactive", onEnded);
1727
+ }
1728
+ else {
1729
+ // Firefox
1730
+ (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
1731
+ }
1732
+ return { stream };
1733
+ }
1734
+ catch (error) {
1735
+ return rejectWithValue(error);
650
1736
  }
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 });
1737
+ }));
1738
+ const doStopScreenshare = createAppThunk(() => (dispatch, getState) => {
1739
+ const state = getState();
1740
+ const screenshareStream = selectLocalScreenshareStream(state);
1741
+ if (!screenshareStream) {
1742
+ return;
1743
+ }
1744
+ screenshareStream.getTracks().forEach((track) => track.stop());
1745
+ dispatch(stopScreenshare({ stream: screenshareStream }));
1746
+ });
1747
+ const selectLocalScreenshareStream = (state) => state.localScreenshare.stream;
1748
+ /**
1749
+ * Reactors
1750
+ */
1751
+ startAppListening({
1752
+ actionCreator: localMediaStopped,
1753
+ effect: (_, { getState }) => {
1754
+ const state = getState();
1755
+ const screenshareStream = selectLocalScreenshareStream(state);
1756
+ if (!screenshareStream) {
1757
+ return;
1758
+ }
1759
+ screenshareStream === null || screenshareStream === void 0 ? void 0 : screenshareStream.getTracks().forEach((track) => {
1760
+ track.stop();
1761
+ });
1762
+ },
1763
+ });
1764
+
1765
+ const initialState$8 = {
1766
+ data: null,
1767
+ isFetching: false,
1768
+ error: null,
1769
+ };
1770
+ const organizationSlice = createSlice({
1771
+ initialState: initialState$8,
1772
+ name: "organization",
1773
+ reducers: {},
1774
+ extraReducers: (builder) => {
1775
+ builder.addCase(doOrganizationFetch.pending, (state) => {
1776
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1777
+ });
1778
+ builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
1779
+ if (!action.payload)
1780
+ return Object.assign(Object.assign({}, state), { isFetching: true });
1781
+ return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
655
1782
  });
1783
+ builder.addCase(doOrganizationFetch.rejected, (state) => {
1784
+ return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
1785
+ });
1786
+ },
1787
+ });
1788
+ /**
1789
+ * Action creators
1790
+ */
1791
+ const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
1792
+ try {
1793
+ const roomUrl = selectAppRoomUrl(getState());
1794
+ const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
1795
+ if (!organization) {
1796
+ throw new Error("Invalid room url");
1797
+ }
1798
+ return organization;
656
1799
  }
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;
669
- }
670
- catch (error) {
671
- this.dispatchEvent(new LocalMediaEvent("device_list_update_error", {
672
- detail: {
673
- error,
674
- },
675
- }));
676
- throw error;
1800
+ catch (error) {
1801
+ console.error(error);
1802
+ }
1803
+ }));
1804
+ /**
1805
+ * Selectors
1806
+ */
1807
+ const selectOrganizationRaw = (state) => state.organization;
1808
+ const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
1809
+ /**
1810
+ * Reducers
1811
+ */
1812
+ const selectShouldFetchOrganization = createSelector(selectAppWantsToJoin, selectOrganizationRaw, selectDeviceCredentialsRaw, (wantsToJoin, organization, deviceCredentials) => {
1813
+ if (wantsToJoin &&
1814
+ !organization.data &&
1815
+ !organization.isFetching &&
1816
+ !organization.error &&
1817
+ !deviceCredentials.isFetching) {
1818
+ return true;
1819
+ }
1820
+ return false;
1821
+ });
1822
+ createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
1823
+ if (shouldFetchOrganization) {
1824
+ dispatch(doOrganizationFetch());
1825
+ }
1826
+ });
1827
+
1828
+ function createRtcEventAction(name) {
1829
+ return createAction(`rtcConnection/event/${name}`);
1830
+ }
1831
+ const rtcEvents = {
1832
+ rtcManagerCreated: createRtcEventAction("rtcManagerCreated"),
1833
+ rtcManagerDestroyed: createRtcEventAction("rtcManagerDestroyed"),
1834
+ streamAdded: createRtcEventAction("streamAdded"),
1835
+ };
1836
+
1837
+ const NON_PERSON_ROLES = ["recorder", "streamer"];
1838
+ /**
1839
+ * State mapping utils
1840
+ */
1841
+ function createParticipant(client, newJoiner = false) {
1842
+ const { streams } = client, rest = __rest(client, ["streams"]);
1843
+ 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 });
1844
+ }
1845
+ function findParticipant(state, participantId) {
1846
+ const index = state.remoteParticipants.findIndex((c) => c.id === participantId);
1847
+ return { index, participant: state.remoteParticipants[index] };
1848
+ }
1849
+ function updateParticipant(state, participantId, updates) {
1850
+ const { participant, index } = findParticipant(state, participantId);
1851
+ if (!participant) {
1852
+ console.error(`Did not find client for update ${participantId}`);
1853
+ return state;
1854
+ }
1855
+ return Object.assign(Object.assign({}, state), { remoteParticipants: [
1856
+ ...state.remoteParticipants.slice(0, index),
1857
+ Object.assign(Object.assign({}, participant), updates),
1858
+ ...state.remoteParticipants.slice(index + 1),
1859
+ ] });
1860
+ }
1861
+ function addParticipant(state, participant) {
1862
+ const { participant: foundParticipant } = findParticipant(state, participant.id);
1863
+ if (foundParticipant) {
1864
+ console.warn(`Client already existing ${participant.id}. Ignoring`);
1865
+ return state;
1866
+ }
1867
+ return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants, participant] });
1868
+ }
1869
+ function updateStreamState(state, participantId, streamId, state_) {
1870
+ const { participant } = findParticipant(state, participantId);
1871
+ if (!participant) {
1872
+ console.error(`No client ${participantId} found to update stream ${streamId} ${state_}`);
1873
+ return state;
1874
+ }
1875
+ const idIdx = participant.streams.findIndex((s) => s.id === streamId);
1876
+ const streams = [...participant.streams];
1877
+ streams[idIdx] = Object.assign(Object.assign({}, streams[idIdx]), { state: state_ });
1878
+ return updateParticipant(state, participantId, { streams });
1879
+ }
1880
+ function removeClient(state, participantId) {
1881
+ return Object.assign(Object.assign({}, state), { remoteParticipants: state.remoteParticipants.filter((c) => c.id !== participantId) });
1882
+ }
1883
+ function addStreamId(state, participantId, streamId) {
1884
+ const { participant } = findParticipant(state, participantId);
1885
+ if (!participant || participant.streams.find((s) => s.id === streamId)) {
1886
+ console.warn(`No participant ${participantId} or stream ${streamId} already exists`);
1887
+ return state;
1888
+ }
1889
+ return updateParticipant(state, participantId, {
1890
+ streams: [...participant.streams, { id: streamId, state: "to_accept" }],
1891
+ });
1892
+ }
1893
+ function removeStreamId(state, participantId, streamId) {
1894
+ var _a, _b;
1895
+ const { participant } = findParticipant(state, participantId);
1896
+ if (!participant) {
1897
+ console.error(`No participant ${participantId} found to remove stream ${streamId}`);
1898
+ return state;
1899
+ }
1900
+ const currentStreamId = participant.stream && participant.stream.id;
1901
+ const presentationId = ((_a = participant.presentationStream) === null || _a === void 0 ? void 0 : _a.inboundId) || ((_b = participant.presentationStream) === null || _b === void 0 ? void 0 : _b.id);
1902
+ const idIdx = participant.streams.findIndex((s) => s.id === streamId);
1903
+ return updateParticipant(state, participantId, Object.assign(Object.assign({ streams: participant.streams.filter((_, i) => i !== idIdx) }, (currentStreamId === streamId && { stream: null })), (presentationId === streamId && { presentationStream: null })));
1904
+ }
1905
+ function addStream(state, payload) {
1906
+ const { clientId, stream, streamType } = payload;
1907
+ let { streamId } = payload;
1908
+ const { participant } = findParticipant(state, clientId);
1909
+ if (!participant) {
1910
+ console.error(`Did not find client ${clientId} for adding stream`);
1911
+ return state;
1912
+ }
1913
+ const remoteParticipants = state.remoteParticipants;
1914
+ if (!streamId) {
1915
+ streamId = stream.id;
1916
+ }
1917
+ const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
1918
+ if (!remoteParticipant) {
1919
+ return state;
1920
+ }
1921
+ const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId);
1922
+ if ((remoteParticipant.stream &&
1923
+ (remoteParticipant.stream.id === streamId || remoteParticipant.stream.inboundId === streamId)) ||
1924
+ (!remoteParticipant.stream && streamType === "webcam") ||
1925
+ (!remoteParticipant.stream && !streamType && !remoteParticipantStream)) {
1926
+ return updateParticipant(state, clientId, { stream });
1927
+ }
1928
+ // screen share
1929
+ return updateParticipant(state, clientId, {
1930
+ presentationStream: stream,
1931
+ });
1932
+ }
1933
+ const initialState$7 = {
1934
+ remoteParticipants: [],
1935
+ };
1936
+ const remoteParticipantsSlice = createSlice({
1937
+ name: "remoteParticipants",
1938
+ initialState: initialState$7,
1939
+ reducers: {
1940
+ streamStatusUpdated: (state, action) => {
1941
+ let newState = state;
1942
+ for (const { clientId, streamId, state } of action.payload) {
1943
+ newState = updateStreamState(newState, clientId, streamId, state);
677
1944
  }
1945
+ return newState;
1946
+ },
1947
+ participantStreamAdded: (state, action) => {
1948
+ const { clientId, stream } = action.payload;
1949
+ return updateParticipant(state, clientId, {
1950
+ stream,
1951
+ });
1952
+ },
1953
+ participantStreamIdAdded: (state, action) => {
1954
+ const { clientId, streamId } = action.payload;
1955
+ return addStreamId(state, clientId, streamId);
1956
+ },
1957
+ },
1958
+ extraReducers: (builder) => {
1959
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
1960
+ var _a;
1961
+ if (!((_a = action.payload) === null || _a === void 0 ? void 0 : _a.room))
1962
+ return state;
1963
+ const selfId = action.payload.selfId;
1964
+ const { clients } = action.payload.room;
1965
+ return Object.assign(Object.assign({}, state), { remoteParticipants: clients
1966
+ .filter((c) => c.id !== selfId)
1967
+ .filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
1968
+ .map((c) => createParticipant(c)) });
678
1969
  });
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
- }
1970
+ builder.addCase(rtcEvents.streamAdded, (state, action) => {
1971
+ return addStream(state, action.payload);
1972
+ });
1973
+ builder.addCase(signalEvents.newClient, (state, action) => {
1974
+ const { client } = action.payload;
1975
+ if (NON_PERSON_ROLES.includes(client.role.roleName)) {
1976
+ return state;
695
1977
  }
696
- this.dispatchEvent(new LocalMediaEvent("stream_updated", {
697
- detail: { stream: this.stream },
698
- }));
699
- return this.stream;
1978
+ return addParticipant(state, createParticipant(client, true));
1979
+ });
1980
+ builder.addCase(signalEvents.clientLeft, (state, action) => {
1981
+ const { clientId } = action.payload;
1982
+ return removeClient(state, clientId);
1983
+ });
1984
+ builder.addCase(signalEvents.audioEnabled, (state, action) => {
1985
+ const { clientId, isAudioEnabled } = action.payload;
1986
+ return updateParticipant(state, clientId, {
1987
+ isAudioEnabled,
1988
+ });
1989
+ });
1990
+ builder.addCase(signalEvents.videoEnabled, (state, action) => {
1991
+ const { clientId, isVideoEnabled } = action.payload;
1992
+ return updateParticipant(state, clientId, {
1993
+ isVideoEnabled,
1994
+ });
1995
+ });
1996
+ builder.addCase(signalEvents.clientMetadataReceived, (state, action) => {
1997
+ const { clientId, displayName } = action.payload.payload;
1998
+ return updateParticipant(state, clientId, {
1999
+ displayName,
2000
+ });
2001
+ });
2002
+ builder.addCase(signalEvents.screenshareStarted, (state, action) => {
2003
+ const { clientId, streamId } = action.payload;
2004
+ return addStreamId(state, clientId, streamId);
2005
+ });
2006
+ builder.addCase(signalEvents.screenshareStopped, (state, action) => {
2007
+ const { clientId, streamId } = action.payload;
2008
+ return removeStreamId(state, clientId, streamId);
2009
+ });
2010
+ },
2011
+ });
2012
+ /**
2013
+ * Action creators
2014
+ */
2015
+ const { participantStreamAdded, participantStreamIdAdded, streamStatusUpdated } = remoteParticipantsSlice.actions;
2016
+ const selectRemoteParticipants = (state) => state.remoteParticipants.remoteParticipants;
2017
+ const selectScreenshares = createSelector(selectLocalScreenshareStream, selectRemoteParticipants, (localScreenshareStream, remoteParticipants) => {
2018
+ const screenshares = [];
2019
+ if (localScreenshareStream) {
2020
+ screenshares.push({
2021
+ id: localScreenshareStream.id,
2022
+ participantId: "local",
2023
+ hasAudioTrack: localScreenshareStream.getAudioTracks().length > 0,
2024
+ stream: localScreenshareStream,
2025
+ isLocal: true,
700
2026
  });
701
2027
  }
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();
2028
+ for (const participant of remoteParticipants) {
2029
+ if (participant.presentationStream) {
2030
+ screenshares.push({
2031
+ id: participant.presentationStream.id,
2032
+ participantId: participant.id,
2033
+ hasAudioTrack: participant.presentationStream.getAudioTracks().length > 0,
2034
+ stream: participant.presentationStream,
2035
+ isLocal: false,
707
2036
  });
708
2037
  }
709
2038
  }
710
- }
2039
+ return screenshares;
2040
+ });
711
2041
 
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,
2042
+ const initialState$6 = {
2043
+ session: null,
2044
+ status: "initializing",
2045
+ error: null,
722
2046
  };
723
- function reducer$1(state, action) {
724
- switch (action.type) {
725
- case "DEVICE_LIST_UPDATED":
726
- 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 } });
2047
+ const roomConnectionSlice = createSlice({
2048
+ initialState: initialState$6,
2049
+ name: "roomConnection",
2050
+ reducers: {
2051
+ connectionStatusChanged: (state, action) => {
2052
+ return Object.assign(Object.assign({}, state), { status: action.payload });
2053
+ },
2054
+ },
2055
+ extraReducers: (builder) => {
2056
+ builder.addCase(signalEvents.roomJoined, (state, action) => {
2057
+ var _a, _b;
2058
+ //TODO: Handle error
2059
+ const { error, isLocked } = action.payload;
2060
+ if (error === "room_locked" && isLocked) {
2061
+ return Object.assign(Object.assign({}, state), { status: "room_locked" });
2062
+ }
2063
+ return Object.assign(Object.assign({}, state), { status: "connected", session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
758
2064
  });
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
- });
2065
+ builder.addCase(signalEvents.newClient, (state, action) => {
2066
+ var _a, _b;
2067
+ return Object.assign(Object.assign({}, state), { session: (_b = (_a = action.payload.room) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : null });
769
2068
  });
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 });
2069
+ builder.addCase(signalEvents.roomSessionEnded, (state, action) => {
2070
+ var _a;
2071
+ if (((_a = state.session) === null || _a === void 0 ? void 0 : _a.id) !== action.payload.roomSessionId) {
2072
+ return state;
778
2073
  }
2074
+ return Object.assign(Object.assign({}, state), { session: null });
779
2075
  });
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
- },
2076
+ builder.addCase(socketReconnecting, (state) => {
2077
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
2078
+ });
2079
+ },
2080
+ });
2081
+ /**
2082
+ * Action creators
2083
+ */
2084
+ const { connectionStatusChanged } = roomConnectionSlice.actions;
2085
+ const doKnockRoom = createAppThunk(() => (dispatch, getState) => {
2086
+ const state = getState();
2087
+ const socket = selectSignalConnectionRaw(state).socket;
2088
+ const roomName = selectAppRoomName(state);
2089
+ const roomKey = selectAppRoomKey(state);
2090
+ const displayName = selectAppDisplayName(state);
2091
+ const sdkVersion = selectAppSdkVersion(state);
2092
+ const externalId = selectAppExternalId(state);
2093
+ const organizationId = selectOrganizationId(state);
2094
+ socket === null || socket === void 0 ? void 0 : socket.emit("knock_room", {
2095
+ avatarUrl: null,
2096
+ config: {
2097
+ isAudioEnabled: true,
2098
+ isVideoEnabled: true,
815
2099
  },
816
- _ref: localMedia,
817
- };
818
- }
2100
+ deviceCapabilities: { canScreenshare: true },
2101
+ displayName,
2102
+ isCoLocated: false,
2103
+ isDevicePermissionDenied: false,
2104
+ kickFromOtherRooms: false,
2105
+ organizationId,
2106
+ roomKey,
2107
+ roomName,
2108
+ selfId: "",
2109
+ userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
2110
+ externalId,
2111
+ });
2112
+ dispatch(connectionStatusChanged("knocking"));
2113
+ });
2114
+ const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
2115
+ const state = getState();
2116
+ const socket = selectSignalConnectionRaw(state).socket;
2117
+ const roomName = selectAppRoomName(state);
2118
+ const roomKey = selectAppRoomKey(state);
2119
+ const displayName = selectAppDisplayName(state);
2120
+ const sdkVersion = selectAppSdkVersion(state);
2121
+ const externalId = selectAppExternalId(state);
2122
+ const organizationId = selectOrganizationId(state);
2123
+ const isCameraEnabled = selectIsCameraEnabled(getState());
2124
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
2125
+ const selfId = selectSelfId(getState());
2126
+ socket === null || socket === void 0 ? void 0 : socket.emit("join_room", {
2127
+ avatarUrl: null,
2128
+ config: {
2129
+ isAudioEnabled: isMicrophoneEnabled,
2130
+ isVideoEnabled: isCameraEnabled,
2131
+ },
2132
+ deviceCapabilities: { canScreenshare: true },
2133
+ displayName,
2134
+ isCoLocated: false,
2135
+ isDevicePermissionDenied: false,
2136
+ kickFromOtherRooms: false,
2137
+ organizationId,
2138
+ roomKey,
2139
+ roomName,
2140
+ selfId,
2141
+ userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
2142
+ externalId,
2143
+ });
2144
+ dispatch(connectionStatusChanged("connecting"));
2145
+ });
2146
+ const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
2147
+ const selectRoomConnectionStatus = (state) => state.roomConnection.status;
2148
+ /**
2149
+ * Reactors
2150
+ */
2151
+ const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
2152
+ if (localMediaStatus === "started" &&
2153
+ signalConnectionDeviceIdentified &&
2154
+ !!hasOrganizationIdFetched &&
2155
+ ["initializing", "reconnect"].includes(roomConnectionStatus)) {
2156
+ return true;
2157
+ }
2158
+ return false;
2159
+ });
2160
+ createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
2161
+ if (shouldConnectRoom) {
2162
+ dispatch(doConnectRoom());
2163
+ }
2164
+ });
2165
+ startAppListening({
2166
+ actionCreator: signalEvents.knockHandled,
2167
+ effect: ({ payload }, { dispatch, getState }) => {
2168
+ const { clientId, resolution } = payload;
2169
+ const state = getState();
2170
+ const selfId = selectSelfId(state);
2171
+ if (clientId !== selfId) {
2172
+ return;
2173
+ }
2174
+ if (resolution === "accepted") {
2175
+ dispatch(setRoomKey(payload.metadata.roomKey));
2176
+ dispatch(doConnectRoom());
2177
+ }
2178
+ else if (resolution === "rejected") {
2179
+ dispatch(connectionStatusChanged("knock_rejected"));
2180
+ }
2181
+ },
2182
+ });
819
2183
 
820
2184
  const EVENTS = {
821
2185
  CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
@@ -1000,132 +2364,6 @@ class RtcStream {
1000
2364
  }
1001
2365
  }
1002
2366
 
1003
- const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
1004
-
1005
- /**
1006
- * Wrapper class that extends the Socket.IO client library.
1007
- */
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();
1052
- }
1053
-
1054
- disconnectOnConnect() {
1055
- this._socket.once("connect", () => {
1056
- this._socket.disconnect();
1057
- });
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
- }
1070
-
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
- }
1080
-
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
2367
  /**
1130
2368
  * Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
1131
2369
  * frequently. A browser restart fixes this.
@@ -5803,76 +7041,666 @@ class RtcManagerDispatcher {
5803
7041
  }
5804
7042
  }
5805
7043
 
5806
- const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
5807
- const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
5808
- const localhostPattern = /^(?:([^.]+)[.])?(localhost:?\d*)/;
5809
- const serverPattern = /^(?:([^.]+)[.])?(server:?\d*)/;
5810
- const ipv4Pattern = /^(?:([^.]+)[.])?((\d+[.]){3}:?\d*)$/;
5811
-
5812
- const subdomainPatterns = [
5813
- { pattern: serverPattern, separator: "." },
5814
- { pattern: localhostPattern, separator: "." },
5815
- { pattern: ipv4Pattern, separator: "." },
5816
- { pattern: localstackPattern, separator: "-" },
5817
- { pattern: defaultSubdomainPattern, separator: "." },
5818
- ];
5819
-
5820
- /**
5821
- * @param {Location} location - the location object to use to resolve branding information.
5822
- * @return {Object} urls - urls created from this location
5823
- */
5824
- function fromLocation({ host = "whereby.com", protocol = "https:" } = {}) {
5825
- let subdomain = "";
5826
- let domain = host;
5827
- let subdomainSeparator = ".";
5828
- for (const { separator, pattern } of subdomainPatterns) {
5829
- const match = pattern.exec(host);
5830
- if (match) {
5831
- subdomain = match[1] || "";
5832
- domain = match[2];
5833
- subdomainSeparator = separator;
5834
- break;
5835
- }
5836
- }
5837
- const organizationDomain = !subdomain ? domain : `${subdomain}${subdomainSeparator}${domain}`;
5838
-
5839
- return {
5840
- domain,
5841
- domainWithSeparator: `${subdomainSeparator}${domain}`,
5842
- organizationDomain,
5843
- organization: `${protocol}//${organizationDomain}`,
5844
- service: `${protocol}//${domain}`,
5845
- subdomain,
5846
- };
5847
- }
5848
-
5849
- fromLocation(window && window.location);
5850
-
5851
- class Response {
5852
- constructor(initialValues = {}) {
5853
- this.data = initialValues.data === undefined ? {} : initialValues.data;
5854
- this.headers = initialValues.headers || {};
5855
- this.status = initialValues.status || 200;
5856
- this.statusText = initialValues.statusText || "OK";
5857
- this.url = initialValues.url || null;
7044
+ const createWebRtcEmitter = (dispatch) => {
7045
+ return {
7046
+ emit: (eventName, data) => {
7047
+ if (eventName === "rtc_manager_created") {
7048
+ dispatch(doRtcManagerCreated(data));
7049
+ }
7050
+ else if (eventName === "stream_added") {
7051
+ dispatch(rtcEvents.streamAdded(data));
7052
+ }
7053
+ else if (eventName === "rtc_manager_destroyed") {
7054
+ dispatch(rtcManagerDestroyed());
7055
+ }
7056
+ else ;
7057
+ },
7058
+ };
7059
+ };
7060
+ const initialState$5 = {
7061
+ dispatcherCreated: false,
7062
+ error: null,
7063
+ isCreatingDispatcher: false,
7064
+ reportedStreamResolutions: {},
7065
+ rtcManager: null,
7066
+ rtcManagerDispatcher: null,
7067
+ rtcManagerInitialized: false,
7068
+ status: "",
7069
+ isAcceptingStreams: false,
7070
+ };
7071
+ const rtcConnectionSlice = createSlice({
7072
+ name: "rtcConnection",
7073
+ initialState: initialState$5,
7074
+ reducers: {
7075
+ isAcceptingStreams: (state, action) => {
7076
+ return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
7077
+ },
7078
+ resolutionReported: (state, action) => {
7079
+ const { streamId, width, height } = action.payload;
7080
+ return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
7081
+ },
7082
+ rtcDisconnected: () => {
7083
+ return Object.assign({}, initialState$5);
7084
+ },
7085
+ rtcDispatcherCreated: (state, action) => {
7086
+ return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
7087
+ },
7088
+ rtcManagerCreated: (state, action) => {
7089
+ return Object.assign(Object.assign({}, state), { rtcManager: action.payload, status: "ready" });
7090
+ },
7091
+ rtcManagerDestroyed: (state) => {
7092
+ return Object.assign(Object.assign({}, state), { rtcManager: null });
7093
+ },
7094
+ rtcManagerInitialized: (state) => {
7095
+ return Object.assign(Object.assign({}, state), { rtcManagerInitialized: true });
7096
+ },
7097
+ },
7098
+ extraReducers: (builder) => {
7099
+ builder.addCase(socketReconnecting, (state) => {
7100
+ return Object.assign(Object.assign({}, state), { status: "reconnect" });
7101
+ });
7102
+ builder.addCase(signalEvents.roomJoined, (state) => {
7103
+ return Object.assign(Object.assign({}, state), { status: state.status === "reconnect" ? "ready" : state.status });
7104
+ });
7105
+ },
7106
+ });
7107
+ /**
7108
+ * Action creators
7109
+ */
7110
+ const { resolutionReported, rtcDispatcherCreated, rtcDisconnected, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, isAcceptingStreams, } = rtcConnectionSlice.actions;
7111
+ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
7112
+ const state = getState();
7113
+ const socket = selectSignalConnectionRaw(state).socket;
7114
+ const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
7115
+ const isCameraEnabled = selectIsCameraEnabled(state);
7116
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
7117
+ if (dispatcher) {
7118
+ return;
5858
7119
  }
5859
- }
5860
-
7120
+ const webrtcProvider = {
7121
+ getMediaConstraints: () => ({
7122
+ audio: isMicrophoneEnabled,
7123
+ video: isCameraEnabled,
7124
+ }),
7125
+ deferrable(clientId) {
7126
+ return !clientId;
7127
+ },
7128
+ };
7129
+ const rtcManagerDispatcher = new RtcManagerDispatcher({
7130
+ emitter: createWebRtcEmitter(dispatch),
7131
+ serverSocket: socket,
7132
+ logger: console,
7133
+ webrtcProvider,
7134
+ features: {
7135
+ lowDataModeEnabled: false,
7136
+ sfuServerOverrideHost: undefined,
7137
+ turnServerOverrideHost: undefined,
7138
+ useOnlyTURN: undefined,
7139
+ vp9On: false,
7140
+ h264On: false,
7141
+ simulcastScreenshareOn: false,
7142
+ },
7143
+ });
7144
+ dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
7145
+ });
7146
+ const doDisconnectRtc = createAppThunk(() => (dispatch, getState) => {
7147
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7148
+ if (rtcManager) {
7149
+ rtcManager.disconnectAll();
7150
+ }
7151
+ dispatch(rtcDisconnected());
7152
+ });
7153
+ const doHandleAcceptStreams = createAppThunk((payload) => (dispatch, getState) => {
7154
+ var _a;
7155
+ dispatch(isAcceptingStreams(true));
7156
+ const state = getState();
7157
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7158
+ const remoteParticipants = selectRemoteParticipants(state);
7159
+ if (!rtcManager) {
7160
+ throw new Error("No rtc manager");
7161
+ }
7162
+ const activeBreakout = false;
7163
+ const shouldAcceptNewClients = (_a = rtcManager.shouldAcceptStreamsFromBothSides) === null || _a === void 0 ? void 0 : _a.call(rtcManager);
7164
+ const updates = [];
7165
+ for (const { clientId, streamId, state } of payload) {
7166
+ const participant = remoteParticipants.find((p) => p.id === clientId);
7167
+ if (!participant)
7168
+ continue;
7169
+ if (state === "to_accept" ||
7170
+ (state === "new_accept" && shouldAcceptNewClients) ||
7171
+ (state === "old_accept" && !shouldAcceptNewClients) // these are done to enable broadcast in legacy/p2p
7172
+ ) {
7173
+ rtcManager.acceptNewStream({
7174
+ streamId: streamId === "0" ? clientId : streamId,
7175
+ clientId,
7176
+ shouldAddLocalVideo: streamId === "0",
7177
+ activeBreakout,
7178
+ });
7179
+ }
7180
+ else if (state === "new_accept" || state === "old_accept") ;
7181
+ else if (state === "to_unaccept") {
7182
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.disconnect(streamId === "0" ? clientId : streamId, activeBreakout);
7183
+ }
7184
+ else if (state !== "done_accept") {
7185
+ continue;
7186
+ // console.warn(`Stream state not handled: ${state} for ${clientId}-${streamId}`);
7187
+ }
7188
+ else ;
7189
+ updates.push({ clientId, streamId, state: state.replace(/to_|new_|old_/, "done_") });
7190
+ }
7191
+ dispatch(streamStatusUpdated(updates));
7192
+ dispatch(isAcceptingStreams(false));
7193
+ });
7194
+ const doRtcReportStreamResolution = createAppThunk(({ streamId, width, height }) => (dispatch, getState) => {
7195
+ const { reportedStreamResolutions, rtcManager } = selectRtcConnectionRaw(getState());
7196
+ const localStream = selectLocalMediaStream(getState());
7197
+ if (!rtcManager || (localStream === null || localStream === void 0 ? void 0 : localStream.id) === streamId) {
7198
+ return;
7199
+ }
7200
+ const old = reportedStreamResolutions[streamId];
7201
+ if (!old || old.width !== width || old.height !== height) {
7202
+ rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
7203
+ }
7204
+ dispatch(resolutionReported({ streamId, width, height }));
7205
+ });
7206
+ const doRtcManagerCreated = createAppThunk((payload) => (dispatch) => {
7207
+ const { rtcManager } = payload;
7208
+ dispatch(rtcManagerCreated(rtcManager));
7209
+ });
7210
+ const doRtcManagerInitialize = createAppThunk(() => (dispatch, getState) => {
7211
+ const localMediaStream = selectLocalMediaStream(getState());
7212
+ const rtcManager = selectRtcConnectionRaw(getState()).rtcManager;
7213
+ const isCameraEnabled = selectIsCameraEnabled(getState());
7214
+ const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
7215
+ if (localMediaStream && rtcManager) {
7216
+ rtcManager.addNewStream("0", localMediaStream, !isMicrophoneEnabled, !isCameraEnabled);
7217
+ }
7218
+ dispatch(rtcManagerInitialized());
7219
+ });
5861
7220
  /**
5862
- * Asserts that value is truthy.
5863
- *
5864
- * @param value - The value to check.
5865
- * @param {string} parameterName - The name of the parameter.
7221
+ * Selectors
5866
7222
  */
5867
- function assertTruthy(value, parameterName) {
5868
- assert$1.ok(value, `${parameterName} is required`);
5869
- return value;
5870
- }
7223
+ const selectRtcConnectionRaw = (state) => state.rtcConnection;
7224
+ const selectRtcManagerInitialized = (state) => state.rtcConnection.rtcManagerInitialized;
7225
+ const selectRtcManager = (state) => state.rtcConnection.rtcManager;
7226
+ const selectRtcDispatcherCreated = (state) => state.rtcConnection.dispatcherCreated;
7227
+ const selectRtcIsCreatingDispatcher = (state) => state.rtcConnection.isCreatingDispatcher;
7228
+ const selectRtcStatus = (state) => state.rtcConnection.status;
7229
+ const selectIsAcceptingStreams = (state) => state.rtcConnection.isAcceptingStreams;
5871
7230
  /**
5872
- * Asserts that value is a number.
5873
- *
5874
- * @param value - The value to check.
5875
- * @param {string} parameterName - The name of the parameter.
7231
+ * Reactors
7232
+ */
7233
+ startAppListening({
7234
+ actionCreator: doSetDevice.fulfilled,
7235
+ effect: ({ payload }, { getState }) => {
7236
+ const { replacedTracks } = payload;
7237
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7238
+ const stream = selectLocalMediaStream(getState());
7239
+ const replace = (kind, oldTrack) => {
7240
+ const track = stream === null || stream === void 0 ? void 0 : stream.getTracks().find((t) => t.kind === kind);
7241
+ return track && (rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.replaceTrack(oldTrack, track));
7242
+ };
7243
+ replacedTracks === null || replacedTracks === void 0 ? void 0 : replacedTracks.forEach((t) => {
7244
+ replace(t.kind, t);
7245
+ });
7246
+ },
7247
+ });
7248
+ startAppListening({
7249
+ actionCreator: doStartScreenshare.fulfilled,
7250
+ effect: ({ payload }, { getState }) => {
7251
+ const { stream } = payload;
7252
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7253
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.addNewStream(stream.id, stream, false, true);
7254
+ },
7255
+ });
7256
+ startAppListening({
7257
+ actionCreator: stopScreenshare,
7258
+ effect: ({ payload }, { getState }) => {
7259
+ const { stream } = payload;
7260
+ const { rtcManager } = selectRtcConnectionRaw(getState());
7261
+ rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.removeStream(stream.id, stream, null);
7262
+ },
7263
+ });
7264
+ const selectShouldConnectRtc = createSelector(selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectSignalConnectionSocket, (dispatcherCreated, isCreatingDispatcher, signalSocket) => {
7265
+ if (!dispatcherCreated && !isCreatingDispatcher && signalSocket) {
7266
+ return true;
7267
+ }
7268
+ return false;
7269
+ });
7270
+ createReactor([selectShouldConnectRtc], ({ dispatch }, shouldConnectRtc) => {
7271
+ if (shouldConnectRtc) {
7272
+ dispatch(doConnectRtc());
7273
+ }
7274
+ });
7275
+ const selectShouldInitializeRtc = createSelector(selectRtcManager, selectRtcManagerInitialized, selectLocalMediaStatus, (rtcManager, rtcManagerInitialized, localMediaStatus) => {
7276
+ if (localMediaStatus === "started" && rtcManager && !rtcManagerInitialized) {
7277
+ return true;
7278
+ }
7279
+ return false;
7280
+ });
7281
+ createReactor([selectShouldInitializeRtc], ({ dispatch }, shouldInitializeRtc) => {
7282
+ if (shouldInitializeRtc) {
7283
+ dispatch(doRtcManagerInitialize());
7284
+ }
7285
+ });
7286
+ // Disonnect and clean up
7287
+ const selectShouldDisconnectRtc = createSelector(selectRtcStatus, selectAppWantsToJoin, (status, wantsToJoin) => {
7288
+ if (!wantsToJoin && !["", "disconnected"].includes(status)) {
7289
+ return true;
7290
+ }
7291
+ return false;
7292
+ });
7293
+ createReactor([selectShouldDisconnectRtc], ({ dispatch }, shouldDisconnectRtc) => {
7294
+ if (shouldDisconnectRtc) {
7295
+ dispatch(doDisconnectRtc());
7296
+ }
7297
+ });
7298
+ // react accept streams
7299
+ const selectStreamsToAccept = createSelector(selectRtcStatus, selectRemoteParticipants, (rtcStatus, remoteParticipants) => {
7300
+ if (rtcStatus !== "ready") {
7301
+ return [];
7302
+ }
7303
+ const upd = [];
7304
+ // This should actually use remoteClientViews for its handling
7305
+ for (const client of remoteParticipants) {
7306
+ const { streams, id: clientId, newJoiner } = client;
7307
+ for (let i = 0; i < streams.length; i++) {
7308
+ const streamId = streams[i].id;
7309
+ const state = streams[i].state;
7310
+ {
7311
+ // Already connected
7312
+ if (state === "done_accept")
7313
+ continue;
7314
+ upd.push({
7315
+ clientId,
7316
+ streamId,
7317
+ state: `${newJoiner && streamId === "0" ? "new" : "to"}_accept`,
7318
+ });
7319
+ }
7320
+ }
7321
+ }
7322
+ return upd;
7323
+ });
7324
+ createReactor([selectStreamsToAccept, selectIsAcceptingStreams], ({ dispatch }, streamsToAccept, isAcceptingStreams) => {
7325
+ if (0 < streamsToAccept.length && !isAcceptingStreams) {
7326
+ dispatch(doHandleAcceptStreams(streamsToAccept));
7327
+ }
7328
+ });
7329
+
7330
+ const rtcAnalyticsCustomEvents = {
7331
+ audioEnabled: {
7332
+ action: doEnableAudio.fulfilled,
7333
+ rtcEventName: "audioEnabled",
7334
+ getValue: (state) => selectIsMicrophoneEnabled(state),
7335
+ getOutput: (value) => ({ enabled: value }),
7336
+ },
7337
+ videoEnabled: {
7338
+ action: doEnableVideo.fulfilled,
7339
+ rtcEventName: "videoEnabled",
7340
+ getValue: (state) => selectIsCameraEnabled(state),
7341
+ getOutput: (value) => ({ enabled: value }),
7342
+ },
7343
+ localStream: {
7344
+ action: doSetDevice.fulfilled,
7345
+ rtcEventName: "localStream",
7346
+ getValue: (state) => {
7347
+ var _a;
7348
+ return (_a = selectLocalMediaStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
7349
+ },
7350
+ getOutput: (value) => ({ stream: value }),
7351
+ },
7352
+ localScreenshareStream: {
7353
+ action: doStartScreenshare.fulfilled,
7354
+ rtcEventName: "localScreenshareStream",
7355
+ getValue: (state) => {
7356
+ var _a;
7357
+ return (_a = selectLocalScreenshareStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
7358
+ },
7359
+ getOutput: (value) => ({ tracks: value }),
7360
+ },
7361
+ localScreenshareStreamStopped: {
7362
+ action: stopScreenshare,
7363
+ rtcEventName: "localScreenshareStream",
7364
+ getValue: () => () => null,
7365
+ getOutput: () => ({}),
7366
+ },
7367
+ displayName: {
7368
+ action: doSetDisplayName.fulfilled,
7369
+ rtcEventName: "displayName",
7370
+ getValue: (state) => selectAppDisplayName(state),
7371
+ getOutput: (value) => ({ displayName: value }),
7372
+ },
7373
+ clientId: {
7374
+ action: null,
7375
+ rtcEventName: "clientId",
7376
+ getValue: (state) => selectSelfId(state),
7377
+ getOutput: (value) => ({ clientId: value }),
7378
+ },
7379
+ deviceId: {
7380
+ action: null,
7381
+ rtcEventName: "deviceId",
7382
+ getValue: (state) => selectDeviceId(state),
7383
+ getOutput: (value) => ({ deviceId: value }),
7384
+ },
7385
+ externalId: {
7386
+ action: null,
7387
+ rtcEventName: "externalId",
7388
+ getValue: (state) => selectAppExternalId(state),
7389
+ getOutput: (value) => ({ externalId: value }),
7390
+ },
7391
+ organizationId: {
7392
+ action: null,
7393
+ rtcEventName: "organizationId",
7394
+ getValue: (state) => selectOrganizationId(state),
7395
+ getOutput: (value) => ({ organizationId: value }),
7396
+ },
7397
+ signalConnectionStatus: {
7398
+ action: null,
7399
+ rtcEventName: "signalConnectionStatus",
7400
+ getValue: (state) => selectSignalStatus(state),
7401
+ getOutput: (value) => ({ status: value }),
7402
+ },
7403
+ roomSessionId: {
7404
+ action: null,
7405
+ rtcEventName: "roomSessionId",
7406
+ getValue: (state) => selectRoomConnectionSessionId(state),
7407
+ getOutput: (value) => ({ roomSessionId: value }),
7408
+ },
7409
+ rtcConnectionStatus: {
7410
+ action: null,
7411
+ rtcEventName: "rtcConnectionStatus",
7412
+ getValue: (state) => selectRtcStatus(state),
7413
+ getOutput: (value) => ({ status: value }),
7414
+ },
7415
+ userRole: {
7416
+ action: null,
7417
+ rtcEventName: "userRole",
7418
+ getValue: (state) => selectLocalParticipantRole(state),
7419
+ getOutput: (value) => ({ userRole: value }),
7420
+ },
7421
+ };
7422
+ const rtcCustomEventActions = Object.values(rtcAnalyticsCustomEvents)
7423
+ .map(({ action }) => action)
7424
+ .filter((action) => action !== null);
7425
+ const makeComparable = (value) => {
7426
+ if (typeof value === "object")
7427
+ return JSON.stringify(value);
7428
+ return value;
7429
+ };
7430
+ const initialState$4 = {
7431
+ reportedValues: {},
7432
+ };
7433
+ const rtcAnalyticsSlice = createSlice({
7434
+ initialState: initialState$4,
7435
+ name: "rtcAnalytics",
7436
+ reducers: {
7437
+ updateReportedValues(state, action) {
7438
+ return Object.assign(Object.assign({}, state), { reportedValues: Object.assign(Object.assign({}, state.reportedValues), { [action.payload.rtcEventName]: action.payload.value }) });
7439
+ },
7440
+ },
7441
+ });
7442
+ const doRtcAnalyticsCustomEventsInitialize = createAppThunk(() => (dispatch, getState) => {
7443
+ const state = getState();
7444
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7445
+ if (!rtcManager)
7446
+ return;
7447
+ // RTC stats require a `insightsStats` event to be sent to set the timestamp.
7448
+ // This is a temporary workaround, we just send one dummy event on initialization.
7449
+ rtcManager.sendStatsCustomEvent("insightsStats", {
7450
+ _time: Date.now(),
7451
+ ls: 0,
7452
+ lr: 0,
7453
+ bs: 0,
7454
+ br: 0,
7455
+ cpu: 0,
7456
+ });
7457
+ Object.values(rtcAnalyticsCustomEvents).forEach(({ rtcEventName, getValue, getOutput }) => {
7458
+ var _a;
7459
+ const value = getValue(state);
7460
+ const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
7461
+ const comparableValue = makeComparable(value);
7462
+ if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
7463
+ rtcManager.sendStatsCustomEvent(rtcEventName, output);
7464
+ dispatch(updateReportedValues({ rtcEventName, value }));
7465
+ }
7466
+ });
7467
+ });
7468
+ /**
7469
+ * Action creators
7470
+ */
7471
+ const { updateReportedValues } = rtcAnalyticsSlice.actions;
7472
+ startAppListening({
7473
+ matcher: isAnyOf(...rtcCustomEventActions),
7474
+ effect: ({ type }, { getState, dispatch }) => {
7475
+ var _a;
7476
+ const state = getState();
7477
+ const rtcManager = selectRtcConnectionRaw(state).rtcManager;
7478
+ if (!rtcManager)
7479
+ return;
7480
+ const rtcCustomEvent = Object.values(rtcAnalyticsCustomEvents).find(({ action }) => (action === null || action === void 0 ? void 0 : action.type) === type);
7481
+ if (!rtcCustomEvent)
7482
+ return;
7483
+ const { getValue, getOutput, rtcEventName } = rtcCustomEvent;
7484
+ const value = getValue(state);
7485
+ const comparableValue = makeComparable(value);
7486
+ const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
7487
+ if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
7488
+ rtcManager.sendStatsCustomEvent(rtcEventName, output);
7489
+ dispatch(updateReportedValues({ rtcEventName, value }));
7490
+ }
7491
+ },
7492
+ });
7493
+ /**
7494
+ * Reactors
7495
+ */
7496
+ createReactor([selectRtcManagerInitialized], ({ dispatch }, selectRtcManagerInitialized) => {
7497
+ if (selectRtcManagerInitialized) {
7498
+ dispatch(doRtcAnalyticsCustomEventsInitialize());
7499
+ }
7500
+ });
7501
+
7502
+ const initialState$3 = {
7503
+ isStreaming: false,
7504
+ error: null,
7505
+ startedAt: undefined,
7506
+ };
7507
+ const streamingSlice = createSlice({
7508
+ name: "streaming",
7509
+ initialState: initialState$3,
7510
+ reducers: {
7511
+ doHandleStreamingStarted: (state) => {
7512
+ return Object.assign(Object.assign({}, state), { isStreaming: true, error: null,
7513
+ // We don't have the streaming start time stored on the
7514
+ // server, so we use the current time instead. This gives
7515
+ // an invalid timestamp for "Client B" if "Client A" has
7516
+ // been streaming for a while before "Client B" joins.
7517
+ startedAt: new Date().getTime() });
7518
+ },
7519
+ doHandleStreamingStopped: (state) => {
7520
+ return Object.assign(Object.assign({}, state), { isStreaming: false });
7521
+ },
7522
+ },
7523
+ });
7524
+ /**
7525
+ * Action creators
7526
+ */
7527
+ streamingSlice.actions;
7528
+ /**
7529
+ * Selectors
7530
+ */
7531
+ const selectStreamingRaw = (state) => state.streaming;
7532
+
7533
+ const initialState$2 = {
7534
+ waitingParticipants: [],
7535
+ };
7536
+ const waitingParticipantsSlice = createSlice({
7537
+ name: "waitingParticipants",
7538
+ initialState: initialState$2,
7539
+ reducers: {},
7540
+ extraReducers: (builder) => {
7541
+ builder.addCase(signalEvents.roomJoined, (state, { payload }) => {
7542
+ var _a;
7543
+ if ((_a = payload.room) === null || _a === void 0 ? void 0 : _a.knockers.length) {
7544
+ return Object.assign(Object.assign({}, state), { waitingParticipants: payload.room.knockers.map((knocker) => ({
7545
+ id: knocker.clientId,
7546
+ displayName: knocker.displayName,
7547
+ })) });
7548
+ }
7549
+ else {
7550
+ return state;
7551
+ }
7552
+ });
7553
+ builder.addCase(signalEvents.roomKnocked, (state, action) => {
7554
+ const { clientId, displayName } = action.payload;
7555
+ return Object.assign(Object.assign({}, state), { waitingParticipants: [...state.waitingParticipants, { id: clientId, displayName }] });
7556
+ });
7557
+ builder.addCase(signalEvents.knockerLeft, (state, action) => {
7558
+ const { clientId } = action.payload;
7559
+ return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((p) => p.id !== clientId) });
7560
+ });
7561
+ },
7562
+ });
7563
+ /**
7564
+ * Action creators
7565
+ */
7566
+ const doAcceptWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
7567
+ const { participantId } = payload;
7568
+ const state = getState();
7569
+ const socket = selectSignalConnectionSocket(state);
7570
+ socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
7571
+ action: "accept",
7572
+ clientId: participantId,
7573
+ response: {},
7574
+ });
7575
+ });
7576
+ const doRejectWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
7577
+ const { participantId } = payload;
7578
+ const state = getState();
7579
+ const socket = selectSignalConnectionSocket(state);
7580
+ socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
7581
+ action: "reject",
7582
+ clientId: participantId,
7583
+ response: {},
7584
+ });
7585
+ });
7586
+ const selectWaitingParticipants = (state) => state.waitingParticipants.waitingParticipants;
7587
+
7588
+ var _a;
7589
+ const IS_DEV = (_a = process.env.REACT_APP_IS_DEV === "true") !== null && _a !== void 0 ? _a : false;
7590
+ const rootReducer = combineReducers({
7591
+ app: appSlice.reducer,
7592
+ chat: chatSlice.reducer,
7593
+ cloudRecording: cloudRecordingSlice.reducer,
7594
+ deviceCredentials: deviceCredentialsSlice.reducer,
7595
+ localMedia: localMediaSlice.reducer,
7596
+ localParticipant: localParticipantSlice.reducer,
7597
+ localScreenshare: localScreenshareSlice.reducer,
7598
+ organization: organizationSlice.reducer,
7599
+ remoteParticipants: remoteParticipantsSlice.reducer,
7600
+ roomConnection: roomConnectionSlice.reducer,
7601
+ rtcAnalytics: rtcAnalyticsSlice.reducer,
7602
+ rtcConnection: rtcConnectionSlice.reducer,
7603
+ signalConnection: signalConnectionSlice.reducer,
7604
+ streaming: streamingSlice.reducer,
7605
+ waitingParticipants: waitingParticipantsSlice.reducer,
7606
+ });
7607
+ const createStore = ({ preloadedState, injectServices, }) => {
7608
+ return configureStore({
7609
+ devTools: IS_DEV,
7610
+ reducer: rootReducer,
7611
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({
7612
+ thunk: {
7613
+ extraArgument: { services: injectServices },
7614
+ },
7615
+ serializableCheck: false,
7616
+ }).prepend(listenerMiddleware.middleware),
7617
+ preloadedState,
7618
+ });
7619
+ };
7620
+ const observeStore = (store, select, onChange) => {
7621
+ let currentState;
7622
+ function handleChange() {
7623
+ const nextState = select(store.getState());
7624
+ if (nextState !== currentState) {
7625
+ currentState = nextState;
7626
+ onChange(currentState);
7627
+ }
7628
+ }
7629
+ const unsubscribe = store.subscribe(handleChange);
7630
+ handleChange();
7631
+ return unsubscribe;
7632
+ };
7633
+
7634
+ const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
7635
+ const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
7636
+ const localhostPattern = /^(?:([^.]+)[.])?(localhost:?\d*)/;
7637
+ const serverPattern = /^(?:([^.]+)[.])?(server:?\d*)/;
7638
+ const ipv4Pattern = /^(?:([^.]+)[.])?((\d+[.]){3}:?\d*)$/;
7639
+
7640
+ const subdomainPatterns = [
7641
+ { pattern: serverPattern, separator: "." },
7642
+ { pattern: localhostPattern, separator: "." },
7643
+ { pattern: ipv4Pattern, separator: "." },
7644
+ { pattern: localstackPattern, separator: "-" },
7645
+ { pattern: defaultSubdomainPattern, separator: "." },
7646
+ ];
7647
+
7648
+ /**
7649
+ * @param {Location} location - the location object to use to resolve branding information.
7650
+ * @return {Object} urls - urls created from this location
7651
+ */
7652
+ function fromLocation({ host = "whereby.com", protocol = "https:" } = {}) {
7653
+ let subdomain = "";
7654
+ let domain = host;
7655
+ let subdomainSeparator = ".";
7656
+ for (const { separator, pattern } of subdomainPatterns) {
7657
+ const match = pattern.exec(host);
7658
+ if (match) {
7659
+ subdomain = match[1] || "";
7660
+ domain = match[2];
7661
+ subdomainSeparator = separator;
7662
+ break;
7663
+ }
7664
+ }
7665
+ const organizationDomain = !subdomain ? domain : `${subdomain}${subdomainSeparator}${domain}`;
7666
+
7667
+ return {
7668
+ domain,
7669
+ domainWithSeparator: `${subdomainSeparator}${domain}`,
7670
+ organizationDomain,
7671
+ organization: `${protocol}//${organizationDomain}`,
7672
+ service: `${protocol}//${domain}`,
7673
+ subdomain,
7674
+ };
7675
+ }
7676
+
7677
+ fromLocation(window && window.location);
7678
+
7679
+ class Response {
7680
+ constructor(initialValues = {}) {
7681
+ this.data = initialValues.data === undefined ? {} : initialValues.data;
7682
+ this.headers = initialValues.headers || {};
7683
+ this.status = initialValues.status || 200;
7684
+ this.statusText = initialValues.statusText || "OK";
7685
+ this.url = initialValues.url || null;
7686
+ }
7687
+ }
7688
+
7689
+ /**
7690
+ * Asserts that value is truthy.
7691
+ *
7692
+ * @param value - The value to check.
7693
+ * @param {string} parameterName - The name of the parameter.
7694
+ */
7695
+ function assertTruthy(value, parameterName) {
7696
+ assert$1.ok(value, `${parameterName} is required`);
7697
+ return value;
7698
+ }
7699
+ /**
7700
+ * Asserts that value is a number.
7701
+ *
7702
+ * @param value - The value to check.
7703
+ * @param {string} parameterName - The name of the parameter.
5876
7704
  */
5877
7705
  function assertBoolean(value, parameterName) {
5878
7706
  assert$1.ok(typeof value === "boolean", `${parameterName}<boolean> is required`);
@@ -5910,17 +7738,6 @@ function assertInstanceOf(value, type, parameterName) {
5910
7738
  assert$1.ok(value instanceof type, `${resolvedParameterName}<${type.name}> is required`);
5911
7739
  return value;
5912
7740
  }
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
7741
  /**
5925
7742
  * Asserts that the provided array is a valid array.
5926
7743
  *
@@ -5931,22 +7748,6 @@ function assertArray(array, parameterName) {
5931
7748
  assert$1.ok(Array.isArray(array), `${parameterName}<array> is required`);
5932
7749
  return array;
5933
7750
  }
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
7751
  /**
5951
7752
  * Asserts that the provided reference is a record.
5952
7753
  *
@@ -6190,22 +7991,6 @@ function extractString(data, propertyName) {
6190
7991
  return assertString(record[propertyName], propertyName);
6191
7992
  }
6192
7993
  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
7994
  /**
6210
7995
  * Extract an Array from the given Json object.
6211
7996
  * If the value is not a valid Array, an error is thrown.
@@ -6522,43 +8307,6 @@ class CredentialsService extends EventEmitter$1 {
6522
8307
  }
6523
8308
  }
6524
8309
 
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
8310
  class EmbeddedFreeTierStatus {
6563
8311
  constructor({ isExhausted, renewsAt, totalMinutesLimit, totalMinutesUsed, }) {
6564
8312
  this.isExhausted = isExhausted;
@@ -6758,1279 +8506,192 @@ class OrganizationService {
6758
8506
  method: "GET",
6759
8507
  })
6760
8508
  .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;
8509
+ return Organization.fromJson(data);
8510
+ })
8511
+ .catch((res) => {
8512
+ if (res instanceof Response) {
8513
+ if (res.status === 404) {
8514
+ return null;
7705
8515
  }
7706
- else ;
7707
- // Update stream state
7708
- participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
7709
- });
8516
+ throw new Error(res.statusText);
8517
+ }
8518
+ throw res;
7710
8519
  });
7711
8520
  }
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(),
8521
+ /**
8522
+ * Retrieves the organizations that contain a user
8523
+ * matching provided the email+code or phoneNumber+code
8524
+ * combination.
8525
+ */
8526
+ getOrganizationsByContactPoint(options) {
8527
+ const { code } = options;
8528
+ const email = "email" in options ? options.email : null;
8529
+ const phoneNumber = "phoneNumber" in options ? options.phoneNumber : null;
8530
+ assert$1.ok((email || phoneNumber) && !(email && phoneNumber), "either email or phoneNumber is required");
8531
+ assertString(code, "code");
8532
+ const contactPoint = email ? { type: "email", value: email } : { type: "phoneNumber", value: phoneNumber };
8533
+ return this._apiClient
8534
+ .request("/organization-queries", {
8535
+ method: "POST",
8536
+ data: {
8537
+ contactPoint,
8538
+ code,
7724
8539
  },
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
- });
8540
+ })
8541
+ .then(({ data }) => {
8542
+ return extractArray(data, "organizations", (organization) => Organization.fromJson(organization));
7766
8543
  });
7767
8544
  }
7768
- knock() {
7769
- this.connectionStatus = "knocking";
7770
- this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
7771
- detail: {
7772
- connectionStatus: this.connectionStatus,
8545
+ /**
8546
+ * Retrieves the organizations that contain a user
8547
+ * matching provided the idToken
8548
+ */
8549
+ getOrganizationsByIdToken({ idToken }) {
8550
+ assertString(idToken, "idToken");
8551
+ return this._apiClient
8552
+ .request("/organization-queries", {
8553
+ method: "POST",
8554
+ data: {
8555
+ idToken,
7773
8556
  },
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,
8557
+ })
8558
+ .then(({ data }) => {
8559
+ return extractArray(data, "organizations", (organization) => {
8560
+ return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(organization, "organization")));
8561
+ });
7784
8562
  });
7785
8563
  }
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,
8564
+ /**
8565
+ * Retrieves the organizations containing a user
8566
+ * with either the email or phoneNumber matching the logged in user.
8567
+ *
8568
+ * This is useful for showing the possible organization that the current
8569
+ * user could log in to.
8570
+ */
8571
+ getOrganizationsByLoggedInUser() {
8572
+ return this._apiClient
8573
+ .request("/user/organizations", {
8574
+ method: "GET",
8575
+ })
8576
+ .then(({ data }) => {
8577
+ return extractArray(data, "organizations", (o) => {
8578
+ return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(o, "organization")));
8579
+ });
7806
8580
  });
7807
8581
  }
7808
- setDisplayName(displayName) {
7809
- this.signalSocket.emit("send_client_metadata", {
7810
- type: "UserData",
7811
- payload: {
7812
- displayName,
7813
- },
8582
+ /**
8583
+ * Checks if a subdomain is available and verifies its format.
8584
+ */
8585
+ getSubdomainAvailability(subdomain) {
8586
+ assertString(subdomain, "subdomain");
8587
+ return this._apiClient
8588
+ .request(`/organization-subdomains/${encodeURIComponent(subdomain)}/availability`, {
8589
+ method: "GET",
8590
+ })
8591
+ .then(({ data }) => {
8592
+ assertInstanceOf(data, Object, "data");
8593
+ return {
8594
+ status: extractString(data, "status"),
8595
+ };
7814
8596
  });
7815
8597
  }
7816
- acceptWaitingParticipant(participantId) {
7817
- this.signalSocket.emit("handle_knock", {
7818
- action: "accept",
7819
- clientId: participantId,
7820
- response: {},
7821
- });
8598
+ /**
8599
+ * Updates preferences of the organization.
8600
+ */
8601
+ updatePreferences({ organizationId, preferences, }) {
8602
+ assertTruthy(organizationId, "organizationId");
8603
+ assertTruthy(preferences, "preferences");
8604
+ return this._apiClient
8605
+ .request(`/organizations/${encodeURIComponent(organizationId)}/preferences`, {
8606
+ method: "PATCH",
8607
+ data: preferences,
8608
+ })
8609
+ .then(() => undefined);
7822
8610
  }
7823
- rejectWaitingParticipant(participantId) {
7824
- this.signalSocket.emit("handle_knock", {
7825
- action: "reject",
7826
- clientId: participantId,
7827
- response: {},
7828
- });
8611
+ /**
8612
+ * Delete organization
8613
+ */
8614
+ deleteOrganization({ organizationId }) {
8615
+ assertTruthy(organizationId, "organizationId");
8616
+ return this._apiClient
8617
+ .request(`/organizations/${encodeURIComponent(organizationId)}`, {
8618
+ method: "DELETE",
8619
+ })
8620
+ .then(() => undefined);
7829
8621
  }
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 });
8622
+ }
8623
+
8624
+ class OrganizationServiceCache {
8625
+ constructor({ organizationService, subdomain }) {
8626
+ this._organizationService = organizationService;
8627
+ this._subdomain = subdomain;
8628
+ this._organizationPromise = null;
7844
8629
  }
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
- });
8630
+ initOrganization() {
8631
+ return this.fetchOrganization().then(() => undefined);
7881
8632
  }
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();
8633
+ fetchOrganization() {
8634
+ if (!this._organizationPromise) {
8635
+ this._organizationPromise = this._organizationService.getOrganizationBySubdomain(this._subdomain);
7890
8636
  }
8637
+ return this._organizationPromise;
7891
8638
  }
7892
- startCloudRecording() {
7893
- this.signalSocket.emit("start_recording", {
7894
- recording: "cloud",
8639
+ }
8640
+
8641
+ const API_BASE_URL = "https://api.whereby.dev" ;
8642
+ function createServices() {
8643
+ const credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
8644
+ const apiClient = new ApiClient({
8645
+ fetchDeviceCredentials: credentialsService.getCredentials.bind(credentialsService),
8646
+ baseUrl: API_BASE_URL,
8647
+ });
8648
+ const organizationService = new OrganizationService({ apiClient });
8649
+ const fetchOrganizationFromRoomUrl = (roomUrl) => {
8650
+ const roomUrlObj = new URL(roomUrl);
8651
+ const urls = fromLocation({ host: roomUrlObj.host });
8652
+ const organizationServiceCache = new OrganizationServiceCache({
8653
+ organizationService,
8654
+ subdomain: urls.subdomain,
7895
8655
  });
7896
- this.dispatchEvent(new RoomConnectionEvent("cloud_recording_request_started"));
7897
- }
7898
- stopCloudRecording() {
7899
- this.signalSocket.emit("stop_recording");
7900
- }
8656
+ return organizationServiceCache.fetchOrganization();
8657
+ };
8658
+ return {
8659
+ credentialsService,
8660
+ apiClient,
8661
+ organizationService,
8662
+ fetchOrganizationFromRoomUrl,
8663
+ };
7901
8664
  }
7902
8665
 
7903
- const initialState = {
8666
+ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants) => {
8667
+ const state = {
8668
+ chatMessages,
8669
+ cloudRecording: cloudRecording.isRecording ? { status: "recording" } : undefined,
8670
+ localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
8671
+ localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
8672
+ remoteParticipants,
8673
+ screenshares,
8674
+ connectionStatus,
8675
+ liveStream: streaming.isStreaming
8676
+ ? {
8677
+ status: "streaming",
8678
+ startedAt: streaming.startedAt,
8679
+ }
8680
+ : undefined,
8681
+ waitingParticipants,
8682
+ };
8683
+ return state;
8684
+ });
8685
+
8686
+ const sdkVersion = "2.0.0";
8687
+
8688
+ const initialState$1 = {
7904
8689
  chatMessages: [],
7905
8690
  remoteParticipants: [],
7906
8691
  connectionStatus: "initializing",
7907
8692
  screenshares: [],
7908
8693
  waitingParticipants: [],
7909
8694
  };
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
8695
  const defaultRoomConnectionOptions = {
8035
8696
  localMediaOptions: {
8036
8697
  audio: true,
@@ -8038,207 +8699,139 @@ const defaultRoomConnectionOptions = {
8038
8699
  },
8039
8700
  };
8040
8701
  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 }));
8702
+ const [store] = React.useState(() => {
8703
+ if (roomConnectionOptions.localMedia) {
8704
+ return roomConnectionOptions.localMedia.store;
8705
+ }
8706
+ const services = createServices();
8707
+ return createStore({ injectServices: services });
8044
8708
  });
8045
- const [state, dispatch] = useReducer(reducer, initialState);
8046
- function createEventListener(eventName, listener, options) {
8047
- return {
8048
- eventName,
8049
- listener,
8050
- options,
8709
+ const [boundVideoView, setBoundVideoView] = React.useState();
8710
+ const [roomConnectionState, setRoomConnectionState] = React.useState(initialState$1);
8711
+ React.useEffect(() => {
8712
+ const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
8713
+ const url = new URL(roomUrl); // Throw if invalid Whereby room url
8714
+ const searchParams = new URLSearchParams(url.search);
8715
+ const roomKey = searchParams.get("roomKey");
8716
+ store.dispatch(doAppJoin({
8717
+ displayName: roomConnectionOptions.displayName || "Guest",
8718
+ localMediaOptions: roomConnectionOptions.localMedia
8719
+ ? undefined
8720
+ : roomConnectionOptions.localMediaOptions,
8721
+ roomKey,
8722
+ roomUrl,
8723
+ sdkVersion: sdkVersion,
8724
+ externalId: roomConnectionOptions.externalId || null,
8725
+ }));
8726
+ return () => {
8727
+ unsubscribe();
8728
+ store.dispatch(appLeft());
8051
8729
  };
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 },
8730
+ }, []);
8731
+ React.useEffect(() => {
8732
+ if (store && !boundVideoView) {
8733
+ setBoundVideoView(() => (props) => {
8734
+ return React.createElement(VideoView, Object.assign({}, props, {
8735
+ onResize: ({ stream, width, height, }) => {
8736
+ store.dispatch(doRtcReportStreamResolution({
8737
+ streamId: stream.id,
8738
+ width,
8739
+ height,
8740
+ }));
8741
+ },
8742
+ }));
8165
8743
  });
8166
- }),
8167
- ], []);
8744
+ }
8745
+ }, [store, boundVideoView]);
8746
+ const sendChatMessage = React.useCallback((text) => store.dispatch(doSendChatMessage({ text })), [store]);
8747
+ const knock = React.useCallback(() => store.dispatch(doKnockRoom()), [store]);
8748
+ const setDisplayName = React.useCallback((displayName) => store.dispatch(doSetDisplayName({ displayName })), [store]);
8749
+ const toggleCamera = React.useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
8750
+ const toggleMicrophone = React.useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
8751
+ const acceptWaitingParticipant = React.useCallback((participantId) => store.dispatch(doAcceptWaitingParticipant({ participantId })), [store]);
8752
+ const rejectWaitingParticipant = React.useCallback((participantId) => store.dispatch(doRejectWaitingParticipant({ participantId })), [store]);
8753
+ const startCloudRecording = React.useCallback(() => store.dispatch(doStartCloudRecording()), [store]);
8754
+ const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
8755
+ const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
8756
+ const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
8757
+ return {
8758
+ state: roomConnectionState,
8759
+ actions: {
8760
+ sendChatMessage,
8761
+ knock,
8762
+ setDisplayName,
8763
+ toggleCamera,
8764
+ toggleMicrophone,
8765
+ acceptWaitingParticipant,
8766
+ rejectWaitingParticipant,
8767
+ startCloudRecording,
8768
+ startScreenshare,
8769
+ stopCloudRecording,
8770
+ stopScreenshare,
8771
+ },
8772
+ components: {
8773
+ VideoView: boundVideoView || VideoView,
8774
+ },
8775
+ _ref: store,
8776
+ };
8777
+ }
8778
+
8779
+ 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) => {
8780
+ const state = {
8781
+ cameraDeviceError,
8782
+ cameraDevices,
8783
+ currentCameraDeviceId,
8784
+ currentMicrophoneDeviceId,
8785
+ isSettingCameraDevice,
8786
+ isSettingMicrophoneDevice,
8787
+ isStarting,
8788
+ localStream,
8789
+ microphoneDeviceError,
8790
+ microphoneDevices,
8791
+ speakerDevices,
8792
+ startError,
8793
+ };
8794
+ return state;
8795
+ });
8796
+
8797
+ const initialState = {
8798
+ cameraDeviceError: null,
8799
+ cameraDevices: [],
8800
+ isSettingCameraDevice: false,
8801
+ isSettingMicrophoneDevice: false,
8802
+ isStarting: false,
8803
+ microphoneDeviceError: null,
8804
+ microphoneDevices: [],
8805
+ speakerDevices: [],
8806
+ startError: null,
8807
+ };
8808
+ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
8809
+ const [store] = useState(() => {
8810
+ const services = createServices();
8811
+ return createStore({ injectServices: services });
8812
+ });
8813
+ const [localMediaState, setLocalMediaState] = useState(initialState);
8168
8814
  useEffect(() => {
8169
- eventListeners.forEach(({ eventName, listener }) => {
8170
- roomConnection.addEventListener(eventName, listener);
8171
- });
8172
- roomConnection.join();
8815
+ const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
8816
+ store.dispatch(doStartLocalMedia(optionsOrStream));
8173
8817
  return () => {
8174
- eventListeners.forEach(({ eventName, listener }) => {
8175
- roomConnection.removeEventListener(eventName, listener);
8176
- });
8177
- roomConnection.leave();
8818
+ unsubscribe();
8819
+ store.dispatch(doStopLocalMedia());
8178
8820
  };
8179
8821
  }, []);
8822
+ const setCameraDevice = useCallback((deviceId) => store.dispatch(setCurrentCameraDeviceId({ deviceId })), [store]);
8823
+ const setMicrophoneDevice = useCallback((deviceId) => store.dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [store]);
8824
+ const toggleCamera = useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
8825
+ const toggleMicrophone = useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
8180
8826
  return {
8181
- state,
8827
+ state: localMediaState,
8182
8828
  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
- })),
8829
+ setCameraDevice,
8830
+ setMicrophoneDevice,
8831
+ toggleCameraEnabled: toggleCamera,
8832
+ toggleMicrophoneEnabled: toggleMicrophone,
8240
8833
  },
8241
- _ref: roomConnection,
8834
+ store,
8242
8835
  };
8243
8836
  }
8244
8837