@whereby.com/browser-sdk 2.0.0-alpha1 → 2.0.0-alpha10

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.
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var heresy = require('heresy');
6
- var React = require('react');
7
6
  var tslib = require('tslib');
7
+ var React = require('react');
8
8
  var adapter = require('webrtc-adapter');
9
9
  var io = require('socket.io-client');
10
10
  var SDPUtils = require('sdp');
@@ -130,7 +130,7 @@ heresy.define("WherebyEmbed", {
130
130
  if (!subdomain)
131
131
  return this.html `Whereby: Missing subdomain attr.`;
132
132
  const url = new URL(room, `https://${subdomain}.whereby.com`);
133
- Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha1", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
133
+ Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha10", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
134
134
  // add to URL if set in any way
135
135
  (o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
136
136
  if (!url.searchParams.has(k) && typeof v === "string") {
@@ -146,15 +146,261 @@ heresy.define("WherebyEmbed", {
146
146
  },
147
147
  });
148
148
 
149
- var VideoElement = ({ stream, style }) => {
150
- const videoEl = React.useCallback((node) => {
151
- if (node !== null && node.srcObject !== stream) {
152
- node.srcObject = stream;
149
+ var VideoView = (_a) => {
150
+ var { muted, stream } = _a, rest = tslib.__rest(_a, ["muted", "stream"]);
151
+ const videoEl = React.useRef(null);
152
+ React.useEffect(() => {
153
+ if (!videoEl.current) {
154
+ return;
153
155
  }
154
- }, []);
155
- return React__default["default"].createElement("video", { ref: videoEl, autoPlay: true, playsInline: true, style: style });
156
+ if (videoEl.current.srcObject !== stream) {
157
+ videoEl.current.srcObject = stream;
158
+ }
159
+ // Handle muting programatically, not as video attribute
160
+ // https://stackoverflow.com/questions/14111917/html5-video-muted-but-still-playing
161
+ if (videoEl.current.muted !== muted) {
162
+ videoEl.current.muted = Boolean(muted);
163
+ }
164
+ }, [muted, stream, videoEl]);
165
+ return React__default["default"].createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest));
156
166
  };
157
167
 
168
+ const TypedLocalMediaEventTarget = EventTarget;
169
+ class LocalMedia extends TypedLocalMediaEventTarget {
170
+ constructor(constraints) {
171
+ super();
172
+ this._constraints = constraints;
173
+ this.stream = new MediaStream();
174
+ this._rtcManagers = [];
175
+ navigator.mediaDevices.addEventListener("devicechange", this._updateDeviceList.bind(this));
176
+ }
177
+ addRtcManager(rtcManager) {
178
+ this._rtcManagers.push(rtcManager);
179
+ }
180
+ removeRtcManager(rtcManager) {
181
+ this._rtcManagers = this._rtcManagers.filter((r) => r !== rtcManager);
182
+ }
183
+ getCameraDeviceId() {
184
+ var _a;
185
+ return (_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
186
+ }
187
+ getMicrophoneDeviceId() {
188
+ var _a;
189
+ return (_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
190
+ }
191
+ isCameraEnabled() {
192
+ var _a;
193
+ return !!((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
194
+ }
195
+ isMicrophoneEnabled() {
196
+ var _a;
197
+ return !!((_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
198
+ }
199
+ toggleCameraEnabled(enabled) {
200
+ const videoTrack = this.stream.getVideoTracks()[0];
201
+ if (!videoTrack) {
202
+ return;
203
+ }
204
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !videoTrack.enabled;
205
+ videoTrack.enabled = newValue;
206
+ this.dispatchEvent(new CustomEvent("camera_enabled", { detail: { enabled: newValue } }));
207
+ }
208
+ toggleMichrophoneEnabled(enabled) {
209
+ const audioTrack = this.stream.getAudioTracks()[0];
210
+ if (!audioTrack) {
211
+ return;
212
+ }
213
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !audioTrack.enabled;
214
+ audioTrack.enabled = newValue;
215
+ this.dispatchEvent(new CustomEvent("microphone_enabled", { detail: { enabled: newValue } }));
216
+ }
217
+ setCameraDevice(deviceId) {
218
+ return tslib.__awaiter(this, void 0, void 0, function* () {
219
+ const newStream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId } });
220
+ const newVideoTrack = newStream.getVideoTracks()[0];
221
+ if (newVideoTrack) {
222
+ const oldVideoTrack = this.stream.getVideoTracks()[0];
223
+ newVideoTrack.enabled = oldVideoTrack.enabled;
224
+ oldVideoTrack === null || oldVideoTrack === void 0 ? void 0 : oldVideoTrack.stop();
225
+ this._rtcManagers.forEach((rtcManager) => {
226
+ rtcManager.replaceTrack(oldVideoTrack, newVideoTrack);
227
+ });
228
+ this.stream.removeTrack(oldVideoTrack);
229
+ this.stream.addTrack(newVideoTrack);
230
+ }
231
+ this.dispatchEvent(new CustomEvent("stream_updated", {
232
+ detail: { stream: this.stream },
233
+ }));
234
+ });
235
+ }
236
+ setMicrophoneDevice(deviceId) {
237
+ return tslib.__awaiter(this, void 0, void 0, function* () {
238
+ const newStream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId } });
239
+ const newAudioTrack = newStream.getAudioTracks()[0];
240
+ const oldAudioTrack = this.stream.getAudioTracks()[0];
241
+ if (oldAudioTrack) {
242
+ newAudioTrack.enabled = oldAudioTrack.enabled;
243
+ oldAudioTrack.stop();
244
+ this.stream.removeTrack(oldAudioTrack);
245
+ }
246
+ this._rtcManagers.forEach((rtcManager) => {
247
+ rtcManager.replaceTrack(oldAudioTrack, newAudioTrack);
248
+ });
249
+ this.stream.addTrack(newAudioTrack);
250
+ this.dispatchEvent(new CustomEvent("stream_updated", {
251
+ detail: { stream: this.stream },
252
+ }));
253
+ });
254
+ }
255
+ _updateDeviceList() {
256
+ return tslib.__awaiter(this, void 0, void 0, function* () {
257
+ try {
258
+ const devices = yield navigator.mediaDevices.enumerateDevices();
259
+ this.dispatchEvent(new CustomEvent("device_list_updated", {
260
+ detail: {
261
+ cameraDevices: devices.filter((d) => d.kind === "videoinput"),
262
+ microphoneDevices: devices.filter((d) => d.kind === "audioinput"),
263
+ speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
264
+ },
265
+ }));
266
+ }
267
+ catch (error) {
268
+ this.dispatchEvent(new CustomEvent("device_list_update_error", {
269
+ detail: {
270
+ error,
271
+ },
272
+ }));
273
+ throw error;
274
+ }
275
+ });
276
+ }
277
+ start() {
278
+ return tslib.__awaiter(this, void 0, void 0, function* () {
279
+ const newStream = yield navigator.mediaDevices.getUserMedia(this._constraints);
280
+ newStream.getTracks().forEach((t) => this.stream.addTrack(t));
281
+ this._updateDeviceList();
282
+ this.dispatchEvent(new CustomEvent("stream_updated", {
283
+ detail: { stream: this.stream },
284
+ }));
285
+ return this.stream;
286
+ });
287
+ }
288
+ stop() {
289
+ var _a;
290
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
291
+ t.stop();
292
+ });
293
+ }
294
+ }
295
+
296
+ const initialState$1 = {
297
+ cameraDeviceError: null,
298
+ cameraDevices: [],
299
+ isSettingCameraDevice: false,
300
+ isSettingMicrophoneDevice: false,
301
+ isStarting: false,
302
+ microphoneDeviceError: null,
303
+ microphoneDevices: [],
304
+ speakerDevices: [],
305
+ startError: null,
306
+ };
307
+ function reducer$1(state, action) {
308
+ switch (action.type) {
309
+ case "DEVICE_LIST_UPDATED":
310
+ return Object.assign(Object.assign({}, state), action.payload);
311
+ case "LOCAL_STREAM_UPDATED":
312
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.currentCameraDeviceId, currentMicrophoneDeviceId: action.payload.currentMicrophoneDeviceId, localStream: action.payload.stream });
313
+ case "SET_CAMERA_DEVICE":
314
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: null, isSettingCameraDevice: true });
315
+ case "SET_CAMERA_DEVICE_COMPLETE":
316
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
317
+ case "SET_CAMERA_DEVICE_ERROR":
318
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
319
+ case "SET_MICROPHONE_DEVICE":
320
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
321
+ case "SET_MICROPHONE_DEVICE_COMPLETE":
322
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
323
+ case "SET_MICROPHONE_DEVICE_ERROR":
324
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
325
+ case "START":
326
+ return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
327
+ case "START_COMPLETE":
328
+ return Object.assign(Object.assign({}, state), { isStarting: false });
329
+ case "START_ERROR":
330
+ return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
331
+ default:
332
+ return state;
333
+ }
334
+ }
335
+ function useLocalMedia(constraints = { audio: true, video: true }) {
336
+ const [localMedia] = React.useState(() => new LocalMedia(constraints));
337
+ const [state, dispatch] = React.useReducer(reducer$1, initialState$1);
338
+ React.useEffect(() => {
339
+ localMedia.addEventListener("device_list_updated", (e) => {
340
+ const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
341
+ dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
342
+ });
343
+ localMedia.addEventListener("stream_updated", (e) => {
344
+ const { stream } = e.detail;
345
+ dispatch({
346
+ type: "LOCAL_STREAM_UPDATED",
347
+ payload: {
348
+ stream,
349
+ currentCameraDeviceId: localMedia.getCameraDeviceId(),
350
+ currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
351
+ },
352
+ });
353
+ });
354
+ const start = () => tslib.__awaiter(this, void 0, void 0, function* () {
355
+ dispatch({ type: "START" });
356
+ try {
357
+ yield localMedia.start();
358
+ dispatch({ type: "START_COMPLETE" });
359
+ }
360
+ catch (error) {
361
+ dispatch({ type: "START_ERROR", payload: error });
362
+ }
363
+ });
364
+ start();
365
+ // Perform cleanup on unmount
366
+ return () => {
367
+ localMedia.stop();
368
+ };
369
+ }, []);
370
+ return {
371
+ state,
372
+ actions: {
373
+ setCameraDevice: (...args) => tslib.__awaiter(this, void 0, void 0, function* () {
374
+ dispatch({ type: "SET_CAMERA_DEVICE" });
375
+ try {
376
+ yield localMedia.setCameraDevice(...args);
377
+ dispatch({ type: "SET_CAMERA_DEVICE_COMPLETE" });
378
+ }
379
+ catch (error) {
380
+ dispatch({ type: "SET_CAMERA_DEVICE_ERROR", payload: error });
381
+ }
382
+ }),
383
+ setMicrophoneDevice: (...args) => tslib.__awaiter(this, void 0, void 0, function* () {
384
+ dispatch({ type: "SET_MICROPHONE_DEVICE" });
385
+ try {
386
+ yield localMedia.setMicrophoneDevice(...args);
387
+ dispatch({ type: "SET_MICROPHONE_DEVICE_COMPLETE" });
388
+ }
389
+ catch (error) {
390
+ dispatch({ type: "SET_MICROPHONE_DEVICE_ERROR", payload: error });
391
+ }
392
+ }),
393
+ toggleCameraEnabled: (...args) => {
394
+ return localMedia.toggleCameraEnabled(...args);
395
+ },
396
+ toggleMicrophoneEnabled: (...args) => {
397
+ return localMedia.toggleMichrophoneEnabled(...args);
398
+ },
399
+ },
400
+ _ref: localMedia,
401
+ };
402
+ }
403
+
158
404
  const EVENTS = {
159
405
  CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
160
406
  STREAM_ADDED: "stream_added",
@@ -285,10 +531,10 @@ class ServerSocket {
285
531
  }
286
532
 
287
533
  this._socket = io__default["default"](hostName, options);
288
- this._socket.on("reconnect", () => {
534
+ this._socket.io.on("reconnect", () => {
289
535
  this._socket.sendBuffer = [];
290
536
  });
291
- this._socket.on("reconnect_attempt", () => {
537
+ this._socket.io.on("reconnect_attempt", () => {
292
538
  if (this._wasConnectedUsingWebsocket) {
293
539
  this._socket.io.opts.transports = ["websocket"];
294
540
  // only fallback to polling if not safari
@@ -346,6 +592,10 @@ class ServerSocket {
346
592
  );
347
593
  }
348
594
 
595
+ getManager() {
596
+ return this._socket.io;
597
+ }
598
+
349
599
  isConnecting() {
350
600
  return this._socket && this._socket.connecting;
351
601
  }
@@ -1979,10 +2229,11 @@ class VegaParser {
1979
2229
  }
1980
2230
 
1981
2231
  class VegaConnection extends EventEmitter.EventEmitter {
1982
- constructor(wsUrl, logger) {
2232
+ constructor(wsUrl, logger, protocol = "whereby-sfu#v4") {
1983
2233
  super();
1984
2234
 
1985
2235
  this.wsUrl = wsUrl;
2236
+ this.protocol = protocol;
1986
2237
  this.logger = logger;
1987
2238
 
1988
2239
  // This is the map of sent requests that are waiting for a response
@@ -1991,7 +2242,7 @@ class VegaConnection extends EventEmitter.EventEmitter {
1991
2242
  }
1992
2243
 
1993
2244
  _setupSocket() {
1994
- this.socket = new WebSocket(this.wsUrl, "whereby-sfu#v4");
2245
+ this.socket = new WebSocket(this.wsUrl, this.protocol);
1995
2246
  this.socket.onopen = this._onOpen.bind(this);
1996
2247
  this.socket.onmessage = this._onMessage.bind(this);
1997
2248
  this.socket.onclose = this._onClose.bind(this);
@@ -2011,7 +2262,9 @@ class VegaConnection extends EventEmitter.EventEmitter {
2011
2262
  }
2012
2263
 
2013
2264
  close() {
2014
- this.socket?.close();
2265
+ if (!this.socket) return;
2266
+
2267
+ this.socket.close();
2015
2268
  }
2016
2269
 
2017
2270
  _onOpen() {
@@ -2023,11 +2276,15 @@ class VegaConnection extends EventEmitter.EventEmitter {
2023
2276
  _onMessage(event) {
2024
2277
  const socketMessage = VegaParser.parse(event.data);
2025
2278
 
2279
+ if (!socketMessage) {
2280
+ return this.logger.log("VegaConnectionManager: Received invalid message", event.data);
2281
+ }
2282
+
2026
2283
  this.logger.log("VegaConnectionManager: Received message", socketMessage);
2027
2284
 
2028
- if (socketMessage?.response) {
2285
+ if (socketMessage.response) {
2029
2286
  this._handleResponse(socketMessage);
2030
- } else if (socketMessage?.message) {
2287
+ } else if (socketMessage.message) {
2031
2288
  this.emit("message", socketMessage);
2032
2289
  }
2033
2290
  }
@@ -2134,7 +2391,7 @@ const VIDEO_SETTINGS_VP9 = {
2134
2391
  codecOptions: {
2135
2392
  videoGoogleStartBitrate: 500,
2136
2393
  },
2137
- encodings: [{ scalabilityMode: "S3T3_KEY", networkPriority: "high" }],
2394
+ encodings: [{ scalabilityMode: "L3T2_KEY", networkPriority: "high" }],
2138
2395
  };
2139
2396
 
2140
2397
  const SCREEN_SHARE_SETTINGS = {
@@ -2143,13 +2400,13 @@ const SCREEN_SHARE_SETTINGS = {
2143
2400
 
2144
2401
  const SCREEN_SHARE_SIMULCAST_SETTINGS = {
2145
2402
  encodings: [
2146
- { dtx: true, maxBitrate: 500000 },
2147
- { dtx: true, maxBitrate: 1500000 },
2403
+ { scaleResolutionDownBy: 2, dtx: true, maxBitrate: 500000 },
2404
+ { scaleResolutionDownBy: 1, dtx: true, maxBitrate: 1500000 },
2148
2405
  ],
2149
2406
  };
2150
2407
 
2151
2408
  const SCREEN_SHARE_SETTINGS_VP9 = {
2152
- encodings: [{ scalabilityMode: "S3T3", dtx: true, networkPriority: "high" }],
2409
+ encodings: [{ scalabilityMode: "L1T1", dtx: true, networkPriority: "high" }],
2153
2410
  };
2154
2411
 
2155
2412
  const getMediaSettings = (kind, isScreenShare, features) => {
@@ -2165,8 +2422,8 @@ const getMediaSettings = (kind, isScreenShare, features) => {
2165
2422
 
2166
2423
  return SCREEN_SHARE_SETTINGS;
2167
2424
  } else {
2168
- if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2169
2425
  if (vp9On) return VIDEO_SETTINGS_VP9;
2426
+ if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2170
2427
 
2171
2428
  return VIDEO_SETTINGS_HD;
2172
2429
  }
@@ -5020,12 +5277,12 @@ class LocalParticipant extends RoomParticipant {
5020
5277
  }
5021
5278
  }
5022
5279
 
5023
- const API_BASE_URL = "https://api.appearin.net";
5280
+ const API_BASE_URL = "https://api.whereby.dev";
5024
5281
  const SIGNAL_BASE_URL = "wss://signal.appearin.net";
5025
5282
  const NON_PERSON_ROLES = ["recorder", "streamer"];
5026
5283
  function createSocket() {
5027
5284
  const parsedUrl = new URL(SIGNAL_BASE_URL);
5028
- const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v1`;
5285
+ const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v4`;
5029
5286
  const SOCKET_HOST = parsedUrl.origin;
5030
5287
  const socketConf = {
5031
5288
  host: SOCKET_HOST,
@@ -5033,6 +5290,7 @@ function createSocket() {
5033
5290
  reconnectionDelay: 5000,
5034
5291
  reconnectionDelayMax: 30000,
5035
5292
  timeout: 10000,
5293
+ withCredentials: true,
5036
5294
  };
5037
5295
  return new ServerSocket(SOCKET_HOST, socketConf);
5038
5296
  }
@@ -5041,12 +5299,17 @@ const noop = () => {
5041
5299
  };
5042
5300
  const TypedEventTarget = EventTarget;
5043
5301
  class RoomConnection extends TypedEventTarget {
5044
- constructor(roomUrl, { displayName, localMediaConstraints, localStream, logger }) {
5302
+ constructor(roomUrl, { displayName, localMedia, localMediaConstraints, logger, roomKey }) {
5045
5303
  super();
5046
5304
  this.localParticipant = null;
5047
5305
  this.remoteParticipants = [];
5048
- this.roomConnectionState = "";
5306
+ this._ownsLocalMedia = false;
5307
+ this.organizationId = "";
5308
+ this.roomConnectionStatus = "";
5049
5309
  this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
5310
+ const searchParams = new URLSearchParams(this.roomUrl.search);
5311
+ this._roomKey = roomKey || searchParams.get("roomKey");
5312
+ this.roomName = this.roomUrl.pathname;
5050
5313
  this.logger = logger || {
5051
5314
  debug: noop,
5052
5315
  error: noop,
@@ -5054,10 +5317,20 @@ class RoomConnection extends TypedEventTarget {
5054
5317
  warn: noop,
5055
5318
  };
5056
5319
  this.displayName = displayName;
5057
- this.localStream = localStream;
5058
5320
  this.localMediaConstraints = localMediaConstraints;
5059
5321
  const urls = fromLocation({ host: this.roomUrl.host });
5060
- // Initialize services
5322
+ // Set up local media
5323
+ if (localMedia) {
5324
+ this.localMedia = localMedia;
5325
+ }
5326
+ else if (localMediaConstraints) {
5327
+ this.localMedia = new LocalMedia(localMediaConstraints);
5328
+ this._ownsLocalMedia = true;
5329
+ }
5330
+ else {
5331
+ throw new Error("Missing constraints");
5332
+ }
5333
+ // Set up services
5061
5334
  this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
5062
5335
  this.apiClient = new ApiClient({
5063
5336
  fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
@@ -5079,10 +5352,30 @@ class RoomConnection extends TypedEventTarget {
5079
5352
  // Create signal socket and set up event listeners
5080
5353
  this.signalSocket = createSocket();
5081
5354
  this.signalSocket.on("new_client", this._handleNewClient.bind(this));
5355
+ this.signalSocket.on("chat_message", this._handleNewChatMessage.bind(this));
5082
5356
  this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
5083
5357
  this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
5084
5358
  this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
5085
5359
  this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
5360
+ this.signalSocket.on("knock_handled", this._handleKnockHandled.bind(this));
5361
+ this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
5362
+ this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
5363
+ this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
5364
+ // Set up local media listeners
5365
+ this.localMedia.addEventListener("camera_enabled", (e) => {
5366
+ const { enabled } = e.detail;
5367
+ this.signalSocket.emit("enable_video", { enabled });
5368
+ });
5369
+ this.localMedia.addEventListener("microphone_enabled", (e) => {
5370
+ const { enabled } = e.detail;
5371
+ this.signalSocket.emit("enable_audio", { enabled });
5372
+ });
5373
+ }
5374
+ get roomKey() {
5375
+ return this._roomKey;
5376
+ }
5377
+ _handleNewChatMessage(message) {
5378
+ this.dispatchEvent(new CustomEvent("chat_message", { detail: message }));
5086
5379
  }
5087
5380
  _handleNewClient({ client }) {
5088
5381
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
@@ -5128,6 +5421,70 @@ class RoomConnection extends TypedEventTarget {
5128
5421
  detail: { participantId: remoteParticipant.id, displayName },
5129
5422
  }));
5130
5423
  }
5424
+ _handleKnockHandled(payload) {
5425
+ const { resolution } = payload;
5426
+ if (resolution === "accepted") {
5427
+ this.roomConnectionStatus = "accepted";
5428
+ this._roomKey = payload.metadata.roomKey;
5429
+ this._joinRoom();
5430
+ }
5431
+ else if (resolution === "rejected") {
5432
+ this.roomConnectionStatus = "rejected";
5433
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5434
+ detail: {
5435
+ roomConnectionStatus: this.roomConnectionStatus,
5436
+ },
5437
+ }));
5438
+ }
5439
+ }
5440
+ _handleKnockerLeft(payload) {
5441
+ const { clientId } = payload;
5442
+ this.dispatchEvent(new CustomEvent("waiting_participant_left", {
5443
+ detail: { participantId: clientId },
5444
+ }));
5445
+ }
5446
+ _handleRoomJoined(event) {
5447
+ const { error, isLocked, room, selfId } = event;
5448
+ if (error === "room_locked" && isLocked) {
5449
+ this.roomConnectionStatus = "room_locked";
5450
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5451
+ detail: {
5452
+ roomConnectionStatus: this.roomConnectionStatus,
5453
+ },
5454
+ }));
5455
+ return;
5456
+ }
5457
+ // Check if we have an error
5458
+ // Check if it is a room joined error
5459
+ // Set state to connect_failed_locked
5460
+ // Set state to connect_failed_no_host
5461
+ if (room) {
5462
+ const { clients, knockers } = room;
5463
+ const localClient = clients.find((c) => c.id === selfId);
5464
+ if (!localClient)
5465
+ throw new Error("Missing local client");
5466
+ this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
5467
+ this.remoteParticipants = clients
5468
+ .filter((c) => c.id !== selfId)
5469
+ .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
5470
+ this.roomConnectionStatus = "connected";
5471
+ this.dispatchEvent(new CustomEvent("room_joined", {
5472
+ detail: {
5473
+ localParticipant: this.localParticipant,
5474
+ remoteParticipants: this.remoteParticipants,
5475
+ waitingParticipants: knockers.map((knocker) => {
5476
+ return { id: knocker.clientId, displayName: knocker.displayName };
5477
+ }),
5478
+ },
5479
+ }));
5480
+ }
5481
+ }
5482
+ _handleRoomKnocked(event) {
5483
+ const { clientId, displayName } = event;
5484
+ this.dispatchEvent(new CustomEvent("waiting_participant_joined", {
5485
+ detail: { participantId: clientId, displayName },
5486
+ }));
5487
+ }
5131
5488
  _handleRtcEvent(eventName, data) {
5132
5489
  if (eventName === "rtc_manager_created") {
5133
5490
  return this._handleRtcManagerCreated(data);
@@ -5140,10 +5497,14 @@ class RoomConnection extends TypedEventTarget {
5140
5497
  }
5141
5498
  }
5142
5499
  _handleRtcManagerCreated({ rtcManager }) {
5143
- var _a, _b, _c;
5500
+ var _a;
5144
5501
  this.rtcManager = rtcManager;
5145
- if (this.localStream) {
5146
- (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localStream, !((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getAudioTracks().find((t) => t.enabled)), !((_c = this.localStream) === null || _c === void 0 ? void 0 : _c.getVideoTracks().find((t) => t.enabled)));
5502
+ this.localMedia.addRtcManager(rtcManager);
5503
+ if (this.localMedia.stream) {
5504
+ (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
5505
+ }
5506
+ if (this.remoteParticipants.length) {
5507
+ this._handleAcceptStreams(this.remoteParticipants);
5147
5508
  }
5148
5509
  }
5149
5510
  _handleAcceptStreams(remoteParticipants) {
@@ -5168,8 +5529,6 @@ class RoomConnection extends TypedEventTarget {
5168
5529
  if (!newState) {
5169
5530
  return;
5170
5531
  }
5171
- // #endregion
5172
- // #region doAcceptStreams
5173
5532
  if (newState === "to_accept" ||
5174
5533
  (newState === "new_accept" && shouldAcceptNewClients) ||
5175
5534
  (newState === "old_accept" && !shouldAcceptNewClients)) {
@@ -5193,7 +5552,6 @@ class RoomConnection extends TypedEventTarget {
5193
5552
  else ;
5194
5553
  // Update stream state
5195
5554
  participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
5196
- // #endregion
5197
5555
  });
5198
5556
  });
5199
5557
  }
@@ -5205,35 +5563,51 @@ class RoomConnection extends TypedEventTarget {
5205
5563
  }
5206
5564
  this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
5207
5565
  }
5208
- /**
5209
- * Public API
5210
- */
5566
+ _joinRoom() {
5567
+ this.signalSocket.emit("join_room", {
5568
+ avatarUrl: null,
5569
+ config: {
5570
+ isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
5571
+ isVideoEnabled: this.localMedia.isCameraEnabled(),
5572
+ },
5573
+ deviceCapabilities: { canScreenshare: true },
5574
+ displayName: this.displayName,
5575
+ isCoLocated: false,
5576
+ isDevicePermissionDenied: false,
5577
+ kickFromOtherRooms: false,
5578
+ organizationId: this.organizationId,
5579
+ roomKey: this.roomKey,
5580
+ roomName: this.roomName,
5581
+ selfId: "",
5582
+ userAgent: `browser-sdk:${sdkVersion }`,
5583
+ });
5584
+ }
5211
5585
  join() {
5212
5586
  return tslib.__awaiter(this, void 0, void 0, function* () {
5213
- if (["connected", "connecting"].includes(this.roomConnectionState)) {
5214
- console.warn(`Trying to join room state is ${this.roomConnectionState}`);
5587
+ if (["connected", "connecting"].includes(this.roomConnectionStatus)) {
5588
+ console.warn(`Trying to join when room state is already ${this.roomConnectionStatus}`);
5215
5589
  return;
5216
5590
  }
5217
5591
  this.logger.log("Joining room");
5218
- this.roomConnectionState = "connecting";
5219
- if (!this.localStream && this.localMediaConstraints) {
5220
- const localStream = yield navigator.mediaDevices.getUserMedia(this.localMediaConstraints);
5221
- this.localStream = localStream;
5222
- }
5592
+ this.roomConnectionStatus = "connecting";
5593
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5594
+ detail: {
5595
+ roomConnectionStatus: this.roomConnectionStatus,
5596
+ },
5597
+ }));
5223
5598
  const organization = yield this.organizationServiceCache.fetchOrganization();
5224
5599
  if (!organization) {
5225
5600
  throw new Error("Invalid room url");
5226
5601
  }
5227
- // TODO: Get room permissions
5228
- // TODO: Get room features
5602
+ this.organizationId = organization.organizationId;
5603
+ if (this._ownsLocalMedia) {
5604
+ yield this.localMedia.start();
5605
+ }
5229
5606
  const webrtcProvider = {
5230
- getMediaConstraints: () => {
5231
- var _a, _b;
5232
- return ({
5233
- audio: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5234
- video: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5235
- });
5236
- },
5607
+ getMediaConstraints: () => ({
5608
+ audio: this.localMedia.isMicrophoneEnabled(),
5609
+ video: this.localMedia.isCameraEnabled(),
5610
+ }),
5237
5611
  deferrable(clientId) {
5238
5612
  return !clientId;
5239
5613
  },
@@ -5256,55 +5630,40 @@ class RoomConnection extends TypedEventTarget {
5256
5630
  });
5257
5631
  // Identify device on signal connection
5258
5632
  const deviceCredentials = yield this.credentialsService.getCredentials();
5259
- // TODO: Handle connection and failed connection properly
5260
- setTimeout(() => {
5261
- this.logger.log("Connected to signal socket");
5262
- this.signalSocket.emit("identify_device", { deviceCredentials });
5263
- }, 2000);
5633
+ this.logger.log("Connected to signal socket");
5634
+ this.signalSocket.emit("identify_device", { deviceCredentials });
5264
5635
  this.signalSocket.once("device_identified", () => {
5265
- var _a, _b;
5266
- this.signalSocket.emit("join_room", {
5267
- avatarUrl: null,
5268
- config: {
5269
- isAudioEnabled: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5270
- isVideoEnabled: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5271
- },
5272
- deviceCapabilities: { canScreenshare: true },
5273
- displayName: this.displayName,
5274
- isCoLocated: false,
5275
- isDevicePermissionDenied: false,
5276
- kickFromOtherRooms: false,
5277
- organizationId: organization.organizationId,
5278
- roomKey: null,
5279
- roomName: this.roomUrl.pathname,
5280
- selfId: "",
5281
- });
5282
- });
5283
- this.signalSocket.once("room_joined", (res) => {
5284
- const { selfId, room: { clients }, } = res;
5285
- const localClient = clients.find((c) => c.id === selfId);
5286
- if (!localClient)
5287
- throw new Error("Missing local client");
5288
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localStream }));
5289
- this.remoteParticipants = clients
5290
- .filter((c) => c.id !== selfId)
5291
- .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
5292
- // Accept remote streams if RTC manager has been initialized
5293
- if (this.rtcManager) {
5294
- this._handleAcceptStreams(this.remoteParticipants);
5295
- }
5296
- this.roomConnectionState = "connected";
5297
- this.dispatchEvent(new CustomEvent("room_joined", {
5298
- detail: {
5299
- localParticipant: this.localParticipant,
5300
- remoteParticipants: this.remoteParticipants,
5301
- },
5302
- }));
5636
+ this._joinRoom();
5303
5637
  });
5304
5638
  });
5305
5639
  }
5640
+ knock() {
5641
+ this.roomConnectionStatus = "knocking";
5642
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5643
+ detail: {
5644
+ roomConnectionStatus: this.roomConnectionStatus,
5645
+ },
5646
+ }));
5647
+ this.signalSocket.emit("knock_room", {
5648
+ displayName: this.displayName,
5649
+ imageUrl: null,
5650
+ kickFromOtherRooms: true,
5651
+ liveVideo: false,
5652
+ organizationId: this.organizationId,
5653
+ roomKey: this._roomKey,
5654
+ roomName: this.roomName,
5655
+ });
5656
+ }
5306
5657
  leave() {
5307
5658
  return new Promise((resolve) => {
5659
+ if (this._ownsLocalMedia) {
5660
+ this.localMedia.stop();
5661
+ }
5662
+ if (this.rtcManager) {
5663
+ this.localMedia.removeRtcManager(this.rtcManager);
5664
+ this.rtcManager.disconnectAll();
5665
+ this.rtcManager = undefined;
5666
+ }
5308
5667
  if (!this.signalSocket) {
5309
5668
  return resolve();
5310
5669
  }
@@ -5319,29 +5678,10 @@ class RoomConnection extends TypedEventTarget {
5319
5678
  });
5320
5679
  });
5321
5680
  }
5322
- toggleCamera(enabled) {
5323
- var _a;
5324
- const localVideoTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
5325
- if (!localVideoTrack) {
5326
- this.logger.log("Tried toggling non-existing video track");
5327
- return;
5328
- }
5329
- // TODO: Do stopOrResumeVideo
5330
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localVideoTrack.enabled;
5331
- localVideoTrack.enabled = newValue;
5332
- this.signalSocket.emit("enable_video", { enabled: newValue });
5333
- }
5334
- toggleMicrophone(enabled) {
5335
- var _a;
5336
- const localAudioTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
5337
- if (!localAudioTrack) {
5338
- this.logger.log("Tried toggling non-existing audio track");
5339
- return;
5340
- }
5341
- // TODO: Do stopOrResumeAudio
5342
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localAudioTrack.enabled;
5343
- localAudioTrack.enabled = newValue;
5344
- this.signalSocket.emit("enable_audio", { enabled: newValue });
5681
+ sendChatMessage(text) {
5682
+ this.signalSocket.emit("chat_message", {
5683
+ text,
5684
+ });
5345
5685
  }
5346
5686
  setDisplayName(displayName) {
5347
5687
  this.signalSocket.emit("send_client_metadata", {
@@ -5351,8 +5691,31 @@ class RoomConnection extends TypedEventTarget {
5351
5691
  },
5352
5692
  });
5353
5693
  }
5694
+ acceptWaitingParticipant(participantId) {
5695
+ this.signalSocket.emit("handle_knock", {
5696
+ action: "accept",
5697
+ clientId: participantId,
5698
+ response: {},
5699
+ });
5700
+ }
5701
+ rejectWaitingParticipant(participantId) {
5702
+ this.signalSocket.emit("handle_knock", {
5703
+ action: "reject",
5704
+ clientId: participantId,
5705
+ response: {},
5706
+ });
5707
+ }
5354
5708
  }
5355
5709
 
5710
+ const initialState = {
5711
+ chatMessages: [],
5712
+ roomConnectionStatus: "",
5713
+ isJoining: false,
5714
+ joinError: null,
5715
+ mostRecentChatMessage: null,
5716
+ remoteParticipants: [],
5717
+ waitingParticipants: [],
5718
+ };
5356
5719
  function updateParticipant(remoteParticipants, participantId, updates) {
5357
5720
  const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
5358
5721
  if (!existingParticipant) {
@@ -5367,8 +5730,12 @@ function updateParticipant(remoteParticipants, participantId, updates) {
5367
5730
  }
5368
5731
  function reducer(state, action) {
5369
5732
  switch (action.type) {
5733
+ case "CHAT_MESSAGE":
5734
+ return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload], mostRecentChatMessage: action.payload });
5370
5735
  case "ROOM_JOINED":
5371
- return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, roomConnectionStatus: "connected" });
5736
+ return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, waitingParticipants: action.payload.waitingParticipants, roomConnectionStatus: "connected" });
5737
+ case "ROOM_CONNECTION_STATUS_CHANGED":
5738
+ return Object.assign(Object.assign({}, state), { roomConnectionStatus: action.payload.roomConnectionStatus });
5372
5739
  case "PARTICIPANT_AUDIO_ENABLED":
5373
5740
  return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
5374
5741
  isAudioEnabled: action.payload.isAudioEnabled,
@@ -5393,21 +5760,28 @@ function reducer(state, action) {
5393
5760
  if (!state.localParticipant)
5394
5761
  return state;
5395
5762
  return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { displayName: action.payload.displayName }) });
5763
+ case "WAITING_PARTICIPANT_JOINED":
5764
+ return Object.assign(Object.assign({}, state), { waitingParticipants: [
5765
+ ...state.waitingParticipants,
5766
+ { id: action.payload.participantId, displayName: action.payload.displayName },
5767
+ ] });
5768
+ case "WAITING_PARTICIPANT_LEFT":
5769
+ return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((wp) => wp.id !== action.payload.participantId) });
5396
5770
  default:
5397
5771
  throw state;
5398
5772
  }
