@whereby.com/browser-sdk 2.0.0-beta1 → 2.0.0-beta2

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # `@whereby.com/browser-sdk`
2
2
 
3
+ > [!WARNING]
3
4
  > This is a pre-release of the v2 version of this library, adding support for
4
5
  > more custom integration using React hooks and plain JavaScript classes in
5
6
  > addition to the web component for embedding.
@@ -7,6 +8,8 @@
7
8
  Whereby browser SDK is a library for seamless integration of Whereby
8
9
  (https://whereby.com) video calls into your web application.
9
10
 
11
+ **For a more detailed set of instructions, including the building of a [simple telehealth app](https://docs.whereby.com/whereby-101/create-your-video-experience/in-a-web-page/using-whereby-react-hooks-build-a-telehealth-app), please see our [documentation](https://docs.whereby.com/reference/react-hooks-reference).**
12
+
10
13
  ## Installation
11
14
 
12
15
  ```shell
@@ -20,7 +23,7 @@ yarn add @whereby.com/browser-sdk
20
23
  ```
21
24
 
22
25
  ## Usage
23
-
26
+ > [!IMPORTANT]
24
27
  > In order to make use of this functionality, you must have a Whereby account
25
28
  > from which you can create room urls, either [manually or through our
26
29
  > API](https://docs.whereby.com/creating-and-deleting-rooms).
@@ -36,7 +39,7 @@ their device selection up-front. This hook works seamlessly with the
36
39
  `useRoomConnection` hook described below.
37
40
 
38
41
  ```js
39
- import { useLocalMedia, VideoView } from "@whereby.com/browser-sdk";
42
+ import { useLocalMedia, VideoView } from "@whereby.com/browser-sdk/react";
40
43
 
41
44
  function MyPreCallUX() {
42
45
  const localMedia = useLocalMedia({ audio: false, video: true });
@@ -71,7 +74,7 @@ room, subscribe to state updates, and perform actions on the connection, like
71
74
  toggling camera or microphone.
72
75
 
73
76
  ```js
74
- import { useRoomConnection } from "@whereby.com/browser-sdk";
77
+ import { useRoomConnection } from "@whereby.com/browser-sdk/react";
75
78
 
76
79
  function MyCallUX( { roomUrl, localStream }) {
77
80
  const { state, actions, components } = useRoomConnection(
@@ -99,7 +102,7 @@ function MyCallUX( { roomUrl, localStream }) {
99
102
 
100
103
  ```
101
104
 
102
- ### Usage with Vite development environment
105
+ #### Usage with Vite development environment
103
106
 
104
107
  There is a [known Vite issue](https://github.com/vitejs/vite/issues/1973) where modules trying to access `process.env` throw `Uncaught ReferenceError: process is not defined`.
105
108
  This can be solved in `vite.config.js` with the following line:
@@ -123,7 +126,7 @@ client";` to the top of component, like in the following example:
123
126
  ```js
124
127
  "use client";
125
128
 
126
- import { VideoView, useLocalMedia } from "@whereby.com/browser-sdk";
129
+ import { VideoView, useLocalMedia } from "@whereby.com/browser-sdk/react";
127
130
 
128
131
  export default function MyNextVideoExperience() {
129
132
  const { state, actions } = useLocalMedia({ audio: false, video: true });
@@ -145,7 +148,7 @@ to learn which attributes are supported.
145
148
  #### React
146
149
 
147
150
  ```js
148
- import "@whereby.com/browser-sdk";
151
+ import "@whereby.com/browser-sdk/embed";
149
152
 
150
153
  const MyComponent = ({ roomUrl }) => {
151
154
  return <whereby-embed chat="off" room={roomUrl} />;
@@ -169,8 +172,7 @@ export default MyComponent;
169
172
  </html>
170
173
  ```
171
174
 
172
- **Note**
173
-
174
- Although we have just higlighted two combinations of how to load and use the
175
- web component, it should be possible to use this library with all the major
176
- frontend frameworks.
175
+ > [!NOTE]
176
+ > Although we have just higlighted two combinations of how to load and use the
177
+ > web component, it should be possible to use this library with all the major
178
+ > frontend frameworks.
@@ -33,9 +33,14 @@ interface LocalMediaEventTarget extends EventTarget {
33
33
  addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
34
34
  }
35
35
  declare const TypedLocalMediaEventTarget: new () => LocalMediaEventTarget;
36
+ export interface LocalMediaOptions {
37
+ audio: boolean;
38
+ video: boolean;
39
+ }
36
40
  export default class LocalMedia extends TypedLocalMediaEventTarget {
37
- private _constraints;
41
+ private _options;
38
42
  _rtcManagers: RtcManager[];
43
+ private _devices;
39
44
  stream: MediaStream;
40
45
  screenshareStream?: MediaStream;
41
46
  private _cameraEnabled;
@@ -43,7 +48,7 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
43
48
  private _isTogglingCameraEnabled;
44
49
  private _microphoneEnabled;
45
50
  private _currentMicrophoneDeviceId;
46
- constructor(constraintsOrStream: MediaStreamConstraints | MediaStream);
51
+ constructor(optionsOrStream: LocalMediaOptions | MediaStream);
47
52
  addRtcManager(rtcManager: RtcManager): void;
48
53
  removeRtcManager(rtcManager: RtcManager): void;
49
54
  getCameraDeviceId(): string | undefined;
@@ -54,6 +59,8 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
54
59
  toggleMichrophoneEnabled(enabled?: boolean): void;
55
60
  startScreenshare(): Promise<MediaStream>;
56
61
  stopScreenshare(): void;
62
+ private _getConstraintsOptions;
63
+ private _setDevice;
57
64
  setCameraDevice(deviceId: string): Promise<void>;
58
65
  setMicrophoneDevice(deviceId: string): Promise<void>;
59
66
  private _updateDeviceList;
@@ -1,4 +1,5 @@
1
1
  import { __awaiter } from "tslib";
2
+ import { getStream } from "@whereby/jslib-media/src/webrtc/MediaDevices";
2
3
  class LocalMediaEvent extends CustomEvent {
3
4
  constructor(eventType, eventInitDict) {
4
5
  super(eventType, eventInitDict);
@@ -6,16 +7,17 @@ class LocalMediaEvent extends CustomEvent {
6
7
  }
7
8
  const TypedLocalMediaEventTarget = EventTarget;
8
9
  export default class LocalMedia extends TypedLocalMediaEventTarget {
9
- constructor(constraintsOrStream) {
10
+ constructor(optionsOrStream) {
10
11
  var _a, _b;
11
12
  super();
12
- this._constraints = null;
13
+ this._options = null;
14
+ this._devices = [];
13
15
  this._isTogglingCameraEnabled = false;
14
- if (constraintsOrStream instanceof MediaStream) {
15
- this.stream = constraintsOrStream;
16
+ if (optionsOrStream instanceof MediaStream) {
17
+ this.stream = optionsOrStream;
16
18
  }
17
19
  else {
18
- this._constraints = constraintsOrStream;
20
+ this._options = optionsOrStream;
19
21
  this.stream = new MediaStream();
20
22
  }
21
23
  this._cameraEnabled = ((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled) || false;
@@ -54,7 +56,7 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
54
56
  }
55
57
  this._cameraEnabled = newValue;
56
58
  this.dispatchEvent(new LocalMediaEvent("camera_enabled", { detail: { enabled: this._cameraEnabled } }));
57
- const shouldStopTrack = !!this._constraints;
59
+ const shouldStopTrack = !!this._options;
58
60
  this._isTogglingCameraEnabled = true;
59
61
  try {
60
62
  if (this._cameraEnabled) {
@@ -62,15 +64,8 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
62
64
  track.enabled = true;
63
65
  }
64
66
  else {
65
- const newStream = yield navigator.mediaDevices.getUserMedia({
66
- video: this._currentCameraDeviceId
67
- ? { deviceId: { exact: this._currentCameraDeviceId } }
68
- : true,
69
- });
70
- track = newStream.getVideoTracks()[0];
71
- if (track) {
72
- this.stream.addTrack(track);
73
- }
67
+ yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId: false, videoId: this._currentCameraDeviceId, type: "exact" }), { replaceStream: this.stream });
68
+ track = this.stream.getVideoTracks()[0];
74
69
  }
75
70
  }
76
71
  else {
@@ -114,44 +109,47 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
114
109
  (_a = this.screenshareStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
115
110
  this.screenshareStream = undefined;
116
111
  }
117
- setCameraDevice(deviceId) {
112
+ _getConstraintsOptions() {
113
+ return {
114
+ devices: this._devices,
115
+ options: {
116
+ disableAEC: false,
117
+ disableAGC: false,
118
+ hd: true,
119
+ lax: false,
120
+ lowDataMode: false,
121
+ simulcast: true,
122
+ widescreen: true,
123
+ },
124
+ };
125
+ }
126
+ _setDevice({ audioId, videoId }) {
118
127
  return __awaiter(this, void 0, void 0, function* () {
119
- this._currentCameraDeviceId = deviceId;
120
- const newStream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } } });
121
- const newVideoTrack = newStream.getVideoTracks()[0];
122
- if (newVideoTrack) {
123
- const oldVideoTrack = this.stream.getVideoTracks()[0];
124
- newVideoTrack.enabled = oldVideoTrack.enabled;
125
- oldVideoTrack === null || oldVideoTrack === void 0 ? void 0 : oldVideoTrack.stop();
126
- this._rtcManagers.forEach((rtcManager) => {
127
- rtcManager.replaceTrack(oldVideoTrack, newVideoTrack);
128
+ const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId,
129
+ videoId, type: "exact" }), { replaceStream: this.stream });
130
+ if (replacedTracks) {
131
+ replacedTracks.forEach((oldTrack) => {
132
+ const newTrack = oldTrack.kind === "audio" ? this.stream.getAudioTracks()[0] : this.stream.getVideoTracks()[0];
133
+ this._rtcManagers.forEach((rtcManager) => {
134
+ rtcManager.replaceTrack(oldTrack, newTrack);
135
+ });
128
136
  });
129
- this.stream.removeTrack(oldVideoTrack);
130
- this.stream.addTrack(newVideoTrack);
131
137
  }
132
138
  this.dispatchEvent(new LocalMediaEvent("stream_updated", {
133
139
  detail: { stream: this.stream },
134
140
  }));
135
141
  });
136
142
  }
143
+ setCameraDevice(deviceId) {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ this._currentCameraDeviceId = deviceId;
146
+ yield this._setDevice({ videoId: this._currentCameraDeviceId, audioId: false });
147
+ });
148
+ }
137
149
  setMicrophoneDevice(deviceId) {
138
150
  return __awaiter(this, void 0, void 0, function* () {
139
151
  this._currentMicrophoneDeviceId = deviceId;
140
- const newStream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId } });
141
- const newAudioTrack = newStream.getAudioTracks()[0];
142
- const oldAudioTrack = this.stream.getAudioTracks()[0];
143
- if (oldAudioTrack) {
144
- newAudioTrack.enabled = oldAudioTrack.enabled;
145
- oldAudioTrack.stop();
146
- this.stream.removeTrack(oldAudioTrack);
147
- }
148
- this._rtcManagers.forEach((rtcManager) => {
149
- rtcManager.replaceTrack(oldAudioTrack, newAudioTrack);
150
- });
151
- this.stream.addTrack(newAudioTrack);
152
- this.dispatchEvent(new LocalMediaEvent("stream_updated", {
153
- detail: { stream: this.stream },
154
- }));
152
+ yield this._setDevice({ audioId: this._currentMicrophoneDeviceId, videoId: false });
155
153
  });
156
154
  }
157
155
  _updateDeviceList() {
@@ -165,6 +163,7 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
165
163
  speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
166
164
  },
167
165
  }));
166
+ this._devices = devices;
168
167
  }
169
168
  catch (error) {
170
169
  this.dispatchEvent(new LocalMediaEvent("device_list_update_error", {
@@ -178,22 +177,20 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
178
177
  }
179
178
  start() {
180
179
  return __awaiter(this, void 0, void 0, function* () {
181
- if (this._constraints) {
182
- const newStream = yield navigator.mediaDevices.getUserMedia(this._constraints);
183
- const cameraTrack = newStream.getVideoTracks()[0];
180
+ yield this._updateDeviceList();
181
+ if (this._options) {
182
+ yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId: this._options.audio, videoId: this._options.video }), { replaceStream: this.stream });
183
+ const cameraTrack = this.stream.getVideoTracks()[0];
184
184
  if (cameraTrack) {
185
185
  this._cameraEnabled = cameraTrack.enabled;
186
186
  this._currentCameraDeviceId = cameraTrack.getSettings().deviceId;
187
- this.stream.addTrack(cameraTrack);
188
187
  }
189
- const microphoneTrack = newStream.getAudioTracks()[0];
188
+ const microphoneTrack = this.stream.getAudioTracks()[0];
190
189
  if (microphoneTrack) {
191
190
  this._microphoneEnabled = microphoneTrack.enabled;
192
191
  this._currentMicrophoneDeviceId = microphoneTrack.getSettings().deviceId;
193
- this.stream.addTrack(microphoneTrack);
194
192
  }
195
193
  }
196
- this._updateDeviceList();
197
194
  this.dispatchEvent(new LocalMediaEvent("stream_updated", {
198
195
  detail: { stream: this.stream },
199
196
  }));
@@ -202,7 +199,7 @@ export default class LocalMedia extends TypedLocalMediaEventTarget {
202
199
  }
203
200
  stop() {
204
201
  var _a;
205
- if (this._constraints) {
202
+ if (this._options) {
206
203
  (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
207
204
  t.stop();
208
205
  });
@@ -1,11 +1,11 @@
1
1
  import { RtcStreamAddedPayload } from "@whereby/jslib-media/src/webrtc/RtcManagerDispatcher";
2
2
  import { LocalParticipant, RemoteParticipant, Screenshare, WaitingParticipant } from "./RoomParticipant";
3
3
  import { ChatMessage as SignalChatMessage } from "@whereby/jslib-media/src/utils/ServerSocket";
4
- import LocalMedia from "./LocalMedia";
5
- type Logger = Pick<Console, "debug" | "error" | "log" | "warn">;
4
+ import LocalMedia, { LocalMediaOptions } from "./LocalMedia";
5
+ type Logger = Pick<Console, "debug" | "error" | "log" | "warn" | "info">;
6
6
  export interface RoomConnectionOptions {
7
7
  displayName?: string;
8
- localMediaConstraints?: MediaStreamConstraints;
8
+ localMediaOptions?: LocalMediaOptions;
9
9
  roomKey?: string;
10
10
  logger?: Logger;
11
11
  localMedia?: LocalMedia;
@@ -14,8 +14,9 @@ export interface RoomConnectionOptions {
14
14
  export type ChatMessage = Pick<SignalChatMessage, "senderId" | "timestamp" | "text">;
15
15
  export type ConnectionStatus = "initializing" | "connecting" | "connected" | "room_locked" | "knocking" | "disconnecting" | "disconnected" | "knock_rejected";
16
16
  export type CloudRecordingState = {
17
- status: "recording";
18
- startedAt: number;
17
+ error?: string;
18
+ status: "recording" | "requested" | "error";
19
+ startedAt?: number;
19
20
  };
20
21
  export type LiveStreamState = {
21
22
  status: "streaming";
@@ -78,7 +79,9 @@ export type LocalMicrophoneEnabledEvent = {
78
79
  };
79
80
  export interface RoomEventsMap {
80
81
  chat_message: (e: CustomEvent<ChatMessage>) => void;
82
+ cloud_recording_request_started: (e: CustomEvent<CloudRecordingState>) => void;
81
83
  cloud_recording_started: (e: CustomEvent<CloudRecordingState>) => void;
84
+ cloud_recording_started_error: (e: CustomEvent<CloudRecordingState>) => void;
82
85
  cloud_recording_stopped: (e: CustomEvent<CloudRecordingState>) => void;
83
86
  local_camera_enabled: (e: CustomEvent<LocalCameraEnabledEvent>) => void;
84
87
  local_microphone_enabled: (e: CustomEvent<LocalMicrophoneEnabledEvent>) => void;
@@ -140,10 +143,11 @@ export default class RoomConnection extends TypedEventTarget {
140
143
  private displayName?;
141
144
  private externalId?;
142
145
  private _roomKey;
143
- constructor(roomUrl: string, { displayName, localMedia, localMediaConstraints, logger, roomKey, externalId }: RoomConnectionOptions);
146
+ constructor(roomUrl: string, { displayName, localMedia, localMediaOptions: localMediaConstraints, logger, roomKey, externalId, }: RoomConnectionOptions);
144
147
  get roomKey(): string | null;
145
148
  private _handleNewChatMessage;
146
149
  private _handleCloudRecordingStarted;
150
+ private _handleRecorderClientJoined;
147
151
  private _handleStreamingStarted;
148
152
  private _handleNewClient;
149
153
  private _handleClientLeft;
@@ -180,5 +184,7 @@ export default class RoomConnection extends TypedEventTarget {
180
184
  }): void;
181
185
  startScreenshare(): Promise<void>;
182
186
  stopScreenshare(): void;
187
+ startCloudRecording(): void;
188
+ stopCloudRecording(): void;
183
189
  }
184
190
  export {};
@@ -54,7 +54,7 @@ const noop = () => {
54
54
  };
55
55
  const TypedEventTarget = EventTarget;
56
56
  export default class RoomConnection extends TypedEventTarget {
57
- constructor(roomUrl, { displayName, localMedia, localMediaConstraints, logger, roomKey, externalId }) {
57
+ constructor(roomUrl, { displayName, localMedia, localMediaOptions: localMediaConstraints, logger, roomKey, externalId, }) {
58
58
  super();
59
59
  this.remoteParticipants = [];
60
60
  this.screenshares = [];
@@ -70,6 +70,7 @@ export default class RoomConnection extends TypedEventTarget {
70
70
  this.logger = logger || {
71
71
  debug: noop,
72
72
  error: noop,
73
+ info: noop,
73
74
  log: noop,
74
75
  warn: noop,
75
76
  };
@@ -116,6 +117,7 @@ export default class RoomConnection extends TypedEventTarget {
116
117
  this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
117
118
  this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
118
119
  this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
120
+ this.signalSocket.on("cloud_recording_started", this._handleCloudRecordingStarted.bind(this));
119
121
  this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
120
122
  this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
121
123
  this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
@@ -158,6 +160,7 @@ export default class RoomConnection extends TypedEventTarget {
158
160
  h264On: false,
159
161
  simulcastScreenshareOn: false,
160
162
  },
163
+ logger: this.logger,
161
164
  });
162
165
  }
163
166
  get roomKey() {
@@ -166,7 +169,14 @@ export default class RoomConnection extends TypedEventTarget {
166
169
  _handleNewChatMessage(message) {
167
170
  this.dispatchEvent(new RoomConnectionEvent("chat_message", { detail: message }));
168
171
  }
169
- _handleCloudRecordingStarted({ client }) {
172
+ _handleCloudRecordingStarted(event) {
173
+ if (event.error) {
174
+ this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started_error", {
175
+ detail: { error: event.error, status: "error" },
176
+ }));
177
+ }
178
+ }
179
+ _handleRecorderClientJoined({ client }) {
170
180
  this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started", {
171
181
  detail: {
172
182
  status: "recording",
@@ -186,7 +196,7 @@ export default class RoomConnection extends TypedEventTarget {
186
196
  }
187
197
  _handleNewClient({ client }) {
188
198
  if (client.role.roleName === "recorder") {
189
- this._handleCloudRecordingStarted({ client });
199
+ this._handleRecorderClientJoined({ client });
190
200
  }
191
201
  if (client.role.roleName === "streamer") {
192
202
  this._handleStreamingStarted();
@@ -280,7 +290,7 @@ export default class RoomConnection extends TypedEventTarget {
280
290
  this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
281
291
  const recorderClient = clients.find((c) => c.role.roleName === "recorder");
282
292
  if (recorderClient) {
283
- this._handleCloudRecordingStarted({ client: recorderClient });
293
+ this._handleRecorderClientJoined({ client: recorderClient });
284
294
  }
285
295
  const streamerClient = clients.find((c) => c.role.roleName === "streamer");
286
296
  if (streamerClient) {
@@ -624,4 +634,13 @@ export default class RoomConnection extends TypedEventTarget {
624
634
  this.localMedia.stopScreenshare();
625
635
  }
626
636
  }
637
+ startCloudRecording() {
638
+ this.signalSocket.emit("start_recording", {
639
+ recording: "cloud",
640
+ });
641
+ this.dispatchEvent(new RoomConnectionEvent("cloud_recording_request_started"));
642
+ }
643
+ stopCloudRecording() {
644
+ this.signalSocket.emit("stop_recording");
645
+ }
627
646
  }
@@ -1,5 +1,5 @@
1
- import assert from "assert";
2
1
  import nodeBtoa from "btoa";
2
+ import assert from "@whereby/jslib-media/src/utils/assert";
3
3
  import HttpClient from "./HttpClient";
4
4
  import MultipartHttpClient from "./MultipartHttpClient";
5
5
  import { assertString } from "./parameterAssertUtils";
@@ -38,8 +38,6 @@ class AuthenticatedHttpClient {
38
38
  }
39
39
  export default class ApiClient {
40
40
  constructor({ baseUrl = "https://api.appearin.net", fetchDeviceCredentials = noCredentials, } = {}) {
41
- assertString(baseUrl, "baseUrl");
42
- assert.ok(typeof fetchDeviceCredentials === "function", "fetchDeviceCredentials<Function> is required");
43
41
  this.authenticatedHttpClient = new AuthenticatedHttpClient({
44
42
  httpClient: new HttpClient({
45
43
  baseUrl,
@@ -50,13 +48,13 @@ export default class ApiClient {
50
48
  }
51
49
  request(url, options) {
52
50
  assertString(url, "url");
53
- assert.equal(url[0], "/", 'url<String> only accepts relative URLs beginning with "/".');
51
+ assert.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
54
52
  assert.ok(options, "options are required");
55
53
  return this.authenticatedHttpClient.request(url, options);
56
54
  }
57
55
  requestMultipart(url, options) {
58
56
  assertString(url, "url");
59
- assert.equal(url[0], "/", 'url<String> only accepts relative URLs beginning with "/".');
57
+ assert.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
60
58
  assert.ok(options, "options are required");
61
59
  return this.authenticatedFormDataHttpClient.request(url, options);
62
60
  }
@@ -1,9 +1,8 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  import axios from "axios";
3
3
  import Response from "./Response";
4
4
  import { assertString } from "./parameterAssertUtils";
5
5
  function _getAbsoluteUrl({ baseUrl, url }) {
6
- assert.ok(typeof url === "string", "url<String> is required");
7
6
  return baseUrl ? baseUrl + url : url;
8
7
  }
9
8
  export default class HttpClient {
@@ -20,7 +19,7 @@ export default class HttpClient {
20
19
  }
21
20
  request(url, options) {
22
21
  assertString(url, "url");
23
- assert.equal(url[0], "/", 'url<String> only accepts relative URLs beginning with "/".');
22
+ assert.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
24
23
  assert.ok(options, "options are required");
25
24
  return this._requestAxios(url, options)
26
25
  .then((response) => {
@@ -1,4 +1,4 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  export default class MultipartHttpClient {
3
3
  constructor({ httpClient }) {
4
4
  assert.ok(httpClient, "httpClient is required");
@@ -1,16 +1,15 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  import { assertString } from "./parameterAssertUtils";
3
3
  const noOrganization = () => Promise.resolve(undefined);
4
4
  export default class OrganizationApiClient {
5
5
  constructor({ apiClient, fetchOrganization = noOrganization, }) {
6
6
  this._apiClient = apiClient;
7
- assert.ok(typeof fetchOrganization === "function", "fetchOrganization<Function> is required");
8
7
  this._fetchOrganization = fetchOrganization;
9
8
  this._apiClient = apiClient;
10
9
  }
11
10
  _callRequestMethod(method, url, options) {
12
11
  assertString(url, "url");
13
- assert.equal(url[0], "/", 'url<String> only accepts relative URLs beginning with "/".');
12
+ assert.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
14
13
  assert.ok(options, "options are required");
15
14
  return this._fetchOrganization().then((organization) => {
16
15
  if (!organization) {
@@ -1,4 +1,4 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  export default class Room {
3
3
  constructor(properties = {}) {
4
4
  assert.ok(properties instanceof Object, "properties<object> must be empty or an object");
@@ -1,4 +1,4 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  import Organization from "../models/Organization";
3
3
  import { assertInstanceOf, assertTruthy, assertString, assertArray, assertNullOrString, assertRecord, } from "../parameterAssertUtils";
4
4
  import Response from "../Response";
@@ -1,4 +1,4 @@
1
- import assert from "assert";
1
+ import assert from "@whereby/jslib-media/src/utils/assert";
2
2
  export function assertTruthy(value, parameterName) {
3
3
  assert.ok(value, `${parameterName} is required`);
4
4
  return value;
@@ -22,7 +22,7 @@ export function assertInstanceOf(value, type, parameterName) {
22
22
  }
23
23
  export function assertRoomName(roomName, parameterName = "roomName") {
24
24
  assertString(roomName, parameterName);
25
- assert.equal(typeof roomName === "string" && roomName[0], "/", `${parameterName} must begin with a '/'`);
25
+ assert.ok(typeof roomName === "string" && roomName[0] === "/", `${parameterName} must begin with a '/'`);
26
26
  return roomName;
27
27
  }
28
28
  export function assertArray(array, parameterName) {
@@ -31,10 +31,10 @@ define("WherebyEmbed", {
31
31
  this.iframe = ref();
32
32
  },
33
33
  onconnected() {
34
- window.addEventListener("message", this.onmessage);
34
+ window.addEventListener("message", this.onmessage.bind(this));
35
35
  },
36
36
  ondisconnected() {
37
- window.removeEventListener("message", this.onmessage);
37
+ window.removeEventListener("message", this.onmessage.bind(this));
38
38
  },
39
39
  observedAttributes: [
40
40
  "displayName",
@@ -87,8 +87,7 @@ define("WherebyEmbed", {
87
87
  this._postCommand("toggle_screenshare", [enabled]);
88
88
  },
89
89
  onmessage({ origin, data }) {
90
- var _a;
91
- if (origin !== ((_a = this.url) === null || _a === void 0 ? void 0 : _a.origin))
90
+ if (origin !== this.url.origin)
92
91
  return;
93
92
  const { type, payload: detail } = data;
94
93
  this.dispatchEvent(new CustomEvent(type, { detail }));
@@ -11,5 +11,5 @@ interface VideoViewSelfProps {
11
11
  }) => void;
12
12
  }
13
13
  type VideoViewProps = VideoViewSelfProps & React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;
14
- declare const _default: ({ muted, mirror, stream, onResize, ...rest }: VideoViewProps) => JSX.Element;
14
+ declare const _default: ({ muted, mirror, stream, onResize, ...rest }: VideoViewProps) => React.JSX.Element;
15
15
  export default _default;
@@ -33,5 +33,5 @@ export default (_a) => {
33
33
  videoEl.current.muted = Boolean(muted);
34
34
  }
35
35
  }, [muted, stream, videoEl]);
36
- return (React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none" }, rest.style) })));
36
+ return (React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
37
37
  };
@@ -1,4 +1,4 @@
1
- import LocalMedia from "../LocalMedia";
1
+ import LocalMedia, { LocalMediaOptions } from "../LocalMedia";
2
2
  interface LocalMediaState {
3
3
  currentCameraDeviceId?: string;
4
4
  currentMicrophoneDeviceId?: string;
@@ -24,5 +24,6 @@ export type LocalMediaRef = {
24
24
  actions: LocalMediaActions;
25
25
  _ref: LocalMedia;
26
26
  };
27
- export default function useLocalMedia(constraintsOrStream?: MediaStreamConstraints | MediaStream): LocalMediaRef;
27
+ export type UseLocalMediaOptions = LocalMediaOptions;
28
+ export default function useLocalMedia(optionsOrStream?: UseLocalMediaOptions | MediaStream): LocalMediaRef;
28
29
  export {};
@@ -40,8 +40,8 @@ function reducer(state, action) {
40
40
  return state;
41
41
  }
42
42
  }
43
- export default function useLocalMedia(constraintsOrStream = { audio: true, video: true }) {
44
- const [localMedia] = useState(() => new LocalMedia(constraintsOrStream));
43
+ export default function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
44
+ const [localMedia] = useState(() => new LocalMedia(optionsOrStream));
45
45
  const [state, dispatch] = useReducer(reducer, initialState);
46
46
  useEffect(() => {
47
47
  localMedia.addEventListener("device_list_updated", (e) => {
@@ -38,7 +38,9 @@ interface RoomConnectionActions {
38
38
  toggleMicrophone(enabled?: boolean): void;
39
39
  acceptWaitingParticipant(participantId: string): void;
40
40
  rejectWaitingParticipant(participantId: string): void;
41
+ startCloudRecording(): void;
41
42
  startScreenshare(): void;
43
+ stopCloudRecording(): void;
42
44
  stopScreenshare(): void;
43
45
  }
44
46
  type VideoViewComponentProps = Omit<React.ComponentProps<typeof VideoView>, "onResize">;
@@ -42,11 +42,20 @@ function reducer(state, action) {
42
42
  switch (action.type) {
43
43
  case "CHAT_MESSAGE":
44
44
  return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload] });
45
+ case "CLOUD_RECORDING_REQUEST_STARTED":
46
+ return Object.assign(Object.assign({}, state), { cloudRecording: {
47
+ status: "requested",
48
+ } });
45
49
  case "CLOUD_RECORDING_STARTED":
46
50
  return Object.assign(Object.assign({}, state), { cloudRecording: {
47
51
  status: action.payload.status,
48
52
  startedAt: action.payload.startedAt,
49
53
  } });
54
+ case "CLOUD_RECORDING_STARTED_ERROR":
55
+ return Object.assign(Object.assign({}, state), { cloudRecording: {
56
+ status: action.payload.status,
57
+ error: action.payload.error,
58
+ } });
50
59
  case "CLOUD_RECORDING_STOPPED":
51
60
  delete state.cloudRecording;
52
61
  return Object.assign({}, state);
@@ -124,7 +133,7 @@ function reducer(state, action) {
124
133
  }
125
134
  }
126
135
  const defaultRoomConnectionOptions = {
127
- localMediaConstraints: {
136
+ localMediaOptions: {
128
137
  audio: true,
129
138
  video: true,
130
139
  },
@@ -146,10 +155,16 @@ export function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomCo
146
155
  createEventListener("chat_message", (e) => {
147
156
  dispatch({ type: "CHAT_MESSAGE", payload: e.detail });
148
157
  }),
158
+ createEventListener("cloud_recording_request_started", () => {
159
+ dispatch({ type: "CLOUD_RECORDING_REQUEST_STARTED" });
160
+ }),
149
161
  createEventListener("cloud_recording_started", (e) => {
150
162
  const { status, startedAt } = e.detail;
151
163
  dispatch({ type: "CLOUD_RECORDING_STARTED", payload: { status, startedAt } });
152
164
  }),
165
+ createEventListener("cloud_recording_started_error", (e) => {
166
+ dispatch({ type: "CLOUD_RECORDING_STARTED_ERROR", payload: e.detail });
167
+ }),
153
168
  createEventListener("cloud_recording_stopped", () => {
154
169
  dispatch({ type: "CLOUD_RECORDING_STOPPED" });
155
170
  }),
@@ -285,6 +300,16 @@ export function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomCo
285
300
  rejectWaitingParticipant: (participantId) => {
286
301
  roomConnection.rejectWaitingParticipant(participantId);
287
302
  },
303
+ startCloudRecording: () => {
304
+ var _a;
305
+ if (state.cloudRecording && ["recording", "requested"].includes((_a = state.cloudRecording) === null || _a === void 0 ? void 0 : _a.status)) {
306
+ return;
307
+ }
308
+ roomConnection.startCloudRecording();
309
+ },
310
+ stopCloudRecording: () => {
311
+ roomConnection.stopCloudRecording();
312
+ },
288
313
  startScreenshare: () => __awaiter(this, void 0, void 0, function* () {
289
314
  dispatch({ type: "LOCAL_SCREENSHARE_STARTING" });
290
315
  try {
@@ -1,6 +1,4 @@
1
- import assert from "assert";
2
1
  export default function debounce(fn, { delay = 500, edges } = {}) {
3
- assert.ok(typeof fn === "function", "fn<function> is required");
4
2
  let timeout;
5
3
  let nCalls = 0;
6
4
  return (...args) => {
@@ -1822,7 +1822,7 @@ var setupIncludes = (Class, tagName2, is, u) => {
1822
1822
  };
1823
1823
 
1824
1824
  // src/lib/version.ts
1825
- var sdkVersion = "2.0.0-beta1";
1825
+ var sdkVersion = "2.0.0-beta2";
1826
1826
 
1827
1827
  // src/lib/embed/index.ts
1828
1828
  var boolAttrs = [
@@ -1856,10 +1856,10 @@ define("WherebyEmbed", {
1856
1856
  this.iframe = ref2();
1857
1857
  },
1858
1858
  onconnected() {
1859
- window.addEventListener("message", this.onmessage);
1859
+ window.addEventListener("message", this.onmessage.bind(this));
1860
1860
  },
1861
1861
  ondisconnected() {
1862
- window.removeEventListener("message", this.onmessage);
1862
+ window.removeEventListener("message", this.onmessage.bind(this));
1863
1863
  },
1864
1864
  observedAttributes: [
1865
1865
  "displayName",
@@ -1913,8 +1913,7 @@ define("WherebyEmbed", {
1913
1913
  this._postCommand("toggle_screenshare", [enabled]);
1914
1914
  },
1915
1915
  onmessage({ origin, data: data2 }) {
1916
- var _a;
1917
- if (origin !== ((_a = this.url) == null ? void 0 : _a.origin))
1916
+ if (origin !== this.url.origin)
1918
1917
  return;
1919
1918
  const { type, payload: detail } = data2;
1920
1919
  this.dispatchEvent(new CustomEvent(type, { detail }));
@@ -1948,7 +1947,7 @@ define("WherebyEmbed", {
1948
1947
  }
1949
1948
  Object.entries(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({
1950
1949
  jsApi: true,
1951
- we: "2.0.0-beta1",
1950
+ we: "2.0.0-beta2",
1952
1951
  iframeSource: subdomain
1953
1952
  }, displayName && { displayName }), lang && { lang }), metadata && { metadata }), externalId && { externalId }), groups && { groups }), virtualBackgroundUrl && { virtualBackgroundUrl }), avatarUrl && { avatarUrl }), minimal != null && { embed: minimal }), boolAttrs.reduce(
1954
1953
  // add to URL if set in any way
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whereby.com/browser-sdk",
3
- "version": "2.0.0-beta1",
3
+ "version": "2.0.0-beta2",
4
4
  "description": "Modules for integration Whereby video in web apps",
5
5
  "author": "Whereby AS",
6
6
  "license": "MIT",
@@ -11,15 +11,21 @@
11
11
  "browserslist": "> 0.5%, last 2 versions, not dead",
12
12
  "source": "src/index.js",
13
13
  "exports": {
14
- "./react": "./dist/react/index.js",
15
- "./embed": "./dist/embed/index.js",
16
- "./utils": "./dist/utils/index.js"
14
+ "./react": "./dist/react/index.js",
15
+ "./embed": "./dist/embed/index.js",
16
+ "./utils": "./dist/utils/index.js"
17
17
  },
18
18
  "typesVersions": {
19
19
  "*": {
20
- "react": ["dist/react/index.d.ts"],
21
- "embed": ["dist/embed/index.d.ts"],
22
- "utils": ["dist/utils/index.d.ts"]
20
+ "react": [
21
+ "dist/react/index.d.ts"
22
+ ],
23
+ "embed": [
24
+ "dist/embed/index.d.ts"
25
+ ],
26
+ "utils": [
27
+ "dist/utils/index.d.ts"
28
+ ]
23
29
  }
24
30
  },
25
31
  "files": [
@@ -30,8 +36,8 @@
30
36
  "prebuild": "rimraf dist",
31
37
  "build": "node build.js",
32
38
  "postbuild": "tsc --project tsconfig.build.json",
33
- "build:storybook": "build-storybook",
34
- "dev": "start-storybook -p 6006",
39
+ "build:storybook": "storybook build",
40
+ "dev": "storybook dev -p 6006",
35
41
  "install:e2e-sample-app": "cd test/sample-app && yarn custom_install",
36
42
  "start:e2e-sample-app": "cd test/sample-app && yarn start",
37
43
  "test": "yarn test:lint && yarn test:unit",
@@ -39,23 +45,25 @@
39
45
  "test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
40
46
  "test:unit:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
41
47
  "test:e2e": "playwright test",
42
- "storybook": "start-storybook -p 6006",
43
- "build-storybook": "build-storybook"
48
+ "storybook": "storybook dev -p 6006",
49
+ "build-storybook": "storybook build"
44
50
  },
45
51
  "devDependencies": {
46
52
  "@babel/core": "^7.18.5",
47
53
  "@babel/plugin-proposal-optional-chaining": "^7.18.9",
54
+ "@babel/preset-env": "^7.23.2",
55
+ "@babel/preset-react": "^7.22.15",
56
+ "@babel/preset-typescript": "^7.23.2",
48
57
  "@playwright/test": "^1.38.1",
49
58
  "@rollup/plugin-commonjs": "^24.0.0",
50
59
  "@rollup/plugin-json": "^6.0.0",
51
60
  "@rollup/plugin-node-resolve": "^13.3.0",
52
61
  "@rollup/plugin-replace": "^4.0.0",
53
- "@storybook/addon-actions": "^6.5.14",
54
- "@storybook/addon-essentials": "^6.5.14",
55
- "@storybook/addon-links": "^6.5.14",
56
- "@storybook/builder-webpack5": "^6.5.14",
57
- "@storybook/manager-webpack5": "^6.5.14",
58
- "@storybook/react": "^6.5.14",
62
+ "@storybook/addon-actions": "^7.5.2",
63
+ "@storybook/addon-essentials": "^7.5.2",
64
+ "@storybook/addon-links": "^7.5.2",
65
+ "@storybook/react": "^7.5.2",
66
+ "@storybook/react-webpack5": "^7.5.2",
59
67
  "@testing-library/react": "^14.0.0",
60
68
  "@types/btoa": "^1.2.3",
61
69
  "@types/chrome": "^0.0.210",
@@ -78,6 +86,7 @@
78
86
  "react": "^18.2.0",
79
87
  "react-dom": "^18.2.0",
80
88
  "rimraf": "^3.0.2",
89
+ "storybook": "^7.5.2",
81
90
  "ts-jest": "29.0.5",
82
91
  "tslib": "^2.4.1",
83
92
  "typescript": "^4.9.4",
@@ -85,8 +94,7 @@
85
94
  },
86
95
  "dependencies": {
87
96
  "@swc/helpers": "^0.3.13",
88
- "@whereby/jslib-media": "whereby/jslib-media.git#1.3.3",
89
- "assert": "^2.0.0",
97
+ "@whereby/jslib-media": "whereby/jslib-media.git#1.4.1",
90
98
  "axios": "^1.2.3",
91
99
  "btoa": "^1.2.1",
92
100
  "events": "^3.3.0",
@@ -95,5 +103,10 @@
95
103
  "peerDependencies": {
96
104
  "react": "^18.2.0",
97
105
  "react-dom": "^18.2.0"
106
+ },
107
+ "resolutions": {
108
+ "string-width": "^4",
109
+ "jackspeak": "2.1.1",
110
+ "wrap-ansi": "7.0.0"
98
111
  }
99
112
  }