@stream-io/video-client 1.4.7 → 1.5.0-0

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 (77) hide show
  1. package/CHANGELOG.md +238 -0
  2. package/dist/index.browser.es.js +1977 -1477
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +1975 -1474
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +1977 -1477
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +93 -9
  9. package/dist/src/StreamSfuClient.d.ts +72 -56
  10. package/dist/src/StreamVideoClient.d.ts +2 -2
  11. package/dist/src/coordinator/connection/client.d.ts +3 -4
  12. package/dist/src/coordinator/connection/types.d.ts +5 -1
  13. package/dist/src/devices/InputMediaDeviceManager.d.ts +4 -0
  14. package/dist/src/devices/MicrophoneManager.d.ts +1 -1
  15. package/dist/src/events/callEventHandlers.d.ts +1 -3
  16. package/dist/src/events/internal.d.ts +4 -0
  17. package/dist/src/gen/video/sfu/event/events.d.ts +106 -4
  18. package/dist/src/gen/video/sfu/models/models.d.ts +64 -65
  19. package/dist/src/helpers/ensureExhausted.d.ts +1 -0
  20. package/dist/src/helpers/withResolvers.d.ts +14 -0
  21. package/dist/src/logger.d.ts +1 -0
  22. package/dist/src/rpc/createClient.d.ts +2 -0
  23. package/dist/src/rpc/index.d.ts +1 -0
  24. package/dist/src/rpc/retryable.d.ts +23 -0
  25. package/dist/src/rtc/Dispatcher.d.ts +1 -1
  26. package/dist/src/rtc/IceTrickleBuffer.d.ts +0 -1
  27. package/dist/src/rtc/Publisher.d.ts +24 -25
  28. package/dist/src/rtc/Subscriber.d.ts +12 -11
  29. package/dist/src/rtc/helpers/rtcConfiguration.d.ts +2 -0
  30. package/dist/src/rtc/helpers/tracks.d.ts +3 -3
  31. package/dist/src/rtc/signal.d.ts +1 -1
  32. package/dist/src/store/CallState.d.ts +46 -2
  33. package/package.json +5 -5
  34. package/src/Call.ts +618 -563
  35. package/src/StreamSfuClient.ts +277 -246
  36. package/src/StreamVideoClient.ts +15 -16
  37. package/src/__tests__/Call.test.ts +145 -2
  38. package/src/__tests__/StreamVideoClient.api.test.ts +168 -0
  39. package/src/coordinator/connection/client.ts +25 -8
  40. package/src/coordinator/connection/connection.ts +2 -1
  41. package/src/coordinator/connection/types.ts +6 -0
  42. package/src/devices/BrowserPermission.ts +1 -1
  43. package/src/devices/CameraManager.ts +1 -1
  44. package/src/devices/InputMediaDeviceManager.ts +12 -3
  45. package/src/devices/MicrophoneManager.ts +3 -3
  46. package/src/devices/devices.ts +1 -1
  47. package/src/events/__tests__/mutes.test.ts +10 -13
  48. package/src/events/__tests__/participant.test.ts +75 -0
  49. package/src/events/callEventHandlers.ts +4 -7
  50. package/src/events/internal.ts +20 -3
  51. package/src/events/mutes.ts +5 -3
  52. package/src/events/participant.ts +48 -15
  53. package/src/gen/video/sfu/event/events.ts +451 -8
  54. package/src/gen/video/sfu/models/models.ts +211 -204
  55. package/src/helpers/ensureExhausted.ts +5 -0
  56. package/src/helpers/withResolvers.ts +43 -0
  57. package/src/logger.ts +3 -1
  58. package/src/rpc/__tests__/retryable.test.ts +72 -0
  59. package/src/rpc/createClient.ts +21 -0
  60. package/src/rpc/index.ts +1 -0
  61. package/src/rpc/retryable.ts +57 -0
  62. package/src/rtc/Dispatcher.ts +6 -2
  63. package/src/rtc/IceTrickleBuffer.ts +2 -2
  64. package/src/rtc/Publisher.ts +127 -163
  65. package/src/rtc/Subscriber.ts +94 -155
  66. package/src/rtc/__tests__/Publisher.test.ts +18 -95
  67. package/src/rtc/__tests__/Subscriber.test.ts +63 -99
  68. package/src/rtc/__tests__/videoLayers.test.ts +2 -2
  69. package/src/rtc/helpers/rtcConfiguration.ts +11 -0
  70. package/src/rtc/helpers/tracks.ts +27 -7
  71. package/src/rtc/signal.ts +3 -3
  72. package/src/rtc/videoLayers.ts +1 -10
  73. package/src/stats/SfuStatsReporter.ts +1 -0
  74. package/src/store/CallState.ts +111 -3
  75. package/src/store/__tests__/CallState.test.ts +58 -37
  76. package/dist/src/rtc/flows/join.d.ts +0 -20
  77. package/src/rtc/flows/join.ts +0 -65