5399
- }
5400
-
5773
+ }
5401
5774
  function useRoomConnection(roomUrl, roomConnectionOptions) {
5402
- const [roomConnection, setRoomConnection] = React.useState(null);
5403
- const [state, dispatch] = React.useReducer(reducer, { remoteParticipants: [] });
5404
- React.useEffect(() => {
5405
- setRoomConnection(new RoomConnection(roomUrl, roomConnectionOptions));
5406
- }, [roomUrl]);
5775
+ const [roomConnection] = React.useState(() => {
5776
+ var _a;
5777
+ return new RoomConnection(roomUrl, Object.assign(Object.assign({}, roomConnectionOptions), { localMedia: ((_a = roomConnectionOptions === null || roomConnectionOptions === void 0 ? void 0 : roomConnectionOptions.localMedia) === null || _a === void 0 ? void 0 : _a._ref) || undefined }));
5778
+ });
5779
+ const [state, dispatch] = React.useReducer(reducer, initialState);
5407
5780
  React.useEffect(() => {
5408
- if (!roomConnection) {
5409
- return;
5410
- }
5781
+ roomConnection.addEventListener("chat_message", (e) => {
5782
+ const chatMessage = e.detail;
5783
+ dispatch({ type: "CHAT_MESSAGE", payload: chatMessage });
5784
+ });
5411
5785
  roomConnection.addEventListener("participant_audio_enabled", (e) => {
5412
5786
  const { participantId, isAudioEnabled } = e.detail;
5413
5787
  dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
@@ -5424,9 +5798,13 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5424
5798
  const { participantId, stream } = e.detail;
5425
5799
  dispatch({ type: "PARTICIPANT_STREAM_ADDED", payload: { participantId, stream } });
5426
5800
  });
5801
+ roomConnection.addEventListener("room_connection_status_changed", (e) => {
5802
+ const { roomConnectionStatus } = e.detail;
5803
+ dispatch({ type: "ROOM_CONNECTION_STATUS_CHANGED", payload: { roomConnectionStatus } });
5804
+ });
5427
5805
  roomConnection.addEventListener("room_joined", (e) => {
5428
- const { localParticipant, remoteParticipants } = e.detail;
5429
- dispatch({ type: "ROOM_JOINED", payload: { localParticipant, remoteParticipants } });
5806
+ const { localParticipant, remoteParticipants, waitingParticipants } = e.detail;
5807
+ dispatch({ type: "ROOM_JOINED", payload: { localParticipant, remoteParticipants, waitingParticipants } });
5430
5808
  });
5431
5809
  roomConnection.addEventListener("participant_video_enabled", (e) => {
5432
5810
  const { participantId, isVideoEnabled } = e.detail;
@@ -5436,32 +5814,55 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5436
5814
  const { participantId, displayName } = e.detail;
5437
5815
  dispatch({ type: "PARTICIPANT_METADATA_CHANGED", payload: { participantId, displayName } });
5438
5816
  });
5817
+ roomConnection.addEventListener("waiting_participant_joined", (e) => {
5818
+ const { participantId, displayName } = e.detail;
5819
+ dispatch({ type: "WAITING_PARTICIPANT_JOINED", payload: { participantId, displayName } });
5820
+ });
5821
+ roomConnection.addEventListener("waiting_participant_left", (e) => {
5822
+ const { participantId } = e.detail;
5823
+ dispatch({ type: "WAITING_PARTICIPANT_LEFT", payload: { participantId } });
5824
+ });
5439
5825
  roomConnection.join();
5440
5826
  return () => {
5441
5827
  roomConnection.leave();
5442
5828
  };
5443
- }, [roomConnection]);
5444
- return [
5829
+ }, []);
5830
+ return {
5445
5831
  state,
5446
- {
5447
- toggleCamera: (enabled) => {
5448
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleCamera(enabled);
5832
+ actions: {
5833
+ knock: () => {
5834
+ roomConnection.knock();
5449
5835
  },
5450
- toggleMicrophone: (enabled) => {
5451
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleMicrophone(enabled);
5836
+ sendChatMessage: (text) => {
5837
+ roomConnection.sendChatMessage(text);
5452
5838
  },
5453
5839
  setDisplayName: (displayName) => {
5454
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.setDisplayName(displayName);
5840
+ roomConnection.setDisplayName(displayName);
5455
5841
  dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
5456
5842
  },
5843
+ toggleCamera: (enabled) => {
5844
+ roomConnection.localMedia.toggleCameraEnabled(enabled);
5845
+ },
5846
+ toggleMicrophone: (enabled) => {
5847
+ roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
5848
+ },
5849
+ acceptWaitingParticipant: (participantId) => {
5850
+ roomConnection.acceptWaitingParticipant(participantId);
5851
+ },
5852
+ rejectWaitingParticipant: (participantId) => {
5853
+ roomConnection.rejectWaitingParticipant(participantId);
5854
+ },
5457
5855
  },
5458
- {
5459
- VideoView: VideoElement,
5856
+ components: {
5857
+ VideoView,
5460
5858
  },
5461
- ];
5859
+ _ref: roomConnection,
5860
+ };
5462
5861
  }
5463
5862
 
5464
- const sdkVersion = "2.0.0-alpha1";
5863
+ const sdkVersion = "2.0.0-alpha10";
5465
5864
 
5865
+ exports.VideoView = VideoView;
5466
5866
  exports.sdkVersion = sdkVersion;
5867
+ exports.useLocalMedia = useLocalMedia;
5467
5868
  exports.useRoomConnection = useRoomConnection;