@fishjam-cloud/ts-client 0.25.1 → 0.25.2
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/index.d.mts +10 -0
- package/dist/index.mjs +1 -1
- package/dist/index.react-native.mjs +17 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.d.ts +130 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.js +1283 -0
- package/dist/protobufs/fishjam/media_events/server/server.d.ts +197 -0
- package/dist/protobufs/fishjam/media_events/server/server.js +2121 -0
- package/dist/protobufs/fishjam/media_events/shared.d.ts +48 -0
- package/dist/protobufs/fishjam/media_events/shared.js +315 -0
- package/dist/protobufs/fishjam/peer_notifications.d.ts +83 -0
- package/dist/protobufs/fishjam/peer_notifications.js +559 -0
- package/dist/ts-client/package.json +82 -0
- package/dist/ts-client/src/FishjamClient.d.ts +438 -0
- package/dist/ts-client/src/FishjamClient.js +821 -0
- package/dist/ts-client/src/auth.d.ts +3 -0
- package/dist/ts-client/src/auth.js +8 -0
- package/dist/ts-client/src/connectEventsHandler.d.ts +2 -0
- package/dist/ts-client/src/connectEventsHandler.js +22 -0
- package/dist/ts-client/src/errors.d.ts +3 -0
- package/dist/ts-client/src/errors.js +5 -0
- package/dist/ts-client/src/guards.d.ts +7 -0
- package/dist/ts-client/src/guards.js +8 -0
- package/dist/ts-client/src/index.d.ts +9 -0
- package/dist/ts-client/src/index.js +6 -0
- package/dist/ts-client/src/livestream.d.ts +20 -0
- package/dist/ts-client/src/livestream.js +78 -0
- package/dist/ts-client/src/messageQueue.d.ts +13 -0
- package/dist/ts-client/src/messageQueue.js +23 -0
- package/dist/ts-client/src/reconnection.d.ts +27 -0
- package/dist/ts-client/src/reconnection.js +124 -0
- package/dist/ts-client/src/tests/messageQueue.test.d.ts +1 -0
- package/dist/ts-client/src/tests/messageQueue.test.js +54 -0
- package/dist/ts-client/src/types.d.ts +216 -0
- package/dist/ts-client/src/types.js +1 -0
- package/dist/ts-client/src/version.d.ts +1 -0
- package/dist/ts-client/src/version.js +2 -0
- package/package.json +18 -11
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const AUTH_ERROR_REASONS: readonly ["missing token", "invalid token", "expired token", "room not found", "peer not found"];
|
|
2
|
+
export type AuthErrorReason = (typeof AUTH_ERROR_REASONS)[number];
|
|
3
|
+
export declare const isAuthError: (error: string) => error is AuthErrorReason;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function connectEventsHandler(fishjamClient) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const onSuccess = () => {
|
|
4
|
+
clearCallbacks();
|
|
5
|
+
resolve();
|
|
6
|
+
};
|
|
7
|
+
const onError = () => {
|
|
8
|
+
clearCallbacks();
|
|
9
|
+
reject();
|
|
10
|
+
};
|
|
11
|
+
fishjamClient.on('joined', onSuccess);
|
|
12
|
+
fishjamClient.on('joinError', onError);
|
|
13
|
+
fishjamClient.on('authError', onError);
|
|
14
|
+
fishjamClient.on('socketError', onError);
|
|
15
|
+
const clearCallbacks = () => {
|
|
16
|
+
fishjamClient.removeListener('joined', onSuccess);
|
|
17
|
+
fishjamClient.removeListener('joinError', onError);
|
|
18
|
+
fishjamClient.removeListener('authError', onError);
|
|
19
|
+
fishjamClient.removeListener('socketError', onError);
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Endpoint } from '@fishjam-cloud/webrtc-client';
|
|
2
|
+
import type { Component, Peer } from './types';
|
|
3
|
+
export declare const isPeer: <PeerMetadata, TrackMetadata>(endpoint: Endpoint) => endpoint is Peer<PeerMetadata, TrackMetadata>;
|
|
4
|
+
export declare const isComponent: (endpoint: Endpoint) => endpoint is Component;
|
|
5
|
+
export declare const JOIN_ERRORS: readonly ["reached peers limit", "room not found", "node not found", "Invalid SDK version"];
|
|
6
|
+
export type JoinErrorReason = (typeof JOIN_ERRORS)[number];
|
|
7
|
+
export declare const isJoinError: (error: string) => error is JoinErrorReason;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const isPeer = (endpoint) => endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc';
|
|
2
|
+
export const isComponent = (endpoint) => endpoint.type === 'recording' ||
|
|
3
|
+
endpoint.type === 'hls' ||
|
|
4
|
+
endpoint.type === 'file' ||
|
|
5
|
+
endpoint.type === 'rtsp' ||
|
|
6
|
+
endpoint.type === 'sip';
|
|
7
|
+
export const JOIN_ERRORS = ['reached peers limit', 'room not found', 'node not found', 'Invalid SDK version'];
|
|
8
|
+
export const isJoinError = (error) => JOIN_ERRORS.some((knownError) => error.trim().toLowerCase().includes(knownError.trim().toLowerCase()));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AUTH_ERROR_REASONS, type AuthErrorReason, isAuthError } from './auth';
|
|
2
|
+
export { TrackTypeError } from './errors';
|
|
3
|
+
export { FishjamClient } from './FishjamClient';
|
|
4
|
+
export { isJoinError, JOIN_ERRORS, type JoinErrorReason } from './guards';
|
|
5
|
+
export { type LivestreamCallbacks, LivestreamError, publishLivestream, type PublishLivestreamResult, receiveLivestream, type ReceiveLivestreamResult, } from './livestream';
|
|
6
|
+
export type { ReconnectConfig, ReconnectionStatus } from './reconnection';
|
|
7
|
+
export type { ClientType, Component, ConnectConfig, CreateConfig, FishjamTrackContext, GenericMetadata, MessageEvents, Metadata, Peer, TrackContextEvents, TrackMetadata, } from './types';
|
|
8
|
+
export type { BandwidthLimit, DataCallback, DataChannelOptions, EncodingReason, Endpoint, Logger, SimulcastBandwidthLimit, SimulcastConfig, TrackBandwidthLimit, TrackContext, VadStatus, WebRTCEndpointEvents, } from '@fishjam-cloud/webrtc-client';
|
|
9
|
+
export * from '@fishjam-cloud/webrtc-client';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AUTH_ERROR_REASONS, isAuthError } from './auth';
|
|
2
|
+
export { TrackTypeError } from './errors';
|
|
3
|
+
export { FishjamClient } from './FishjamClient';
|
|
4
|
+
export { isJoinError, JOIN_ERRORS } from './guards';
|
|
5
|
+
export { LivestreamError, publishLivestream, receiveLivestream, } from './livestream';
|
|
6
|
+
export * from '@fishjam-cloud/webrtc-client';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ReceiveLivestreamResult = {
|
|
2
|
+
stream: MediaStream;
|
|
3
|
+
stop: () => Promise<void>;
|
|
4
|
+
getStatistics: () => Promise<RTCStatsReport>;
|
|
5
|
+
};
|
|
6
|
+
export type PublishLivestreamResult = {
|
|
7
|
+
stopPublishing: () => Promise<void>;
|
|
8
|
+
getStatistics: () => Promise<RTCStatsReport>;
|
|
9
|
+
};
|
|
10
|
+
export declare enum LivestreamError {
|
|
11
|
+
UNAUTHORIZED = "unauthorized",
|
|
12
|
+
STREAM_NOT_FOUND = "stream_not_found",
|
|
13
|
+
UNKNOWN_ERROR = "unknown_error",
|
|
14
|
+
STREAMER_ALREADY_CONNECTED = "streamer_already_connected"
|
|
15
|
+
}
|
|
16
|
+
export type LivestreamCallbacks = {
|
|
17
|
+
onConnectionStateChange?: (pc: RTCPeerConnection) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function receiveLivestream(url: string, token?: string, callbacks?: LivestreamCallbacks): Promise<ReceiveLivestreamResult>;
|
|
20
|
+
export declare function publishLivestream(stream: MediaStream, url: string, token: string, callbacks?: LivestreamCallbacks): Promise<PublishLivestreamResult>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export var LivestreamError;
|
|
2
|
+
(function (LivestreamError) {
|
|
3
|
+
LivestreamError["UNAUTHORIZED"] = "unauthorized";
|
|
4
|
+
LivestreamError["STREAM_NOT_FOUND"] = "stream_not_found";
|
|
5
|
+
LivestreamError["UNKNOWN_ERROR"] = "unknown_error";
|
|
6
|
+
LivestreamError["STREAMER_ALREADY_CONNECTED"] = "streamer_already_connected";
|
|
7
|
+
})(LivestreamError || (LivestreamError = {}));
|
|
8
|
+
export async function receiveLivestream(url, token, callbacks) {
|
|
9
|
+
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
|
10
|
+
pc.addTransceiver('video', { direction: 'recvonly' });
|
|
11
|
+
pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
12
|
+
pc.onconnectionstatechange = (_ev) => callbacks?.onConnectionStateChange?.(pc);
|
|
13
|
+
const { WHEPClient } = await import('@binbat/whip-whep/whep');
|
|
14
|
+
const whep = new WHEPClient();
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
pc.ontrack = (event) => {
|
|
17
|
+
const stream = event.streams[0];
|
|
18
|
+
if (stream) {
|
|
19
|
+
resolve({
|
|
20
|
+
getStatistics: () => pc.getStats(),
|
|
21
|
+
stream,
|
|
22
|
+
stop: async () => {
|
|
23
|
+
await whep.stop();
|
|
24
|
+
callbacks?.onConnectionStateChange?.(pc);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
whep.view(pc, url, token).catch((e) => {
|
|
30
|
+
if (e instanceof Error) {
|
|
31
|
+
let error = LivestreamError.UNKNOWN_ERROR;
|
|
32
|
+
if (e.message.includes('401')) {
|
|
33
|
+
error = LivestreamError.UNAUTHORIZED;
|
|
34
|
+
}
|
|
35
|
+
else if (e.message.includes('404')) {
|
|
36
|
+
error = LivestreamError.STREAM_NOT_FOUND;
|
|
37
|
+
}
|
|
38
|
+
reject(error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export async function publishLivestream(stream, url, token, callbacks) {
|
|
44
|
+
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
|
45
|
+
pc.onconnectionstatechange = (_ev) => callbacks?.onConnectionStateChange?.(pc);
|
|
46
|
+
const video = stream.getVideoTracks().at(0);
|
|
47
|
+
const audio = stream.getAudioTracks().at(0);
|
|
48
|
+
if (!video && !audio) {
|
|
49
|
+
throw Error('To publish a livestream with WHIP, you need to supply at least one video or audio track.');
|
|
50
|
+
}
|
|
51
|
+
if (video)
|
|
52
|
+
pc.addTransceiver(video, { direction: 'sendonly' });
|
|
53
|
+
if (audio)
|
|
54
|
+
pc.addTransceiver(audio, { direction: 'sendonly' });
|
|
55
|
+
const { WHIPClient } = await import('@binbat/whip-whep/whip');
|
|
56
|
+
const whip = new WHIPClient();
|
|
57
|
+
try {
|
|
58
|
+
await whip.publish(pc, url, token);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
if (e instanceof Error) {
|
|
62
|
+
if (e.message.includes('401'))
|
|
63
|
+
throw LivestreamError.UNAUTHORIZED;
|
|
64
|
+
if (e.message.includes('404'))
|
|
65
|
+
throw LivestreamError.STREAM_NOT_FOUND;
|
|
66
|
+
if (e.message.includes('409'))
|
|
67
|
+
throw LivestreamError.STREAMER_ALREADY_CONNECTED;
|
|
68
|
+
}
|
|
69
|
+
throw LivestreamError.UNKNOWN_ERROR;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
getStatistics: () => pc.getStats(),
|
|
73
|
+
stopPublishing: async () => {
|
|
74
|
+
await whip.stop();
|
|
75
|
+
callbacks?.onConnectionStateChange?.(pc);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type MessageQueueParams = {
|
|
2
|
+
checkIsReconnecting: () => boolean;
|
|
3
|
+
sendMessage: (message: Uint8Array) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare class MessageQueue {
|
|
6
|
+
private queuedMessages;
|
|
7
|
+
private checkIsReconnecting;
|
|
8
|
+
private sendMessage;
|
|
9
|
+
constructor({ checkIsReconnecting, sendMessage }: MessageQueueParams);
|
|
10
|
+
enqueueMessage(mediaEvent: Uint8Array): void;
|
|
11
|
+
attemptToSendAll(): void;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class MessageQueue {
|
|
2
|
+
queuedMessages = [];
|
|
3
|
+
checkIsReconnecting;
|
|
4
|
+
sendMessage;
|
|
5
|
+
constructor({ checkIsReconnecting, sendMessage }) {
|
|
6
|
+
this.checkIsReconnecting = checkIsReconnecting;
|
|
7
|
+
this.sendMessage = sendMessage;
|
|
8
|
+
}
|
|
9
|
+
enqueueMessage(mediaEvent) {
|
|
10
|
+
this.queuedMessages.push(mediaEvent);
|
|
11
|
+
this.attemptToSendAll();
|
|
12
|
+
}
|
|
13
|
+
attemptToSendAll() {
|
|
14
|
+
const isReconnecting = this.checkIsReconnecting();
|
|
15
|
+
if (isReconnecting)
|
|
16
|
+
return;
|
|
17
|
+
while (this.queuedMessages.length > 0) {
|
|
18
|
+
const oldestMessage = this.queuedMessages.shift();
|
|
19
|
+
if (oldestMessage)
|
|
20
|
+
this.sendMessage(oldestMessage);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FishjamClient } from './FishjamClient';
|
|
2
|
+
export type ReconnectionStatus = 'reconnecting' | 'idle' | 'error';
|
|
3
|
+
export type ReconnectConfig = {
|
|
4
|
+
maxAttempts?: number;
|
|
5
|
+
initialDelay?: number;
|
|
6
|
+
delay?: number;
|
|
7
|
+
addTracksOnReconnect?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare class ReconnectManager<PeerMetadata, ServerMetadata> {
|
|
10
|
+
private readonly reconnectConfig;
|
|
11
|
+
private readonly connect;
|
|
12
|
+
private readonly client;
|
|
13
|
+
private initialPeerMetadata;
|
|
14
|
+
private reconnectAttempt;
|
|
15
|
+
private reconnectTimeoutId;
|
|
16
|
+
private status;
|
|
17
|
+
private lastLocalEndpoint;
|
|
18
|
+
private removeEventListeners;
|
|
19
|
+
constructor(client: FishjamClient<PeerMetadata, ServerMetadata>, connect: (metadata: PeerMetadata) => Promise<void>, config?: ReconnectConfig | boolean);
|
|
20
|
+
isReconnecting(): boolean;
|
|
21
|
+
reset(initialPeerMetadata: PeerMetadata): void;
|
|
22
|
+
private getLastPeerMetadata;
|
|
23
|
+
private reconnect;
|
|
24
|
+
handleReconnect(): Promise<void>;
|
|
25
|
+
cleanup(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare const createReconnectConfig: (config?: ReconnectConfig | boolean) => Required<ReconnectConfig>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { isAuthError } from './auth';
|
|
2
|
+
import { isJoinError } from './guards';
|
|
3
|
+
const DISABLED_RECONNECT_CONFIG = {
|
|
4
|
+
maxAttempts: 0,
|
|
5
|
+
initialDelay: 0,
|
|
6
|
+
delay: 0,
|
|
7
|
+
addTracksOnReconnect: false,
|
|
8
|
+
};
|
|
9
|
+
const DEFAULT_RECONNECT_CONFIG = {
|
|
10
|
+
maxAttempts: 3,
|
|
11
|
+
initialDelay: 500,
|
|
12
|
+
delay: 500,
|
|
13
|
+
addTracksOnReconnect: false,
|
|
14
|
+
};
|
|
15
|
+
export class ReconnectManager {
|
|
16
|
+
reconnectConfig;
|
|
17
|
+
connect;
|
|
18
|
+
client;
|
|
19
|
+
initialPeerMetadata = undefined;
|
|
20
|
+
reconnectAttempt = 0;
|
|
21
|
+
reconnectTimeoutId = null;
|
|
22
|
+
status = 'idle';
|
|
23
|
+
lastLocalEndpoint = null;
|
|
24
|
+
removeEventListeners = () => { };
|
|
25
|
+
constructor(client, connect, config) {
|
|
26
|
+
this.client = client;
|
|
27
|
+
this.connect = connect;
|
|
28
|
+
this.reconnectConfig = createReconnectConfig(config);
|
|
29
|
+
const onSocketError = () => {
|
|
30
|
+
this.reconnect();
|
|
31
|
+
};
|
|
32
|
+
this.client.on('socketError', onSocketError);
|
|
33
|
+
const onConnectionError = () => {
|
|
34
|
+
this.reconnect();
|
|
35
|
+
};
|
|
36
|
+
this.client.on('connectionError', onConnectionError);
|
|
37
|
+
const onSocketClose = (event) => {
|
|
38
|
+
if (isAuthError(event.reason))
|
|
39
|
+
return;
|
|
40
|
+
if (isJoinError(event.reason))
|
|
41
|
+
return;
|
|
42
|
+
this.reconnect();
|
|
43
|
+
};
|
|
44
|
+
this.client.on('socketClose', onSocketClose);
|
|
45
|
+
const onAuthSuccess = () => {
|
|
46
|
+
this.reset(this.initialPeerMetadata);
|
|
47
|
+
};
|
|
48
|
+
this.client.on('authSuccess', onAuthSuccess);
|
|
49
|
+
this.removeEventListeners = () => {
|
|
50
|
+
this.client.off('socketError', onSocketError);
|
|
51
|
+
this.client.off('connectionError', onConnectionError);
|
|
52
|
+
this.client.off('socketClose', onSocketClose);
|
|
53
|
+
this.client.off('authSuccess', onAuthSuccess);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
isReconnecting() {
|
|
57
|
+
return this.status === 'reconnecting';
|
|
58
|
+
}
|
|
59
|
+
reset(initialPeerMetadata) {
|
|
60
|
+
this.initialPeerMetadata = initialPeerMetadata;
|
|
61
|
+
this.reconnectAttempt = 0;
|
|
62
|
+
if (this.reconnectTimeoutId)
|
|
63
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
64
|
+
this.reconnectTimeoutId = null;
|
|
65
|
+
}
|
|
66
|
+
getLastPeerMetadata() {
|
|
67
|
+
const endpointMetadata = this.lastLocalEndpoint?.metadata;
|
|
68
|
+
return endpointMetadata?.peer;
|
|
69
|
+
}
|
|
70
|
+
reconnect() {
|
|
71
|
+
if (this.reconnectTimeoutId)
|
|
72
|
+
return;
|
|
73
|
+
if (this.reconnectAttempt >= this.reconnectConfig.maxAttempts) {
|
|
74
|
+
if (this.status === 'reconnecting') {
|
|
75
|
+
this.status = 'error';
|
|
76
|
+
this.client.emit('reconnectionRetriesLimitReached');
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this.status !== 'reconnecting') {
|
|
81
|
+
this.status = 'reconnecting';
|
|
82
|
+
this.client.emit('reconnectionStarted');
|
|
83
|
+
this.lastLocalEndpoint = this.client.getLocalPeer() || null;
|
|
84
|
+
}
|
|
85
|
+
const timeout = this.reconnectConfig.initialDelay + this.reconnectAttempt * this.reconnectConfig.delay;
|
|
86
|
+
this.reconnectAttempt += 1;
|
|
87
|
+
this.reconnectTimeoutId = setTimeout(() => {
|
|
88
|
+
this.reconnectTimeoutId = null;
|
|
89
|
+
const peerMetadata = this.getLastPeerMetadata() ?? this.initialPeerMetadata;
|
|
90
|
+
this.connect(peerMetadata);
|
|
91
|
+
}, timeout);
|
|
92
|
+
}
|
|
93
|
+
async handleReconnect() {
|
|
94
|
+
if (this.status !== 'reconnecting')
|
|
95
|
+
return;
|
|
96
|
+
if (this.lastLocalEndpoint && this.reconnectConfig.addTracksOnReconnect) {
|
|
97
|
+
for await (const element of this.lastLocalEndpoint.tracks) {
|
|
98
|
+
const [_, track] = element;
|
|
99
|
+
if (!track.track || track.track.readyState !== 'live')
|
|
100
|
+
return;
|
|
101
|
+
await this.client.addTrack(track.track, track.metadata, track.simulcastConfig, track.maxBandwidth);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this.lastLocalEndpoint = null;
|
|
105
|
+
this.status = 'idle';
|
|
106
|
+
this.client.emit('reconnected');
|
|
107
|
+
}
|
|
108
|
+
cleanup() {
|
|
109
|
+
this.removeEventListeners();
|
|
110
|
+
this.removeEventListeners = () => { };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export const createReconnectConfig = (config) => {
|
|
114
|
+
if (!config)
|
|
115
|
+
return DISABLED_RECONNECT_CONFIG;
|
|
116
|
+
if (config === true)
|
|
117
|
+
return DEFAULT_RECONNECT_CONFIG;
|
|
118
|
+
return {
|
|
119
|
+
maxAttempts: config?.maxAttempts ?? DEFAULT_RECONNECT_CONFIG.maxAttempts,
|
|
120
|
+
initialDelay: config?.initialDelay ?? DEFAULT_RECONNECT_CONFIG.initialDelay,
|
|
121
|
+
delay: config?.delay ?? DEFAULT_RECONNECT_CONFIG.delay,
|
|
122
|
+
addTracksOnReconnect: config?.addTracksOnReconnect ?? DEFAULT_RECONNECT_CONFIG.addTracksOnReconnect,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { MessageQueue } from '../messageQueue';
|
|
3
|
+
describe('Message queue tests', () => {
|
|
4
|
+
let isReconnecting = false;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
isReconnecting = false;
|
|
7
|
+
});
|
|
8
|
+
const getQueueWithUtils = () => {
|
|
9
|
+
const sendMessage = vi.fn();
|
|
10
|
+
const queue = new MessageQueue({ sendMessage, checkIsReconnecting: () => isReconnecting });
|
|
11
|
+
const message1 = new Uint8Array([1, 2, 3]);
|
|
12
|
+
const message2 = new Uint8Array([4, 5, 6]);
|
|
13
|
+
return { sendMessage, queue, message1, message2 };
|
|
14
|
+
};
|
|
15
|
+
test('Queue dispatches messages while not reconnecting', () => {
|
|
16
|
+
const { queue, sendMessage, message1, message2 } = getQueueWithUtils();
|
|
17
|
+
queue.enqueueMessage(message1);
|
|
18
|
+
queue.enqueueMessage(message2);
|
|
19
|
+
expect(sendMessage).toHaveBeenCalledWith(message1);
|
|
20
|
+
expect(sendMessage).toHaveBeenCalledWith(message2);
|
|
21
|
+
});
|
|
22
|
+
test('Queue holds messages while reconnecting', () => {
|
|
23
|
+
const { queue, sendMessage, message1, message2 } = getQueueWithUtils();
|
|
24
|
+
isReconnecting = true;
|
|
25
|
+
queue.enqueueMessage(message1);
|
|
26
|
+
queue.enqueueMessage(message2);
|
|
27
|
+
expect(sendMessage).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
test('Queue dispatches all messages upon calling attemptToSendOut', async () => {
|
|
30
|
+
const { queue, sendMessage, message1, message2 } = getQueueWithUtils();
|
|
31
|
+
isReconnecting = true;
|
|
32
|
+
queue.enqueueMessage(message1);
|
|
33
|
+
queue.enqueueMessage(message2);
|
|
34
|
+
isReconnecting = false;
|
|
35
|
+
// attemptToSendOut not called yet
|
|
36
|
+
expect(sendMessage).not.toHaveBeenCalled();
|
|
37
|
+
queue.attemptToSendAll();
|
|
38
|
+
// all messages dispatched
|
|
39
|
+
expect(sendMessage).toHaveBeenCalledTimes(2);
|
|
40
|
+
});
|
|
41
|
+
test('Queue dispatches all messages upon enqueuing another event', async () => {
|
|
42
|
+
const { queue, sendMessage, message1, message2 } = getQueueWithUtils();
|
|
43
|
+
isReconnecting = true;
|
|
44
|
+
queue.enqueueMessage(message1);
|
|
45
|
+
queue.enqueueMessage(message2);
|
|
46
|
+
isReconnecting = false;
|
|
47
|
+
// no trigger to send out yet
|
|
48
|
+
expect(sendMessage).not.toHaveBeenCalled();
|
|
49
|
+
const anotherMessage = new Uint8Array([7, 8, 9]);
|
|
50
|
+
queue.enqueueMessage(anotherMessage);
|
|
51
|
+
// all messages dispatched
|
|
52
|
+
expect(sendMessage).toHaveBeenCalledTimes(3);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { EncodingReason, Endpoint, SimulcastConfig, TrackBandwidthLimit, VadStatus, Variant, WebRTCEndpointEvents } from '@fishjam-cloud/webrtc-client';
|
|
2
|
+
import type TypedEmitter from 'typed-emitter';
|
|
3
|
+
import type { AuthErrorReason } from './auth';
|
|
4
|
+
import type { JoinErrorReason } from './guards';
|
|
5
|
+
import type { ReconnectConfig } from './reconnection';
|
|
6
|
+
/**
|
|
7
|
+
* Metadata attached to a track published by a peer.
|
|
8
|
+
* Sent over the signaling channel so other peers know what kind of track they're receiving.
|
|
9
|
+
* @category Tracks
|
|
10
|
+
*/
|
|
11
|
+
export type TrackMetadata = {
|
|
12
|
+
/** The kind of media this track carries. */
|
|
13
|
+
type: 'camera' | 'microphone' | 'screenShareVideo' | 'screenShareAudio' | 'customVideo' | 'customAudio';
|
|
14
|
+
/** Whether the track is currently muted/disabled. */
|
|
15
|
+
paused: boolean;
|
|
16
|
+
/** The peer's display name, used in recordings. */
|
|
17
|
+
displayName?: string;
|
|
18
|
+
};
|
|
19
|
+
export type GenericMetadata = Record<string, unknown> | undefined;
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @category Connection
|
|
23
|
+
* @typeParam PeerMetadata Type of metadata set by peer while connecting to a room.
|
|
24
|
+
* @typeParam ServerMetadata Type of metadata set by the server while creating a peer.
|
|
25
|
+
*/
|
|
26
|
+
export type Metadata<PeerMetadata = GenericMetadata, ServerMetadata = GenericMetadata> = {
|
|
27
|
+
peer: PeerMetadata;
|
|
28
|
+
server: ServerMetadata;
|
|
29
|
+
};
|
|
30
|
+
export type TrackContextEvents = {
|
|
31
|
+
encodingChanged: (context: FishjamTrackContext) => void;
|
|
32
|
+
voiceActivityChanged: (context: FishjamTrackContext) => void;
|
|
33
|
+
};
|
|
34
|
+
export interface FishjamTrackContext extends TypedEmitter<TrackContextEvents> {
|
|
35
|
+
readonly track: MediaStreamTrack | null;
|
|
36
|
+
readonly stream: MediaStream | null;
|
|
37
|
+
readonly endpoint: Endpoint;
|
|
38
|
+
readonly trackId: string;
|
|
39
|
+
readonly simulcastConfig?: SimulcastConfig;
|
|
40
|
+
readonly metadata?: TrackMetadata;
|
|
41
|
+
readonly maxBandwidth?: TrackBandwidthLimit;
|
|
42
|
+
readonly vadStatus: VadStatus;
|
|
43
|
+
readonly encoding?: Variant;
|
|
44
|
+
readonly encodingReason?: EncodingReason;
|
|
45
|
+
}
|
|
46
|
+
export type Peer<PeerMetadata = GenericMetadata, ServerMetadata = GenericMetadata> = {
|
|
47
|
+
id: string;
|
|
48
|
+
type: string;
|
|
49
|
+
metadata?: Metadata<PeerMetadata, ServerMetadata>;
|
|
50
|
+
tracks: Map<string, FishjamTrackContext>;
|
|
51
|
+
};
|
|
52
|
+
export type Component = Omit<Endpoint, 'type'> & {
|
|
53
|
+
type: 'recording' | 'hls' | 'file' | 'rtsp' | 'sip';
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Events emitted by the client with their arguments.
|
|
57
|
+
*/
|
|
58
|
+
export type MessageEvents<P, S> = {
|
|
59
|
+
/**
|
|
60
|
+
* Emitted when connect method invoked
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
connectionStarted: () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Emitted when the websocket connection is closed
|
|
66
|
+
*
|
|
67
|
+
* @param {CloseEvent} event - Close event object from the websocket
|
|
68
|
+
*/
|
|
69
|
+
socketClose: (event: CloseEvent) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Emitted when occurs an error in the websocket connection
|
|
72
|
+
*
|
|
73
|
+
* @param {Event} event - Event object from the websocket
|
|
74
|
+
*/
|
|
75
|
+
socketError: (event: Event) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Emitted when the websocket connection is opened
|
|
78
|
+
*
|
|
79
|
+
* @param {Event} event - Event object from the websocket
|
|
80
|
+
*/
|
|
81
|
+
socketOpen: (event: Event) => void;
|
|
82
|
+
/** Emitted when authentication is successful */
|
|
83
|
+
authSuccess: () => void;
|
|
84
|
+
/** Emitted when authentication fails */
|
|
85
|
+
authError: (reason: AuthErrorReason) => void;
|
|
86
|
+
/** Emitted when the connection is closed */
|
|
87
|
+
disconnected: () => void;
|
|
88
|
+
/** Emitted when the process of reconnection starts */
|
|
89
|
+
reconnectionStarted: () => void;
|
|
90
|
+
/** Emitted on successful reconnection */
|
|
91
|
+
reconnected: () => void;
|
|
92
|
+
/** Emitted when the maximum number of reconnection retries is reached */
|
|
93
|
+
reconnectionRetriesLimitReached: () => void;
|
|
94
|
+
/**
|
|
95
|
+
* Called when peer was accepted.
|
|
96
|
+
*/
|
|
97
|
+
joined: (peerId: string, peers: Peer<P, S>[], components: Component[]) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Called when peer was not accepted
|
|
100
|
+
* @param metadata - Pass through for client application to communicate further actions to frontend
|
|
101
|
+
*/
|
|
102
|
+
joinError: (metadata: JoinErrorReason | unknown) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Called when data in a new track arrives.
|
|
105
|
+
*
|
|
106
|
+
* This callback is always called after {@link MessageEvents.trackAdded}.
|
|
107
|
+
* It informs user that data related to the given track arrives and can be played or displayed.
|
|
108
|
+
*/
|
|
109
|
+
trackReady: (ctx: FishjamTrackContext) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Called each time the peer which was already in the room, adds new track. Fields track and stream will be set to null.
|
|
112
|
+
* These fields will be set to non-null value in {@link MessageEvents.trackReady}
|
|
113
|
+
*/
|
|
114
|
+
trackAdded: (ctx: FishjamTrackContext) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Called when some track will no longer be sent.
|
|
117
|
+
*
|
|
118
|
+
* It will also be called before {@link MessageEvents.peerLeft} for each track of this peer.
|
|
119
|
+
*/
|
|
120
|
+
trackRemoved: (ctx: FishjamTrackContext) => void;
|
|
121
|
+
/**
|
|
122
|
+
* Called each time peer has its track metadata updated.
|
|
123
|
+
*/
|
|
124
|
+
trackUpdated: (ctx: FishjamTrackContext) => void;
|
|
125
|
+
/**
|
|
126
|
+
* Called each time new peer joins the room.
|
|
127
|
+
*/
|
|
128
|
+
peerJoined: (peer: Peer<P, S>) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Called each time peer leaves the room.
|
|
131
|
+
*/
|
|
132
|
+
peerLeft: (peer: Peer<P, S>) => void;
|
|
133
|
+
/**
|
|
134
|
+
* Called each time peer has its metadata updated.
|
|
135
|
+
*/
|
|
136
|
+
peerUpdated: (peer: Peer<P, S>) => void;
|
|
137
|
+
/**
|
|
138
|
+
* Called each time new peer joins the room.
|
|
139
|
+
*/
|
|
140
|
+
componentAdded: (peer: Component) => void;
|
|
141
|
+
/**
|
|
142
|
+
* Called each time peer leaves the room.
|
|
143
|
+
*/
|
|
144
|
+
componentRemoved: (peer: Component) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Called each time peer has its metadata updated.
|
|
147
|
+
*/
|
|
148
|
+
componentUpdated: (peer: Component) => void;
|
|
149
|
+
/**
|
|
150
|
+
* Called in case of errors related to multimedia session e.g. ICE connection.
|
|
151
|
+
*/
|
|
152
|
+
connectionError: (error: {
|
|
153
|
+
message: string;
|
|
154
|
+
event?: Event;
|
|
155
|
+
}) => void;
|
|
156
|
+
/**
|
|
157
|
+
* Currently, this callback is only invoked when DisplayManager in RTC Engine is
|
|
158
|
+
* enabled and simulcast is disabled.
|
|
159
|
+
*
|
|
160
|
+
* Called when priority of video tracks have changed.
|
|
161
|
+
* @param enabledTracks - list of tracks which will be sent to client from SFU
|
|
162
|
+
* @param disabledTracks - list of tracks which will not be sent to client from SFU
|
|
163
|
+
*/
|
|
164
|
+
tracksPriorityChanged: (enabledTracks: FishjamTrackContext[], disabledTracks: FishjamTrackContext[]) => void;
|
|
165
|
+
/**
|
|
166
|
+
* Called every time the server estimates client's bandwidth.
|
|
167
|
+
*
|
|
168
|
+
* @param {bigint} estimation - client's available incoming bitrate estimated
|
|
169
|
+
* by the server. It's measured in bits per second.
|
|
170
|
+
*/
|
|
171
|
+
bandwidthEstimationChanged: (estimation: bigint) => void;
|
|
172
|
+
targetTrackEncodingRequested: (event: Parameters<WebRTCEndpointEvents['targetTrackEncodingRequested']>[0]) => void;
|
|
173
|
+
localTrackAdded: (event: Parameters<WebRTCEndpointEvents['localTrackAdded']>[0]) => void;
|
|
174
|
+
localTrackRemoved: (event: Parameters<WebRTCEndpointEvents['localTrackRemoved']>[0]) => void;
|
|
175
|
+
localTrackReplaced: (event: Parameters<WebRTCEndpointEvents['localTrackReplaced']>[0]) => void;
|
|
176
|
+
localTrackMuted: (event: Parameters<WebRTCEndpointEvents['localTrackMuted']>[0]) => void;
|
|
177
|
+
localTrackUnmuted: (event: Parameters<WebRTCEndpointEvents['localTrackUnmuted']>[0]) => void;
|
|
178
|
+
localTrackBandwidthSet: (event: Parameters<WebRTCEndpointEvents['localTrackBandwidthSet']>[0]) => void;
|
|
179
|
+
localTrackEncodingBandwidthSet: (event: Parameters<WebRTCEndpointEvents['localTrackEncodingBandwidthSet']>[0]) => void;
|
|
180
|
+
localTrackEncodingEnabled: (event: Parameters<WebRTCEndpointEvents['localTrackEncodingEnabled']>[0]) => void;
|
|
181
|
+
localTrackEncodingDisabled: (event: Parameters<WebRTCEndpointEvents['localTrackEncodingDisabled']>[0]) => void;
|
|
182
|
+
localPeerMetadataChanged: (event: Parameters<WebRTCEndpointEvents['localEndpointMetadataChanged']>[0]) => void;
|
|
183
|
+
localTrackMetadataChanged: (event: Parameters<WebRTCEndpointEvents['localTrackMetadataChanged']>[0]) => void;
|
|
184
|
+
disconnectRequested: (event: Parameters<WebRTCEndpointEvents['disconnectRequested']>[0]) => void;
|
|
185
|
+
/**
|
|
186
|
+
* Emitted when data channel publishers (both reliable and lossy) are created and ready to send data.
|
|
187
|
+
*/
|
|
188
|
+
dataChannelsReady: () => void;
|
|
189
|
+
/**
|
|
190
|
+
* Emitted when data channel publishers (both reliable or lossy) fail.
|
|
191
|
+
*/
|
|
192
|
+
dataChannelsError: (error: Error) => void;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Represents the type of client used.
|
|
196
|
+
* @category Connection
|
|
197
|
+
*/
|
|
198
|
+
export type ClientType = 'web' | 'mobile';
|
|
199
|
+
/** Configuration object for the client */
|
|
200
|
+
export interface ConnectConfig<PeerMetadata> {
|
|
201
|
+
/** Metadata for the peer */
|
|
202
|
+
peerMetadata: PeerMetadata;
|
|
203
|
+
/** Token for authentication */
|
|
204
|
+
token: string;
|
|
205
|
+
/** Fishjam url */
|
|
206
|
+
url: string;
|
|
207
|
+
}
|
|
208
|
+
export type CreateConfig = {
|
|
209
|
+
reconnect?: ReconnectConfig | boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Enables Fishjam SDK's debug logs in the console.
|
|
212
|
+
*/
|
|
213
|
+
debug?: boolean;
|
|
214
|
+
/** Type of client used */
|
|
215
|
+
clientType?: ClientType;
|
|
216
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const packageVersion: string;
|