@@ -286,49 +286,10 @@ export interface TrackInfo {
286
286
  * @generated from protobuf field: bool red = 9;
287
287
  */
288
288
  red: boolean;
289
- }
290
- /**
291
- * todo remove this
292
- *
293
- * @generated from protobuf message stream.video.sfu.models.Call
294
- */
295
- export interface Call {
296
289
  /**
297
- * the call type
298
- *
299
- * @generated from protobuf field: string type = 1;
290
+ * @generated from protobuf field: bool muted = 10;
300
291
  */
301
- type: string;
302
- /**
303
- * the call id
304
- *
305
- * @generated from protobuf field: string id = 2;
306
- */
307
- id: string;
308
- /**
309
- * the id of the user that created this call
310
- *
311
- * @generated from protobuf field: string created_by_user_id = 3;
312
- */
313
- createdByUserId: string;
314
- /**
315
- * the id of the current host for this call
316
- *
317
- * @generated from protobuf field: string host_user_id = 4;
318
- */
319
- hostUserId: string;
320
- /**
321
- * @generated from protobuf field: google.protobuf.Struct custom = 5;
322
- */
323
- custom?: Struct;
324
- /**
325
- * @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
326
- */
327
- createdAt?: Timestamp;
328
- /**
329
- * @generated from protobuf field: google.protobuf.Timestamp updated_at = 7;
330
- */
331
- updatedAt?: Timestamp;
292
+ muted: boolean;
332
293
  }
333
294
  /**
334
295
  * @generated from protobuf message stream.video.sfu.models.Error
@@ -432,6 +393,47 @@ export interface Device {
432
393
  */
433
394
  version: string;
434
395
  }
396
+ /**
397
+ * @generated from protobuf message stream.video.sfu.models.Call
398
+ */
399
+ export interface Call {
400
+ /**
401
+ * the call type
402
+ *
403
+ * @generated from protobuf field: string type = 1;
404
+ */
405
+ type: string;
406
+ /**
407
+ * the call id
408
+ *
409
+ * @generated from protobuf field: string id = 2;
410
+ */
411
+ id: string;
412
+ /**
413
+ * the id of the user that created this call
414
+ *
415
+ * @generated from protobuf field: string created_by_user_id = 3;
416
+ */
417
+ createdByUserId: string;
418
+ /**
419
+ * the id of the current host for this call
420
+ *
421
+ * @generated from protobuf field: string host_user_id = 4;
422
+ */
423
+ hostUserId: string;
424
+ /**
425
+ * @generated from protobuf field: google.protobuf.Struct custom = 5;
426
+ */
427
+ custom?: Struct;
428
+ /**
429
+ * @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
430
+ */
431
+ createdAt?: Timestamp;
432
+ /**
433
+ * @generated from protobuf field: google.protobuf.Timestamp updated_at = 7;
434
+ */
435
+ updatedAt?: Timestamp;
436
+ }
435
437
  /**
436
438
  * CallGrants represents the set of permissions given
437
439
  * to the user for the current call.
@@ -741,6 +743,10 @@ export enum CallEndedReason {
741
743
  * @generated from protobuf enum value: CALL_ENDED_REASON_KICKED = 3;
742
744
  */
