@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.
package/dist/lib.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { define, ref } from 'heresy';
2
- import React, { useCallback, useState, useReducer, useEffect } from 'react';
3
- import { __awaiter } from 'tslib';
2
+ import { __rest, __awaiter } from 'tslib';
3
+ import React, { useRef, useEffect, useState, useReducer } from 'react';
4
4
  import adapter from 'webrtc-adapter';
5
5
  import io from 'socket.io-client';
6
6
  import SDPUtils from 'sdp';
@@ -115,7 +115,7 @@ define("WherebyEmbed", {
115
115
  if (!subdomain)
116
116
  return this.html `Whereby: Missing subdomain attr.`;
117
117
  const url = new URL(room, `https://${subdomain}.whereby.com`);
118
- 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(
118
+ 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(
119
119
  // add to URL if set in any way
120
120
  (o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
121
121
  if (!url.searchParams.has(k) && typeof v === "string") {
@@ -131,15 +131,261 @@ define("WherebyEmbed", {
131
131
  },
132
132
  });
133
133
 
134
- var VideoElement = ({ stream, style }) => {
135
- const videoEl = useCallback((node) => {
136
- if (node !== null && node.srcObject !== stream) {
137
- node.srcObject = stream;
134
+ var VideoView = (_a) => {
135
+ var { muted, stream } = _a, rest = __rest(_a, ["muted", "stream"]);
136
+ const videoEl = useRef(null);
137
+ useEffect(() => {
138
+ if (!videoEl.current) {
139
+ return;
138
140
  }
139
- }, []);
140
- return React.createElement("video", { ref: videoEl, autoPlay: true, playsInline: true, style: style });
141
+ if (videoEl.current.srcObject !== stream) {
142
+ videoEl.current.srcObject = stream;
143
+ }
144
+ // Handle muting programatically, not as video attribute
145
+ // https://stackoverflow.com/questions/14111917/html5-video-muted-but-still-playing
146
+ if (videoEl.current.muted !== muted) {
147
+ videoEl.current.muted = Boolean(muted);
148
+ }
149
+ }, [muted, stream, videoEl]);
150
+ return React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest));
141
151
  };
142
152
 
153
+ const TypedLocalMediaEventTarget = EventTarget;
154
+ class LocalMedia extends TypedLocalMediaEventTarget {
155
+ constructor(constraints) {
156
+ super();
157
+ this._constraints = constraints;
158
+ this.stream = new MediaStream();
159
+ this._rtcManagers = [];
160
+ navigator.mediaDevices.addEventListener("devicechange", this._updateDeviceList.bind(this));
161
+ }
162
+ addRtcManager(rtcManager) {
163
+ this._rtcManagers.push(rtcManager);
164
+ }
165
+ removeRtcManager(rtcManager) {
166
+ this._rtcManagers = this._rtcManagers.filter((r) => r !== rtcManager);
167
+ }
168
+ getCameraDeviceId() {
169
+ var _a;
170
+ return (_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
171
+ }
172
+ getMicrophoneDeviceId() {
173
+ var _a;
174
+ return (_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
175
+ }
176
+ isCameraEnabled() {
177
+ var _a;
178
+ return !!((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
179
+ }
180
+ isMicrophoneEnabled() {
181
+ var _a;
182
+ return !!((_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
183
+ }
184
+ toggleCameraEnabled(enabled) {
185
+ const videoTrack = this.stream.getVideoTracks()[0];
186
+ if (!videoTrack) {
187
+ return;
188
+ }
189
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !videoTrack.enabled;
190
+ videoTrack.enabled = newValue;
191
+ this.dispatchEvent(new CustomEvent("camera_enabled", { detail: { enabled: newValue } }));
192
+ }
193
+ toggleMichrophoneEnabled(enabled) {
194
+ const audioTrack = this.stream.getAudioTracks()[0];
195
+ if (!audioTrack) {
196
+ return;
197
+ }
198
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !audioTrack.enabled;
199
+ audioTrack.enabled = newValue;
200
+ this.dispatchEvent(new CustomEvent("microphone_enabled", { detail: { enabled: newValue } }));
201
+ }
202
+ setCameraDevice(deviceId) {
203
+ return __awaiter(this, void 0, void 0, function* () {
204
+ const newStream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId } });
205
+ const newVideoTrack = newStream.getVideoTracks()[0];
206
+ if (newVideoTrack) {
207
+ const oldVideoTrack = this.stream.getVideoTracks()[0];
208
+ newVideoTrack.enabled = oldVideoTrack.enabled;
209
+ oldVideoTrack === null || oldVideoTrack === void 0 ? void 0 : oldVideoTrack.stop();
210
+ this._rtcManagers.forEach((rtcManager) => {
211
+ rtcManager.replaceTrack(oldVideoTrack, newVideoTrack);
212
+ });
213
+ this.stream.removeTrack(oldVideoTrack);
214
+ this.stream.addTrack(newVideoTrack);
215
+ }
216
+ this.dispatchEvent(new CustomEvent("stream_updated", {
217
+ detail: { stream: this.stream },
218
+ }));
219
+ });
220
+ }
221
+ setMicrophoneDevice(deviceId) {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ const newStream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId } });
224
+ const newAudioTrack = newStream.getAudioTracks()[0];
225
+ const oldAudioTrack = this.stream.getAudioTracks()[0];
226
+ if (oldAudioTrack) {
227
+ newAudioTrack.enabled = oldAudioTrack.enabled;
228
+ oldAudioTrack.stop();
229
+ this.stream.removeTrack(oldAudioTrack);
230
+ }
231
+ this._rtcManagers.forEach((rtcManager) => {
232
+ rtcManager.replaceTrack(oldAudioTrack, newAudioTrack);
233
+ });
234
+ this.stream.addTrack(newAudioTrack);
235
+ this.dispatchEvent(new CustomEvent("stream_updated", {
236
+ detail: { stream: this.stream },
237
+ }));
238
+ });
239
+ }
240
+ _updateDeviceList() {
241
+ return __awaiter(this, void 0, void 0, function* () {
242
+ try {
243
+ const devices = yield navigator.mediaDevices.enumerateDevices();
244
+ this.dispatchEvent(new CustomEvent("device_list_updated", {
245
+ detail: {
246
+ cameraDevices: devices.filter((d) => d.kind === "videoinput"),
247
+ microphoneDevices: devices.filter((d) => d.kind === "audioinput"),
248
+ speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
249
+ },
250
+ }));
251
+ }
252
+ catch (error) {
253
+ this.dispatchEvent(new CustomEvent("device_list_update_error", {
254
+ detail: {
255
+ error,
256
+ },
257
+ }));
258
+ throw error;
259
+ }
260
+ });
261
+ }
262
+ start() {
263
+ return __awaiter(this, void 0, void 0, function* () {
264
+ const newStream = yield navigator.mediaDevices.getUserMedia(this._constraints);
265
+ newStream.getTracks().forEach((t) => this.stream.addTrack(t));
266
+ this._updateDeviceList();
267
+ this.dispatchEvent(new CustomEvent("stream_updated", {
268
+ detail: { stream: this.stream },
269
+ }));
270
+ return this.stream;
271
+ });
272
+ }
273
+ stop() {
274
+ var _a;
275
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
276
+ t.stop();
277
+ });
278
+ }
279
+ }
280
+
281
+ const initialState$1 = {
282
+ cameraDeviceError: null,
283
+ cameraDevices: [],
284
+ isSettingCameraDevice: false,
285
+ isSettingMicrophoneDevice: false,
286
+ isStarting: false,
287
+ microphoneDeviceError: null,
288
+ microphoneDevices: [],
289
+ speakerDevices: [],
290
+ startError: null,
291
+ };
292
+ function reducer$1(state, action) {
293
+ switch (action.type) {
294
+ case "DEVICE_LIST_UPDATED":
295
+ return Object.assign(Object.assign({}, state), action.payload);
296
+ case "LOCAL_STREAM_UPDATED":
297
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.currentCameraDeviceId, currentMicrophoneDeviceId: action.payload.currentMicrophoneDeviceId, localStream: action.payload.stream });
298
+ case "SET_CAMERA_DEVICE":
299
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: null, isSettingCameraDevice: true });
300
+ case "SET_CAMERA_DEVICE_COMPLETE":
301
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
302
+ case "SET_CAMERA_DEVICE_ERROR":
303
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
304
+ case "SET_MICROPHONE_DEVICE":
305
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
306
+ case "SET_MICROPHONE_DEVICE_COMPLETE":
307
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
308
+ case "SET_MICROPHONE_DEVICE_ERROR":
309
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
310
+ case "START":
311
+ return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
312
+ case "START_COMPLETE":
313
+ return Object.assign(Object.assign({}, state), { isStarting: false });
314
+ case "START_ERROR":
315
+ return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
316
+ default:
317
+ return state;
318
+ }
319
+ }
320
+ function useLocalMedia(constraints = { audio: true, video: true }) {
321
+ const [localMedia] = useState(() => new LocalMedia(constraints));
322
+ const [state, dispatch] = useReducer(reducer$1, initialState$1);
323
+ useEffect(() => {
324
+ localMedia.addEventListener("device_list_updated", (e) => {
325
+ const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
326
+ dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
327
+ });
328
+ localMedia.addEventListener("stream_updated", (e) => {
329
+ const { stream } = e.detail;
330
+ dispatch({
331
+ type: "LOCAL_STREAM_UPDATED",
332
+ payload: {
333
+ stream,
334
+ currentCameraDeviceId: localMedia.getCameraDeviceId(),
335
+ currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
336
+ },
337
+ });
338
+ });
339
+ const start = () => __awaiter(this, void 0, void 0, function* () {
340
+ dispatch({ type: "START" });
341
+ try {
342
+ yield localMedia.start();
343
+ dispatch({ type: "START_COMPLETE" });
344
+ }
345
+ catch (error) {
346
+ dispatch({ type: "START_ERROR", payload: error });
347
+ }
348
+ });
349
+ start();
350
+ // Perform cleanup on unmount
351
+ return () => {
352
+ localMedia.stop();
353
+ };
354
+ }, []);
355
+ return {
356
+ state,
357
+ actions: {
358
+ setCameraDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
359
+ dispatch({ type: "SET_CAMERA_DEVICE" });
360
+ try {
361
+ yield localMedia.setCameraDevice(...args);
362
+ dispatch({ type: "SET_CAMERA_DEVICE_COMPLETE" });
363
+ }
364
+ catch (error) {
365
+ dispatch({ type: "SET_CAMERA_DEVICE_ERROR", payload: error });
366
+ }
367
+ }),
368
+ setMicrophoneDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
369
+ dispatch({ type: "SET_MICROPHONE_DEVICE" });
370
+ try {
371
+ yield localMedia.setMicrophoneDevice(...args);
372
+ dispatch({ type: "SET_MICROPHONE_DEVICE_COMPLETE" });
373
+ }
374
+ catch (error) {
375
+ dispatch({ type: "SET_MICROPHONE_DEVICE_ERROR", payload: error });
376
+ }
377
+ }),
378
+ toggleCameraEnabled: (...args) => {
379
+ return localMedia.toggleCameraEnabled(...args);
380
+ },
381
+ toggleMicrophoneEnabled: (...args) => {
382
+ return localMedia.toggleMichrophoneEnabled(...args);
383
+ },
384
+ },
385
+ _ref: localMedia,
386
+ };
387
+ }
388
+
143
389
  const EVENTS = {
144
390
  CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
145
391
  STREAM_ADDED: "stream_added",
@@ -270,10 +516,10 @@ class ServerSocket {
270
516
  }
271
517
 
272
518
  this._socket = io(hostName, options);
273
- this._socket.on("reconnect", () => {
519
+ this._socket.io.on("reconnect", () => {
274
520
  this._socket.sendBuffer = [];
275
521
  });
276
- this._socket.on("reconnect_attempt", () => {
522
+ this._socket.io.on("reconnect_attempt", () => {
277
523
  if (this._wasConnectedUsingWebsocket) {
278
524
  this._socket.io.opts.transports = ["websocket"];
279
525
  // only fallback to polling if not safari
@@ -331,6 +577,10 @@ class ServerSocket {
331
577
  );
332
578
  }
333
579
 
580
+ getManager() {
581
+ return this._socket.io;
582
+ }
583
+
334
584
  isConnecting() {
335
585
  return this._socket && this._socket.connecting;
336
586
  }
@@ -1964,10 +2214,11 @@ class VegaParser {
1964
2214
  }
1965
2215
 
1966
2216
  class VegaConnection extends EventEmitter {
1967
- constructor(wsUrl, logger) {
2217
+ constructor(wsUrl, logger, protocol = "whereby-sfu#v4") {
1968
2218
  super();
1969
2219
 
1970
2220
  this.wsUrl = wsUrl;
2221
+ this.protocol = protocol;
1971
2222
  this.logger = logger;
1972
2223
 
1973
2224
  // This is the map of sent requests that are waiting for a response
@@ -1976,7 +2227,7 @@ class VegaConnection extends EventEmitter {
1976
2227
  }
1977
2228
 
1978
2229
  _setupSocket() {
1979
- this.socket = new WebSocket(this.wsUrl, "whereby-sfu#v4");
2230
+ this.socket = new WebSocket(this.wsUrl, this.protocol);
1980
2231
  this.socket.onopen = this._onOpen.bind(this);
1981
2232
  this.socket.onmessage = this._onMessage.bind(this);
1982
2233
  this.socket.onclose = this._onClose.bind(this);
@@ -1996,7 +2247,9 @@ class VegaConnection extends EventEmitter {
1996
2247
  }
1997
2248
 
1998
2249
  close() {
1999
- this.socket?.close();
2250
+ if (!this.socket) return;
2251
+
2252
+ this.socket.close();
2000
2253
  }
2001
2254
 
2002
2255
  _onOpen() {
@@ -2008,11 +2261,15 @@ class VegaConnection extends EventEmitter {
2008
2261
  _onMessage(event) {
2009
2262
  const socketMessage = VegaParser.parse(event.data);
2010
2263
 
2264
+ if (!socketMessage) {
2265
+ return this.logger.log("VegaConnectionManager: Received invalid message", event.data);
2266
+ }
2267
+
2011
2268
  this.logger.log("VegaConnectionManager: Received message", socketMessage);
2012
2269
 
2013
- if (socketMessage?.response) {
2270
+ if (socketMessage.response) {
2014
2271
  this._handleResponse(socketMessage);
2015
- } else if (socketMessage?.message) {
2272
+ } else if (socketMessage.message) {
2016
2273
  this.emit("message", socketMessage);
2017
2274
  }
2018
2275
  }
@@ -2119,7 +2376,7 @@ const VIDEO_SETTINGS_VP9 = {
2119
2376
  codecOptions: {
2120
2377
  videoGoogleStartBitrate: 500,
2121
2378
  },
2122
- encodings: [{ scalabilityMode: "S3T3_KEY", networkPriority: "high" }],
2379
+ encodings: [{ scalabilityMode: "L3T2_KEY", networkPriority: "high" }],
2123
2380
  };
2124
2381
 
2125
2382
  const SCREEN_SHARE_SETTINGS = {
@@ -2128,13 +2385,13 @@ const SCREEN_SHARE_SETTINGS = {
2128
2385
 
2129
2386
  const SCREEN_SHARE_SIMULCAST_SETTINGS = {
2130
2387
  encodings: [
2131
- { dtx: true, maxBitrate: 500000 },
2132
- { dtx: true, maxBitrate: 1500000 },
2388
+ { scaleResolutionDownBy: 2, dtx: true, maxBitrate: 500000 },
2389
+ { scaleResolutionDownBy: 1, dtx: true, maxBitrate: 1500000 },
2133
2390
  ],
2134
2391
  };
2135
2392
 
2136
2393
  const SCREEN_SHARE_SETTINGS_VP9 = {
2137
- encodings: [{ scalabilityMode: "S3T3", dtx: true, networkPriority: "high" }],
2394
+ encodings: [{ scalabilityMode: "L1T1", dtx: true, networkPriority: "high" }],
2138
2395
  };
2139
2396
 
2140
2397
  const getMediaSettings = (kind, isScreenShare, features) => {
@@ -2150,8 +2407,8 @@ const getMediaSettings = (kind, isScreenShare, features) => {
2150
2407
 
2151
2408
  return SCREEN_SHARE_SETTINGS;
2152
2409
  } else {
2153
- if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2154
2410
  if (vp9On) return VIDEO_SETTINGS_VP9;
2411
+ if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2155
2412
 
2156
2413
  return VIDEO_SETTINGS_HD;
2157
2414
  }
@@ -5005,12 +5262,12 @@ class LocalParticipant extends RoomParticipant {
5005
5262
  }
5006
5263
  }
5007
5264
 
5008
- const API_BASE_URL = "https://api.appearin.net";
5265
+ const API_BASE_URL = "https://api.whereby.dev";
5009
5266
  const SIGNAL_BASE_URL = "wss://signal.appearin.net";
5010
5267
  const NON_PERSON_ROLES = ["recorder", "streamer"];
5011
5268
  function createSocket() {
5012
5269
  const parsedUrl = new URL(SIGNAL_BASE_URL);
5013
- const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v1`;
5270
+ const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v4`;
5014
5271
  const SOCKET_HOST = parsedUrl.origin;
5015
5272
  const socketConf = {
5016
5273
  host: SOCKET_HOST,
@@ -5018,6 +5275,7 @@ function createSocket() {
5018
5275
  reconnectionDelay: 5000,
5019
5276
  reconnectionDelayMax: 30000,
5020
5277
  timeout: 10000,
5278
+ withCredentials: true,
5021
5279
  };
5022
5280
  return new ServerSocket(SOCKET_HOST, socketConf);
5023
5281
  }
@@ -5026,12 +5284,17 @@ const noop = () => {
5026
5284
  };
5027
5285
  const TypedEventTarget = EventTarget;
5028
5286
  class RoomConnection extends TypedEventTarget {
5029
- constructor(roomUrl, { displayName, localMediaConstraints, localStream, logger }) {
5287
+ constructor(roomUrl, { displayName, localMedia, localMediaConstraints, logger, roomKey }) {
5030
5288
  super();
5031
5289
  this.localParticipant = null;
5032
5290
  this.remoteParticipants = [];
5033
- this.roomConnectionState = "";
5291
+ this._ownsLocalMedia = false;
5292
+ this.organizationId = "";
5293
+ this.roomConnectionStatus = "";
5034
5294
  this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
5295
+ const searchParams = new URLSearchParams(this.roomUrl.search);
5296
+ this._roomKey = roomKey || searchParams.get("roomKey");
5297
+ this.roomName = this.roomUrl.pathname;
5035
5298
  this.logger = logger || {
5036
5299
  debug: noop,
5037
5300
  error: noop,
@@ -5039,10 +5302,20 @@ class RoomConnection extends TypedEventTarget {
5039
5302
  warn: noop,
5040
5303
  };
5041
5304
  this.displayName = displayName;
5042
- this.localStream = localStream;
5043
5305
  this.localMediaConstraints = localMediaConstraints;
5044
5306
  const urls = fromLocation({ host: this.roomUrl.host });
5045
- // Initialize services
5307
+ // Set up local media
5308
+ if (localMedia) {
5309
+ this.localMedia = localMedia;
5310
+ }
5311
+ else if (localMediaConstraints) {
5312
+ this.localMedia = new LocalMedia(localMediaConstraints);
5313
+ this._ownsLocalMedia = true;
5314
+ }
5315
+ else {
5316
+ throw new Error("Missing constraints");
5317
+ }
5318
+ // Set up services
5046
5319
  this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
5047
5320
  this.apiClient = new ApiClient({
5048
5321
  fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
@@ -5064,10 +5337,30 @@ class RoomConnection extends TypedEventTarget {
5064
5337
  // Create signal socket and set up event listeners
5065
5338
  this.signalSocket = createSocket();
5066
5339
  this.signalSocket.on("new_client", this._handleNewClient.bind(this));
5340
+ this.signalSocket.on("chat_message", this._handleNewChatMessage.bind(this));
5067
5341
  this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
5068
5342
  this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
5069
5343
  this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
5070
5344
  this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
5345
+ this.signalSocket.on("knock_handled", this._handleKnockHandled.bind(this));
5346
+ this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
5347
+ this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
5348
+ this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
5349
+ // Set up local media listeners
5350
+ this.localMedia.addEventListener("camera_enabled", (e) => {
5351
+ const { enabled } = e.detail;
5352
+ this.signalSocket.emit("enable_video", { enabled });
5353
+ });
5354
+ this.localMedia.addEventListener("microphone_enabled", (e) => {
5355
+ const { enabled } = e.detail;
5356
+ this.signalSocket.emit("enable_audio", { enabled });
5357
+ });
5358
+ }
5359
+ get roomKey() {
5360
+ return this._roomKey;
5361
+ }
5362
+ _handleNewChatMessage(message) {
5363
+ this.dispatchEvent(new CustomEvent("chat_message", { detail: message }));
5071
5364
  }
5072
5365
  _handleNewClient({ client }) {
5073
5366
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
@@ -5113,6 +5406,70 @@ class RoomConnection extends TypedEventTarget {
5113
5406
  detail: { participantId: remoteParticipant.id, displayName },
5114
5407
  }));
5115
5408
  }
5409
+ _handleKnockHandled(payload) {
5410
+ const { resolution } = payload;
5411
+ if (resolution === "accepted") {
5412
+ this.roomConnectionStatus = "accepted";
5413
+ this._roomKey = payload.metadata.roomKey;
5414
+ this._joinRoom();
5415
+ }
5416
+ else if (resolution === "rejected") {
5417
+ this.roomConnectionStatus = "rejected";
5418
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5419
+ detail: {
5420
+ roomConnectionStatus: this.roomConnectionStatus,
5421
+ },
5422
+ }));
5423
+ }
5424
+ }
5425
+ _handleKnockerLeft(payload) {
5426
+ const { clientId } = payload;
5427
+ this.dispatchEvent(new CustomEvent("waiting_participant_left", {
5428
+ detail: { participantId: clientId },
5429
+ }));
5430
+ }
5431
+ _handleRoomJoined(event) {
5432
+ const { error, isLocked, room, selfId } = event;
5433
+ if (error === "room_locked" && isLocked) {
5434
+ this.roomConnectionStatus = "room_locked";
5435
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5436
+ detail: {
5437
+ roomConnectionStatus: this.roomConnectionStatus,
5438
+ },
5439
+ }));
5440
+ return;
5441
+ }
5442
+ // Check if we have an error
5443
+ // Check if it is a room joined error
5444
+ // Set state to connect_failed_locked
5445
+ // Set state to connect_failed_no_host
5446
+ if (room) {
5447
+ const { clients, knockers } = room;
5448
+ const localClient = clients.find((c) => c.id === selfId);
5449
+ if (!localClient)
5450
+ throw new Error("Missing local client");
5451
+ this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
5452
+ this.remoteParticipants = clients
5453
+ .filter((c) => c.id !== selfId)
5454
+ .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
5455
+ this.roomConnectionStatus = "connected";
5456
+ this.dispatchEvent(new CustomEvent("room_joined", {
5457
+ detail: {
5458
+ localParticipant: this.localParticipant,
5459
+ remoteParticipants: this.remoteParticipants,
5460
+ waitingParticipants: knockers.map((knocker) => {
5461
+ return { id: knocker.clientId, displayName: knocker.displayName };
5462
+ }),
5463
+ },
5464
+ }));
5465
+ }
5466
+ }
5467
+ _handleRoomKnocked(event) {
5468
+ const { clientId, displayName } = event;
5469
+ this.dispatchEvent(new CustomEvent("waiting_participant_joined", {
5470
+ detail: { participantId: clientId, displayName },
5471
+ }));
5472
+ }
5116
5473
  _handleRtcEvent(eventName, data) {
5117
5474
  if (eventName === "rtc_manager_created") {
5118
5475
  return this._handleRtcManagerCreated(data);
@@ -5125,10 +5482,14 @@ class RoomConnection extends TypedEventTarget {
5125
5482
  }
5126
5483
  }
5127
5484
  _handleRtcManagerCreated({ rtcManager }) {
5128
- var _a, _b, _c;
5485
+ var _a;
5129
5486
  this.rtcManager = rtcManager;
5130
- if (this.localStream) {
5131
- (_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)));
5487
+ this.localMedia.addRtcManager(rtcManager);
5488
+ if (this.localMedia.stream) {
5489
+ (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
5490
+ }
5491
+ if (this.remoteParticipants.length) {
5492
+ this._handleAcceptStreams(this.remoteParticipants);
5132
5493
  }
5133
5494
  }
5134
5495
  _handleAcceptStreams(remoteParticipants) {
@@ -5153,8 +5514,6 @@ class RoomConnection extends TypedEventTarget {
5153
5514
  if (!newState) {
5154
5515
  return;
5155
5516
  }
5156
- // #endregion
5157
- // #region doAcceptStreams
5158
5517
  if (newState === "to_accept" ||
5159
5518
  (newState === "new_accept" && shouldAcceptNewClients) ||
5160
5519
  (newState === "old_accept" && !shouldAcceptNewClients)) {
@@ -5178,7 +5537,6 @@ class RoomConnection extends TypedEventTarget {
5178
5537
  else ;
5179
5538
  // Update stream state
5180
5539
  participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
5181
- // #endregion
5182
5540
  });
5183
5541
  });
5184
5542
  }
@@ -5190,35 +5548,51 @@ class RoomConnection extends TypedEventTarget {
5190
5548
  }
5191
5549
  this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
5192
5550
  }
5193
- /**
5194
- * Public API
5195
- */
5551
+ _joinRoom() {
5552
+ this.signalSocket.emit("join_room", {
5553
+ avatarUrl: null,
5554
+ config: {
5555
+ isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
5556
+ isVideoEnabled: this.localMedia.isCameraEnabled(),
5557
+ },
5558
+ deviceCapabilities: { canScreenshare: true },
5559
+ displayName: this.displayName,
5560
+ isCoLocated: false,
5561
+ isDevicePermissionDenied: false,
5562
+ kickFromOtherRooms: false,
5563
+ organizationId: this.organizationId,
5564
+ roomKey: this.roomKey,
5565
+ roomName: this.roomName,
5566
+ selfId: "",
5567
+ userAgent: `browser-sdk:${sdkVersion }`,
5568
+ });
5569
+ }
5196
5570
  join() {
5197
5571
  return __awaiter(this, void 0, void 0, function* () {
5198
- if (["connected", "connecting"].includes(this.roomConnectionState)) {
5199
- console.warn(`Trying to join room state is ${this.roomConnectionState}`);
5572
+ if (["connected", "connecting"].includes(this.roomConnectionStatus)) {
5573
+ console.warn(`Trying to join when room state is already ${this.roomConnectionStatus}`);
5200
5574
  return;
5201
5575
  }
5202
5576
  this.logger.log("Joining room");
5203
- this.roomConnectionState = "connecting";
5204
- if (!this.localStream && this.localMediaConstraints) {
5205
- const localStream = yield navigator.mediaDevices.getUserMedia(this.localMediaConstraints);
5206
- this.localStream = localStream;
5207
- }
5577
+ this.roomConnectionStatus = "connecting";
5578
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5579
+ detail: {
5580
+ roomConnectionStatus: this.roomConnectionStatus,
5581
+ },
5582
+ }));
5208
5583
  const organization = yield this.organizationServiceCache.fetchOrganization();
5209
5584
  if (!organization) {
5210
5585
  throw new Error("Invalid room url");
5211
5586
  }
5212
- // TODO: Get room permissions
5213
- // TODO: Get room features
5587
+ this.organizationId = organization.organizationId;
5588
+ if (this._ownsLocalMedia) {
5589
+ yield this.localMedia.start();
5590
+ }
5214
5591
  const webrtcProvider = {
5215
- getMediaConstraints: () => {
5216
- var _a, _b;
5217
- return ({
5218
- audio: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5219
- video: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5220
- });
5221
- },
5592
+ getMediaConstraints: () => ({
5593
+ audio: this.localMedia.isMicrophoneEnabled(),
5594
+ video: this.localMedia.isCameraEnabled(),
5595
+ }),
5222
5596
  deferrable(clientId) {
5223
5597
  return !clientId;
5224
5598
  },
@@ -5241,55 +5615,40 @@ class RoomConnection extends TypedEventTarget {
5241
5615
  });
5242
5616
  // Identify device on signal connection
5243
5617
  const deviceCredentials = yield this.credentialsService.getCredentials();
5244
- // TODO: Handle connection and failed connection properly
5245
- setTimeout(() => {
5246
- this.logger.log("Connected to signal socket");
5247
- this.signalSocket.emit("identify_device", { deviceCredentials });
5248
- }, 2000);
5618
+ this.logger.log("Connected to signal socket");
5619
+ this.signalSocket.emit("identify_device", { deviceCredentials });
5249
5620
  this.signalSocket.once("device_identified", () => {
5250
- var _a, _b;
5251
- this.signalSocket.emit("join_room", {
5252
- avatarUrl: null,
5253
- config: {
5254
- isAudioEnabled: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5255
- isVideoEnabled: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5256
- },
5257
- deviceCapabilities: { canScreenshare: true },
5258
- displayName: this.displayName,
5259
- isCoLocated: false,
5260
- isDevicePermissionDenied: false,
5261
- kickFromOtherRooms: false,
5262
- organizationId: organization.organizationId,
5263
- roomKey: null,
5264
- roomName: this.roomUrl.pathname,
5265
- selfId: "",
5266
- });
5267
- });
5268
- this.signalSocket.once("room_joined", (res) => {
5269
- const { selfId, room: { clients }, } = res;
5270
- const localClient = clients.find((c) => c.id === selfId);
5271
- if (!localClient)
5272
- throw new Error("Missing local client");
5273
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localStream }));
5274
- this.remoteParticipants = clients
5275
- .filter((c) => c.id !== selfId)
5276
- .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
5277
- // Accept remote streams if RTC manager has been initialized
5278
- if (this.rtcManager) {
5279
- this._handleAcceptStreams(this.remoteParticipants);
5280
- }
5281
- this.roomConnectionState = "connected";
5282
- this.dispatchEvent(new CustomEvent("room_joined", {
5283
- detail: {
5284
- localParticipant: this.localParticipant,
5285
- remoteParticipants: this.remoteParticipants,
5286
- },
5287
- }));
5621
+ this._joinRoom();
5288
5622
  });
5289
5623
  });
5290
5624
  }
5625
+ knock() {
5626
+ this.roomConnectionStatus = "knocking";
5627
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
5628
+ detail: {
5629
+ roomConnectionStatus: this.roomConnectionStatus,
5630
+ },
5631
+ }));
5632
+ this.signalSocket.emit("knock_room", {
5633
+ displayName: this.displayName,
5634
+ imageUrl: null,
5635
+ kickFromOtherRooms: true,
5636
+ liveVideo: false,
5637
+ organizationId: this.organizationId,
5638
+ roomKey: this._roomKey,
5639
+ roomName: this.roomName,
5640
+ });
5641
+ }
5291
5642
  leave() {
5292
5643
  return new Promise((resolve) => {
5644
+ if (this._ownsLocalMedia) {
5645
+ this.localMedia.stop();
5646
+ }
5647
+ if (this.rtcManager) {
5648
+ this.localMedia.removeRtcManager(this.rtcManager);
5649
+ this.rtcManager.disconnectAll();
5650
+ this.rtcManager = undefined;
5651
+ }
5293
5652
  if (!this.signalSocket) {
5294
5653
  return resolve();
5295
5654
  }
@@ -5304,29 +5663,10 @@ class RoomConnection extends TypedEventTarget {
5304
5663
  });
5305
5664
  });
5306
5665
  }
5307
- toggleCamera(enabled) {
5308
- var _a;
5309
- const localVideoTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
5310
- if (!localVideoTrack) {
5311
- this.logger.log("Tried toggling non-existing video track");
5312
- return;
5313
- }
5314
- // TODO: Do stopOrResumeVideo
5315
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localVideoTrack.enabled;
5316
- localVideoTrack.enabled = newValue;
5317
- this.signalSocket.emit("enable_video", { enabled: newValue });
5318
- }
5319
- toggleMicrophone(enabled) {
5320
- var _a;
5321
- const localAudioTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
5322
- if (!localAudioTrack) {
5323
- this.logger.log("Tried toggling non-existing audio track");
5324
- return;
5325
- }
5326
- // TODO: Do stopOrResumeAudio
5327
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localAudioTrack.enabled;
5328
- localAudioTrack.enabled = newValue;
5329
- this.signalSocket.emit("enable_audio", { enabled: newValue });
5666
+ sendChatMessage(text) {
5667
+ this.signalSocket.emit("chat_message", {
5668
+ text,
5669
+ });
5330
5670
  }
5331
5671
  setDisplayName(displayName) {
5332
5672
  this.signalSocket.emit("send_client_metadata", {
@@ -5336,8 +5676,31 @@ class RoomConnection extends TypedEventTarget {
5336
5676
  },
5337
5677
  });
5338
5678
  }
5679
+ acceptWaitingParticipant(participantId) {
5680
+ this.signalSocket.emit("handle_knock", {
5681
+ action: "accept",
5682
+ clientId: participantId,
5683
+ response: {},
5684
+ });
5685
+ }
5686
+ rejectWaitingParticipant(participantId) {
5687
+ this.signalSocket.emit("handle_knock", {
5688
+ action: "reject",
5689
+ clientId: participantId,
5690
+ response: {},
5691
+ });
5692
+ }
5339
5693
  }
5340
5694
 
5695
+ const initialState = {
5696
+ chatMessages: [],
5697
+ roomConnectionStatus: "",
5698
+ isJoining: false,
5699
+ joinError: null,
5700
+ mostRecentChatMessage: null,
5701
+ remoteParticipants: [],
5702
+ waitingParticipants: [],
5703
+ };
5341
5704
  function updateParticipant(remoteParticipants, participantId, updates) {
5342
5705
  const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
5343
5706
  if (!existingParticipant) {
@@ -5352,8 +5715,12 @@ function updateParticipant(remoteParticipants, participantId, updates) {
5352
5715
  }
5353
5716
  function reducer(state, action) {
5354
5717
  switch (action.type) {
5718
+ case "CHAT_MESSAGE":
5719
+ return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload], mostRecentChatMessage: action.payload });
5355
5720
  case "ROOM_JOINED":
5356
- return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, roomConnectionStatus: "connected" });
5721
+ return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, waitingParticipants: action.payload.waitingParticipants, roomConnectionStatus: "connected" });
5722
+ case "ROOM_CONNECTION_STATUS_CHANGED":
5723
+ return Object.assign(Object.assign({}, state), { roomConnectionStatus: action.payload.roomConnectionStatus });
5357
5724
  case "PARTICIPANT_AUDIO_ENABLED":
5358
5725
  return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
5359
5726
  isAudioEnabled: action.payload.isAudioEnabled,
@@ -5378,21 +5745,28 @@ function reducer(state, action) {
5378
5745
  if (!state.localParticipant)
5379
5746
  return state;
5380
5747
  return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { displayName: action.payload.displayName }) });
