@stream-io/video-client 0.0.13 → 0.0.15

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.browser.es.js +237 -130
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +239 -129
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.es.js +237 -130
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/Call.d.ts +2 -1
  10. package/dist/src/StreamSfuClient.d.ts +1 -0
  11. package/dist/src/StreamVideoClient.d.ts +5 -1
  12. package/dist/src/coordinator/connection/types.d.ts +3 -2
  13. package/dist/src/coordinator/connection/utils.d.ts +2 -1
  14. package/dist/src/logger.d.ts +4 -0
  15. package/dist/src/rtc/Dispatcher.d.ts +2 -0
  16. package/dist/src/rtc/IceTrickleBuffer.d.ts +2 -0
  17. package/dist/src/rtc/publisher.d.ts +1 -0
  18. package/dist/src/store/CallState.d.ts +2 -0
  19. package/dist/src/types.d.ts +1 -1
  20. package/index.ts +1 -0
  21. package/package.json +1 -1
  22. package/src/Call.ts +69 -42
  23. package/src/StreamSfuClient.ts +70 -29
  24. package/src/StreamVideoClient.ts +46 -3
  25. package/src/coordinator/connection/client.ts +22 -29
  26. package/src/coordinator/connection/connection.ts +2 -3
  27. package/src/coordinator/connection/connection_fallback.ts +0 -1
  28. package/src/coordinator/connection/types.ts +4 -2
  29. package/src/coordinator/connection/utils.ts +5 -2
  30. package/src/devices/devices.ts +10 -3
  31. package/src/events/__tests__/call-permissions.test.ts +2 -2
  32. package/src/events/call.ts +11 -4
  33. package/src/events/sessions.ts +7 -2
  34. package/src/logger.ts +45 -0
  35. package/src/rtc/Dispatcher.ts +14 -4
  36. package/src/rtc/IceTrickleBuffer.ts +8 -1
  37. package/src/rtc/__tests__/publisher.test.ts +1 -1
  38. package/src/rtc/codecs.ts +7 -5
  39. package/src/rtc/flows/join.ts +4 -1
  40. package/src/rtc/publisher.ts +31 -12
  41. package/src/rtc/signal.ts +8 -7
  42. package/src/rtc/subscriber.ts +16 -10
  43. package/src/stats/state-store-stats-reporter.ts +12 -4
  44. package/src/store/CallState.ts +7 -2
  45. package/src/types.ts +3 -2