743
745
  KICKED = 3,
746
+ /**
747
+ * @generated from protobuf enum value: CALL_ENDED_REASON_SESSION_ENDED = 4;
748
+ */
749
+ SESSION_ENDED = 4,
744
750
  }
745
751
  /**
746
752
  * WebsocketReconnectStrategy defines the ws strategies available for handling reconnections.
@@ -753,7 +759,7 @@ export enum WebsocketReconnectStrategy {
753
759
  */
754
760
  UNSPECIFIED = 0,
755
761
  /**
756
- * Sent after reaching the maximum reconnection attempts, leading to permanent disconnect.
762
+ * Sent after reaching the maximum reconnection attempts, or any other unrecoverable error leading to permanent disconnect.
757
763
  *
758
764
  * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT = 1;
759
765
  */
@@ -766,25 +772,18 @@ export enum WebsocketReconnectStrategy {
766
772
  */
767
773
  FAST = 2,
768
774
  /**
769
- * SDK should drop existing pc instances and creates a fresh WebSocket connection,
770
- * ensuring a clean state for the reconnection.
771
- *
772
- * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_CLEAN = 3;
773
- */
774
- CLEAN = 3,
775
- /**
776
- * SDK should obtain new credentials from the coordinator, drops existing pc instances, and initializes
775
+ * SDK should obtain new credentials from the coordinator, drops existing pc instances, set a new session_id and initializes
777
776
  * a completely new WebSocket connection, ensuring a comprehensive reset.
778
777
  *
779
- * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_FULL = 4;
778
+ * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_REJOIN = 3;
780
779
  */
781
- FULL = 4,
780
+ REJOIN = 3,
782
781
  /**
783
782
  * SDK should migrate to a new SFU instance
784
783
  *
785
- * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_MIGRATE = 5;
784
+ * @generated from protobuf enum value: WEBSOCKET_RECONNECT_STRATEGY_MIGRATE = 4;
786
785
  */
787
- MIGRATE = 5,
786
+ MIGRATE = 4,
788
787
  }
789
788
  // @generated message type with reflection information, may provide speed optimized methods
