@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.
- package/README.md +1 -5
- package/dist/cdn/v2-embed.js +16 -0
- package/dist/embed/index.d.ts +20 -6
- package/dist/embed/index.esm.js +55 -31
- package/dist/react/index.d.ts +728 -292
- package/dist/react/index.esm.js +2593 -2000
- package/package.json +5 -1
- package/dist/v2-beta3.js +0 -15
package/dist/react/index.esm.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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 (
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
584
|
-
|
|
1238
|
+
if (videoTrack) {
|
|
1239
|
+
cameraEnabled = videoTrack.enabled;
|
|
1240
|
+
cameraDeviceId = videoTrack.getSettings().deviceId;
|
|
585
1241
|
}
|
|
586
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
(
|
|
612
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
641
|
-
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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$
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
return Object.assign(Object.assign({}, state), {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
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
|
-
*
|
|
5863
|
-
*
|
|
5864
|
-
* @param value - The value to check.
|
|
5865
|
-
* @param {string} parameterName - The name of the parameter.
|
|
7221
|
+
* Selectors
|
|
5866
7222
|
*/
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
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
|
-
*
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
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
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
});
|
|
8516
|
+
throw new Error(res.statusText);
|
|
8517
|
+
}
|
|
8518
|
+
throw res;
|
|
7710
8519
|
});
|
|
7711
8520
|
}
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
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
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
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
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
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
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
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
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
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
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
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
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
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
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
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
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
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
|
-
|
|
7846
|
-
|
|
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
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
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
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
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
|
-
|
|
7897
|
-
}
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
8656
|
+
return organizationServiceCache.fetchOrganization();
|
|
8657
|
+
};
|
|
8658
|
+
return {
|
|
8659
|
+
credentialsService,
|
|
8660
|
+
apiClient,
|
|
8661
|
+
organizationService,
|
|
8662
|
+
fetchOrganizationFromRoomUrl,
|
|
8663
|
+
};
|
|
7901
8664
|
}
|
|
7902
8665
|
|
|
7903
|
-
const
|
|
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 [
|
|
8042
|
-
|
|
8043
|
-
|
|
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 [
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
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
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
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
|
-
|
|
8170
|
-
|
|
8171
|
-
});
|
|
8172
|
-
roomConnection.join();
|
|
8815
|
+
const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
|
|
8816
|
+
store.dispatch(doStartLocalMedia(optionsOrStream));
|
|
8173
8817
|
return () => {
|
|
8174
|
-
|
|
8175
|
-
|
|
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
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
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
|
-
|
|
8834
|
+
store,
|
|
8242
8835
|
};
|
|
8243
8836
|
}
|
|
8244
8837
|
|