@@ -5,7 +5,7 @@ import { AcceptCallResponse, BlockUserResponse, EndCallResponse, GetCallResponse
5
5
  import { CallConstructor, CallLeaveOptions, DebounceType, JoinCallData, PublishOptions, SubscriptionChanges } from './types';
6
6
  import { ViewportTracker } from './helpers/ViewportTracker';
7
7
  import { PermissionsContext } from './permissions';
8
- import { CallEventHandler, CallEventTypes, StreamCallEvent } from './coordinator/connection/types';
8
+ import { CallEventHandler, CallEventTypes, Logger, StreamCallEvent } from './coordinator/connection/types';
9
9
  /**
10
10
  * An object representation of a `Call`.
11
11
  */
@@ -49,6 +49,7 @@ export declare class Call {
49
49
  * The permissions context of this call.
50
50
  */
51
51
  readonly permissionsContext: PermissionsContext;
52
+ readonly logger: Logger;
52
53
  /**
53
54
  * The event dispatcher instance dedicated to this Call instance.
54
55
  * @private
@@ -55,6 +55,7 @@ export declare class StreamSfuClient {
55
55
  private unhealthyTimeoutInMs;
56
56
  private lastMessageTimestamp?;
57
57
  private readonly unsubscribeIceTrickle;
58
+ private readonly logger;
58
59
  /**
59
60
  * Constructs a new SFU client.
60
61
  *
@@ -2,7 +2,7 @@ import { Call } from './Call';
2
2
  import { StreamClient } from './coordinator/connection/client';
3
3
  import { StreamVideoReadOnlyStateStore } from './store';
4
4
  import type { ConnectedEvent, CreateCallTypeRequest, CreateCallTypeResponse, CreateDeviceRequest, CreateGuestRequest, CreateGuestResponse, GetCallTypeResponse, GetEdgesResponse, ListCallTypeResponse, ListDevicesResponse, QueryCallsRequest, UpdateCallTypeRequest, UpdateCallTypeResponse } from './gen/coordinator';
5
- import type { EventHandler, EventTypes, StreamClientOptions, TokenOrProvider, TokenProvider, User } from './coordinator/connection/types';
5
+ import type { EventHandler, EventTypes, LogLevel, Logger, StreamClientOptions, TokenOrProvider, TokenProvider, User } from './coordinator/connection/types';
6
6
  /**
7
7
  * A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
8
8
  */
@@ -13,11 +13,14 @@ export declare class StreamVideoClient {
13
13
  readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
14
14
  readonly user?: User;
15
15
  readonly token?: TokenOrProvider;
16
+ readonly logLevel: LogLevel;
17
+ readonly logger: Logger;
16
18
  private readonly writeableStateStore;
17
19
  streamClient: StreamClient;
18
20
  private eventHandlersToUnregister;
19
21
  private connectionPromise;
20
22
  private disconnectionPromise;
23
+ private logLevels;
21
24
  /**
22
25
  * You should create only one instance of `StreamVideoClient`.
23
26
  */
@@ -144,4 +147,5 @@ export declare class StreamVideoClient {
144
147
  * @param tokenOrProvider a token or a function that returns a token.
145
148
  */
146
149
  private connectAnonymousUser;
150
+ private filterLogs;
147
151
  }
@@ -20,7 +20,7 @@ export type UserWithId = (UserRequest & {
20
20
  });
21
21
  export type { OwnUserResponse } from '../../gen/coordinator';
22
22
  export type ConnectAPIResponse = Promise<void | ConnectedEvent>;
23
- export type LogLevel = 'info' | 'error' | 'warn';
23
+ export type LogLevel = 'debug' | 'info' | 'error' | 'warn';
24
24
  type ErrorResponseDetails = {
25
25
  code: number;
26
26
  messages: string[];
@@ -59,7 +59,7 @@ export type EventHandler = (event: StreamVideoEvent) => void;
59
59
  export type CallEventHandler = (event: StreamCallEvent) => void;
60
60
  export type EventTypes = 'all' | StreamVideoEvent['type'];
61
61
  export type CallEventTypes = StreamCallEvent['type'];
62
- export type Logger = (logLevel: LogLevel, message: string, extraData?: Record<string, unknown>) => void;
62
+ export type Logger = (logLevel: LogLevel, message: string, extraData?: any, tags?: string[]) => void;
63
63
  export type StreamClientOptions = Partial<AxiosRequestConfig> & {
64
64
  /**
65
65
  * Used to disable warnings that are triggered by using connectUser or connectAnonymousUser server-side.
@@ -76,6 +76,7 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
76
76
  /** experimental feature, please contact support if you want this feature enabled for you */
77
77
  enableWSFallback?: boolean;
78
78
  logger?: Logger;
79
+ logLevel?: LogLevel;
79
80
  /**
80
81
  * When true, user will be persisted on client. Otherwise if `connectUser` call fails, then you need to
81
82
  * call `connectUser` again to retry.
@@ -1,3 +1,4 @@
1
+ import { Logger } from './types';
1
2
  export declare const sleep: (m: number) => Promise<void>;
2
3
  export declare function isFunction<T>(value: Function | T): value is Function;
3
4
  /**
@@ -21,7 +22,7 @@ export declare function convertErrorToJson(err: Error): Record<string, unknown>;
21
22
  * isOnline safely return the navigator.online value for browser env
22
23
  * if navigator is not in global object, it always return true
23
24
  */
24
- export declare function isOnline(): boolean;
25
+ export declare function isOnline(logger: Logger): boolean;
25
26
  /**
26
27
  * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
27
28
  */
@@ -0,0 +1,4 @@
1
+ import { Logger } from './coordinator/connection/types';
2
+ export declare const logToConsole: Logger;
3
+ export declare const setLogger: (l: Logger) => void;
4
+ export declare const getLogger: (withTags?: string[]) => Logger;
@@ -5,6 +5,8 @@ export declare const isSfuEvent: (eventName: SfuEventKinds | CallEventTypes) =>
5
5
  export type SfuEventListener = (event: SfuEvent) => void;
6
6
  export declare class Dispatcher {
7
7
  private subscribers;
8
+ private logger?;
9
+ constructor();
8
10
  dispatch: (message: SfuEvent) => void;
9
11
  on: (eventName: SfuEventKinds, fn: SfuEventListener) => () => void;
10
12
  off: (eventName: SfuEventKinds, fn: SfuEventListener) => void;
@@ -7,5 +7,7 @@ import { ICETrickle } from '../gen/video/sfu/models/models';
7
7
  export declare class IceTrickleBuffer {
8
8
  readonly subscriberCandidates: ReplaySubject<ICETrickle>;
9
9
  readonly publisherCandidates: ReplaySubject<ICETrickle>;
10
+ private logger?;
11
+ constructor();
10
12
  push: (iceTrickle: ICETrickle) => void;
11
13
  }
@@ -23,6 +23,7 @@ export declare class Publisher {
23
23
  private isDtxEnabled;
24
24
  private isRedEnabled;
25
25
  private preferredVideoCodec?;
26
+ private logger?;
26
27
  constructor({ connectionConfig, sfuClient, state, isDtxEnabled, isRedEnabled, preferredVideoCodec, }: PublisherOpts);
27
28
  /**
28
29
  * Starts publishing the given track of the given media stream.
@@ -4,6 +4,7 @@ import { StreamVideoLocalParticipant, StreamVideoParticipant, StreamVideoPartici
4
4
  import { CallStatsReport } from '../stats/types';
5
5
  import { CallRecording, CallResponse, MemberResponse, OwnCapability, PermissionRequestEvent } from '../gen/coordinator';
6
6
  import { Comparator } from '../sorting';
7
+ import { Logger } from '../coordinator/connection/types';
7
8
  /**
8
9
  * Represents the state of the current call.
9
10
  */
@@ -201,6 +202,7 @@ export declare class CallState {
201
202
  * The calling state.
202
203
  */
203
204
  callingState$: Observable<CallingState>;
205
+ readonly logger: Logger;
204
206
  /**
205
207
  * A list of comparators that are used to sort the participants.
206
208
  *
@@ -45,7 +45,7 @@ export interface StreamVideoParticipant extends Participant {
45
45
  /**
46
46
  * True if the participant is the local participant.
47
47
  */
48
- isLoggedInUser?: boolean;
48
+ isLocalParticipant?: boolean;
49
49
  /**
50
50
  * Timestamp of when the participant is pinned
51
51
  */
package/index.ts CHANGED
@@ -22,3 +22,4 @@ export * from './src/helpers/ViewportTracker';
22
22
  export * from './src/helpers/sound-detector';
23
23
  export * as Browsers from './src/helpers/browsers';
24
24
  export * from './src/sdk-info';
25
+ export * from './src/logger';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
package/src/Call.ts CHANGED
@@ -100,11 +100,13 @@ import {
100
100
  CallEventHandler,
101
101
  CallEventTypes,
102
102
  EventHandler,
103
+ Logger,
103
104
  StreamCallEvent,
104
105
  } from './coordinator/connection/types';
105
106
  import { UAParser } from 'ua-parser-js';
106
107
  import { getSdkInfo } from './sdk-info';
107
108
  import { isReactNative } from './helpers/platforms';
109
+ import { getLogger } from './logger';
108
110
 
109
111
  /**
110
112
  * An object representation of a `Call`.
@@ -160,6 +162,7 @@ export class Call {
160
162
  * The permissions context of this call.
161
163
  */
162
164
  readonly permissionsContext = new PermissionsContext();
165
+ readonly logger: Logger;
163
166
 
164
167
  /**
165
168
  * The event dispatcher instance dedicated to this Call instance.
@@ -220,6 +223,7 @@ export class Call {
220
223
  this.streamClient = streamClient;
221
224
  this.clientStore = clientStore;
222
225
  this.streamClientBasePath = `/call/${this.type}/${this.id}`;
226
+ this.logger = getLogger(['call']);
223
227
 
224
228
  const callTypeConfig = CallTypes.get(type);
225
229
  const participantSorter =
@@ -280,7 +284,7 @@ export class Call {
280
284
  );
281
285
  if (!hasPermission && this.publisher.isPublishing(trackType)) {
282
286
  this.stopPublish(trackType).catch((err) => {
283
- console.error('Error stopping publish', trackType, err);
287
+ this.logger('error', `Error stopping publish ${trackType}`, err);
284
288
  });
285
289
  }
286
290
  }
@@ -579,6 +583,12 @@ export class Call {
579
583
  throw new Error(`Illegal State: Already joined.`);
580
584
  }
581
585
 
586
+ if (this.state.callingState === CallingState.LEFT) {
587
+ throw new Error(
588
+ 'Illegal State: Cannot join already left call. Create a new Call instance to join a call.',
589
+ );
590
+ }
591
+
582
592
  const previousCallingState = this.state.callingState;
583
593
  this.state.setCallingState(CallingState.JOINING);
584
594
 
@@ -641,7 +651,10 @@ export class Call {
641
651
  * A closure which hides away the re-connection logic.
642
652
  */
643
653
  const rejoin = async () => {
644
- console.log(`Rejoining call ${this.cid} (${this.reconnectAttempts})...`);
654
+ this.logger(
655
+ 'debug',
656
+ `Rejoining call ${this.cid} (${this.reconnectAttempts})...`,
657
+ );
645
658
  this.reconnectAttempts++;
646
659
  this.state.setCallingState(CallingState.RECONNECTING);
647
660
 
@@ -656,7 +669,7 @@ export class Call {
656
669
 
657
670
  await sleep(retryInterval(this.reconnectAttempts));
658
671
  await this.join(data);
659
- console.log(`Rejoin: ${this.reconnectAttempts} successful!`);
672
+ this.logger('info', `Rejoin: ${this.reconnectAttempts} successful!`);
660
673
  if (localParticipant && !isReactNative()) {
661
674
  const {
662
675
  audioStream,
@@ -669,7 +682,7 @@ export class Call {
669
682
  if (videoStream) await this.publishVideoStream(videoStream);
670
683
  if (screenShare) await this.publishScreenShareStream(screenShare);
671
684
  }
672
- console.log(`Rejoin: state restored ${this.reconnectAttempts}`);
685
+ this.logger('info', `Rejoin: state restored ${this.reconnectAttempts}`);
673
686
  };
674
687
 
675
688
  this.rejoinPromise = rejoin;
@@ -688,13 +701,14 @@ export class Call {
688
701
  if (isReactNative()) return;
689
702
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
690
703
  rejoin().catch(() => {
691
- console.log(
704
+ this.logger(
705
+ 'error',
692
706
  `Rejoin failed for ${this.reconnectAttempts} times. Giving up.`,
693
707
  );
694
708
  this.state.setCallingState(CallingState.RECONNECTING_FAILED);
695
709
  });
696
710
  } else {
697
- console.log('Reconnect attempts exceeded. Giving up...');
711
+ this.logger('error', 'Reconnect attempts exceeded. Giving up...');
698
712
  this.state.setCallingState(CallingState.RECONNECTING_FAILED);
699
713
  }
700
714
  });
@@ -705,16 +719,17 @@ export class Call {
705
719
  if (typeof window !== 'undefined' && window.addEventListener) {
706
720
  const handleOnOffline = () => {
707
721
  window.removeEventListener('offline', handleOnOffline);
708
- console.log('Join: Going offline...');
722
+ this.logger('warn', 'Join: Going offline...');
709
723
  this.state.setCallingState(CallingState.OFFLINE);
710
724
  };
711
725
 
712
726
  const handleOnOnline = () => {
713
727
  window.removeEventListener('online', handleOnOnline);
714
728
  if (this.state.callingState === CallingState.OFFLINE) {
715
- console.log('Join: Going online...');
729
+ this.logger('info', 'Join: Going online...');
716
730
  rejoin().catch(() => {
717
- console.log(
731
+ this.logger(
732
+ 'error',
718
733
  `Rejoin failed for ${this.reconnectAttempts} times. Giving up.`,
719
734
  );
720
735
  this.state.setCallingState(CallingState.RECONNECTING_FAILED);
@@ -783,7 +798,7 @@ export class Call {
783
798
  clientDetails.sdk = getSdkInfo();
784
799
  // 1. wait for the signal server to be ready before sending "joinRequest"
785
800
  sfuClient.signalReady
786
- .catch((err) => console.warn('Signal ready failed', err))
801
+ .catch((err) => this.logger('error', 'Signal ready failed', err))
787
802
  // prepare a generic SDP and send it to the SFU.
788
803
  // this is a throw-away SDP that the SFU will use to determine
789
804
  // the capabilities of the client (codec support, etc.)
@@ -794,12 +809,15 @@ export class Call {
794
809
  this.streamClient.options.preferredVideoCodec,
795
810
  ),
796
811
  )
797
- .then((sdp) =>
798
- sfuClient.join({
812
+ .then((sdp) => {
813
+ const joinRequest = {
799
814
  subscriberSdp: sdp || '',
800
815
  clientDetails,
801
- }),
802
- );
816
+ };
817
+ this.logger('info', 'Sending join request to SFU');
818
+ this.logger('debug', 'Join request payload', joinRequest);
819
+ sfuClient.join(joinRequest);
820
+ });
803
821
 
804
822
  // 2. in parallel, wait for the SFU to send us the "joinResponse"
805
823
  // this will throw an error if the SFU rejects the join request or
@@ -813,7 +831,7 @@ export class Call {
813
831
  this.state.setParticipants(
814
832
  currentParticipants.map<StreamVideoParticipant>((participant) => ({
815
833
  ...participant,
816
- isLoggedInUser: participant.sessionId === sfuClient.sessionId,
834
+ isLocalParticipant: participant.sessionId === sfuClient.sessionId,
817
835
  viewportVisibilityState: VisibilityState.UNKNOWN,
818
836
  })),
819
837
  );
@@ -823,14 +841,15 @@ export class Call {
823
841
 
824
842
  this.reconnectAttempts = 0; // reset the reconnect attempts counter
825
843
  this.state.setCallingState(CallingState.JOINED);
826
- console.log(`Joined call ${this.cid}`);
844
+ this.logger('info', `Joined call ${this.cid}`);
827
845
  } catch (err) {
828
846
  // join failed, try to rejoin
829
847
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
830
848
  await rejoin();
831
- console.log(`Rejoin ${this.reconnectAttempts} successful!`);
849
+ this.logger('info', `Rejoin ${this.reconnectAttempts} successful!`);
832
850
  } else {
833
- console.log(
851
+ this.logger(
852
+ 'error',
834
853
  `Rejoin failed for ${this.reconnectAttempts} times. Giving up.`,
835
854
  );
836
855
  this.state.setCallingState(CallingState.RECONNECTING_FAILED);
@@ -862,7 +881,8 @@ export class Call {
862
881
 
863
882
  const [videoTrack] = videoStream.getVideoTracks();
864
883
  if (!videoTrack) {
865
- return console.error(`There is no video track in the stream.`);
884
+ this.logger('error', `There is no video track to publish in the stream.`);
885
+ return;
866
886
  }
867
887
 
868
888
  await this.publisher.publishStream(
@@ -893,7 +913,8 @@ export class Call {
893
913
 
894
914
  const [audioTrack] = audioStream.getAudioTracks();
895
915
  if (!audioTrack) {
896
- return console.error(`There is no audio track in the stream`);
916
+ this.logger('error', `There is no audio track in the stream to publish`);
917
+ return;
897
918
  }
898
919
 
899
920
  await this.publisher.publishStream(
@@ -922,7 +943,11 @@ export class Call {
922
943
 
923
944
  const [screenShareTrack] = screenShareStream.getVideoTracks();
924
945
  if (!screenShareTrack) {
925
- return console.error(`There is no video track in the stream`);
946
+ this.logger(
947
+ 'error',
948
+ `There is no video track in the screen share stream to publish`,
949
+ );
950
+ return;
926
951
  }
927
952
 
928
953
  await this.publisher.publishStream(
@@ -942,7 +967,7 @@ export class Call {
942
967
  * @param trackType the track type to stop publishing.
943
968
  */
944
969
  stopPublish = async (trackType: TrackType) => {
945
- console.log(`stopPublish`, TrackType[trackType]);
970
+ this.logger('info', `stopPublish ${TrackType[trackType]}`);
946
971
  await this.publisher?.unpublishStream(trackType);
947
972
  };
948
973
 
@@ -992,7 +1017,7 @@ export class Call {
992
1017
  const subscriptions: TrackSubscriptionDetails[] = [];
993
1018
  participants.forEach((p) => {
994
1019
  // we don't want to subscribe to our own tracks
995
- if (p.isLoggedInUser) return;
1020
+ if (p.isLocalParticipant) return;
996
1021
 
997
1022
  // NOTE: audio tracks don't have to be requested explicitly
998
1023
  // as the SFU will implicitly subscribe us to all of them,
@@ -1116,39 +1141,38 @@ export class Call {
1116
1141
  const [primaryStream] = e.streams;
1117
1142
  // example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
1118
1143
  const [trackId, trackType] = primaryStream.id.split(':');
1119
- console.log(`Got remote ${trackType} track:`, e.track);
1144
+ this.logger('info', `Got remote ${trackType} track:`);
1145
+ this.logger('debug', `Track: `, e.track);
1120
1146
  const participantToUpdate = this.state.participants.find(
1121
1147
  (p) => p.trackLookupPrefix === trackId,
1122
1148
  );
1123
1149
  if (!participantToUpdate) {
1124
- console.error('Received track for unknown participant', trackId, e);
1150
+ this.logger(
1151
+ 'error',
1152
+ `'Received track for unknown participant: ${trackId}'`,
1153
+ e,
1154
+ );
1125
1155
  return;
1126
1156
  }
1127
1157
 
1128
1158
  e.track.addEventListener('mute', () => {
1129
- console.log(
1130
- `Track muted:`,
1131
- participantToUpdate.userId,
1132
- `${trackType}:${trackId}`,
1133
- e.track,
1159
+ this.logger(
1160
+ 'info',
1161
+ `Track muted: ${participantToUpdate.userId} ${trackType}:${trackId}`,
1134
1162
  );
1135
1163
  });
1136
1164
 
1137
1165
  e.track.addEventListener('unmute', () => {
1138
- console.log(
1139
- `Track unmuted:`,
1140
- participantToUpdate.userId,
1141
- `${trackType}:${trackId}`,
1142
- e.track,
1166
+ this.logger(
1167
+ 'info',
1168
+ `Track unmuted: ${participantToUpdate.userId} ${trackType}:${trackId}`,
1143
1169
  );
1144
1170
  });
1145
1171
 
1146
1172
  e.track.addEventListener('ended', () => {
1147
- console.log(
1148
- `Track ended:`,
1149
- participantToUpdate.userId,
1150
- `${trackType}:${trackId}`,
1151
- e.track,
1173
+ this.logger(
1174
+ 'info',
1175
+ `Track ended: ${participantToUpdate.userId} ${trackType}:${trackId}`,
1152
1176
  );
1153
1177
  });
1154
1178
 
@@ -1161,12 +1185,15 @@ export class Call {
1161
1185
  )[trackType];
1162
1186
 
1163
1187
  if (!streamKindProp) {
1164
- console.error('Unknown track type', trackType);
1188
+ this.logger('error', `Unknown track type: ${trackType}`);
1165
1189
  return;
1166
1190
  }
1167
1191
  const previousStream = participantToUpdate[streamKindProp];
1168
1192
  if (previousStream) {
1169
- console.log(`Cleaning up previous remote tracks`, e.track.kind);
1193
+ this.logger(
1194
+ 'info',
1195
+ `Cleaning up previous remote tracks: ${e.track.kind}`,
1196
+ );
1170
1197
  previousStream.getTracks().forEach((t) => {
1171
1198
  t.stop();
1172
1199
  previousStream.removeTrack(t);
@@ -1,4 +1,11 @@
1
- import type { FinishedUnaryCall, UnaryCall } from '@protobuf-ts/runtime-rpc';
1
+ import type {
2
+ FinishedUnaryCall,
3
+ MethodInfo,
4
+ NextUnaryFn,
5
+ RpcInterceptor,
6
+ RpcOptions,
7
+ UnaryCall,
8
+ } from '@protobuf-ts/runtime-rpc';
2
9
  import { SignalServerClient } from './gen/video/sfu/signal_rpc/signal.client';
3
10
  import { createSignalClient, withHeaders } from './rpc';
4
11
  import {
@@ -23,6 +30,8 @@ import {
23
30
  retryInterval,
24
31
  sleep,
25
32
  } from './coordinator/connection/utils';
33
+ import { Logger } from './coordinator/connection/types';
34
+ import { getLogger } from './logger';
26
35
 
27
36
  export type StreamSfuClientConstructor = {
28
37
  /**
@@ -84,6 +93,7 @@ export class StreamSfuClient {
84
93
  private unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
85
94
  private lastMessageTimestamp?: Date;
86
95
  private readonly unsubscribeIceTrickle: () => void;
96
+ private readonly logger: Logger;
87
97
 
88
98
  /**
89
99
  * Constructs a new SFU client.
@@ -103,12 +113,27 @@ export class StreamSfuClient {
103
113
  }: StreamSfuClientConstructor) {
104
114
  this.sessionId = sessionId || generateUUIDv4();
105
115
  this.token = token;
116
+ this.logger = getLogger(['sfu-client']);
117
+ const logger = this.logger;
118
+ const logInterceptor: RpcInterceptor = {
119
+ interceptUnary(
120
+ next: NextUnaryFn,
121
+ method: MethodInfo,
122
+ input: object,
123
+ options: RpcOptions,
124
+ ): UnaryCall {
125
+ logger('info', `Calling SFU RPC method ${method.name}`);
126
+ logger('debug', `Method call payload`, { input, options });
127
+ return next(method, input, options);
128
+ },
129
+ };
106
130
  this.rpc = createSignalClient({
107
131
  baseUrl: url,
108
132
  interceptors: [
109
133
  withHeaders({
110
134
  Authorization: `Bearer ${token}`,
111
135
  }),
136
+ logInterceptor,
112
137
  ],
113
138
  });
114
139
 
@@ -152,38 +177,46 @@ export class StreamSfuClient {
152
177
  };
153
178
 
154
179
  updateSubscriptions = async (subscriptions: TrackSubscriptionDetails[]) => {
155
- return retryable(() =>
156
- this.rpc.updateSubscriptions({
157
- sessionId: this.sessionId,
158
- tracks: subscriptions,
159
- }),
180
+ return retryable(
181
+ () =>
182
+ this.rpc.updateSubscriptions({
183
+ sessionId: this.sessionId,
184
+ tracks: subscriptions,
185
+ }),
186
+ this.logger,
160
187
  );
161
188
  };
162
189
 
163
190
  setPublisher = async (data: Omit<SetPublisherRequest, 'sessionId'>) => {
164
- return retryable(() =>
165
- this.rpc.setPublisher({
166
- ...data,
167
- sessionId: this.sessionId,
168
- }),
191
+ return retryable(
192
+ () =>
193
+ this.rpc.setPublisher({
194
+ ...data,
195
+ sessionId: this.sessionId,
196
+ }),
197
+ this.logger,
169
198
  );
170
199
  };
171
200
 
172
201
  sendAnswer = async (data: Omit<SendAnswerRequest, 'sessionId'>) => {
173
- return retryable(() =>
174
- this.rpc.sendAnswer({
175
- ...data,
176
- sessionId: this.sessionId,
177
- }),
202
+ return retryable(
203
+ () =>
204
+ this.rpc.sendAnswer({
205
+ ...data,
206
+ sessionId: this.sessionId,
207
+ }),
208
+ this.logger,
178
209
  );
179
210
  };
180
211
 
181
212
  iceTrickle = async (data: Omit<ICETrickle, 'sessionId'>) => {
182
- return retryable(() =>
183
- this.rpc.iceTrickle({
184
- ...data,
185
- sessionId: this.sessionId,
186
- }),
213
+ return retryable(
214
+ () =>
215
+ this.rpc.iceTrickle({
216
+ ...data,
217
+ sessionId: this.sessionId,
218
+ }),
219
+ this.logger,
187
220
  );
188
221
  };
189
222
 
@@ -201,11 +234,13 @@ export class StreamSfuClient {
201
234
  updateMuteStates = async (
202
235
  data: Omit<UpdateMuteStatesRequest, 'sessionId'>,
203
236
  ) => {
204
- return retryable(() =>
205
- this.rpc.updateMuteStates({
206
- ...data,
207
- sessionId: this.sessionId,
208
- }),
237
+ return retryable(
238
+ () =>
239
+ this.rpc.updateMuteStates({
240
+ ...data,
241
+ sessionId: this.sessionId,
242
+ }),
243
+ this.logger,
209
244
  );
210
245
  };
211
246
 
@@ -236,7 +271,7 @@ export class StreamSfuClient {
236
271
  clearInterval(this.keepAliveInterval);
237
272
  }
238
273
  this.keepAliveInterval = setInterval(() => {
239
- console.log('Sending healthCheckRequest to SFU');
274
+ this.logger('info', 'Sending healthCheckRequest to SFU');
240
275
  const message = SfuRequest.create({
241
276
  requestPayload: {
242
277
  oneofKind: 'healthCheckRequest',
@@ -258,7 +293,7 @@ export class StreamSfuClient {
258
293
  new Date().getTime() - this.lastMessageTimestamp.getTime();
259
294
 
260
295
  if (timeSinceLastMessage > this.unhealthyTimeoutInMs) {
261
- console.log('SFU connection unhealthy, closing');
296
+ this.logger('error', 'SFU connection unhealthy, closing');
262
297
  this.close(
263
298
  4001,
264
299
  `SFU connection unhealthy. Didn't receive any healthcheck messages for ${this.unhealthyTimeoutInMs}ms`,
@@ -296,6 +331,7 @@ const MAX_RETRIES = 5;
296
331
  */
297
332
  const retryable = async <I extends object, O extends SfuResponseWithError>(
298
333
  rpc: () => UnaryCall<I, O>,
334
+ logger: Logger,
299
335
  ) => {
300
336
  let retryAttempt = 0;
301
337
  let rpcCallResult: FinishedUnaryCall<I, O>;
@@ -306,10 +342,15 @@ const retryable = async <I extends object, O extends SfuResponseWithError>(
306
342
  }
307
343
 
308
344
  rpcCallResult = await rpc();
345
+ logger(
346
+ 'info',
347
+ `SFU RPC response received for ${rpcCallResult.method.name}`,
348
+ );
349
+ logger('debug', `Response payload`, rpcCallResult);
309
350
 
310
351
  // if the RPC call failed, log the error and retry
311
352
  if (rpcCallResult.response.error) {
312
- console.error('SFU Error:', rpcCallResult.response.error);
353
+ logger('error', 'SFU RPC Error:', rpcCallResult.response.error);
313
354
  }
314
355
  retryAttempt++;
315
356
  } while (