790
789
  class CallState$Type extends MessageType<CallState> {
@@ -1838,6 +1837,7 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1838
1837
  { no: 7, name: 'dtx', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1839
1838
  { no: 8, name: 'stereo', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1840
1839
  { no: 9, name: 'red', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1840
+ { no: 10, name: 'muted', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1841
1841
  ]);
1842
1842
  }
1843
1843
  create(value?: PartialMessage<TrackInfo>): TrackInfo {
@@ -1849,6 +1849,7 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1849
1849
  message.dtx = false;
1850
1850
  message.stereo = false;
1851
1851
  message.red = false;
1852
+ message.muted = false;
1852
1853
  if (value !== undefined)
1853
1854
  reflectionMergePartial<TrackInfo>(this, message, value);
1854
1855
  return message;
@@ -1887,6 +1888,9 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1887
1888
  case /* bool red */ 9:
1888
1889
  message.red = reader.bool();
1889
1890
  break;
1891
+ case /* bool muted */ 10:
1892
+ message.muted = reader.bool();
1893
+ break;
1890
1894
  default:
1891
1895
  let u = options.readUnknownField;
1892
1896
  if (u === 'throw')
@@ -1934,6 +1938,9 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1934
1938
  writer.tag(8, WireType.Varint).bool(message.stereo);
1935
1939
  /* bool red = 9; */
1936
1940
  if (message.red !== false) writer.tag(9, WireType.Varint).bool(message.red);
1941
+ /* bool muted = 10; */
1942
+ if (message.muted !== false)
1943
+ writer.tag(10, WireType.Varint).bool(message.muted);
1937
1944
  let u = options.writeUnknownFields;
1938
1945
  if (u !== false)
1939
1946
  (u == true ? UnknownFieldHandler.onWrite : u)(
@@ -1949,156 +1956,6 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1949
1956
  */
1950
1957
  export const TrackInfo = new TrackInfo$Type();
1951
1958
  // @generated message type with reflection information, may provide speed optimized methods
1952
- class Call$Type extends MessageType<Call> {
1953
- constructor() {
1954
- super('stream.video.sfu.models.Call', [
1955
- { no: 1, name: 'type', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
1956
- { no: 2, name: 'id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
1957
- {
1958
- no: 3,
1959
- name: 'created_by_user_id',
1960
- kind: 'scalar',
1961
- T: 9 /*ScalarType.STRING*/,
1962
- },
1963
- {
1964
- no: 4,
1965
- name: 'host_user_id',
1966
- kind: 'scalar',
1967
- T: 9 /*ScalarType.STRING*/,
1968
- },
1969
- { no: 5, name: 'custom', kind: 'message', T: () => Struct },
1970
- { no: 6, name: 'created_at', kind: 'message', T: () => Timestamp },
1971
- { no: 7, name: 'updated_at', kind: 'message', T: () => Timestamp },
1972
- ]);
1973
- }
1974
- create(value?: PartialMessage<Call>): Call {
1975
- const message = globalThis.Object.create(this.messagePrototype!);
1976
- message.type = '';
1977
- message.id = '';
1978
- message.createdByUserId = '';
1979
- message.hostUserId = '';
1980
- if (value !== undefined) reflectionMergePartial<Call>(this, message, value);
1981
- return message;
1982
- }
1983
- internalBinaryRead(
1984
- reader: IBinaryReader,
1985
- length: number,
1986
- options: BinaryReadOptions,
1987
- target?: Call,
1988
- ): Call {
1989
- let message = target ?? this.create(),
1990
- end = reader.pos + length;
1991
- while (reader.pos < end) {
1992
- let [fieldNo, wireType] = reader.tag();
1993
- switch (fieldNo) {
1994
- case /* string type */ 1:
1995
- message.type = reader.string();
1996
- break;
1997
- case /* string id */ 2:
1998
- message.id = reader.string();
1999
- break;
2000
- case /* string created_by_user_id */ 3:
2001
- message.createdByUserId = reader.string();
2002
- break;
2003
- case /* string host_user_id */ 4:
2004
- message.hostUserId = reader.string();
2005
- break;
2006
- case /* google.protobuf.Struct custom */ 5:
2007
- message.custom = Struct.internalBinaryRead(
2008
- reader,
2009
- reader.uint32(),
2010
- options,
2011
- message.custom,
2012
- );
2013
- break;
2014
- case /* google.protobuf.Timestamp created_at */ 6:
2015
- message.createdAt = Timestamp.internalBinaryRead(
2016
- reader,
2017
- reader.uint32(),
2018
- options,
2019
- message.createdAt,
2020
- );
2021
- break;
2022
- case /* google.protobuf.Timestamp updated_at */ 7:
2023
- message.updatedAt = Timestamp.internalBinaryRead(
2024
- reader,
2025
- reader.uint32(),
2026
- options,
2027
- message.updatedAt,
2028
- );
2029
- break;
2030
- default:
2031
- let u = options.readUnknownField;
2032
- if (u === 'throw')
2033
- throw new globalThis.Error(
2034
- `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`,
2035
- );
2036
- let d = reader.skip(wireType);
2037
- if (u !== false)
2038
- (u === true ? UnknownFieldHandler.onRead : u)(
2039
- this.typeName,
2040
- message,
2041
- fieldNo,
2042
- wireType,
2043
- d,
2044
- );
2045
- }
2046
- }
2047
- return message;
2048
- }
2049
- internalBinaryWrite(
2050
- message: Call,
2051
- writer: IBinaryWriter,
2052
- options: BinaryWriteOptions,
2053
- ): IBinaryWriter {
2054
- /* string type = 1; */
2055
- if (message.type !== '')
2056
- writer.tag(1, WireType.LengthDelimited).string(message.type);
2057
- /* string id = 2; */
2058
- if (message.id !== '')
2059
- writer.tag(2, WireType.LengthDelimited).string(message.id);
2060
- /* string created_by_user_id = 3; */
2061
- if (message.createdByUserId !== '')
2062
- writer.tag(3, WireType.LengthDelimited).string(message.createdByUserId);
2063
- /* string host_user_id = 4; */
2064
- if (message.hostUserId !== '')
2065
- writer.tag(4, WireType.LengthDelimited).string(message.hostUserId);
2066
- /* google.protobuf.Struct custom = 5; */
2067
- if (message.custom)
2068
- Struct.internalBinaryWrite(
2069
- message.custom,
2070
- writer.tag(5, WireType.LengthDelimited).fork(),
2071
- options,
2072
- ).join();
2073
- /* google.protobuf.Timestamp created_at = 6; */
2074
- if (message.createdAt)
2075
- Timestamp.internalBinaryWrite(
2076
- message.createdAt,
2077
- writer.tag(6, WireType.LengthDelimited).fork(),
2078
- options,
2079
- ).join();
2080
- /* google.protobuf.Timestamp updated_at = 7; */
2081
- if (message.updatedAt)
2082
- Timestamp.internalBinaryWrite(
2083
- message.updatedAt,
2084
- writer.tag(7, WireType.LengthDelimited).fork(),
2085
- options,
2086
- ).join();
2087
- let u = options.writeUnknownFields;
2088
- if (u !== false)
2089
- (u == true ? UnknownFieldHandler.onWrite : u)(
2090
- this.typeName,
2091
- message,
2092
- writer,
2093
- );
2094
- return writer;
2095
- }
2096
- }
2097
- /**
2098
- * @generated MessageType for protobuf message stream.video.sfu.models.Call
2099
- */
2100
- export const Call = new Call$Type();
2101
- // @generated message type with reflection information, may provide speed optimized methods
2102
1959
  class Error$Type extends MessageType<Error> {
2103
1960
  constructor() {
2104
1961
  super('stream.video.sfu.models.Error', [
@@ -2656,6 +2513,156 @@ class Device$Type extends MessageType<Device> {
2656
2513
  */
2657
2514
  export const Device = new Device$Type();
2658
2515
  // @generated message type with reflection information, may provide speed optimized methods
2516
+ class Call$Type extends MessageType<Call> {
2517
+ constructor() {
2518
+ super('stream.video.sfu.models.Call', [
2519
+ { no: 1, name: 'type', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2520
+ { no: 2, name: 'id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2521
+ {
2522
+ no: 3,
2523
+ name: 'created_by_user_id',
2524
+ kind: 'scalar',
2525
+ T: 9 /*ScalarType.STRING*/,
2526
+ },
2527
+ {
2528
+ no: 4,
2529
+ name: 'host_user_id',
2530
+ kind: 'scalar',
2531
+ T: 9 /*ScalarType.STRING*/,
2532
+ },
2533
+ { no: 5, name: 'custom', kind: 'message', T: () => Struct },
2534
+ { no: 6, name: 'created_at', kind: 'message', T: () => Timestamp },
2535
+ { no: 7, name: 'updated_at', kind: 'message', T: () => Timestamp },
2536
+ ]);
2537
+ }
2538
+ create(value?: PartialMessage<Call>): Call {
2539
+ const message = globalThis.Object.create(this.messagePrototype!);
2540
+ message.type = '';
2541
+ message.id = '';
2542
+ message.createdByUserId = '';
2543
+ message.hostUserId = '';
2544
+ if (value !== undefined) reflectionMergePartial<Call>(this, message, value);
2545
+ return message;
2546
+ }
2547
+ internalBinaryRead(
2548
+ reader: IBinaryReader,
2549
+ length: number,
2550
+ options: BinaryReadOptions,
2551
+ target?: Call,
2552
+ ): Call {
2553
+ let message = target ?? this.create(),
2554
+ end = reader.pos + length;
2555
+ while (reader.pos < end) {
2556
+ let [fieldNo, wireType] = reader.tag();
2557
+ switch (fieldNo) {
2558
+ case /* string type */ 1:
2559
+ message.type = reader.string();
2560
+ break;
2561
+ case /* string id */ 2:
2562
+ message.id = reader.string();
2563
+ break;
2564
+ case /* string created_by_user_id */ 3:
2565
+ message.createdByUserId = reader.string();
2566
+ break;
2567
+ case /* string host_user_id */ 4:
2568
+ message.hostUserId = reader.string();
2569
+ break;
2570
+ case /* google.protobuf.Struct custom */ 5:
2571
+ message.custom = Struct.internalBinaryRead(
2572
+ reader,
2573
+ reader.uint32(),
2574
+ options,
2575
+ message.custom,
2576
+ );
2577
+ break;
2578
+ case /* google.protobuf.Timestamp created_at */ 6:
2579
+ message.createdAt = Timestamp.internalBinaryRead(
2580
+ reader,
2581
+ reader.uint32(),
2582
+ options,
2583
+ message.createdAt,
2584
+ );
2585
+ break;
2586
+ case /* google.protobuf.Timestamp updated_at */ 7:
2587
+ message.updatedAt = Timestamp.internalBinaryRead(
2588
+ reader,
2589
+ reader.uint32(),
2590
+ options,
2591
+ message.updatedAt,
2592
+ );
2593
+ break;
2594
+ default:
2595
+ let u = options.readUnknownField;
2596
+ if (u === 'throw')
2597
+ throw new globalThis.Error(
2598
+ `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`,
2599
+ );
2600
+ let d = reader.skip(wireType);
2601
+ if (u !== false)
2602
+ (u === true ? UnknownFieldHandler.onRead : u)(
2603
+ this.typeName,
2604
+ message,
2605
+ fieldNo,
2606
+ wireType,
2607
+ d,
2608
+ );
2609
+ }
2610
+ }
2611
+ return message;
2612
+ }
2613
+ internalBinaryWrite(
2614
+ message: Call,
2615
+ writer: IBinaryWriter,
2616
+ options: BinaryWriteOptions,
2617
+ ): IBinaryWriter {
2618
+ /* string type = 1; */
2619
+ if (message.type !== '')
2620
+ writer.tag(1, WireType.LengthDelimited).string(message.type);
2621
+ /* string id = 2; */
2622
+ if (message.id !== '')
2623
+ writer.tag(2, WireType.LengthDelimited).string(message.id);
2624
+ /* string created_by_user_id = 3; */
2625
+ if (message.createdByUserId !== '')
2626
+ writer.tag(3, WireType.LengthDelimited).string(message.createdByUserId);
2627
+ /* string host_user_id = 4; */
2628
+ if (message.hostUserId !== '')
2629
+ writer.tag(4, WireType.LengthDelimited).string(message.hostUserId);
2630
+ /* google.protobuf.Struct custom = 5; */
2631
+ if (message.custom)
2632
+ Struct.internalBinaryWrite(
2633
+ message.custom,
2634
+ writer.tag(5, WireType.LengthDelimited).fork(),
2635
+ options,
2636
+ ).join();
2637
+ /* google.protobuf.Timestamp created_at = 6; */
2638
+ if (message.createdAt)
2639
+ Timestamp.internalBinaryWrite(
2640
+ message.createdAt,
2641
+ writer.tag(6, WireType.LengthDelimited).fork(),
2642
+ options,
2643
+ ).join();
2644
+ /* google.protobuf.Timestamp updated_at = 7; */
2645
+ if (message.updatedAt)
2646
+ Timestamp.internalBinaryWrite(
2647
+ message.updatedAt,
2648
+ writer.tag(7, WireType.LengthDelimited).fork(),
2649
+ options,
2650
+ ).join();
2651
+ let u = options.writeUnknownFields;
2652
+ if (u !== false)
2653
+ (u == true ? UnknownFieldHandler.onWrite : u)(
2654
+ this.typeName,
2655
+ message,
2656
+ writer,
2657
+ );
2658
+ return writer;
2659
+ }
2660
+ }
2661
+ /**
2662
+ * @generated MessageType for protobuf message stream.video.sfu.models.Call
2663
+ */
2664
+ export const Call = new Call$Type();
2665
+ // @generated message type with reflection information, may provide speed optimized methods
2659
2666
  class CallGrants$Type extends MessageType<CallGrants> {
2660
2667
  constructor() {
2661
2668
  super('stream.video.sfu.models.CallGrants', [
@@ -0,0 +1,5 @@
1
+ import { getLogger } from '../logger';
2
+
3
+ export const ensureExhausted = (x: never, message: string) => {
4
+ getLogger(['helpers'])('warn', message, x);
5
+ };
@@ -0,0 +1,43 @@
1
+ export type PromiseWithResolvers<T> = {
2
+ promise: Promise<T>;
3
+ resolve: (value: T | PromiseLike<T>) => void;
4
+ reject: (reason: any) => void;
5
+ isResolved: boolean;
6
+ isRejected: boolean;
7
+ };
8
+
9
+ /**
10
+ * Creates a new promise with resolvers.
11
+ *
12
+ * Based on:
13
+ * - https://github.com/tc39/proposal-promise-with-resolvers/blob/main/polyfills.js
14
+ */
15
+ export const promiseWithResolvers = <T = void>(): PromiseWithResolvers<T> => {
16
+ let resolve: (value: T | PromiseLike<T>) => void;
17
+ let reject: (reason: any) => void;
18
+ const promise = new Promise<T>((_resolve, _reject) => {
19
+ resolve = _resolve;
20
+ reject = _reject;
21
+ });
22
+
23
+ let isResolved = false;
24
+ let isRejected = false;
25
+
26
+ const resolver = (value: T | PromiseLike<T>) => {
27
+ isResolved = true;
28
+ resolve(value);
29
+ };
30
+
31
+ const rejecter = (reason: any) => {
32
+ isRejected = true;
33
+ reject(reason);
34
+ };
35
+
36
+ return {
37
+ promise,
38
+ resolve: resolver,
39
+ reject: rejecter,
40
+ isResolved,
41
+ isRejected,
42
+ };
43
+ };
package/src/logger.ts CHANGED
@@ -57,9 +57,11 @@ export const setLogLevel = (l: LogLevel) => {
57
57
  level = l;
58
58
  };
59
59
 
60
+ export const getLogLevel = (): LogLevel => level;
61
+
60
62
  export const getLogger = (withTags?: string[]) => {
61
63
  const loggerMethod = logger || logToConsole;
62
- const tags = (withTags || []).join(':');
64
+ const tags = (withTags || []).filter(Boolean).join(':');
63
65
  const result: Logger = (logLevel, message, ...args) => {
64
66
  if (logLevels[logLevel] >= logLevels[level]) {
65
67
  loggerMethod(logLevel, `[${tags}]: ${message}`, ...args);
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { RpcError, UnaryCall } from '@protobuf-ts/runtime-rpc';
3
+ import { TwirpErrorCode } from '@protobuf-ts/twirp-transport';
4
+ import { retryable, SfuResponseWithError } from '../retryable';
5
+
6
+ interface TestResponseWithError extends SfuResponseWithError {
7
+ value: number;
8
+ }
9
+
10
+ describe('retryable', () => {
11
+ it('retries the RPC when the SFU instructs to do so', async () => {
12
+ // @ts-expect-error incomplete unary call
13
+ const rpc = vi.fn<any[], UnaryCall<{}, TestResponseWithError>>(() => {
14
+ if (rpc.mock.calls.length <= 2) {
15
+ return { response: { error: { shouldRetry: true } } };
16
+ }
17
+ return { response: { value: 10 } };
18
+ });
19
+
20
+ const result = await retryable(rpc);
21
+ expect(result).toBeDefined();
22
+ expect(result.response.value).toBe(10);
23
+ expect(rpc).toHaveBeenCalledTimes(3);
24
+ });
25
+
26
+ it('retries when the RPC fails', async () => {
27
+ // @ts-expect-error incomplete unary call
28
+ const rpc = vi.fn<any[], UnaryCall<{}, TestResponseWithError>>(() => {
29
+ if (rpc.mock.calls.length === 1) throw new Error('failed');
30
+ return { response: { value: 10 } };
31
+ });
32
+
33
+ const result = await retryable(rpc);
34
+ expect(result).toBeDefined();
35
+ expect(result.response.value).toBe(10);
36
+ expect(rpc).toHaveBeenCalledTimes(2);
37
+ });
38
+
39
+ it('stops retrying when the RPC is rejected with cancellation error', async () => {
40
+ // @ts-expect-error incomplete unary call
41
+ const rpc = vi.fn<any[], UnaryCall<{}, TestResponseWithError>>(() => {
42
+ if (rpc.mock.calls.length <= 1) {
43
+ throw new Error('Generic error, should retry');
44
+ }
45
+ if (rpc.mock.calls.length === 2) {
46
+ throw new RpcError(
47
+ 'Request aborted, should not retry',
48
+ TwirpErrorCode[TwirpErrorCode.cancelled],
49
+ );
50
+ }
51
+ });
52
+
53
+ const result = retryable(rpc);
54
+ await expect(result).rejects.toThrow('Request aborted, should not retry');
55
+ expect(rpc).toHaveBeenCalledTimes(2);
56
+ });
57
+
58
+ it('stops retrying when the aborted via signal', async () => {
59
+ const controller = new AbortController();
60
+ const rpc = vi.fn<any[], UnaryCall<{}, TestResponseWithError>>(() => {
61
+ if (rpc.mock.calls.length <= 1) {
62
+ throw new Error('Generic error, should retry');
63
+ }
64
+ controller.abort();
65
+ throw new Error('Request aborted, should not retry');
66
+ });
67
+
68
+ const result = retryable(rpc, controller.signal);
69
+ await expect(result).rejects.toThrow('Request aborted, should not retry');
70
+ expect(rpc).toHaveBeenCalledTimes(2);
71
+ });
72
+ });
@@ -10,6 +10,7 @@ import {
10
10
  TwirpOptions,
11
11
  } from '@protobuf-ts/twirp-transport';
12
12
  import { SignalServerClient } from '../gen/video/sfu/signal_rpc/signal.client';
13
+ import { Logger, LogLevel } from '../coordinator/connection/types';
13
14
 
14
15
  const defaultOptions: TwirpOptions = {
15
16
  baseUrl: '',
@@ -36,6 +37,26 @@ export const withHeaders = (
36
37
  };
37
38
  };
38
39
 
40
+ export const withRequestLogger = (
41
+ logger: Logger,
42
+ level: LogLevel,
43
+ ): RpcInterceptor => {
44
+ return {
45
+ interceptUnary: (
46
+ next: NextUnaryFn,
47
+ method: MethodInfo,
48
+ input: object,
49
+ options: RpcOptions,
50
+ ): UnaryCall => {
51
+ logger(level, `Calling SFU RPC method ${method.name}`, {
52
+ input,
53
+ options,
54
+ });
55
+ return next(method, input, options);
56
+ },
57
+ };
58
+ };
59
+
39
60
  /**
40
61
  * Creates new SignalServerClient instance.
41
62
  *
package/src/rpc/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './createClient';
2
+ export * from './retryable';