5748
+ case "WAITING_PARTICIPANT_JOINED":
5749
+ return Object.assign(Object.assign({}, state), { waitingParticipants: [
5750
+ ...state.waitingParticipants,
5751
+ { id: action.payload.participantId, displayName: action.payload.displayName },
5752
+ ] });
5753
+ case "WAITING_PARTICIPANT_LEFT":
5754
+ return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((wp) => wp.id !== action.payload.participantId) });
5381
5755
  default:
5382
5756
  throw state;
5383
5757
  }
5384
- }
5385
-
5758
+ }
5386
5759
  function useRoomConnection(roomUrl, roomConnectionOptions) {
5387
- const [roomConnection, setRoomConnection] = useState(null);
5388
- const [state, dispatch] = useReducer(reducer, { remoteParticipants: [] });
5389
- useEffect(() => {
5390
- setRoomConnection(new RoomConnection(roomUrl, roomConnectionOptions));
5391
- }, [roomUrl]);
5760
+ const [roomConnection] = useState(() => {
5761
+ var _a;
5762
+ 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 }));
5763
+ });
5764
+ const [state, dispatch] = useReducer(reducer, initialState);
5392
5765
  useEffect(() => {
5393
- if (!roomConnection) {
5394
- return;
5395
- }
5766
+ roomConnection.addEventListener("chat_message", (e) => {
5767
+ const chatMessage = e.detail;
5768
+ dispatch({ type: "CHAT_MESSAGE", payload: chatMessage });
5769
+ });
5396
5770
  roomConnection.addEventListener("participant_audio_enabled", (e) => {
5397
5771
  const { participantId, isAudioEnabled } = e.detail;
5398
5772
  dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
@@ -5409,9 +5783,13 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5409
5783
  const { participantId, stream } = e.detail;
5410
5784
  dispatch({ type: "PARTICIPANT_STREAM_ADDED", payload: { participantId, stream } });
5411
5785
  });
5786
+ roomConnection.addEventListener("room_connection_status_changed", (e) => {
5787
+ const { roomConnectionStatus } = e.detail;
5788
+ dispatch({ type: "ROOM_CONNECTION_STATUS_CHANGED", payload: { roomConnectionStatus } });
5789
+ });
5412
5790
  roomConnection.addEventListener("room_joined", (e) => {
5413
- const { localParticipant, remoteParticipants } = e.detail;
5414
- dispatch({ type: "ROOM_JOINED", payload: { localParticipant, remoteParticipants } });
5791
+ const { localParticipant, remoteParticipants, waitingParticipants } = e.detail;
5792
+ dispatch({ type: "ROOM_JOINED", payload: { localParticipant, remoteParticipants, waitingParticipants } });
5415
5793
  });
5416
5794
  roomConnection.addEventListener("participant_video_enabled", (e) => {
5417
5795
  const { participantId, isVideoEnabled } = e.detail;
@@ -5421,31 +5799,52 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5421
5799
  const { participantId, displayName } = e.detail;
5422
5800
  dispatch({ type: "PARTICIPANT_METADATA_CHANGED", payload: { participantId, displayName } });
5423
5801
  });
5802
+ roomConnection.addEventListener("waiting_participant_joined", (e) => {
5803
+ const { participantId, displayName } = e.detail;
5804
+ dispatch({ type: "WAITING_PARTICIPANT_JOINED", payload: { participantId, displayName } });
5805
+ });
5806
+ roomConnection.addEventListener("waiting_participant_left", (e) => {
5807
+ const { participantId } = e.detail;
5808
+ dispatch({ type: "WAITING_PARTICIPANT_LEFT", payload: { participantId } });
5809
+ });
5424
5810
  roomConnection.join();
5425
5811
  return () => {
5426
5812
  roomConnection.leave();
5427
5813
  };
5428
- }, [roomConnection]);
5429
- return [
5814
+ }, []);
5815
+ return {
5430
5816
  state,
5431
- {
5432
- toggleCamera: (enabled) => {
5433
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleCamera(enabled);
5817
+ actions: {
5818
+ knock: () => {
5819
+ roomConnection.knock();
5434
5820
  },
5435
- toggleMicrophone: (enabled) => {
5436
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleMicrophone(enabled);
5821
+ sendChatMessage: (text) => {
5822
+ roomConnection.sendChatMessage(text);
5437
5823
  },
5438
5824
  setDisplayName: (displayName) => {
5439
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.setDisplayName(displayName);
5825
+ roomConnection.setDisplayName(displayName);
5440
5826
  dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
5441
5827
  },
5828
+ toggleCamera: (enabled) => {
5829
+ roomConnection.localMedia.toggleCameraEnabled(enabled);
5830
+ },
5831
+ toggleMicrophone: (enabled) => {
5832
+ roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
5833
+ },
5834
+ acceptWaitingParticipant: (participantId) => {
5835
+ roomConnection.acceptWaitingParticipant(participantId);
5836
+ },
5837
+ rejectWaitingParticipant: (participantId) => {
5838
+ roomConnection.rejectWaitingParticipant(participantId);
5839
+ },
5442
5840
  },
5443
- {
5444
- VideoView: VideoElement,
5841
+ components: {
5842
+ VideoView,
5445
5843
  },
5446
- ];
5844
+ _ref: roomConnection,
5845
+ };
5447
5846
  }
5448
5847
 
5449
- const sdkVersion = "2.0.0-alpha1";
5848
+ const sdkVersion = "2.0.0-alpha10";
5450
5849
 
5451
- export { sdkVersion, useRoomConnection };
5850
+ export { VideoView, sdkVersion, useLocalMedia, useRoomConnection };