@fishjam-cloud/ts-client 0.6.0 → 0.7.1

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
@@ -4,7 +4,12 @@
4
4
 
5
5
  # Fishjam TS Client
6
6
 
7
- TypeScript client library for [Fishjam Cloud](https://cloud.fishjam.stream).
7
+ TypeScript client library for [Fishjam Cloud](https://fishjam.io).
8
+
9
+ > [!WARNING]
10
+ > This SDK is not stable yet. We recommend to use
11
+ > [React Client](https://github.com/fishjam-cloud/web-client-sdk/tree/main/packages/react-client) for Fishjam Cloud
12
+ > services.
8
13
 
9
14
  ## Documentation
10
15
 
@@ -33,10 +38,11 @@ yarn @fishjam-cloud/ts-client
33
38
 
34
39
  Prerequisites:
35
40
 
36
- - Account on [Fishjam Cloud](https://cloud.fishjam.stream) with App configured.
41
+ - Account on [Fishjam Cloud](https://https://fishjam.io) with App configured.
37
42
  - Created room and token of peer in that room. You can use Room Manager to create room and peer token.
38
43
 
39
- The following code snippet is based on the [minimal](../../examples/ts-client/minimal/) example.
44
+ The following code snippet is based on the
45
+ [minimal](https://github.com/fishjam-cloud/web-client-sdk/tree/main/examples/ts-client/minimal/) example.
40
46
 
41
47
  ```ts
42
48
  import { FishjamClient, WebRTCEndpoint } from '@fishjam-cloud/ts-client';
@@ -119,12 +125,18 @@ async function startScreenSharing(webrtc: WebRTCEndpoint) {
119
125
 
120
126
  ## Examples
121
127
 
122
- For more examples, see the [examples](../../examples/ts-client/) folder.
128
+ For more examples, see the [examples](https://github.com/fishjam-cloud/web-client-sdk/tree/main/examples/ts-client/)
129
+ folder.
130
+
131
+ ## License
123
132
 
124
- ## Copyright and License
133
+ Licensed under the [Apache License, Version 2.0](LICENSE)
125
134
 
126
- Copyright 2024, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam-ts)
135
+ ## Fishjam Cloud is created by Software Mansion
127
136
 
128
- [![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=fishjam-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=fishjam-ts)
137
+ Since 2012 [Software Mansion](https://swmansion.com) is a software agency with experience in building web and mobile
138
+ apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues. We can help
139
+ you build your next dream product –
140
+ [Hire us](https://swmansion.com/contact/projects?utm_source=fishjam&utm_medium=web-readme).
129
141
 
130
- Licensed under the [Apache License, Version 2.0](LICENSE)
142
+ [![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=react-client)](https://swmansion.com/contact/projects?utm_source=fishjam&utm_medium=web-readme)
@@ -12,6 +12,11 @@ export type Component<PeerMetadata, TrackMetadata> = Omit<Endpoint<PeerMetadata,
12
12
  * Events emitted by the client with their arguments.
13
13
  */
14
14
  export interface MessageEvents<PeerMetadata, TrackMetadata> {
15
+ /**
16
+ * Emitted when connect method invoked
17
+ *
18
+ */
19
+ connectionStarted: () => void;
15
20
  /**
16
21
  * Emitted when the websocket connection is closed
17
22
  *
@@ -210,7 +215,7 @@ export declare class FishjamClient<PeerMetadata, TrackMetadata> extends FishjamC
210
215
  *
211
216
  * @param {ConnectConfig} config - Configuration object for the client
212
217
  */
213
- connect(config: ConnectConfig<PeerMetadata>): void;
218
+ connect(config: ConnectConfig<PeerMetadata>): Promise<void>;
214
219
  private initConnection;
215
220
  private getUrl;
216
221
  private initWebsocket;
@@ -3,6 +3,7 @@ import { EventEmitter } from 'events';
3
3
  import { PeerMessage } from './protos';
4
4
  import { ReconnectManager } from './reconnection';
5
5
  import { isAuthError } from './auth';
6
+ import { connectEventsHandler } from './connectEventsHandler';
6
7
  const STATISTICS_INTERVAL = 10_000;
7
8
  const isPeer = (endpoint) => endpoint.type === 'webrtc';
8
9
  const isComponent = (endpoint) => endpoint.type === 'recording' ||
@@ -77,10 +78,13 @@ export class FishjamClient extends EventEmitter {
77
78
  *
78
79
  * @param {ConnectConfig} config - Configuration object for the client
79
80
  */
80
- connect(config) {
81
+ async connect(config) {
82
+ this.emit('connectionStarted');
83
+ const result = connectEventsHandler(this);
81
84
  this.reconnectManager.reset(config.peerMetadata);
82
85
  this.connectConfig = config;
83
86
  this.initConnection(config.peerMetadata);
87
+ return result;
84
88
  }
85
89
  async initConnection(peerMetadata) {
86
90
  if (this.status === 'initialized') {
@@ -0,0 +1,2 @@
1
+ import type { FishjamClient } from './FishjamClient';
2
+ export declare function connectEventsHandler<P, T>(fishjamClient: FishjamClient<P, T>): Promise<void>;
@@ -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
+ }
@@ -61,7 +61,7 @@ export class ReconnectManager {
61
61
  this.reconnectTimeoutId = null;
62
62
  }
63
63
  getLastPeerMetadata() {
64
- return this.lastLocalEndpoint?.metadata;
64
+ return this.lastLocalEndpoint?.metadata?.peer;
65
65
  }
66
66
  reconnect() {
67
67
  if (this.reconnectTimeoutId)
@@ -15,14 +15,12 @@ export declare class ConnectionManager {
15
15
  */
16
16
  private getIceServers;
17
17
  getConnection: () => RTCPeerConnection;
18
- setTransceiversToReadOnly: () => void;
19
18
  addTransceiversIfNeeded: (serverTracks: Map<string, number>) => void;
20
19
  private getNeededTransceiversTypes;
21
20
  addTransceiver: (track: MediaStreamTrack, transceiverConfig: RTCRtpTransceiverInit) => void;
22
21
  setOnTrackReady: (onTrackReady: (event: RTCTrackEvent) => void) => void;
23
22
  setRemoteDescription: (data: RTCSessionDescriptionInit) => Promise<void>;
24
23
  isTrackInUse: (track: MediaStreamTrack) => boolean;
25
- setTransceiverDirection: () => void;
26
24
  removeTrack: (sender: RTCRtpSender) => void;
27
25
  findSender: (mediaStreamTrackId: MediaStreamTrackId) => RTCRtpSender;
28
26
  addIceCandidate: (iceCandidate: RTCIceCandidate) => Promise<void>;
@@ -34,9 +34,6 @@ export class ConnectionManager {
34
34
  getConnection = () => {
35
35
  return this.connection;
36
36
  };
37
- setTransceiversToReadOnly = () => {
38
- this.connection.getTransceivers().forEach((transceiver) => (transceiver.direction = 'sendonly'));
39
- };
40
37
  addTransceiversIfNeeded = (serverTracks) => {
41
38
  const recvTransceivers = this.connection.getTransceivers().filter((elem) => elem.direction === 'recvonly');
42
39
  ['audio', 'video']
@@ -58,14 +55,6 @@ export class ConnectionManager {
58
55
  await this.connection.setRemoteDescription(data);
59
56
  };
60
57
  isTrackInUse = (track) => this.connection.getSenders().some((val) => val.track === track);
61
- setTransceiverDirection = () => {
62
- this.connection
63
- .getTransceivers()
64
- .filter((transceiver) => transceiver.direction === 'sendrecv')
65
- .forEach((transceiver) => {
66
- transceiver.direction = 'sendonly';
67
- });
68
- };
69
58
  removeTrack = (sender) => {
70
59
  this.connection.removeTrack(sender);
71
60
  };
@@ -28,7 +28,6 @@ export declare class Local<EndpointMetadata, TrackMetadata> {
28
28
  updateConnection: (connection: ConnectionManager) => void;
29
29
  createSdpOfferEvent: (offer: RTCSessionDescriptionInit) => MediaEvent;
30
30
  addTrack: (connection: ConnectionManager | undefined, trackId: string, track: MediaStreamTrack, stream: MediaStream, trackMetadata: TrackMetadata | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => LocalTrack<EndpointMetadata, TrackMetadata>;
31
- disableAllLocalTrackEncodings: () => Promise<void>;
32
31
  getTrackByMidOrNull: (mid: string) => LocalTrack<EndpointMetadata, TrackMetadata> | null;
33
32
  removeTrack: (trackId: TrackId) => void;
34
33
  replaceTrack: (webrtc: WebRTCEndpoint<EndpointMetadata, TrackMetadata>, trackId: TrackId, newTrack: MediaStreamTrack | null) => Promise<void>;
@@ -13,8 +13,14 @@ export class Local {
13
13
  localEndpoint = {
14
14
  id: '',
15
15
  type: 'webrtc',
16
- metadata: undefined,
17
- rawMetadata: undefined,
16
+ metadata: {
17
+ peer: undefined,
18
+ server: undefined,
19
+ },
20
+ rawMetadata: {
21
+ peer: undefined,
22
+ server: undefined,
23
+ },
18
24
  tracks: new Map(),
19
25
  };
20
26
  endpointMetadataParser;
@@ -74,16 +80,6 @@ export class Local {
74
80
  this.localTracks[trackId] = trackManager;
75
81
  return trackManager;
76
82
  };
77
- disableAllLocalTrackEncodings = async () => {
78
- // TODO: Why are we disabling already disabled encodings?
79
- // I think this part of the code is invoked to mutate RTCRtpSender.getParameters().encodings.active.
80
- // We should probably implement a separate method for that, because disabling already disabled tracks seems weird.
81
- for (const [trackId, trackManager] of Object.entries(this.localTracks)) {
82
- for (const encoding of trackManager.getDisabledEncodings()) {
83
- await this.disableLocalTrackEncoding(trackId, encoding);
84
- }
85
- }
86
- };
87
83
  getTrackByMidOrNull = (mid) => {
88
84
  return Object.values(this.localTracks).find((track) => track.mLineId === mid) ?? null;
89
85
  };
@@ -106,15 +102,15 @@ export class Local {
106
102
  };
107
103
  setEndpointMetadata = (metadata) => {
108
104
  try {
109
- this.localEndpoint.metadata = this.endpointMetadataParser(metadata);
105
+ this.localEndpoint.metadata.peer = this.endpointMetadataParser(metadata);
110
106
  this.localEndpoint.metadataParsingError = undefined;
111
107
  }
112
108
  catch (error) {
113
- this.localEndpoint.metadata = undefined;
109
+ this.localEndpoint.metadata.peer = undefined;
114
110
  this.localEndpoint.metadataParsingError = error;
115
111
  throw error;
116
112
  }
117
- this.localEndpoint.rawMetadata = metadata;
113
+ this.localEndpoint.rawMetadata.peer = metadata;
118
114
  };
119
115
  getEndpoint = () => {
120
116
  return this.localEndpoint;
@@ -159,11 +155,11 @@ export class Local {
159
155
  });
160
156
  };
161
157
  updateEndpointMetadata = (metadata) => {
162
- this.localEndpoint.metadata = this.endpointMetadataParser(metadata);
158
+ this.localEndpoint.metadata.peer = this.endpointMetadataParser(metadata);
163
159
  this.localEndpoint.rawMetadata = this.localEndpoint.metadata;
164
160
  this.localEndpoint.metadataParsingError = undefined;
165
161
  const mediaEvent = generateMediaEvent('updateEndpointMetadata', {
166
- metadata: this.localEndpoint.metadata,
162
+ metadata: this.localEndpoint.metadata.peer,
167
163
  });
168
164
  this.sendMediaEvent(mediaEvent);
169
165
  this.emit('localEndpointMetadataChanged', {
@@ -58,5 +58,5 @@ export declare class LocalTrack<EndpointMetadata, TrackMetadata> implements Trac
58
58
  setMLineId: (mLineId: MLineId) => void;
59
59
  private isNotSimulcastTrack;
60
60
  getTrackBitrates: () => Bitrates;
61
- createTrackVariantBitratesEvent: () => import("../mediaEvent").MediaEvent;
61
+ createTrackVariantBitratesEvent: () => import("..").MediaEvent;
62
62
  }
@@ -48,7 +48,6 @@ export class LocalTrackManager {
48
48
  const trackManager = this.local.addTrack(this.connection, trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth);
49
49
  if (this.connection) {
50
50
  trackManager.addTrackToConnection();
51
- this.connection.setTransceiverDirection();
52
51
  }
53
52
  const mediaEvent = generateCustomEvent({ type: 'renegotiateTracks' });
54
53
  this.sendMediaEvent(mediaEvent);
@@ -56,13 +56,13 @@ export class Remote {
56
56
  const newEndpoint = {
57
57
  id: endpoint.id,
58
58
  type: endpoint.type,
59
- metadata: undefined,
60
- rawMetadata: undefined,
59
+ metadata: { peer: undefined, server: undefined },
60
+ rawMetadata: { peer: undefined, server: undefined },
61
61
  metadataParsingError: undefined,
62
62
  tracks: new Map(),
63
63
  };
64
64
  // mutation in place
65
- this.updateEndpointMetadata(newEndpoint, endpoint.metadata);
65
+ this.updateEndpointMetadata(newEndpoint, endpoint?.metadata?.peer);
66
66
  this.addEndpoint(newEndpoint);
67
67
  this.addTracks(newEndpoint.id, endpoint.tracks, endpoint.trackIdToMetadata);
68
68
  if (sendNotification) {
@@ -77,19 +77,19 @@ export class Remote {
77
77
  if (!endpoint)
78
78
  throw new Error(`Endpoint ${data.id} not found`);
79
79
  // mutation in place
80
- this.updateEndpointMetadata(endpoint, data.metadata);
80
+ this.updateEndpointMetadata(endpoint, data.metadata.peer);
81
81
  this.emit('endpointUpdated', endpoint);
82
82
  };
83
83
  updateEndpointMetadata = (endpoint, metadata) => {
84
84
  try {
85
- endpoint.metadata = this.endpointMetadataParser(metadata);
85
+ endpoint.metadata.peer = this.endpointMetadataParser(metadata);
86
86
  endpoint.metadataParsingError = undefined;
87
87
  }
88
88
  catch (error) {
89
- endpoint.metadata = undefined;
89
+ endpoint.metadata.peer = undefined;
90
90
  endpoint.metadataParsingError = error;
91
91
  }
92
- endpoint.rawMetadata = metadata;
92
+ endpoint.rawMetadata.peer = metadata;
93
93
  };
94
94
  removeRemoteEndpoint = (endpointId) => {
95
95
  const endpoint = this.remoteEndpoints[endpointId];
@@ -268,7 +268,10 @@ export interface WebRTCEndpointEvents<EndpointMetadata, TrackMetadata> {
268
268
  encoding: Encoding;
269
269
  }) => void;
270
270
  localEndpointMetadataChanged: (event: {
271
- metadata: EndpointMetadata;
271
+ metadata: {
272
+ peer?: EndpointMetadata;
273
+ server?: unknown;
274
+ };
272
275
  }) => void;
273
276
  localTrackMetadataChanged: (event: {
274
277
  trackId: string;
@@ -294,8 +297,14 @@ export interface Endpoint<EndpointMetadata, TrackMetadata> {
294
297
  /**
295
298
  * Any information that was provided in {@link WebRTCEndpoint.connect}.
296
299
  */
297
- metadata?: EndpointMetadata;
298
- rawMetadata: any;
300
+ metadata: {
301
+ peer?: EndpointMetadata;
302
+ server: any;
303
+ };
304
+ rawMetadata: {
305
+ peer?: any;
306
+ server: any;
307
+ };
299
308
  metadataParsingError?: any;
300
309
  /**
301
310
  * List of tracks that are sent by the endpoint.
@@ -265,7 +265,6 @@ export class WebRTCEndpoint extends EventEmitter {
265
265
  });
266
266
  try {
267
267
  await this.connectionManager.setRemoteDescription(data);
268
- await this.local.disableAllLocalTrackEncodings();
269
268
  }
270
269
  catch (err) {
271
270
  console.error(err);
@@ -626,7 +625,6 @@ export class WebRTCEndpoint extends EventEmitter {
626
625
  connection.getConnection().addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
627
626
  this.commandsQueue.initConnection(connection);
628
627
  this.local.addAllTracksToConnection();
629
- connection.setTransceiversToReadOnly();
630
628
  }
631
629
  this.localTrackManager.updateSenders();
632
630
  const tracks = new Map(Object.entries(offerData.data.tracksTypes));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishjam-cloud/ts-client",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Typescript client library for Fishjam Cloud",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Fishjam Cloud Team",
@@ -51,22 +51,22 @@
51
51
  "uuid": "^10.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@playwright/test": "^1.46.1",
54
+ "@playwright/test": "^1.47.2",
55
55
  "@types/events": "^3.0.3",
56
- "@types/node": "^22.4.1",
56
+ "@types/node": "^22.7.4",
57
57
  "@types/uuid": "^10.0.0",
58
- "@vitest/coverage-v8": "^2.0.5",
58
+ "@vitest/coverage-v8": "^2.1.1",
59
59
  "fake-mediastreamtrack": "^1.2.0",
60
- "husky": "^9.1.4",
61
- "lint-staged": "^15.2.9",
60
+ "husky": "^9.1.6",
61
+ "lint-staged": "^15.2.10",
62
62
  "react": "^18.2.0",
63
- "ts-proto": "^2.0.2",
63
+ "ts-proto": "^2.2.1",
64
64
  "typed-emitter": "^2.1.0",
65
- "typedoc": "^0.26.6",
65
+ "typedoc": "^0.26.7",
66
66
  "typedoc-plugin-external-resolver": "^1.0.3",
67
- "typedoc-plugin-mdn-links": "^3.2.10",
68
- "typescript": "^5.5.4",
69
- "vitest": "^2.0.5",
67
+ "typedoc-plugin-mdn-links": "^3.3.2",
68
+ "typescript": "^5.6.2",
69
+ "vitest": "^2.1.1",
70
70
  "zod": "^3.23.6"
71
71
  },
72
72
  "lint-staged": {