@whereby.com/browser-sdk 2.0.0-alpha → 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/README.md +39 -2
- package/dist/lib.cjs +5868 -0
- package/dist/lib.esm.js +5850 -0
- package/dist/types.d.ts +308 -0
- package/dist/v2-alpha10.js +43 -0
- package/package.json +12 -6
- package/.eslintrc +0 -23
- package/.github/actions/build/action.yml +0 -17
- package/.github/workflows/deploy.yml +0 -102
- package/.github/workflows/test.yml +0 -24
- package/.prettierignore +0 -7
- package/.prettierrc +0 -4
- package/.storybook/main.cjs +0 -16
- package/.storybook/preview.js +0 -9
- package/jest.config.js +0 -6
- package/rollup.config.js +0 -70
- package/src/lib/RoomConnection.ts +0 -516
- package/src/lib/RoomParticipant.ts +0 -77
- package/src/lib/__tests__/embed.unit.ts +0 -77
- package/src/lib/api/ApiClient.ts +0 -111
- package/src/lib/api/Credentials.ts +0 -45
- package/src/lib/api/HttpClient.ts +0 -95
- package/src/lib/api/MultipartHttpClient.ts +0 -53
- package/src/lib/api/OrganizationApiClient.ts +0 -64
- package/src/lib/api/Response.ts +0 -34
- package/src/lib/api/credentialsService/index.ts +0 -159
- package/src/lib/api/credentialsService/test/index.spec.ts +0 -181
- package/src/lib/api/deviceService/index.ts +0 -42
- package/src/lib/api/deviceService/tests/index.spec.ts +0 -74
- package/src/lib/api/extractUtils.ts +0 -160
- package/src/lib/api/index.ts +0 -8
- package/src/lib/api/localStorageWrapper/index.ts +0 -15
- package/src/lib/api/models/Account.ts +0 -48
- package/src/lib/api/models/Meeting.ts +0 -42
- package/src/lib/api/models/Organization.ts +0 -186
- package/src/lib/api/models/Room.ts +0 -44
- package/src/lib/api/models/account/EmbeddedFreeTierStatus.ts +0 -34
- package/src/lib/api/models/tests/Account.spec.ts +0 -128
- package/src/lib/api/models/tests/Organization.spec.ts +0 -161
- package/src/lib/api/models/tests/Room.spec.ts +0 -74
- package/src/lib/api/modules/AbstractStore.ts +0 -18
- package/src/lib/api/modules/ChromeStorageStore.ts +0 -44
- package/src/lib/api/modules/LocalStorageStore.ts +0 -57
- package/src/lib/api/modules/tests/ChromeStorageStore.spec.ts +0 -67
- package/src/lib/api/modules/tests/LocalStorageStore.spec.ts +0 -79
- package/src/lib/api/modules/tests/__mocks__/storage.ts +0 -24
- package/src/lib/api/organizationService/index.ts +0 -284
- package/src/lib/api/organizationService/tests/index.spec.ts +0 -781
- package/src/lib/api/organizationServiceCache/index.ts +0 -28
- package/src/lib/api/organizationServiceCache/tests/index.spec.ts +0 -101
- package/src/lib/api/parameterAssertUtils.ts +0 -166
- package/src/lib/api/roomService/index.ts +0 -310
- package/src/lib/api/roomService/tests/index.spec.ts +0 -668
- package/src/lib/api/test/ApiClient.spec.ts +0 -139
- package/src/lib/api/test/HttpClient.spec.ts +0 -120
- package/src/lib/api/test/MultipartHttpClient.spec.ts +0 -145
- package/src/lib/api/test/OrganizationApiClient.spec.ts +0 -132
- package/src/lib/api/test/extractUtils.spec.ts +0 -357
- package/src/lib/api/test/helpers.ts +0 -41
- package/src/lib/api/test/parameterAssertUtils.spec.ts +0 -265
- package/src/lib/api/types.ts +0 -6
- package/src/lib/embed.ts +0 -172
- package/src/lib/index.ts +0 -3
- package/src/lib/react/VideoElement.tsx +0 -16
- package/src/lib/react/index.ts +0 -3
- package/src/lib/react/useLocalMedia.ts +0 -25
- package/src/lib/react/useRoomConnection.ts +0 -92
- package/src/lib/reducer.ts +0 -142
- package/src/stories/custom-ui.stories.tsx +0 -133
- package/src/stories/prebuilt-ui.stories.tsx +0 -131
- package/src/stories/styles.css +0 -74
- package/src/types.d.ts +0 -175
- package/tsconfig.json +0 -30
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
import RtcManagerDispatcher, {
|
|
2
|
-
RtcEvents,
|
|
3
|
-
RtcManagerCreatedPayload,
|
|
4
|
-
RtcStreamAddedPayload,
|
|
5
|
-
} from "@whereby/jslib-media/src/webrtc/RtcManagerDispatcher";
|
|
6
|
-
import RtcManager from "@whereby/jslib-media/src/webrtc/RtcManager";
|
|
7
|
-
import { fromLocation } from "@whereby/jslib-media/src/utils/urls";
|
|
8
|
-
import {
|
|
9
|
-
ApiClient,
|
|
10
|
-
CredentialsService,
|
|
11
|
-
OrganizationApiClient,
|
|
12
|
-
OrganizationService,
|
|
13
|
-
OrganizationServiceCache,
|
|
14
|
-
RoomService,
|
|
15
|
-
} from "./api";
|
|
16
|
-
|
|
17
|
-
import { LocalParticipant, RemoteParticipant, StreamState } from "./RoomParticipant";
|
|
18
|
-
|
|
19
|
-
import ServerSocket, {
|
|
20
|
-
ClientLeftEvent,
|
|
21
|
-
ClientMetadataReceivedEvent,
|
|
22
|
-
NewClientEvent,
|
|
23
|
-
RoomJoinedEvent as SignalRoomJoinedEvent,
|
|
24
|
-
SignalClient,
|
|
25
|
-
} from "@whereby/jslib-media/src/utils/ServerSocket";
|
|
26
|
-
|
|
27
|
-
type Logger = Pick<Console, "debug" | "error" | "log" | "warn">;
|
|
28
|
-
|
|
29
|
-
export interface RoomConnectionOptions {
|
|
30
|
-
displayName?: string; // Might not be needed at all
|
|
31
|
-
localStream?: MediaStream;
|
|
32
|
-
localMediaConstraints?: MediaStreamConstraints;
|
|
33
|
-
roomKey?: string;
|
|
34
|
-
logger?: Logger;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type RoomJoinedEvent = {
|
|
38
|
-
localParticipant: LocalParticipant;
|
|
39
|
-
remoteParticipants: RemoteParticipant[];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type ParticipantJoinedEvent = {
|
|
43
|
-
remoteParticipant: RemoteParticipant;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type ParticipantLeftEvent = {
|
|
47
|
-
participantId: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type ParticipantStreamAddedEvent = {
|
|
51
|
-
participantId: string;
|
|
52
|
-
stream: MediaStream;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
type ParticipantAudioEnabledEvent = {
|
|
56
|
-
participantId: string;
|
|
57
|
-
isAudioEnabled: boolean;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
type ParticipantVideoEnabledEvent = {
|
|
61
|
-
participantId: string;
|
|
62
|
-
isVideoEnabled: boolean;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
type ParticipantMetadataChangedEvent = {
|
|
66
|
-
participantId: string;
|
|
67
|
-
displayName: string;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
interface RoomEventsMap {
|
|
71
|
-
participant_audio_enabled: CustomEvent<ParticipantAudioEnabledEvent>;
|
|
72
|
-
participant_joined: CustomEvent<ParticipantJoinedEvent>;
|
|
73
|
-
participant_left: CustomEvent<ParticipantLeftEvent>;
|
|
74
|
-
participant_metadata_changed: CustomEvent<ParticipantMetadataChangedEvent>;
|
|
75
|
-
participant_stream_added: CustomEvent<ParticipantStreamAddedEvent>;
|
|
76
|
-
participant_video_enabled: CustomEvent<ParticipantVideoEnabledEvent>;
|
|
77
|
-
room_joined: CustomEvent<RoomJoinedEvent>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const API_BASE_URL = "https://api.appearin.net";
|
|
81
|
-
const SIGNAL_BASE_URL = "wss://signal.appearin.net";
|
|
82
|
-
|
|
83
|
-
const NON_PERSON_ROLES = ["recorder", "streamer"];
|
|
84
|
-
|
|
85
|
-
function createSocket() {
|
|
86
|
-
const parsedUrl = new URL(SIGNAL_BASE_URL);
|
|
87
|
-
const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v1`;
|
|
88
|
-
const SOCKET_HOST = parsedUrl.origin;
|
|
89
|
-
|
|
90
|
-
const socketConf = {
|
|
91
|
-
host: SOCKET_HOST,
|
|
92
|
-
path,
|
|
93
|
-
reconnectionDelay: 5000,
|
|
94
|
-
reconnectionDelayMax: 30000,
|
|
95
|
-
timeout: 10000,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
return new ServerSocket(SOCKET_HOST, socketConf);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/*
|
|
102
|
-
* This is the topmost interface when dealing with Whereby.
|
|
103
|
-
*
|
|
104
|
-
*/
|
|
105
|
-
interface RoomEventTarget extends EventTarget {
|
|
106
|
-
addEventListener<K extends keyof RoomEventsMap>(
|
|
107
|
-
type: K,
|
|
108
|
-
listener: (ev: RoomEventsMap[K]) => void,
|
|
109
|
-
options?: boolean | AddEventListenerOptions
|
|
110
|
-
): void;
|
|
111
|
-
addEventListener(
|
|
112
|
-
type: string,
|
|
113
|
-
callback: EventListenerOrEventListenerObject | null,
|
|
114
|
-
options?: EventListenerOptions | boolean
|
|
115
|
-
): void;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const noop = () => {
|
|
119
|
-
return;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const TypedEventTarget = EventTarget as { new (): RoomEventTarget };
|
|
123
|
-
export default class RoomConnection extends TypedEventTarget {
|
|
124
|
-
public localParticipant: LocalParticipant | null = null;
|
|
125
|
-
public roomUrl: URL;
|
|
126
|
-
public remoteParticipants: RemoteParticipant[] = [];
|
|
127
|
-
public readonly localMediaConstraints?: MediaStreamConstraints;
|
|
128
|
-
|
|
129
|
-
private credentialsService: CredentialsService;
|
|
130
|
-
private apiClient: ApiClient;
|
|
131
|
-
private organizationService: OrganizationService;
|
|
132
|
-
private organizationServiceCache: OrganizationServiceCache;
|
|
133
|
-
private organizationApiClient: OrganizationApiClient;
|
|
134
|
-
private roomService: RoomService;
|
|
135
|
-
|
|
136
|
-
private signalSocket: ServerSocket;
|
|
137
|
-
private rtcManagerDispatcher?: RtcManagerDispatcher;
|
|
138
|
-
private rtcManager?: RtcManager;
|
|
139
|
-
private roomConnectionState: "" | "connecting" | "connected" | "disconnected" = "";
|
|
140
|
-
private logger: Logger;
|
|
141
|
-
private localStream?: MediaStream;
|
|
142
|
-
private displayName?: string;
|
|
143
|
-
|
|
144
|
-
constructor(roomUrl: string, { displayName, localMediaConstraints, localStream, logger }: RoomConnectionOptions) {
|
|
145
|
-
super();
|
|
146
|
-
this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
|
|
147
|
-
this.logger = logger || {
|
|
148
|
-
debug: noop,
|
|
149
|
-
error: noop,
|
|
150
|
-
log: noop,
|
|
151
|
-
warn: noop,
|
|
152
|
-
};
|
|
153
|
-
this.displayName = displayName;
|
|
154
|
-
this.localStream = localStream;
|
|
155
|
-
this.localMediaConstraints = localMediaConstraints;
|
|
156
|
-
|
|
157
|
-
const urls = fromLocation({ host: this.roomUrl.host });
|
|
158
|
-
|
|
159
|
-
// Initialize services
|
|
160
|
-
this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
|
|
161
|
-
this.apiClient = new ApiClient({
|
|
162
|
-
fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
|
|
163
|
-
baseUrl: API_BASE_URL,
|
|
164
|
-
});
|
|
165
|
-
this.organizationService = new OrganizationService({ apiClient: this.apiClient });
|
|
166
|
-
this.organizationServiceCache = new OrganizationServiceCache({
|
|
167
|
-
organizationService: this.organizationService,
|
|
168
|
-
subdomain: urls.subdomain,
|
|
169
|
-
});
|
|
170
|
-
this.organizationApiClient = new OrganizationApiClient({
|
|
171
|
-
apiClient: this.apiClient,
|
|
172
|
-
fetchOrganization: async () => {
|
|
173
|
-
const organization = await this.organizationServiceCache.fetchOrganization();
|
|
174
|
-
return organization || undefined;
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
this.roomService = new RoomService({ organizationApiClient: this.organizationApiClient });
|
|
178
|
-
|
|
179
|
-
// Create signal socket and set up event listeners
|
|
180
|
-
this.signalSocket = createSocket();
|
|
181
|
-
this.signalSocket.on("new_client", this._handleNewClient.bind(this));
|
|
182
|
-
this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
|
|
183
|
-
this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
|
|
184
|
-
this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
|
|
185
|
-
this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private _handleNewClient({ client }: NewClientEvent) {
|
|
189
|
-
if (NON_PERSON_ROLES.includes(client.role.roleName)) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const remoteParticipant = new RemoteParticipant({ ...client, newJoiner: true });
|
|
193
|
-
this.remoteParticipants = [...this.remoteParticipants, remoteParticipant];
|
|
194
|
-
this._handleAcceptStreams([remoteParticipant]);
|
|
195
|
-
this.dispatchEvent(new CustomEvent("participant_joined", { detail: { remoteParticipant } }));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private _handleClientLeft({ clientId }: ClientLeftEvent) {
|
|
199
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
200
|
-
this.remoteParticipants = this.remoteParticipants.filter((p) => p.id !== clientId);
|
|
201
|
-
if (!remoteParticipant) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
this.dispatchEvent(new CustomEvent("participant_left", { detail: { participantId: remoteParticipant.id } }));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private _handleClientAudioEnabled({ clientId, isAudioEnabled }: { clientId: string; isAudioEnabled: boolean }) {
|
|
208
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
209
|
-
if (!remoteParticipant) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
this.dispatchEvent(
|
|
213
|
-
new CustomEvent("participant_audio_enabled", {
|
|
214
|
-
detail: { participantId: remoteParticipant.id, isAudioEnabled },
|
|
215
|
-
})
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private _handleClientVideoEnabled({ clientId, isVideoEnabled }: { clientId: string; isVideoEnabled: boolean }) {
|
|
220
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
221
|
-
if (!remoteParticipant) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
this.dispatchEvent(
|
|
225
|
-
new CustomEvent("participant_video_enabled", {
|
|
226
|
-
detail: { participantId: remoteParticipant.id, isVideoEnabled },
|
|
227
|
-
})
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private _handleClientMetadataReceived({ payload: { clientId, displayName } }: ClientMetadataReceivedEvent) {
|
|
232
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
233
|
-
if (!remoteParticipant) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
this.dispatchEvent(
|
|
237
|
-
new CustomEvent("participant_metadata_changed", {
|
|
238
|
-
detail: { participantId: remoteParticipant.id, displayName },
|
|
239
|
-
})
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private _handleRtcEvent<K extends keyof RtcEvents>(eventName: K, data: RtcEvents[K]) {
|
|
244
|
-
if (eventName === "rtc_manager_created") {
|
|
245
|
-
return this._handleRtcManagerCreated(data as RtcManagerCreatedPayload);
|
|
246
|
-
} else if (eventName === "stream_added") {
|
|
247
|
-
return this._handleStreamAdded(data as RtcStreamAddedPayload);
|
|
248
|
-
} else {
|
|
249
|
-
this.logger.log(`Unhandled RTC event ${eventName}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private _handleRtcManagerCreated({ rtcManager }: RtcManagerCreatedPayload) {
|
|
254
|
-
this.rtcManager = rtcManager;
|
|
255
|
-
if (this.localStream) {
|
|
256
|
-
this.rtcManager?.addNewStream(
|
|
257
|
-
"0",
|
|
258
|
-
this.localStream,
|
|
259
|
-
!this.localStream?.getAudioTracks().find((t) => t.enabled),
|
|
260
|
-
!this.localStream?.getVideoTracks().find((t) => t.enabled)
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private _handleAcceptStreams(remoteParticipants: RemoteParticipant[]) {
|
|
266
|
-
if (!this.rtcManager) {
|
|
267
|
-
this.logger.log("Unable to accept streams, no rtc manager");
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const shouldAcceptNewClients = this.rtcManager.shouldAcceptStreamsFromBothSides?.();
|
|
272
|
-
const activeBreakout = false; // TODO: Remove this once breakout is implemented
|
|
273
|
-
const myselfBroadcasting = false; // TODO: Remove once breakout is implemented
|
|
274
|
-
|
|
275
|
-
remoteParticipants.forEach((participant) => {
|
|
276
|
-
const { id: participantId, streams, newJoiner } = participant;
|
|
277
|
-
|
|
278
|
-
streams.forEach((stream) => {
|
|
279
|
-
const { id: streamId, state: streamState } = stream;
|
|
280
|
-
let newState: StreamState | undefined = undefined;
|
|
281
|
-
|
|
282
|
-
// Determine the new state of the client, equivalent of "reactAcceptStreams"
|
|
283
|
-
// TODO: Replace this with correct logic catering for breakouts etc
|
|
284
|
-
|
|
285
|
-
// #region reactAcceptStreams
|
|
286
|
-
const isInSameRoomOrGroupOrClientBroadcasting = true; // TODO: Remove once breakout is implemented
|
|
287
|
-
|
|
288
|
-
if (isInSameRoomOrGroupOrClientBroadcasting) {
|
|
289
|
-
if (streamState !== "done_accept") {
|
|
290
|
-
newState = `${newJoiner && streamId === "0" ? "new" : "to"}_accept`;
|
|
291
|
-
}
|
|
292
|
-
} else if (myselfBroadcasting) {
|
|
293
|
-
if (streamState !== "done_accept") {
|
|
294
|
-
newState = `${newJoiner && streamId === "0" ? "done" : "old"}_accept`;
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
if (streamState !== "done_unaccept") {
|
|
298
|
-
newState = "to_unaccept";
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!newState) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// #endregion
|
|
307
|
-
|
|
308
|
-
// #region doAcceptStreams
|
|
309
|
-
if (
|
|
310
|
-
newState === "to_accept" ||
|
|
311
|
-
(newState === "new_accept" && shouldAcceptNewClients) ||
|
|
312
|
-
(newState === "old_accept" && !shouldAcceptNewClients)
|
|
313
|
-
) {
|
|
314
|
-
this.logger.log(`Accepting stream ${streamId} from ${participantId}`);
|
|
315
|
-
this.rtcManager?.acceptNewStream({
|
|
316
|
-
streamId: streamId === "0" ? participantId : streamId,
|
|
317
|
-
clientId: participantId,
|
|
318
|
-
shouldAddLocalVideo: streamId === "0",
|
|
319
|
-
activeBreakout,
|
|
320
|
-
});
|
|
321
|
-
} else if (newState === "new_accept" || newState === "old_accept") {
|
|
322
|
-
// do nothing - let this be marked as done_accept as the rtcManager
|
|
323
|
-
// will trigger accept from other end
|
|
324
|
-
} else if (newState === "to_unaccept") {
|
|
325
|
-
this.logger.log(`Disconnecting stream ${streamId} from ${participantId}`);
|
|
326
|
-
this.rtcManager?.disconnect(streamId === "0" ? participantId : streamId, activeBreakout);
|
|
327
|
-
} else if (newState !== "done_accept") {
|
|
328
|
-
this.logger.warn(`Stream state not handled: ${newState} for ${participantId}-${streamId}`);
|
|
329
|
-
return;
|
|
330
|
-
} else {
|
|
331
|
-
// done_accept
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Update stream state
|
|
335
|
-
participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_") as StreamState);
|
|
336
|
-
|
|
337
|
-
// #endregion
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private _handleStreamAdded({ clientId, stream, streamId }: RtcStreamAddedPayload) {
|
|
343
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
344
|
-
if (!remoteParticipant) {
|
|
345
|
-
this.logger.log("WARN: Could not find participant for incoming stream");
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
this.dispatchEvent(
|
|
350
|
-
new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } })
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Public API
|
|
356
|
-
*/
|
|
357
|
-
|
|
358
|
-
async join() {
|
|
359
|
-
if (["connected", "connecting"].includes(this.roomConnectionState)) {
|
|
360
|
-
console.warn(`Trying to join room state is ${this.roomConnectionState}`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
this.logger.log("Joining room");
|
|
365
|
-
this.roomConnectionState = "connecting";
|
|
366
|
-
|
|
367
|
-
if (!this.localStream && this.localMediaConstraints) {
|
|
368
|
-
const localStream = await navigator.mediaDevices.getUserMedia(this.localMediaConstraints);
|
|
369
|
-
this.localStream = localStream;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const organization = await this.organizationServiceCache.fetchOrganization();
|
|
373
|
-
if (!organization) {
|
|
374
|
-
throw new Error("Invalid room url");
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// TODO: Get room permissions
|
|
378
|
-
// TODO: Get room features
|
|
379
|
-
|
|
380
|
-
const webrtcProvider = {
|
|
381
|
-
getMediaConstraints: () => ({
|
|
382
|
-
audio: !!this.localStream?.getAudioTracks().find((t) => t.enabled),
|
|
383
|
-
video: !!this.localStream?.getVideoTracks().find((t) => t.enabled),
|
|
384
|
-
}),
|
|
385
|
-
deferrable(clientId: string) {
|
|
386
|
-
return !clientId;
|
|
387
|
-
},
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
this.rtcManagerDispatcher = new RtcManagerDispatcher({
|
|
391
|
-
emitter: {
|
|
392
|
-
emit: this._handleRtcEvent.bind(this),
|
|
393
|
-
},
|
|
394
|
-
serverSocket: this.signalSocket,
|
|
395
|
-
webrtcProvider,
|
|
396
|
-
features: {
|
|
397
|
-
lowDataModeEnabled: false,
|
|
398
|
-
sfuServerOverrideHost: undefined,
|
|
399
|
-
turnServerOverrideHost: undefined,
|
|
400
|
-
useOnlyTURN: undefined,
|
|
401
|
-
vp9On: false,
|
|
402
|
-
h264On: false,
|
|
403
|
-
simulcastScreenshareOn: false,
|
|
404
|
-
},
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// Identify device on signal connection
|
|
408
|
-
const deviceCredentials = await this.credentialsService.getCredentials();
|
|
409
|
-
|
|
410
|
-
// TODO: Handle connection and failed connection properly
|
|
411
|
-
setTimeout(() => {
|
|
412
|
-
this.logger.log("Connected to signal socket");
|
|
413
|
-
this.signalSocket.emit("identify_device", { deviceCredentials });
|
|
414
|
-
}, 2000);
|
|
415
|
-
|
|
416
|
-
this.signalSocket.once("device_identified", () => {
|
|
417
|
-
this.signalSocket.emit("join_room", {
|
|
418
|
-
avatarUrl: null,
|
|
419
|
-
config: {
|
|
420
|
-
isAudioEnabled: !!this.localStream?.getAudioTracks().find((t) => t.enabled),
|
|
421
|
-
isVideoEnabled: !!this.localStream?.getVideoTracks().find((t) => t.enabled),
|
|
422
|
-
},
|
|
423
|
-
deviceCapabilities: { canScreenshare: true },
|
|
424
|
-
displayName: this.displayName,
|
|
425
|
-
isCoLocated: false,
|
|
426
|
-
isDevicePermissionDenied: false,
|
|
427
|
-
kickFromOtherRooms: false,
|
|
428
|
-
organizationId: organization.organizationId,
|
|
429
|
-
roomKey: null,
|
|
430
|
-
roomName: this.roomUrl.pathname,
|
|
431
|
-
selfId: "",
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
this.signalSocket.once("room_joined", (res: SignalRoomJoinedEvent) => {
|
|
436
|
-
const {
|
|
437
|
-
selfId,
|
|
438
|
-
room: { clients },
|
|
439
|
-
} = res;
|
|
440
|
-
|
|
441
|
-
const localClient = clients.find((c) => c.id === selfId);
|
|
442
|
-
if (!localClient) throw new Error("Missing local client");
|
|
443
|
-
|
|
444
|
-
this.localParticipant = new LocalParticipant({ ...localClient, stream: this.localStream });
|
|
445
|
-
this.remoteParticipants = clients
|
|
446
|
-
.filter((c) => c.id !== selfId)
|
|
447
|
-
.map((c) => new RemoteParticipant({ ...c, newJoiner: false }));
|
|
448
|
-
|
|
449
|
-
// Accept remote streams if RTC manager has been initialized
|
|
450
|
-
if (this.rtcManager) {
|
|
451
|
-
this._handleAcceptStreams(this.remoteParticipants);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
this.roomConnectionState = "connected";
|
|
455
|
-
this.dispatchEvent(
|
|
456
|
-
new CustomEvent("room_joined", {
|
|
457
|
-
detail: {
|
|
458
|
-
localParticipant: this.localParticipant,
|
|
459
|
-
remoteParticipants: this.remoteParticipants,
|
|
460
|
-
},
|
|
461
|
-
})
|
|
462
|
-
);
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
leave(): Promise<void> {
|
|
467
|
-
return new Promise<void>((resolve) => {
|
|
468
|
-
if (!this.signalSocket) {
|
|
469
|
-
return resolve();
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
this.signalSocket.emit("leave_room");
|
|
473
|
-
const leaveTimeout = setTimeout(() => {
|
|
474
|
-
resolve();
|
|
475
|
-
}, 200);
|
|
476
|
-
this.signalSocket.once("room_left", () => {
|
|
477
|
-
clearTimeout(leaveTimeout);
|
|
478
|
-
this.signalSocket.disconnect();
|
|
479
|
-
resolve();
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
toggleCamera(enabled?: boolean): void {
|
|
485
|
-
const localVideoTrack = this.localStream?.getVideoTracks()[0];
|
|
486
|
-
if (!localVideoTrack) {
|
|
487
|
-
this.logger.log("Tried toggling non-existing video track");
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
// TODO: Do stopOrResumeVideo
|
|
491
|
-
const newValue = enabled ?? !localVideoTrack.enabled;
|
|
492
|
-
localVideoTrack.enabled = newValue;
|
|
493
|
-
this.signalSocket.emit("enable_video", { enabled: newValue });
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
toggleMicrophone(enabled?: boolean): void {
|
|
497
|
-
const localAudioTrack = this.localStream?.getAudioTracks()[0];
|
|
498
|
-
if (!localAudioTrack) {
|
|
499
|
-
this.logger.log("Tried toggling non-existing audio track");
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
// TODO: Do stopOrResumeAudio
|
|
503
|
-
const newValue = enabled ?? !localAudioTrack.enabled;
|
|
504
|
-
localAudioTrack.enabled = newValue;
|
|
505
|
-
this.signalSocket.emit("enable_audio", { enabled: newValue });
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
setDisplayName(displayName: string): void {
|
|
509
|
-
this.signalSocket.emit("send_client_metadata", {
|
|
510
|
-
type: "UserData",
|
|
511
|
-
payload: {
|
|
512
|
-
displayName,
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
interface RoomParticipantData {
|
|
2
|
-
displayName: string;
|
|
3
|
-
id: string;
|
|
4
|
-
stream?: MediaStream;
|
|
5
|
-
isAudioEnabled: boolean;
|
|
6
|
-
isVideoEnabled: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default class RoomParticipant {
|
|
10
|
-
public readonly displayName;
|
|
11
|
-
public readonly id;
|
|
12
|
-
public readonly stream?: MediaStream;
|
|
13
|
-
public readonly isAudioEnabled: boolean;
|
|
14
|
-
public readonly isLocalParticipant: boolean = false;
|
|
15
|
-
public readonly isVideoEnabled: boolean;
|
|
16
|
-
|
|
17
|
-
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }: RoomParticipantData) {
|
|
18
|
-
this.displayName = displayName;
|
|
19
|
-
this.id = id;
|
|
20
|
-
this.stream = stream;
|
|
21
|
-
this.isAudioEnabled = isAudioEnabled;
|
|
22
|
-
this.isVideoEnabled = isVideoEnabled;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface RemoteParticipantData {
|
|
27
|
-
newJoiner: boolean;
|
|
28
|
-
streams: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type StreamState =
|
|
32
|
-
| "new_accept"
|
|
33
|
-
| "to_accept"
|
|
34
|
-
| "old_accept"
|
|
35
|
-
| "done_accept"
|
|
36
|
-
| "to_unaccept"
|
|
37
|
-
| "done_unaccept"
|
|
38
|
-
| "auto";
|
|
39
|
-
|
|
40
|
-
interface Stream {
|
|
41
|
-
id: string;
|
|
42
|
-
state: StreamState;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export class RemoteParticipant extends RoomParticipant {
|
|
46
|
-
public readonly newJoiner: boolean;
|
|
47
|
-
public readonly streams: Stream[];
|
|
48
|
-
|
|
49
|
-
constructor({
|
|
50
|
-
displayName,
|
|
51
|
-
id,
|
|
52
|
-
newJoiner,
|
|
53
|
-
streams,
|
|
54
|
-
isAudioEnabled,
|
|
55
|
-
isVideoEnabled,
|
|
56
|
-
}: RoomParticipantData & RemoteParticipantData) {
|
|
57
|
-
super({ displayName, id, isAudioEnabled, isVideoEnabled });
|
|
58
|
-
this.newJoiner = newJoiner;
|
|
59
|
-
|
|
60
|
-
this.streams = streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" }));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
updateStreamState(streamId: string, state: StreamState) {
|
|
64
|
-
const stream = this.streams.find((s) => s.id === streamId);
|
|
65
|
-
if (stream) {
|
|
66
|
-
stream.state = state;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export class LocalParticipant extends RoomParticipant {
|
|
72
|
-
public readonly isLocalParticipant = true;
|
|
73
|
-
|
|
74
|
-
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }: RoomParticipantData) {
|
|
75
|
-
super({ displayName, id, stream, isAudioEnabled, isVideoEnabled });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { jest } from "@jest/globals";
|
|
2
|
-
|
|
3
|
-
const define = jest.fn();
|
|
4
|
-
const ref = jest.fn();
|
|
5
|
-
jest.mock("heresy", () => ({
|
|
6
|
-
__esModule: true,
|
|
7
|
-
define,
|
|
8
|
-
ref,
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
describe("@whereby/browser-sdk", () => {
|
|
12
|
-
describe("web component", () => {
|
|
13
|
-
it("should define <whereby-embed />", async () => {
|
|
14
|
-
await import("../embed");
|
|
15
|
-
expect(define).toBeCalledWith("WherebyEmbed", expect.any(Object));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("should expose attributes", async () => {
|
|
19
|
-
await import("../embed");
|
|
20
|
-
expect(define).toBeCalledWith(
|
|
21
|
-
expect.any(String),
|
|
22
|
-
expect.objectContaining({
|
|
23
|
-
observedAttributes: [
|
|
24
|
-
"displayname",
|
|
25
|
-
"minimal",
|
|
26
|
-
"room",
|
|
27
|
-
"subdomain",
|
|
28
|
-
"lang",
|
|
29
|
-
"metadata",
|
|
30
|
-
"groups",
|
|
31
|
-
"virtualbackgroundurl",
|
|
32
|
-
"avatarurl",
|
|
33
|
-
"audio",
|
|
34
|
-
"background",
|
|
35
|
-
"cameraaccess",
|
|
36
|
-
"chat",
|
|
37
|
-
"people",
|
|
38
|
-
"embed",
|
|
39
|
-
"emptyroominvitation",
|
|
40
|
-
"help",
|
|
41
|
-
"leavebutton",
|
|
42
|
-
"precallreview",
|
|
43
|
-
"screenshare",
|
|
44
|
-
"video",
|
|
45
|
-
"floatself",
|
|
46
|
-
"recording",
|
|
47
|
-
"logo",
|
|
48
|
-
"locking",
|
|
49
|
-
"participantcount",
|
|
50
|
-
"settingsbutton",
|
|
51
|
-
"pipbutton",
|
|
52
|
-
"morebutton",
|
|
53
|
-
"personality",
|
|
54
|
-
"subgridlabels",
|
|
55
|
-
"lowdata",
|
|
56
|
-
"breakout",
|
|
57
|
-
],
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should expose commands", async () => {
|
|
63
|
-
await import("../embed");
|
|
64
|
-
|
|
65
|
-
expect(define).toBeCalledWith(
|
|
66
|
-
expect.any(String),
|
|
67
|
-
expect.objectContaining({
|
|
68
|
-
startRecording: expect.any(Function),
|
|
69
|
-
stopRecording: expect.any(Function),
|
|
70
|
-
toggleCamera: expect.any(Function),
|
|
71
|
-
toggleMicrophone: expect.any(Function),
|
|
72
|
-
toggleScreenshare: expect.any(Function),
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
});
|