@observertc/observer-js 0.42.9 → 1.0.1-24e14e5.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 (287) hide show
  1. package/.prettierrc +6 -6
  2. package/README.md +0 -104
  3. package/lib/ObservedCall.d.ts +61 -43
  4. package/lib/ObservedCall.d.ts.map +1 -1
  5. package/lib/ObservedCall.js +172 -108
  6. package/lib/ObservedCallSummary.d.ts +18 -0
  7. package/lib/ObservedCallSummary.d.ts.map +1 -0
  8. package/lib/ObservedCertificate.d.ts +19 -0
  9. package/lib/ObservedCertificate.d.ts.map +1 -0
  10. package/lib/ObservedCertificate.js +38 -0
  11. package/lib/ObservedClient.d.ts +79 -130
  12. package/lib/ObservedClient.d.ts.map +1 -1
  13. package/lib/ObservedClient.js +463 -744
  14. package/lib/ObservedClientEventMonitor.d.ts +89 -0
  15. package/lib/ObservedClientEventMonitor.d.ts.map +1 -0
  16. package/lib/ObservedClientEventMonitor.js +247 -0
  17. package/lib/ObservedClientSummary.d.ts +23 -0
  18. package/lib/ObservedClientSummary.d.ts.map +1 -0
  19. package/lib/ObservedClientSummary.js +2 -0
  20. package/lib/ObservedCodec.d.ts +22 -0
  21. package/lib/ObservedCodec.d.ts.map +1 -0
  22. package/lib/ObservedCodec.js +45 -0
  23. package/lib/ObservedDataChannel.d.ts +25 -69
  24. package/lib/ObservedDataChannel.d.ts.map +1 -1
  25. package/lib/ObservedDataChannel.js +69 -109
  26. package/lib/ObservedIceCandidate.d.ts +29 -0
  27. package/lib/ObservedIceCandidate.d.ts.map +1 -0
  28. package/lib/ObservedIceCandidate.js +59 -0
  29. package/lib/ObservedIceCandidatePair.d.ts +44 -0
  30. package/lib/ObservedIceCandidatePair.d.ts.map +1 -0
  31. package/lib/ObservedIceCandidatePair.js +111 -0
  32. package/lib/ObservedIceTransport.d.ts +36 -0
  33. package/lib/ObservedIceTransport.d.ts.map +1 -0
  34. package/lib/ObservedIceTransport.js +93 -0
  35. package/lib/ObservedInboundRtp.d.ts +92 -0
  36. package/lib/ObservedInboundRtp.d.ts.map +1 -0
  37. package/lib/ObservedInboundRtp.js +193 -0
  38. package/lib/ObservedInboundTrack.d.ts +31 -0
  39. package/lib/ObservedInboundTrack.d.ts.map +1 -0
  40. package/lib/ObservedInboundTrack.js +59 -0
  41. package/lib/ObservedMediaPlayout.d.ts +22 -0
  42. package/lib/ObservedMediaPlayout.d.ts.map +1 -0
  43. package/lib/ObservedMediaPlayout.js +42 -0
  44. package/lib/ObservedMediaSource.d.ts +28 -0
  45. package/lib/ObservedMediaSource.d.ts.map +1 -0
  46. package/lib/ObservedMediaSource.js +55 -0
  47. package/lib/ObservedOutboundRtp.d.ts +62 -0
  48. package/lib/ObservedOutboundRtp.d.ts.map +1 -0
  49. package/lib/ObservedOutboundRtp.js +144 -0
  50. package/lib/ObservedOutboundTrack.d.ts +31 -0
  51. package/lib/ObservedOutboundTrack.d.ts.map +1 -0
  52. package/lib/ObservedOutboundTrack.js +59 -0
  53. package/lib/ObservedPeerConnection.d.ts +132 -80
  54. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  55. package/lib/ObservedPeerConnection.js +646 -357
  56. package/lib/ObservedPeerConnectionTransport.d.ts +17 -0
  57. package/lib/ObservedPeerConnectionTransport.d.ts.map +1 -0
  58. package/lib/ObservedPeerConnectionTransport.js +34 -0
  59. package/lib/ObservedRemoteInboundRtp.d.ts +30 -0
  60. package/lib/ObservedRemoteInboundRtp.d.ts.map +1 -0
  61. package/lib/ObservedRemoteInboundRtp.js +60 -0
  62. package/lib/ObservedRemoteOutboundRtp.d.ts +30 -0
  63. package/lib/ObservedRemoteOutboundRtp.d.ts.map +1 -0
  64. package/lib/ObservedRemoteOutboundRtp.js +60 -0
  65. package/lib/ObservedTURN.d.ts +31 -0
  66. package/lib/ObservedTURN.d.ts.map +1 -0
  67. package/lib/ObservedTURN.js +58 -0
  68. package/lib/ObservedTurnServer.d.ts +25 -0
  69. package/lib/ObservedTurnServer.d.ts.map +1 -0
  70. package/lib/ObservedTurnServer.js +59 -0
  71. package/lib/Observer.d.ts +44 -51
  72. package/lib/Observer.d.ts.map +1 -1
  73. package/lib/Observer.js +131 -226
  74. package/lib/ObserverEventMonitor.d.ts +104 -0
  75. package/lib/ObserverEventMonitor.d.ts.map +1 -0
  76. package/lib/ObserverEventMonitor.js +307 -0
  77. package/lib/ObserverSummary.d.ts +13 -0
  78. package/lib/ObserverSummary.d.ts.map +1 -0
  79. package/lib/ObserverSummary.js +2 -0
  80. package/lib/common/Middleware.d.ts +10 -1
  81. package/lib/common/Middleware.d.ts.map +1 -1
  82. package/lib/common/Middleware.js +55 -36
  83. package/lib/common/logger.d.ts +8 -4
  84. package/lib/common/logger.d.ts.map +1 -1
  85. package/lib/common/logger.js +44 -68
  86. package/lib/common/types.d.ts +0 -21
  87. package/lib/common/types.d.ts.map +1 -1
  88. package/lib/common/types.js +1 -0
  89. package/lib/common/utils.d.ts +3 -1
  90. package/lib/common/utils.d.ts.map +1 -1
  91. package/lib/common/utils.js +23 -3
  92. package/lib/detectors/Detector.d.ts +5 -0
  93. package/lib/detectors/Detector.d.ts.map +1 -0
  94. package/lib/detectors/Detector.js +3 -0
  95. package/lib/detectors/Detectors.d.ts +11 -0
  96. package/lib/detectors/Detectors.d.ts.map +1 -0
  97. package/lib/detectors/Detectors.js +34 -0
  98. package/lib/index.d.ts +25 -25
  99. package/lib/index.d.ts.map +1 -1
  100. package/lib/index.js +43 -41
  101. package/lib/monitors/TurnUsageMonitor.d.ts +0 -52
  102. package/lib/monitors/TurnUsageMonitor.d.ts.map +1 -1
  103. package/lib/monitors/TurnUsageMonitor.js +145 -120
  104. package/lib/schema/ClientEventTypes.d.ts +160 -0
  105. package/lib/schema/ClientEventTypes.d.ts.map +1 -0
  106. package/lib/schema/ClientEventTypes.js +38 -0
  107. package/lib/schema/ClientMetaTypes.d.ts +34 -0
  108. package/lib/schema/ClientMetaTypes.d.ts.map +1 -0
  109. package/lib/schema/ClientMetaTypes.js +16 -0
  110. package/lib/schema/ClientSample.d.ts +1333 -0
  111. package/lib/schema/ClientSample.d.ts.map +1 -0
  112. package/lib/schema/ClientSample.js +4 -0
  113. package/lib/scores/CalculatedScore.d.ts +6 -0
  114. package/lib/scores/CalculatedScore.d.ts.map +1 -0
  115. package/lib/scores/CalculatedScore.js +2 -0
  116. package/lib/scores/DefaultCallScoreCalculator.d.ts +7 -0
  117. package/lib/scores/DefaultCallScoreCalculator.d.ts.map +1 -0
  118. package/lib/scores/DefaultCallScoreCalculator.js +21 -0
  119. package/lib/scores/ScoreCalculator.d.ts +4 -0
  120. package/lib/scores/ScoreCalculator.d.ts.map +1 -0
  121. package/lib/scores/ScoreCalculator.js +2 -0
  122. package/lib/updaters/CallUpdater.d.ts +5 -0
  123. package/lib/updaters/CallUpdater.d.ts.map +1 -0
  124. package/lib/updaters/CallUpdater.js +2 -0
  125. package/lib/updaters/ObserverUpdater.d.ts +5 -0
  126. package/lib/updaters/ObserverUpdater.d.ts.map +1 -0
  127. package/lib/updaters/ObserverUpdater.js +2 -0
  128. package/lib/updaters/OnAllCallObserverUpdater_.d.ts +13 -0
  129. package/lib/updaters/OnAllCallObserverUpdater_.d.ts.map +1 -0
  130. package/lib/updaters/OnAllCallObserverUpdater_.js +38 -0
  131. package/lib/updaters/OnAllClientCallUpdater.d.ts +13 -0
  132. package/lib/updaters/OnAllClientCallUpdater.d.ts.map +1 -0
  133. package/lib/updaters/OnAllClientCallUpdater.js +37 -0
  134. package/lib/updaters/OnAnyCallObserverUpdater_.d.ts +11 -0
  135. package/lib/updaters/OnAnyCallObserverUpdater_.d.ts.map +1 -0
  136. package/lib/updaters/OnAnyCallObserverUpdater_.js +27 -0
  137. package/lib/updaters/OnAnyClientCallUpdater.d.ts +11 -0
  138. package/lib/updaters/OnAnyClientCallUpdater.d.ts.map +1 -0
  139. package/lib/updaters/OnAnyClientCallUpdater.js +27 -0
  140. package/lib/updaters/OnIntervalUpdater.d.ts +10 -0
  141. package/lib/updaters/OnIntervalUpdater.d.ts.map +1 -0
  142. package/lib/updaters/OnIntervalUpdater.js +17 -0
  143. package/lib/updaters/Updater.d.ts +6 -0
  144. package/lib/updaters/Updater.d.ts.map +1 -0
  145. package/lib/updaters/Updater.js +2 -0
  146. package/lib/utils/RemoteTrackResolver.d.ts +7 -0
  147. package/lib/utils/RemoteTrackResolver.d.ts.map +1 -0
  148. package/lib/utils/RemoteTrackResolver.js +2 -0
  149. package/package.json +4 -4
  150. package/src/ObservedCall.ts +262 -166
  151. package/src/ObservedCallSummary.ts +22 -0
  152. package/src/ObservedCertificate.ts +43 -0
  153. package/src/ObservedClient.ts +519 -1020
  154. package/src/ObservedClientEventMonitor.ts +301 -0
  155. package/src/ObservedClientSummary.ts +24 -0
  156. package/src/ObservedCodec.ts +49 -0
  157. package/src/ObservedDataChannel.ts +71 -181
  158. package/src/ObservedIceCandidate.ts +64 -0
  159. package/src/ObservedIceCandidatePair.ts +118 -0
  160. package/src/ObservedIceTransport.ts +99 -0
  161. package/src/ObservedInboundRtp.ts +205 -0
  162. package/src/ObservedInboundTrack.ts +73 -0
  163. package/src/ObservedMediaPlayout.ts +49 -0
  164. package/src/ObservedMediaSource.ts +60 -0
  165. package/src/ObservedOutboundRtp.ts +156 -0
  166. package/src/ObservedOutboundTrack.ts +72 -0
  167. package/src/ObservedPeerConnection.ts +854 -408
  168. package/src/ObservedPeerConnectionTransport.ts +38 -0
  169. package/src/ObservedRemoteInboundRtp.ts +64 -0
  170. package/src/ObservedRemoteOutboundRtp.ts +65 -0
  171. package/src/ObservedTURN.ts +86 -0
  172. package/src/ObservedTurnServer.ts +72 -0
  173. package/src/Observer.ts +176 -295
  174. package/src/ObserverEventMonitor.ts +385 -0
  175. package/src/ObserverSummary.ts +15 -0
  176. package/src/common/Middleware.ts +70 -42
  177. package/src/common/logger.ts +60 -69
  178. package/src/common/types.ts +0 -42
  179. package/src/common/utils.ts +24 -2
  180. package/src/detectors/Detector.ts +6 -0
  181. package/src/detectors/Detectors.ts +38 -0
  182. package/src/index.ts +25 -90
  183. package/src/monitors/TurnUsageMonitor.ts +164 -164
  184. package/src/schema/ClientEventTypes.ts +176 -0
  185. package/src/schema/ClientMetaTypes.ts +41 -0
  186. package/src/schema/ClientSample.ts +1682 -0
  187. package/src/scores/CalculatedScore.ts +6 -0
  188. package/src/scores/DefaultCallScoreCalculator.ts +23 -0
  189. package/src/scores/ScoreCalculator.ts +3 -0
  190. package/src/updaters/CallUpdater.ts +5 -0
  191. package/src/updaters/ObserverUpdater.ts +5 -0
  192. package/src/updaters/OnAllCallObserverUpdater_.ts +46 -0
  193. package/src/updaters/OnAllClientCallUpdater.ts +46 -0
  194. package/src/updaters/OnAnyCallObserverUpdater_.ts +32 -0
  195. package/src/updaters/OnAnyClientCallUpdater.ts +32 -0
  196. package/src/updaters/OnIntervalUpdater.ts +19 -0
  197. package/src/updaters/Updater.ts +5 -0
  198. package/src/utils/RemoteTrackResolver.ts +12 -0
  199. package/tsconfig.json +3 -3
  200. package/lib/ObservedICE.d.ts +0 -64
  201. package/lib/ObservedICE.d.ts.map +0 -1
  202. package/lib/ObservedICE.js +0 -141
  203. package/lib/ObservedInboundAudioTrack.d.ts +0 -96
  204. package/lib/ObservedInboundAudioTrack.d.ts.map +0 -1
  205. package/lib/ObservedInboundAudioTrack.js +0 -217
  206. package/lib/ObservedInboundVideoTrack.d.ts +0 -101
  207. package/lib/ObservedInboundVideoTrack.d.ts.map +0 -1
  208. package/lib/ObservedInboundVideoTrack.js +0 -232
  209. package/lib/ObservedOutboundAudioTrack.d.ts +0 -87
  210. package/lib/ObservedOutboundAudioTrack.d.ts.map +0 -1
  211. package/lib/ObservedOutboundAudioTrack.js +0 -186
  212. package/lib/ObservedOutboundVideoTrack.d.ts +0 -91
  213. package/lib/ObservedOutboundVideoTrack.d.ts.map +0 -1
  214. package/lib/ObservedOutboundVideoTrack.js +0 -193
  215. package/lib/ObservedSfu.d.ts +0 -59
  216. package/lib/ObservedSfu.d.ts.map +0 -1
  217. package/lib/ObservedSfu.js +0 -157
  218. package/lib/ObservedSfuInboundRtpPad.d.ts +0 -42
  219. package/lib/ObservedSfuInboundRtpPad.d.ts.map +0 -1
  220. package/lib/ObservedSfuInboundRtpPad.js +0 -71
  221. package/lib/ObservedSfuOutboundRtpPad.d.ts +0 -42
  222. package/lib/ObservedSfuOutboundRtpPad.d.ts.map +0 -1
  223. package/lib/ObservedSfuOutboundRtpPad.js +0 -72
  224. package/lib/ObservedSfuSctpChannel.d.ts +0 -42
  225. package/lib/ObservedSfuSctpChannel.d.ts.map +0 -1
  226. package/lib/ObservedSfuSctpChannel.js +0 -57
  227. package/lib/ObservedSfuTransport.d.ts +0 -58
  228. package/lib/ObservedSfuTransport.d.ts.map +0 -1
  229. package/lib/ObservedSfuTransport.js +0 -120
  230. package/lib/RemoteTrackAssigner.d.ts +0 -14
  231. package/lib/RemoteTrackAssigner.d.ts.map +0 -1
  232. package/lib/RemoteTrackAssigner.js +0 -52
  233. package/lib/ReportsCollector.d.ts +0 -109
  234. package/lib/ReportsCollector.d.ts.map +0 -1
  235. package/lib/ReportsCollector.js +0 -207
  236. package/lib/common/CalculatedScore.d.ts +0 -80
  237. package/lib/common/CalculatedScore.d.ts.map +0 -1
  238. package/lib/common/CalculatedScore.js +0 -162
  239. package/lib/common/CallEventType.d.ts +0 -63
  240. package/lib/common/CallEventType.d.ts.map +0 -1
  241. package/lib/common/CallEventType.js +0 -39
  242. package/lib/common/CallMetaReports.d.ts +0 -42
  243. package/lib/common/CallMetaReports.d.ts.map +0 -1
  244. package/lib/common/CallMetaReports.js +0 -36
  245. package/lib/common/QualityScoreUtils.d.ts +0 -1
  246. package/lib/common/QualityScoreUtils.d.ts.map +0 -1
  247. package/lib/common/QualityScoreUtils.js +0 -1
  248. package/lib/common/TypedEventEmitter.d.ts +0 -22
  249. package/lib/common/TypedEventEmitter.d.ts.map +0 -1
  250. package/lib/common/TypedEventEmitter.js +0 -11
  251. package/lib/common/callEventReports.d.ts +0 -10
  252. package/lib/common/callEventReports.d.ts.map +0 -1
  253. package/lib/common/callEventReports.js +0 -112
  254. package/lib/monitors/CallSummary.d.ts +0 -37
  255. package/lib/monitors/CallSummary.d.ts.map +0 -1
  256. package/lib/monitors/CallSummaryMonitor.d.ts +0 -32
  257. package/lib/monitors/CallSummaryMonitor.d.ts.map +0 -1
  258. package/lib/monitors/CallSummaryMonitor.js +0 -152
  259. package/lib/monitors/ClientIssueMonitor.d.ts +0 -32
  260. package/lib/monitors/ClientIssueMonitor.d.ts.map +0 -1
  261. package/lib/monitors/ClientIssueMonitor.js +0 -49
  262. package/lib/monitors/SfuServerMonitor.d.ts +0 -79
  263. package/lib/monitors/SfuServerMonitor.d.ts.map +0 -1
  264. package/lib/monitors/SfuServerMonitor.js +0 -198
  265. package/src/ObservedICE.ts +0 -237
  266. package/src/ObservedInboundAudioTrack.ts +0 -344
  267. package/src/ObservedInboundVideoTrack.ts +0 -361
  268. package/src/ObservedOutboundAudioTrack.ts +0 -307
  269. package/src/ObservedOutboundVideoTrack.ts +0 -319
  270. package/src/ObservedSfu.ts +0 -231
  271. package/src/ObservedSfuInboundRtpPad.ts +0 -106
  272. package/src/ObservedSfuOutboundRtpPad.ts +0 -107
  273. package/src/ObservedSfuSctpChannel.ts +0 -103
  274. package/src/ObservedSfuTransport.ts +0 -185
  275. package/src/RemoteTrackAssigner.ts +0 -68
  276. package/src/ReportsCollector.ts +0 -336
  277. package/src/common/CalculatedScore.ts +0 -188
  278. package/src/common/CallEventType.ts +0 -69
  279. package/src/common/CallMetaReports.ts +0 -82
  280. package/src/common/QualityScoreUtils.ts +0 -0
  281. package/src/common/TypedEventEmitter.ts +0 -32
  282. package/src/common/callEventReports.ts +0 -178
  283. package/src/monitors/CallSummary.ts +0 -38
  284. package/src/monitors/CallSummaryMonitor.ts +0 -192
  285. package/src/monitors/ClientIssueMonitor.ts +0 -88
  286. package/src/monitors/SfuServerMonitor.ts +0 -285
  287. /package/lib/{monitors/CallSummary.js → ObservedCallSummary.js} +0 -0
@@ -1,123 +1,96 @@
1
- import { Browser, ClientSample, Engine, IceLocalCandidate, IceRemoteCandidate, MediaCodecStats, MediaDevice, OperationSystem, Platform } from '@observertc/sample-schemas-js';
2
- import { ObservedCall } from './ObservedCall';
3
1
  import { EventEmitter } from 'events';
4
2
  import { ObservedPeerConnection } from './ObservedPeerConnection';
5
3
  import { createLogger } from './common/logger';
6
- import { CallMetaType, createCallMetaReport } from './common/CallMetaReports';
7
4
  // eslint-disable-next-line camelcase
8
- import { PartialBy, isValidUuid } from './common/utils';
9
- import { CallEventType } from './common/CallEventType';
10
- import { ObservedSfu } from './ObservedSfu';
11
- import { ClientIssue } from './monitors/CallSummary';
12
- import { CallEventReport } from '@observertc/report-schemas-js';
13
- import { CalculatedScore } from './common/CalculatedScore';
5
+ import { ClientEvent, ClientMetaData, ClientSample, PeerConnectionSample, ClientIssue, ExtensionStat } from './schema/ClientSample';
6
+ import * as MetaData from './schema/ClientMetaTypes';
7
+ import { ClientEventTypes } from './schema/ClientEventTypes';
8
+ import { ObservedCall } from './ObservedCall';
9
+ import { ClientMetaTypes } from './schema/ClientMetaTypes';
10
+ import { parseJsonAs } from './common/utils';
11
+ import { CalculatedScore } from './scores/CalculatedScore';
12
+ import { Detectors } from './detectors/Detectors';
14
13
 
15
14
  const logger = createLogger('ObservedClient');
16
15
 
17
- export type ObservedClientModel= {
16
+ export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
18
17
  clientId: string;
19
- mediaUnitId: string;
20
- userId?: string,
21
- marker?: string,
22
- operationSystem?: OperationSystem,
23
- engine?: Engine,
24
- platform?: Platform,
25
- browser?: Browser,
26
- coordinates?: {
27
- latitude: number;
28
- longitude: number;
29
- },
18
+ appData?: AppData;
30
19
  };
31
20
 
32
21
  export type ObservedClientEvents = {
33
- update: [{
34
- sample: ClientSample,
35
- elapsedTimeInMs: number,
36
- }],
37
- close: [],
38
- joined: [],
39
- left: [],
40
- newpeerconnection: [ObservedPeerConnection],
41
- iceconnectionstatechange: [{
42
- peerConnection: ObservedPeerConnection,
43
- state: string,
44
- }],
45
- icegatheringstatechange: [{
46
- peerConnection: ObservedPeerConnection,
47
- state: string,
48
- }],
49
- connectionstatechange: [{
50
- peerConnection: ObservedPeerConnection,
51
- state: string,
52
- }],
53
- selectedcandidatepair: [{
54
- peerConnection: ObservedPeerConnection,
55
- localCandidate: IceLocalCandidate,
56
- remoteCandidate: IceRemoteCandidate,
57
- }],
58
- issue: [ClientIssue],
59
- usingturn: [boolean],
60
- usermediaerror: [string],
61
- rejoined: [{
62
- lastJoined: number,
63
- }],
64
- score: [CalculatedScore],
22
+ update: [
23
+ {
24
+ sample: ClientSample;
25
+ elapsedTimeInMs: number;
26
+ }
27
+ ];
28
+ close: [];
29
+ joined: [];
30
+ issue: [ClientIssue];
31
+ metaData: [ClientMetaData];
32
+ rejoined: [timestamp: number];
33
+ left: [];
34
+ usingturn: [boolean];
35
+ usermediaerror: [string];
36
+ extensionStats: [ExtensionStat];
37
+ clientEvent: [ClientEvent];
38
+
39
+ newpeerconnection: [ObservedPeerConnection];
65
40
  };
66
41
 
67
- export declare interface ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> {
42
+ export declare interface ObservedClient {
68
43
  on<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
69
44
  off<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
70
45
  once<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
71
46
  emit<U extends keyof ObservedClientEvents>(event: U, ...args: ObservedClientEvents[U]): boolean;
72
- readonly appData: AppData;
73
47
  }
74
48
 
75
- type PendingPeerConnectionTimestamp = {
76
- type: 'opened' | 'closed'
77
- peerConnectionId: string;
78
- timestamp: number;
79
- }
49
+ export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
50
+ public readonly detectors: Detectors;
80
51
 
81
- type PendingMediaTrackTimestamp = {
82
- type: 'added' | 'removed'
83
- peerConnectionId: string;
84
- mediaTrackId: string;
85
- timestamp: number;
86
- }
52
+ public readonly clientId: string;
53
+ public readonly observedPeerConnections = new Map<string, ObservedPeerConnection>();
54
+ public readonly calculatedScore: CalculatedScore = {
55
+ weight: 1,
56
+ value: undefined,
57
+ };
58
+
59
+ public appData: AppData;
60
+ public attachments?: Record<string, unknown>;
87
61
 
88
- export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
89
-
90
- public readonly created = Date.now();
91
62
  public updated = Date.now();
92
- public sfuId?: string;
93
-
94
- private readonly _peerConnections = new Map<string, ObservedPeerConnection>();
95
-
96
- private _closed = false;
97
-
98
- private _acceptedSamples = 0;
99
- private _timeZoneOffsetInHours?: number;
63
+ public acceptedSamples = 0;
64
+ public closed = false;
100
65
 
66
+ public joinedAt?: number;
67
+ public leftAt?: number;
68
+ public closedAt?: number;
101
69
  // the timestamp of the CLIENT_JOINED event
102
- private _joined?: number;
103
- private _left?: number;
104
- public lastStatsTimestamp?: number;
70
+ public operationSystem?: MetaData.OperationSystem;
71
+ public engine?: MetaData.Engine;
72
+ public platform?: MetaData.Platform;
73
+ public browser?: MetaData.Browser;
74
+ public mediaConstraints: string[] = [];
105
75
 
106
- public score?: CalculatedScore;
107
76
  public usingTURN = false;
77
+ public usingTCP = false;
108
78
  public availableOutgoingBitrate = 0;
109
79
  public availableIncomingBitrate = 0;
80
+
110
81
  public totalInboundPacketsLost = 0;
111
82
  public totalInboundPacketsReceived = 0;
112
83
  public totalOutboundPacketsSent = 0;
113
84
  public totalDataChannelBytesSent = 0;
114
85
  public totalDataChannelBytesReceived = 0;
115
- public totalSentBytes = 0;
116
- public totalReceivedBytes = 0;
86
+ public totalDataChannelMessagesSent = 0;
87
+ public totalDataChannelMessagesReceived = 0;
117
88
  public totalReceivedAudioBytes = 0;
118
89
  public totalReceivedVideoBytes = 0;
119
90
  public totalSentAudioBytes = 0;
120
91
  public totalSentVideoBytes = 0;
92
+ public totalSentBytes = 0;
93
+ public totalReceivedBytes = 0;
121
94
 
122
95
  public deltaReceivedAudioBytes = 0;
123
96
  public deltaReceivedVideoBytes = 0;
@@ -125,1047 +98,573 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
125
98
  public deltaSentVideoBytes = 0;
126
99
  public deltaDataChannelBytesSent = 0;
127
100
  public deltaDataChannelBytesReceived = 0;
101
+ public deltaDataChannelMessagesSent = 0;
102
+ public deltaDataChannelMessagesReceived = 0;
128
103
  public deltaInboundPacketsLost = 0;
129
104
  public deltaInboundPacketsReceived = 0;
130
105
  public deltaOutboundPacketsSent = 0;
131
-
132
- public avgRttInMs?: number;
133
- public outboundAudioBitrate?: number;
134
- public outboundVideoBitrate?: number;
135
- public inboundAudioBitrate?: number;
136
- public inboundVideoBitrate?: number;
137
- public mediaConstraints: string[] = [];
138
-
139
- public readonly mediaDevices: MediaDevice[] = [];
140
- public readonly mediaCodecs: MediaCodecStats[] = [];
141
- public readonly userMediaErrors: string[] = [];
106
+ public deltaTransportSentBytes = 0;
107
+ public deltaTransportReceivedBytes = 0;
108
+ public deltaRttLt50Measurements = 0;
109
+ public deltaRttLt150Measurements = 0;
110
+ public deltaRttLt300Measurements = 0;
111
+ public deltaRttGtOrEq300Measurements = 0;
112
+
113
+ public currentMaxRttInMs?: number;
114
+ public currentMinRttInMs?: number;
115
+ public currentAvgRttInMs?: number;
116
+
117
+ public sendingAudioBitrate = 0;
118
+ public sendingVideoBitrate = 0;
119
+ public receivingAudioBitrate = 0;
120
+ public receivingVideoBitrate = 0;
121
+
122
+ public numberOfInboundRtpStreams = 0;
123
+ public numberOfInbundTracks = 0;
124
+ public numberOfOutboundRtpStreams = 0;
125
+ public numberOfOutboundTracks = 0;
126
+ public numberOfDataChannels = 0;
127
+
128
+ public totalRttLt50Measurements = 0;
129
+ public totalRttLt150Measurements = 0;
130
+ public totalRttLt300Measurements = 0;
131
+ public totalRttGtOrEq300Measurements = 0;
132
+ public deltaNumberOfIssues = 0;
133
+
134
+ public totalScoreSum = 0;
135
+ public numberOfScoreMeasurements = 0;
136
+ public totalNumberOfIssues = 0;
137
+
138
+ public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
142
139
  public issues: ClientIssue[] = [];
143
140
 
144
- public ωpendingPeerConnectionTimestamps: PendingPeerConnectionTimestamp[] = [];
145
- public ωpendingMediaTrackTimestamps: PendingMediaTrackTimestamp[] = [];
146
-
147
- public constructor(
148
- private readonly _model: ObservedClientModel,
149
- public readonly call: ObservedCall,
150
- public readonly appData: AppData
151
- ) {
141
+ private _injections: Pick<ClientSample, 'clientEvents' | 'clientIssues' | 'extensionStats' | 'attachments' | 'clientMetaItems'> = {};
142
+
143
+ public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) {
152
144
  super();
153
145
  this.setMaxListeners(Infinity);
154
- }
155
-
156
- public getSfu<T extends Record<string, unknown> = Record<string, unknown>>(): ObservedSfu<T> | undefined {
157
- const sfu = this.call.observer.observedSfus.get(this.sfuId ?? '');
158
-
159
- if (!sfu) return;
160
-
161
- return sfu as ObservedSfu<T>;
162
- }
163
-
164
- public get joined() {
165
- return this._joined;
166
- }
167
-
168
- public set joined(value: number | undefined) {
169
- this._joined = value;
170
-
171
- if (this._joined) {
172
- this.emit('joined');
173
- }
174
- }
175
-
176
- public get left() {
177
- return this._left;
178
- }
179
146
 
180
- public set left(value: number | undefined) {
181
- this._left = value;
147
+ this.clientId = settings.clientId;
148
+ this.appData = settings.appData ?? {} as AppData;
182
149
 
183
- if (this._left) {
184
- this.emit('left');
185
- }
186
- }
187
-
188
- public get sfu(): ObservedSfu | undefined {
189
- if (!this.sfuId) return;
190
-
191
- return this.call.observer.observedSfus.get(this.sfuId);
192
- }
193
-
194
- public get coordinates() {
195
- return this._model.coordinates;
196
- }
197
-
198
- public set coordinates(value: { latitude: number; longitude: number } | undefined) {
199
- this._model.coordinates = value;
200
- }
201
-
202
- public get clientId(): string {
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- return this._model.clientId!;
205
- }
206
-
207
- public get roomId() {
208
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209
- return this.call.roomId!;
210
- }
211
-
212
- public get serviceId() {
213
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
214
- return this.call.serviceId!;
215
- }
216
-
217
- public get callId() {
218
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
- return this.call.callId!;
220
- }
221
-
222
- public get mediaUnitId() {
223
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
224
- return this._model.mediaUnitId!;
225
- }
226
-
227
- public get reports() {
228
- return this.call.reports;
229
- }
230
-
231
- public get userId() {
232
- return this._model.userId;
233
- }
234
-
235
- public set userId(userId: string | undefined) {
236
- this._model.userId = userId;
237
- }
238
-
239
- public get timeZoneOffsetInHours() {
240
- return this._timeZoneOffsetInHours;
241
- }
242
-
243
- public get marker() {
244
- return this._model.marker;
245
- }
246
-
247
- public get browser() {
248
- return this._model.browser;
249
- }
250
-
251
- public get engine() {
252
- return this._model.engine;
253
- }
254
-
255
- public get operationSystem() {
256
- return this._model.operationSystem;
257
- }
258
-
259
- public get platform() {
260
- return this._model.platform;
150
+ this.detectors = new Detectors();
261
151
  }
262
-
263
- public get acceptedSamples() {
264
- return this._acceptedSamples;
152
+
153
+ public get numberOfPeerConnections() {
154
+ return this.observedPeerConnections.size;
265
155
  }
266
156
 
267
- public get closed() {
268
- return this._closed;
157
+ public get score() {
158
+ return this.calculatedScore.value;
269
159
  }
270
160
 
271
- public get peerConnections(): ReadonlyMap<string, ObservedPeerConnection> {
272
- return this._peerConnections;
273
- }
274
-
275
161
  public close() {
276
- if (this._closed) return;
277
- this._closed = true;
162
+ if (this.closed) return;
163
+ this.closed = true;
278
164
 
279
- Array.from(this._peerConnections.values()).forEach((peerConnection) => peerConnection.close());
165
+ Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close());
280
166
 
281
- this.emit('close');
282
- }
283
-
284
- public addEventReport(params: PartialBy<Omit<CallEventReport, 'serviceId' | 'roomId' | 'callId' | 'clientId' | 'userId' | 'marker' | 'attachments'>, 'timestamp'> & { attachments?: Record<string, unknown> }) {
285
- const {
286
- attachments,
287
- ...fields
288
- } = params;
289
-
290
- this.reports.addCallEventReport({
291
- ...fields,
292
- serviceId: this.serviceId,
293
- mediaUnitId: this.mediaUnitId,
294
- roomId: this.roomId,
295
- callId: this.callId,
296
- clientId: this.clientId,
297
- userId: this.userId,
298
- timestamp: params.timestamp ?? Date.now(),
299
- marker: this.marker,
300
- attachments: attachments ? JSON.stringify(attachments) : undefined,
301
- });
302
- }
167
+ this.closedAt = Date.now();
303
168
 
304
- public addExtensionStatsReport(extensionType: string, payload?: Record<string, unknown>) {
305
- this.reports.addClientExtensionReport({
306
- serviceId: this.serviceId,
307
- mediaUnitId: this.mediaUnitId,
308
- roomId: this.roomId,
309
- callId: this.callId,
310
- clientId: this.clientId,
311
- userId: this.userId,
312
- timestamp: Date.now(),
313
- extensionType,
314
- payload: JSON.stringify(payload),
315
- marker: this.marker,
316
- });
317
- }
318
-
319
- public addIssue(issue: ClientIssue) {
320
- try {
321
-
322
- this.reports.addCallEventReport({
323
- serviceId: this.serviceId,
324
- mediaUnitId: this.mediaUnitId,
325
- roomId: this.roomId,
326
- callId: this.callId,
327
- clientId: this.clientId,
328
- userId: this.userId,
329
-
330
- name: CallEventType.CLIENT_ISSUE,
331
- value: issue.severity,
332
- peerConnectionId: issue.peerConnectionId,
333
- mediaTrackId: issue.trackId,
334
- message: issue.description,
335
- timestamp: issue.timestamp ?? Date.now(),
336
- attachments: issue.attachments ? JSON.stringify(issue.attachments): undefined,
337
- });
338
- } catch (err) {
339
- logger.warn(`Error adding client issue: ${(err as Error)?.message}`);
340
- }
341
-
342
- this._addAndEmitIssue(issue);
169
+ this.emit('close');
343
170
  }
344
171
 
345
172
  public accept(sample: ClientSample): void {
346
- if (this._closed) throw new Error(`Client ${this.clientId} is closed`);
347
- if (sample.clientId && sample.clientId !== 'NULL' && sample.clientId !== this.clientId) {
348
- throw new Error(`Sample client id (${sample.clientId}) does not match the client id of the observed client (${this.clientId})`);
349
- }
350
- const now = Date.now();
173
+ if (this.closed) throw new Error(`Client ${this.clientId} is closed`);
351
174
 
352
- ++this._acceptedSamples;
353
- if (0 < this.issues.length) {
354
- // we reset the issues every time we accept a new sample
355
- this.issues = [];
356
- }
357
- for (const peerConnection of this._peerConnections.values()) {
358
- if (peerConnection.closed) continue;
359
- peerConnection.resetMetrics();
360
- }
361
-
362
- if (this._model.userId) {
363
- if (sample.userId && sample.userId !== 'NULL' && sample.userId !== this._model.userId) {
364
- this._model.userId = sample.userId;
365
- }
366
- } else if (sample.userId && sample.userId !== 'NULL') {
367
- this._model.userId = sample.userId;
368
- }
175
+ const now = Date.now();
176
+ const elapsedInMs = now - this.updated;
177
+ const elapsedInSeconds = elapsedInMs / 1000;
178
+ let sumOfRtts = 0;
179
+ let numberOfRttMeasurements = 0;
369
180
 
370
- if (this._model.marker !== sample.marker) {
371
- this._model.marker = sample.marker;
372
- this._peerConnections.forEach((peerConnection) => (peerConnection.marker = sample.marker));
373
- }
181
+ ++this.acceptedSamples;
182
+
183
+ this.availableIncomingBitrate = 0;
184
+ this.availableOutgoingBitrate = 0;
185
+ this.deltaDataChannelBytesReceived = 0;
186
+ this.deltaDataChannelBytesSent = 0;
187
+ this.deltaDataChannelMessagesReceived = 0;
188
+ this.deltaDataChannelMessagesSent = 0;
189
+ this.deltaInboundPacketsLost = 0;
190
+ this.deltaInboundPacketsReceived = 0;
191
+ this.deltaOutboundPacketsSent = 0;
192
+ this.deltaReceivedAudioBytes = 0;
193
+ this.deltaReceivedVideoBytes = 0;
194
+ this.deltaSentAudioBytes = 0;
195
+ this.deltaSentVideoBytes = 0;
196
+ this.deltaTransportReceivedBytes = 0;
197
+ this.deltaTransportSentBytes = 0;
198
+ this.deltaRttLt50Measurements = 0;
199
+ this.deltaRttLt150Measurements = 0;
200
+ this.deltaRttLt300Measurements = 0;
201
+ this.deltaRttGtOrEq300Measurements = 0;
202
+ this.deltaNumberOfIssues = 0;
203
+
204
+ this.numberOfDataChannels = 0;
205
+ this.numberOfInboundRtpStreams = 0;
206
+ this.numberOfInbundTracks = 0;
207
+ this.numberOfOutboundRtpStreams = 0;
208
+ this.numberOfOutboundTracks = 0;
209
+ this.usingTURN = false;
210
+ this.usingTCP = false;
211
+ this.currentMinRttInMs = undefined;
212
+ this.currentMaxRttInMs = undefined;
374
213
 
375
- if (this._timeZoneOffsetInHours !== sample.timeZoneOffsetInHours) {
376
- this._timeZoneOffsetInHours = sample.timeZoneOffsetInHours;
377
- }
214
+ this._mergeInjections(sample);
378
215
 
379
- if (sample.os && (
380
- this._model.operationSystem?.name !== sample.os.name ||
381
- this._model.operationSystem?.version !== sample.os.version ||
382
- this._model.operationSystem?.versionName !== sample.os.versionName
383
- )) {
384
- this._model.operationSystem = sample.os;
385
-
386
- const callMetaReport = createCallMetaReport(
387
- this.serviceId,
388
- this.mediaUnitId,
389
- this.roomId,
390
- this.callId,
391
- this.clientId,
392
- {
393
- type: CallMetaType.OPERATION_SYSTEM,
394
- payload: sample.os,
395
- },
396
- this.userId,
397
- undefined,
398
- sample.timestamp
399
- );
400
-
401
- this.reports.addCallMetaReport(callMetaReport);
402
- }
216
+ const clientEventsPostBuffer: ClientEvent[] = [];
403
217
 
404
- if (sample.engine && (
405
- this._model.engine?.name !== sample.engine.name ||
406
- this._model.engine?.version !== sample.engine.version
407
- )) {
408
- this._model.engine = sample.engine;
409
-
410
- const callMetaReport = createCallMetaReport(
411
- this.serviceId,
412
- this.mediaUnitId,
413
- this.roomId,
414
- this.callId,
415
- this.clientId,
416
- {
417
- type: CallMetaType.ENGINE,
418
- payload: sample.engine,
419
- },
420
- this.userId,
421
- undefined,
422
- sample.timestamp
423
- );
424
-
425
- this.reports.addCallMetaReport(callMetaReport);
426
- }
218
+ for (const clientEvent of sample.clientEvents ?? []) {
219
+ this._processClientEvent(clientEvent, clientEventsPostBuffer);
427
220
 
428
- if (sample.platform && (
429
- this._model.platform?.model !== sample.platform.model ||
430
- this._model.platform?.type !== sample.platform.type ||
431
- this._model.platform?.vendor !== sample.platform.vendor
432
- )) {
433
- this._model.platform = sample.platform;
434
-
435
- const callMetaReport = createCallMetaReport(
436
- this.serviceId,
437
- this.mediaUnitId,
438
- this.roomId,
439
- this.callId,
440
- this.clientId,
441
- {
442
- type: CallMetaType.PLATFORM,
443
- payload: sample.platform,
444
- },
445
- this.userId,
446
- undefined,
447
- sample.timestamp
448
- );
449
-
450
- this.reports.addCallMetaReport(callMetaReport);
221
+ this.call.observer.emit('client-event', this, clientEvent);
451
222
  }
452
223
 
453
- if (sample.browser && (
454
- this._model.browser?.name !== sample.browser.name ||
455
- this._model.browser?.version !== sample.browser.version
456
- )) {
457
- this._model.browser = sample.browser;
458
-
459
- const callMetaReport = createCallMetaReport(
460
- this.serviceId,
461
- this.mediaUnitId,
462
- this.roomId,
463
- this.callId,
464
- this.clientId,
465
- {
466
- type: CallMetaType.BROWSER,
467
- payload: sample.browser,
468
- },
469
- this.userId,
470
- undefined,
471
- sample.timestamp
472
- );
473
-
474
- this.reports.addCallMetaReport(callMetaReport);
224
+ for (const metaData of sample.clientMetaItems ?? []) {
225
+ this.addMetadata(metaData);
475
226
  }
476
227
 
477
- for (const mediaConstraint of sample.mediaConstraints ?? []) {
478
- const callMetaReport = createCallMetaReport(
479
- this.serviceId,
480
- this.mediaUnitId,
481
- this.roomId,
482
- this.callId,
483
- this.clientId,
484
- {
485
- type: CallMetaType.MEDIA_CONSTRAINT,
486
- payload: mediaConstraint,
487
- },
488
- this.userId,
489
- undefined,
490
- sample.timestamp
491
- );
492
-
493
- this.reports.addCallMetaReport(callMetaReport);
494
- this.mediaConstraints.push(mediaConstraint);
495
- }
228
+ for (const issue of sample.clientIssues ?? []) {
229
+ this.addIssue(issue);
496
230
 
497
- for (const localSDP of sample.localSDPs ?? []) {
498
- const callMetaReport = createCallMetaReport(
499
- this.serviceId,
500
- this.mediaUnitId,
501
- this.roomId,
502
- this.callId,
503
- this.clientId,
504
- {
505
- type: CallMetaType.LOCAL_SDP,
506
- payload: localSDP,
507
- },
508
- this.userId,
509
- undefined,
510
- sample.timestamp
511
- );
512
-
513
- this.reports.addCallMetaReport(callMetaReport);
231
+ ++this.deltaNumberOfIssues;
514
232
  }
515
233
 
516
- for (const extensionStats of sample.extensionStats ?? []) {
517
- this.reports.addClientExtensionReport({
518
- serviceId: this.serviceId,
519
- mediaUnitId: this.mediaUnitId,
520
- roomId: this.roomId,
521
- callId: this.callId,
522
- clientId: this.clientId,
523
- userId: this.userId,
524
- timestamp: now,
525
- payload: extensionStats.payload,
526
- extensionType: extensionStats.type,
527
- });
234
+ for (const extensionStat of sample.extensionStats ?? []) {
235
+ this.addExtensionStats(extensionStat);
528
236
  }
529
237
 
530
- for (const { timestamp, ...callEvent } of sample.customCallEvents ?? []) {
531
- switch (callEvent.name) {
532
- case CallEventType.CLIENT_JOINED: {
533
- const lastJoined = this.joined;
534
-
535
- this.joined = timestamp;
536
-
537
- // if it is joined before and it is joined again
538
- if (lastJoined && this.joined && lastJoined !== this.joined) {
539
- this.emit('rejoined', { lastJoined });
540
- }
541
- // in case we have a left event before the joined event
542
- if (this.left && this.joined && this.left < this.joined) {
543
- this.left = undefined;
544
- }
545
- break;
546
- }
547
- case CallEventType.CLIENT_LEFT: {
548
- this.left = timestamp;
549
- break;
550
- }
551
- case CallEventType.PEER_CONNECTION_OPENED: {
552
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
553
- type: 'opened',
554
- peerConnectionId: callEvent.peerConnectionId,
555
- timestamp: timestamp ?? sample.timestamp,
556
- });
557
- break;
558
- }
559
- case CallEventType.PEER_CONNECTION_CLOSED: {
560
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
561
- type: 'closed',
562
- peerConnectionId: callEvent.peerConnectionId,
563
- timestamp: timestamp ?? sample.timestamp,
564
- });
565
- break;
566
- }
567
- case CallEventType.MEDIA_TRACK_ADDED: {
568
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
569
- type: 'added',
570
- peerConnectionId: callEvent.peerConnectionId,
571
- mediaTrackId: callEvent.mediaTrackId,
572
- timestamp: timestamp ?? sample.timestamp,
573
- });
574
- break;
575
- }
576
- case CallEventType.MEDIA_TRACK_REMOVED: {
577
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
578
- type: 'removed',
579
- peerConnectionId: callEvent.peerConnectionId,
580
- mediaTrackId: callEvent.mediaTrackId,
581
- timestamp: timestamp ?? sample.timestamp,
582
- });
583
- break;
584
- }
585
- case CallEventType.CLIENT_ISSUE: {
586
- const severity = callEvent.value ? callEvent.value as ClientIssue['severity'] : 'minor';
587
-
588
- try {
589
- const issue: ClientIssue = {
590
- severity,
591
- timestamp: timestamp ?? Date.now(),
592
- description: callEvent.message,
593
- peerConnectionId: callEvent.peerConnectionId,
594
- trackId: callEvent.mediaTrackId,
595
- attachments: callEvent.attachments ? JSON.parse(callEvent.attachments) : undefined,
596
- };
597
-
598
- this._addAndEmitIssue(issue);
599
- } catch (err) {
600
- logger.warn(`Error parsing client issue: ${(err as Error)?.message}`);
601
- }
602
- break;
603
- }
604
- }
238
+ for (const pcSample of sample.peerConnections ?? []) {
239
+ const observedPeerConnection = this._updatePeerConnection(pcSample);
605
240
 
606
- this.reports.addCallEventReport({
607
- serviceId: this.serviceId,
608
- mediaUnitId: this.mediaUnitId,
609
- roomId: this.roomId,
610
- callId: this.callId,
611
- clientId: this.clientId,
612
- userId: this.userId,
613
- timestamp: timestamp ?? now,
614
- ...callEvent,
615
- });
241
+ if (!observedPeerConnection) continue;
616
242
 
617
- this.call.emit('callevent', {
618
- mediaUnitId: this.mediaUnitId,
619
- clientId: this.clientId,
620
- userId: this.userId,
621
- timestamp: timestamp ?? now,
622
- ...callEvent,
623
- });
243
+ this.deltaDataChannelBytesReceived += observedPeerConnection.deltaDataChannelBytesReceived;
244
+ this.deltaDataChannelBytesSent += observedPeerConnection.deltaDataChannelBytesSent;
245
+ this.deltaDataChannelMessagesReceived += observedPeerConnection.deltaDataChannelMessagesReceived;
246
+ this.deltaDataChannelMessagesSent += observedPeerConnection.deltaDataChannelMessagesSent;
247
+ this.deltaInboundPacketsLost += observedPeerConnection.deltaInboundPacketsLost;
248
+ this.deltaInboundPacketsReceived += observedPeerConnection.deltaInboundPacketsReceived;
249
+ this.deltaOutboundPacketsSent += observedPeerConnection.deltaOutboundPacketsSent;
250
+ this.deltaReceivedAudioBytes += observedPeerConnection.deltaReceivedAudioBytes;
251
+ this.deltaReceivedVideoBytes += observedPeerConnection.deltaReceivedVideoBytes;
252
+ this.deltaSentAudioBytes += observedPeerConnection.deltaSentAudioBytes;
253
+ this.deltaSentVideoBytes += observedPeerConnection.deltaSentVideoBytes;
254
+ this.deltaTransportReceivedBytes += observedPeerConnection.deltaTransportReceivedBytes;
255
+ this.deltaTransportSentBytes += observedPeerConnection.deltaTransportSentBytes;
624
256
 
625
- }
257
+ this.availableIncomingBitrate += observedPeerConnection.availableIncomingBitrate;
258
+ this.availableOutgoingBitrate += observedPeerConnection.availableOutgoingBitrate;
626
259
 
627
- for (const userMediaError of sample.userMediaErrors ?? []) {
628
- const callMetaReport = createCallMetaReport(
629
- this.serviceId,
630
- this.mediaUnitId,
631
- this.roomId,
632
- this.callId,
633
- this.clientId, {
634
- type: CallMetaType.USER_MEDIA_ERROR,
635
- payload: userMediaError,
636
- },
637
- this.userId,
638
- );
639
-
640
- this.reports.addCallMetaReport(callMetaReport);
641
- this.userMediaErrors.push(userMediaError);
642
- this.emit('usermediaerror', userMediaError);
643
- }
260
+ this.numberOfDataChannels += observedPeerConnection.observedDataChannels.size;
261
+ this.numberOfInbundTracks += observedPeerConnection.observedInboundTracks.size;
262
+ this.numberOfOutboundRtpStreams += observedPeerConnection.observedOutboundRtps.size;
263
+ this.numberOfOutboundTracks += observedPeerConnection.observedOutboundTracks.size;
264
+ this.numberOfInboundRtpStreams += observedPeerConnection.observedInboundRtps.size;
644
265
 
645
- for (const certificate of sample.certificates ?? []) {
646
- const callMetaReport = createCallMetaReport(
647
- this.serviceId,
648
- this.mediaUnitId,
649
- this.roomId,
650
- this.callId,
651
- this.clientId, {
652
- type: CallMetaType.CERTIFICATE,
653
- payload: certificate,
654
- },
655
- this.userId
656
- );
657
-
658
- this.reports.addCallMetaReport(callMetaReport);
659
- }
660
-
661
- for (const codec of sample.codecs ?? []) {
662
- const callMetaReport = createCallMetaReport(
663
- this.serviceId,
664
- this.mediaUnitId,
665
- this.roomId,
666
- this.callId,
667
- this.clientId, {
668
- type: CallMetaType.CODEC,
669
- payload: codec,
670
- }, this.userId);
671
-
672
- this.reports.addCallMetaReport(callMetaReport);
673
- if (codec.mimeType && !this.mediaCodecs.find((c) => c.mimeType === codec.mimeType)) {
674
- this.mediaCodecs.push(codec);
266
+ if (observedPeerConnection.usingTURN) {
267
+ this.usingTURN = true;
675
268
  }
676
- }
677
-
678
- for (const iceServer of sample.iceServers ?? []) {
679
- const callMetaReport = createCallMetaReport(
680
- this.serviceId,
681
- this.mediaUnitId,
682
- this.roomId,
683
- this.callId,
684
- this.clientId, {
685
- type: CallMetaType.ICE_SERVER,
686
- payload: iceServer,
687
- }, this.userId);
688
-
689
- this.reports.addCallMetaReport(callMetaReport);
690
- }
691
-
692
- for (const mediaDevice of sample.mediaDevices ?? []) {
693
- const callMetaReport = createCallMetaReport(
694
- this.serviceId,
695
- this.mediaUnitId,
696
- this.roomId,
697
- this.callId,
698
- this.clientId, {
699
- type: CallMetaType.MEDIA_DEVICE,
700
- payload: mediaDevice,
701
- }, this.userId);
702
-
703
- this.reports.addCallMetaReport(callMetaReport);
704
- if (mediaDevice.id && !this.mediaDevices.find((d) => d.id === mediaDevice.id)) {
705
- this.mediaDevices.push(mediaDevice);
269
+ if (observedPeerConnection.usingTCP) {
270
+ this.usingTCP = true;
706
271
  }
707
- }
708
-
709
- for (const mediaSource of sample.mediaSources ?? []) {
710
- const callMetaReport = createCallMetaReport(
711
- this.serviceId,
712
- this.mediaUnitId,
713
- this.roomId,
714
- this.callId,
715
- this.clientId, {
716
- type: CallMetaType.MEDIA_SOURCE,
717
- payload: mediaSource,
718
- }, this.userId);
719
-
720
- this.reports.addCallMetaReport(callMetaReport);
721
- }
722
272
 
723
- for (const transport of sample.pcTransports ?? []) {
724
- try {
725
- const peerConnection = this._peerConnections.get(transport.transportId) ?? this._createPeerConnection(transport.peerConnectionId);
273
+ if (observedPeerConnection.currentRttInMs) {
274
+ if (this.currentMinRttInMs === undefined || observedPeerConnection.currentRttInMs < this.currentMinRttInMs) {
275
+ this.currentMinRttInMs = observedPeerConnection.currentRttInMs;
276
+ }
277
+ if (this.currentMaxRttInMs === undefined || observedPeerConnection.currentRttInMs > this.currentMaxRttInMs) {
278
+ this.currentMaxRttInMs = observedPeerConnection.currentRttInMs;
279
+ }
280
+ if (observedPeerConnection.currentRttInMs < 50) {
281
+ this.deltaRttLt50Measurements += 1;
282
+ } else if (observedPeerConnection.currentRttInMs < 150) {
283
+ this.deltaRttLt150Measurements += 1;
284
+ } else if (observedPeerConnection.currentRttInMs < 300) {
285
+ this.deltaRttLt300Measurements += 1;
286
+ } else if (300 <= observedPeerConnection.currentRttInMs) {
287
+ this.deltaRttGtOrEq300Measurements += 1;
288
+ }
726
289
 
727
- !peerConnection.label && transport.label && (peerConnection.label = transport.label);
728
- peerConnection.update(transport, sample.timestamp);
729
- } catch (err) {
730
- logger.error(`Error creating peer connection: ${(err as Error)?.message}`);
290
+ sumOfRtts += observedPeerConnection.currentRttInMs;
291
+ ++numberOfRttMeasurements;
731
292
  }
732
-
733
293
  }
734
294
 
735
- for (const track of sample.inboundAudioTracks ?? []) {
736
- if (!track.peerConnectionId || !track.trackId) {
737
- logger.warn(`InboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
295
+ for (const clientEvent of clientEventsPostBuffer) {
296
+ this._processClientEvent(clientEvent);
297
+ }
738
298
 
739
- continue;
740
- }
299
+ // emit new attachments?
300
+ this.attachments = sample.attachments;
741
301
 
742
- try {
743
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
302
+ this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
303
+ this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
304
+ this.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
305
+ this.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
306
+ this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
307
+ this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
308
+ this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
309
+ this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
310
+ this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
311
+ this.totalSentAudioBytes += this.deltaSentAudioBytes;
312
+ this.totalSentVideoBytes += this.deltaSentVideoBytes;
313
+ this.totalReceivedBytes += this.deltaTransportReceivedBytes;
314
+ this.totalSentBytes += this.deltaTransportSentBytes;
315
+ this.totalRttLt50Measurements += this.deltaRttLt50Measurements;
316
+ this.totalRttLt150Measurements += this.deltaRttLt150Measurements;
317
+ this.totalRttLt300Measurements += this.deltaRttLt300Measurements;
318
+ this.totalRttGtOrEq300Measurements += this.deltaRttGtOrEq300Measurements;
319
+ this.totalNumberOfIssues += this.deltaNumberOfIssues;
320
+
321
+ this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (elapsedInSeconds);
322
+ this.receivingVideoBitrate = (this.totalReceivedVideoBytes * 8) / (elapsedInSeconds);
323
+ this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / (elapsedInSeconds);
324
+ this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / (elapsedInSeconds);
325
+ this.currentAvgRttInMs = 0 < numberOfRttMeasurements ? sumOfRtts / numberOfRttMeasurements : undefined;
326
+
327
+ this.calculatedScore.value = sample.score;
328
+ this.detectors.update();
329
+
330
+ // emit update
331
+ this.emit('update', {
332
+ sample,
333
+ elapsedTimeInMs: now - this.updated,
334
+ });
335
+ this.updated = now;
744
336
 
745
- if (!peerConnection) continue;
746
-
747
- const inboundAudioTrack = peerConnection.inboundAudioTracks.get(track.trackId) ?? peerConnection.createInboundAudioTrack({
748
- trackId: track.trackId,
749
- sfuStreamId: track.sfuStreamId,
750
- sfuSinkId: track.sfuSinkId,
751
- });
752
-
753
- inboundAudioTrack.update(track, sample.timestamp);
754
-
755
- if (!inboundAudioTrack.remoteOutboundTrack) {
756
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundAudioTrack.get(inboundAudioTrack.sfuStreamId ?? '');
757
-
758
- if (remoteOutboundTrack) {
759
- inboundAudioTrack.remoteOutboundTrack = remoteOutboundTrack;
760
-
761
- inboundAudioTrack.once('close', () => {
762
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundAudioTrack.trackId ?? '');
763
- });
764
- remoteOutboundTrack.remoteInboundTracks.set(inboundAudioTrack.trackId ?? '', inboundAudioTrack);
337
+ // if result changed after update
338
+ if (this.calculatedScore.value) {
339
+ this.totalScoreSum += this.calculatedScore.value;
340
+ ++this.numberOfScoreMeasurements;
341
+ }
342
+ }
343
+
344
+ private _processClientEvent(event: ClientEvent, postBuffer?: ClientEvent[]) {
345
+ // eslint-disable-next-line no-console
346
+ // console.warn('ClientEvent', event);
347
+ switch (event.type) {
348
+ case ClientEventTypes.CLIENT_JOINED: {
349
+ if (event.timestamp) {
350
+ if (!this.joinedAt) {
351
+ this.joinedAt = event.timestamp;
352
+ this.emit('joined');
353
+ } else if (this.joinedAt < event.timestamp) {
354
+ this.emit('rejoined', event.timestamp);
355
+ } else {
356
+ this.joinedAt = event.timestamp;
357
+ logger.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
765
358
  }
766
359
  }
767
- } catch (err) {
768
- logger.error(`Error creating inbound audio track: ${(err as Error)?.message}`);
769
- }
770
- }
771
-
772
- for (const track of sample.inboundVideoTracks ?? []) {
773
- if (!track.peerConnectionId || !track.trackId) {
774
- logger.warn(`InboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
775
-
776
- continue;
360
+ break;
777
361
  }
778
- if (isValidUuid(track.trackId) === false) {
779
- // mediasoup-probator trackId is not a valid UUID, no need to warn about it
780
- if (track.trackId === 'probator') continue;
781
- logger.warn(`InboundVideoTrack with invalid trackId: ${track.trackId}`);
782
- continue;
783
- }
784
-
785
- try {
786
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
787
-
788
- if (!peerConnection) continue;
789
-
790
- const inboundVideoTrack = peerConnection.inboundVideoTracks.get(track.trackId) ?? peerConnection.createInboundVideoTrack({
791
- trackId: track.trackId,
792
- sfuStreamId: track.sfuStreamId,
793
- sfuSinkId: track.sfuSinkId,
794
- });
795
-
796
- inboundVideoTrack.update(track, sample.timestamp);
797
-
798
- if (!inboundVideoTrack.remoteOutboundTrack) {
799
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundVideoTrack.get(inboundVideoTrack.sfuStreamId ?? '');
800
-
801
- if (remoteOutboundTrack) {
802
- inboundVideoTrack.remoteOutboundTrack = remoteOutboundTrack;
803
-
804
- inboundVideoTrack.once('close', () => {
805
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundVideoTrack.trackId ?? '');
806
- });
807
- remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
362
+ case ClientEventTypes.CLIENT_LEFT: {
363
+ if (event.timestamp) {
364
+ if (!this.leftAt) {
365
+ this.leftAt = event.timestamp;
366
+ this.emit('left');
367
+ } else {
368
+ logger.warn(`Client ${this.clientId} leftAt timestamp was already set`);
808
369
  }
809
370
  }
810
- } catch (err) {
811
- logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
371
+ break;
812
372
  }
813
- }
814
-
815
- for (const track of sample.outboundAudioTracks ?? []) {
816
- if (!track.peerConnectionId || !track.trackId) {
817
- logger.warn(`OutboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
818
-
819
- continue;
373
+ case ClientEventTypes.PEER_CONNECTION_OPENED: {
374
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
375
+
376
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') {
377
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
378
+
379
+ if (observedPeerConnection) {
380
+ observedPeerConnection.openedAt = event.timestamp;
381
+ } else if (postBuffer) {
382
+ postBuffer.push(event);
383
+ } else {
384
+ logger.warn('Received PEER_CONNECTION_OPENED event without a corresponding observedPeerConnection', event);
385
+ }
386
+
387
+ }
388
+ break;
820
389
  }
821
-
822
- try {
823
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
824
-
825
- if (!peerConnection) continue;
826
-
827
- const outboundAudioTrack = peerConnection.outboundAudioTracks.get(track.trackId) ?? peerConnection.createOutboundAudioTrack({
828
- trackId: track.trackId,
829
- sfuStreamId: track.sfuStreamId,
830
- });
831
-
832
- outboundAudioTrack.update(track, sample.timestamp);
833
-
834
- if (outboundAudioTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundAudioTrack.has(outboundAudioTrack.sfuStreamId)) {
835
- this.call.sfuStreamIdToOutboundAudioTrack.set(outboundAudioTrack.sfuStreamId, outboundAudioTrack);
390
+ case ClientEventTypes.PEER_CONNECTION_CLOSED: {
391
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
392
+
393
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') {
394
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
395
+
396
+ if (observedPeerConnection) {
397
+ observedPeerConnection.closedAt = event.timestamp;
398
+ } else if (postBuffer) {
399
+ postBuffer.push(event);
400
+ } else {
401
+ logger.warn('Received PEER_CONNECTION_CLOSED event without a corresponding observedPeerConnection', event);
402
+ }
836
403
  }
837
-
838
- } catch (err) {
839
- logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
404
+ break;
840
405
  }
841
-
842
- }
843
-
844
- for (const track of sample.outboundVideoTracks ?? []) {
845
- if (!track.peerConnectionId || !track.trackId) {
846
- logger.warn(`OutboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
847
-
848
- continue;
406
+ case ClientEventTypes.MEDIA_TRACK_ADDED: {
407
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
408
+
409
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
410
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
411
+ const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
412
+
413
+ if (observedTrack) {
414
+ observedTrack.addedAt = event.timestamp;
415
+ } else if (postBuffer) {
416
+ postBuffer.push(event);
417
+ } else {
418
+ logger.warn('Received MEDIA_TRACK_ADDED event without a corresponding observedPeerConnection or observedTrack', event);
419
+ }
420
+ }
421
+ break;
849
422
  }
850
-
851
- try {
852
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
853
-
854
- if (!peerConnection) continue;
855
-
856
- const outboundVideoTrack = peerConnection.outboundVideoTracks.get(track.trackId) ?? peerConnection.createOutboundVideoTrack({
857
- trackId: track.trackId,
858
- sfuStreamId: track.sfuStreamId,
859
- });
860
-
861
- outboundVideoTrack.update(track, sample.timestamp);
862
-
863
- if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
864
- this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
423
+ case ClientEventTypes.MEDIA_TRACK_REMOVED: {
424
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
425
+
426
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
427
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
428
+ const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
429
+
430
+ if (observedTrack) {
431
+ observedTrack.removedAt = event.timestamp;
432
+ } else if (postBuffer) {
433
+ postBuffer.push(event);
434
+ } else {
435
+ logger.warn('Received MEDIA_TRACK_REMOVED event without a corresponding observedPeerConnection or observedTrack', event);
436
+ }
865
437
  }
866
-
867
- } catch (err) {
868
- logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
438
+ break;
869
439
  }
870
- }
871
-
872
- for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
873
- if (!iceLocalCandidate.peerConnectionId) {
874
- logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
875
- continue;
440
+ case ClientEventTypes.DATA_CHANNEL_OPEN: {
441
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
442
+
443
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') {
444
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
445
+ const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId);
446
+
447
+ if (observedDataChannel) {
448
+ observedDataChannel.addedAt = event.timestamp;
449
+ } else if (postBuffer) {
450
+ postBuffer.push(event);
451
+ } else {
452
+ logger.warn('Received DATA_CHANNEL_OPENED event without a corresponding observedPeerConnection or observedDataChannel', event);
453
+ }
454
+ }
455
+ break;
876
456
  }
877
- const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
878
-
879
- if (!peerConnection) {
880
- logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
881
- continue;
457
+ case ClientEventTypes.DATA_CHANNEL_CLOSED: {
458
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
459
+
460
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') {
461
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
462
+ const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId);
463
+
464
+ if (observedDataChannel) {
465
+ observedDataChannel.removedAt = event.timestamp;
466
+ } else if (postBuffer) {
467
+ postBuffer.push(event);
468
+ } else {
469
+ logger.warn('Received DATA_CHANNEL_CLOSE event without a corresponding observedPeerConnection or observedDataChannel', event);
470
+ }
471
+ }
472
+ break;
882
473
  }
883
-
884
- peerConnection.ICE.addLocalCandidate(iceLocalCandidate, sample.timestamp);
885
474
  }
886
-
887
- for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
888
- if (!iceRemoteCandidate.peerConnectionId) {
889
- logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
890
- continue;
891
- }
892
- const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
475
+ this.emit('clientEvent', event);
476
+ }
893
477
 
894
- if (!peerConnection) {
895
- logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
896
- continue;
897
- }
478
+ public injectMetaData(metaData: ClientMetaData) {
479
+ if (this.closed) return;
480
+
481
+ if (!this._injections.clientMetaItems) this._injections.clientMetaItems = [];
898
482
 
899
- peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate, sample.timestamp);
900
- }
483
+ this._injections.clientMetaItems.push(metaData);
484
+ }
901
485
 
902
- for (const candidatePair of sample.iceCandidatePairs ?? []) {
903
- const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
486
+ public injectEvent(event: ClientEvent) {
487
+ if (this.closed) return;
904
488
 
905
- if (!peerConnection) {
906
- logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
489
+ if (!this._injections.clientEvents) this._injections.clientEvents = [];
907
490
 
908
- continue;
909
- }
491
+ this._injections.clientEvents.push(event);
492
+ }
910
493
 
911
- peerConnection.ICE.update(candidatePair, sample.timestamp);
912
- }
494
+ public injectIssue(issue: ClientIssue) {
495
+ if (this.closed) return;
913
496
 
914
- for (const dataChannel of sample.dataChannels ?? []) {
915
- if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
916
- logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
497
+ if (!this._injections.clientIssues) this._injections.clientIssues = [];
917
498
 
918
- continue;
919
- }
499
+ this._injections.clientIssues.push(issue);
500
+ }
920
501
 
921
- try {
922
- const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
502
+ public injectExtensionStat(stat: ExtensionStat) {
503
+ if (this.closed) return;
923
504
 
924
- if (!peerConnection) continue;
925
-
926
- const observedDataChannel = peerConnection.dataChannels.get(dataChannel.dataChannelIdentifier) ?? peerConnection.createDataChannel(dataChannel.dataChannelIdentifier);
927
-
928
- observedDataChannel.update(dataChannel, sample.timestamp);
929
- } catch (err) {
930
- logger.error(`Error creating data channel: ${(err as Error)?.message}`);
931
- }
932
- }
505
+ if (!this._injections.extensionStats) this._injections.extensionStats = [];
933
506
 
934
- // try to set the timestamps of the peer connections
935
- if (0 < this.ωpendingPeerConnectionTimestamps.length) {
936
- const newPendingTimestamps: PendingPeerConnectionTimestamp[] = [];
507
+ this._injections.extensionStats.push(stat);
508
+ }
937
509
 
938
- for (const pendingTimestamp of this.ωpendingPeerConnectionTimestamps) {
939
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
510
+ public injectAttachment(key: string, value: unknown) {
511
+ if (this.closed) return;
940
512
 
941
- if (!peerConnection) {
942
- newPendingTimestamps.push(pendingTimestamp);
943
- continue;
944
- }
945
- if (pendingTimestamp.type === 'opened') peerConnection.opened = pendingTimestamp.timestamp;
946
- else if (pendingTimestamp.type === 'closed') peerConnection.closedTimestamp = pendingTimestamp.timestamp;
947
- }
513
+ if (!this._injections.attachments) this._injections.attachments = {};
948
514
 
949
- this.ωpendingPeerConnectionTimestamps = newPendingTimestamps;
950
- }
515
+ this._injections.attachments[key] = value;
951
516
 
952
- // try to set the timestamps of the media tracks
953
- if (0 < this.ωpendingMediaTrackTimestamps.length) {
954
- const newPendingTimestamps: PendingMediaTrackTimestamp[] = [];
955
-
956
- for (const pendingTimestamp of this.ωpendingMediaTrackTimestamps) {
957
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
958
- const mediaTrack = peerConnection?.inboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
959
- peerConnection?.inboundVideoTracks.get(pendingTimestamp.mediaTrackId) ??
960
- peerConnection?.outboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
961
- peerConnection?.outboundVideoTracks.get(pendingTimestamp.mediaTrackId);
962
-
963
- if (!mediaTrack) {
964
- newPendingTimestamps.push(pendingTimestamp);
965
- continue;
966
- }
967
-
968
- if (pendingTimestamp.type === 'added') mediaTrack.added = pendingTimestamp.timestamp;
969
- else if (pendingTimestamp.type === 'removed') mediaTrack.removed = pendingTimestamp.timestamp;
970
- }
971
-
972
- this.ωpendingMediaTrackTimestamps = newPendingTimestamps;
973
- }
974
-
975
- // close resources that are not visited
976
- for (const peerConnection of this._peerConnections.values()) {
977
- if (!peerConnection.visited) {
978
- peerConnection.close();
979
-
980
- continue;
981
- }
982
-
983
- for (const track of peerConnection.inboundAudioTracks.values()) {
984
- if (!track.visited) {
985
- track.close();
986
-
987
- continue;
988
- }
517
+ }
989
518
 
990
- track.visited = false;
519
+ public addMetadata(metadata: ClientMetaData) {
520
+ if (this.closed) return;
521
+
522
+ switch (metadata.type) {
523
+ case ClientMetaTypes.BROWSER: {
524
+ this.browser = parseJsonAs(metadata.payload);
525
+ break;
991
526
  }
992
- for (const track of peerConnection.inboundVideoTracks.values()) {
993
- if (!track.visited) {
994
- track.close();
995
-
996
- continue;
997
- }
998
-
999
- track.visited = false;
527
+ case ClientMetaTypes.ENGINE: {
528
+ this.engine = parseJsonAs(metadata.payload);
529
+ break;
1000
530
  }
1001
- for (const track of peerConnection.outboundAudioTracks.values()) {
1002
- if (!track.visited) {
1003
- track.close();
1004
-
1005
- continue;
1006
- }
1007
-
1008
- track.visited = false;
531
+ case ClientMetaTypes.PLATFORM: {
532
+ this.platform = parseJsonAs(metadata.payload);
533
+ break;
1009
534
  }
1010
- for (const dataChannel of peerConnection.dataChannels.values()) {
1011
- if (!dataChannel.visited) {
1012
- dataChannel.close();
1013
-
1014
- continue;
1015
- }
1016
-
1017
- dataChannel.visited = false;
535
+ case ClientMetaTypes.OPERATION_SYSTEM: {
536
+ this.operationSystem = parseJsonAs(metadata.payload);
537
+ break;
1018
538
  }
1019
-
1020
- peerConnection.visited = false;
1021
539
  }
1022
540
 
1023
- // update metrics
1024
- const wasUsingTURN = this.usingTURN;
1025
- const elapsedTimeInMs = now - this.updated;
1026
-
1027
- this.usingTURN = false;
1028
- this.availableIncomingBitrate = 0;
1029
- this.availableOutgoingBitrate = 0;
1030
- this.deltaInboundPacketsLost = 0;
1031
- this.deltaInboundPacketsReceived = 0;
1032
- this.deltaOutboundPacketsSent = 0;
1033
- this.deltaReceivedAudioBytes = 0;
1034
- this.deltaReceivedVideoBytes = 0;
1035
- this.deltaSentAudioBytes = 0;
1036
- this.deltaSentVideoBytes = 0;
1037
- this.deltaDataChannelBytesReceived = 0;
1038
- this.deltaDataChannelBytesSent = 0;
1039
-
1040
- this.outboundAudioBitrate = 0;
1041
- this.outboundVideoBitrate = 0;
1042
- this.inboundAudioBitrate = 0;
1043
- this.inboundVideoBitrate = 0;
1044
- let sumRttInMs = 0;
1045
- let anyPeerConnectionUsingTurn = false;
1046
-
1047
- let minPcScore: number | undefined;
1048
- let maxPcScore: number | undefined;
1049
- let numberOfScoredPeerConnections = 0;
1050
-
1051
- for (const peerConnection of this._peerConnections.values()) {
1052
- if (peerConnection.closed) continue;
1053
-
1054
- peerConnection.updateMetrics();
1055
-
1056
- this.deltaInboundPacketsLost += peerConnection.deltaInboundPacketsLost;
1057
- this.deltaInboundPacketsReceived += peerConnection.deltaInboundPacketsReceived;
1058
- this.deltaOutboundPacketsSent += peerConnection.deltaOutboundPacketsSent;
1059
- this.deltaReceivedAudioBytes += peerConnection.deltaReceivedAudioBytes;
1060
- this.deltaReceivedVideoBytes += peerConnection.deltaReceivedVideoBytes;
1061
- this.deltaSentAudioBytes += peerConnection.deltaSentAudioBytes;
1062
- this.deltaSentVideoBytes += peerConnection.deltaSentVideoBytes;
1063
- this.deltaDataChannelBytesReceived += peerConnection.deltaDataChannelBytesReceived;
1064
- this.deltaDataChannelBytesSent += peerConnection.deltaDataChannelBytesSent;
541
+ this.call.observer.emit('client-metadata', this, metadata);
542
+ }
1065
543
 
1066
- this.availableIncomingBitrate += peerConnection.availableIncomingBitrate ?? 0;
1067
- this.availableOutgoingBitrate += peerConnection.availableOutgoingBitrate ?? 0;
544
+ public addIssue(issue: ClientIssue) {
545
+ if (this.closed) return;
1068
546
 
1069
- sumRttInMs += peerConnection.avgRttInMs ?? 0;
547
+ this.emit('issue', issue);
548
+ this.call.observer.emit('client-issue', this, issue);
549
+ }
1070
550
 
1071
- anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
551
+ public addExtensionStats(stats: ExtensionStat) {
552
+ this.call.observer.emit('client-extension-stats', this, stats);
553
+ this.emit('extensionStats', stats);
554
+ }
1072
555
 
1073
- if (peerConnection.score) {
1074
- if (minPcScore === undefined || peerConnection.score.score < minPcScore) minPcScore = peerConnection.score.score;
1075
- if (maxPcScore === undefined || peerConnection.score.score > maxPcScore) maxPcScore = peerConnection.score.score;
556
+ private _updatePeerConnection(sample: PeerConnectionSample): ObservedPeerConnection | undefined {
557
+ let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
1076
558
 
1077
- ++numberOfScoredPeerConnections;
559
+ if (!observedPeerConnection) {
560
+ if (!sample.peerConnectionId) {
561
+ return (logger.warn(
562
+ `ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
563
+ sample
564
+ ), void 0);
1078
565
  }
1079
- }
1080
566
 
1081
- this.score = {
1082
- score: minPcScore ?? -1,
1083
- remarks: [ {
1084
- severity: 'none',
1085
- text: `Min and max score of all peer connections: ${minPcScore}, ${maxPcScore}, number of PeerConnections with scores: ${numberOfScoredPeerConnections}`,
1086
- } ],
1087
- timestamp: sample.timestamp,
1088
- };
1089
- this.emit('score', this.score);
1090
-
1091
- this.usingTURN = anyPeerConnectionUsingTurn === true;
1092
-
1093
- if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
567
+ observedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
1094
568
 
1095
- this.totalSentBytes += this.deltaSentAudioBytes + this.deltaSentVideoBytes;
1096
- this.totalReceivedBytes += this.deltaReceivedAudioBytes + this.deltaReceivedVideoBytes;
1097
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
1098
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
1099
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
1100
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
1101
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
1102
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
1103
- this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
1104
- this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
1105
- this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
1106
-
1107
- this.outboundAudioBitrate = (this.deltaSentAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1108
- this.outboundVideoBitrate = (this.deltaSentVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1109
- this.inboundAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1110
- this.inboundVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
569
+ observedPeerConnection.once('close', () => {
570
+ this.observedPeerConnections.delete(sample.peerConnectionId);
571
+ });
572
+ this.observedPeerConnections.set(sample.peerConnectionId, observedPeerConnection);
573
+
574
+ this.emit('newpeerconnection', observedPeerConnection);
575
+ }
1111
576
 
1112
- this.avgRttInMs = this._peerConnections.size ? sumRttInMs / this._peerConnections.size : undefined;
1113
-
1114
- // to make sure when sample is emitted it can be associated to this client
1115
- sample.clientId = this.clientId;
577
+ observedPeerConnection.accept(sample);
1116
578
 
1117
- this.updated = now;
1118
- this.lastStatsTimestamp = sample.timestamp;
1119
- this.emit('update', {
1120
- sample,
1121
- elapsedTimeInMs,
1122
- });
579
+ return observedPeerConnection;
1123
580
  }
1124
581
 
1125
- private _addAndEmitIssue(issue: ClientIssue) {
1126
- this.issues.push(issue);
1127
-
1128
- if (issue.peerConnectionId) {
1129
- const peerConnection = this._peerConnections.get(issue.peerConnectionId);
1130
-
1131
- if (peerConnection) {
1132
-
1133
- if (issue.trackId) {
1134
- const track = peerConnection.inboundAudioTracks.get(issue.trackId) ?? peerConnection.inboundVideoTracks.get(issue.trackId);
582
+ private _mergeInjections(sample: ClientSample): ClientSample {
583
+ if (this.closed) return sample;
1135
584
 
1136
- if (track) track.ωpendingIssuesForScores.push(issue);
1137
- } else {
1138
- peerConnection.ωpendingIssuesForScores.push(issue);
1139
- }
1140
- }
585
+ if (this._injections.clientEvents) {
586
+ if (!sample.clientEvents) sample.clientEvents = [];
587
+ sample.clientEvents.push(...this._injections.clientEvents);
588
+
589
+ this._injections.clientEvents = undefined;
1141
590
  }
1142
591
 
1143
- this.emit('issue', issue);
1144
- }
592
+ if (this._injections.clientIssues) {
593
+ if (!sample.clientIssues) sample.clientIssues = [];
594
+ sample.clientIssues.push(...this._injections.clientIssues);
595
+
596
+ this._injections.clientIssues = undefined;
597
+ }
1145
598
 
1146
- private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
1147
- const result = new ObservedPeerConnection({
1148
- peerConnectionId,
1149
- label,
1150
- }, this);
1151
-
1152
- const onNewSelectedCandidatePair = ({ localCandidate, remoteCandidate }: { localCandidate: IceLocalCandidate, remoteCandidate: IceRemoteCandidate }) => {
1153
- this.emit('selectedcandidatepair', {
1154
- peerConnection: result,
1155
- localCandidate,
1156
- remoteCandidate,
1157
- });
1158
- };
599
+ if (this._injections.extensionStats) {
600
+ if (!sample.extensionStats) sample.extensionStats = [];
601
+ sample.extensionStats.push(...this._injections.extensionStats);
602
+
603
+ this._injections.extensionStats = undefined;
604
+ }
1159
605
 
1160
- result.once('close', () => {
1161
- result.ICE.off('new-selected-candidate-pair', onNewSelectedCandidatePair);
1162
- this._peerConnections.delete(peerConnectionId);
1163
- });
1164
- result.ICE.on('new-selected-candidate-pair', onNewSelectedCandidatePair);
1165
- this._peerConnections.set(peerConnectionId, result);
606
+ if (this._injections.attachments) {
607
+ if (!sample.attachments) sample.attachments = {};
608
+ Object.assign(sample.attachments, this._injections.attachments);
609
+
610
+ this._injections.attachments = undefined;
611
+ }
1166
612
 
1167
- this.emit('newpeerconnection', result);
613
+ if (this._injections.clientMetaItems) {
614
+ if (!sample.clientMetaItems) sample.clientMetaItems = [];
615
+ sample.clientMetaItems.push(...this._injections.clientMetaItems);
616
+
617
+ this._injections.clientMetaItems = undefined;
618
+ }
619
+
620
+ return sample;
621
+ }
622
+
623
+ // public resetSummaryMetrics() {
624
+ // this.totalDataChannelBytesReceived = 0;
625
+ // this.totalDataChannelBytesSent = 0;
626
+ // this.totalDataChannelMessagesReceived = 0;
627
+ // this.totalDataChannelMessagesSent = 0;
628
+ // this.totalInboundPacketsLost = 0;
629
+ // this.totalInboundPacketsReceived = 0;
630
+ // this.totalOutboundPacketsSent = 0;
631
+ // this.totalReceivedAudioBytes = 0;
632
+ // this.totalReceivedVideoBytes = 0;
633
+ // this.totalSentAudioBytes = 0;
634
+ // this.totalSentVideoBytes = 0;
635
+ // this.totalSentBytes = 0;
636
+ // this.totalReceivedBytes = 0;
637
+
638
+ // this.totalNumberOfIssues = 0;
639
+
640
+ // this.totalScoreSum = 0;
641
+ // this.numberOfScoreMeasurements = 0;
642
+ // }
643
+
644
+ // public createSummary(): ObservedClientSummary {
645
+ // return {
646
+ // totalRttLt50Measurements: this.totalRttLt50Measurements,
647
+ // totalRttLt150Measurements: this.totalRttLt150Measurements,
648
+ // totalRttLt300Measurements: this.totalRttLt300Measurements,
649
+ // totalRttGtOrEq300Measurements: this.totalRttGtOrEq300Measurements,
650
+ // totalDataChannelBytesReceived: this.totalDataChannelBytesReceived,
651
+ // totalDataChannelBytesSent: this.totalDataChannelBytesSent,
652
+ // totalDataChannelMessagesReceived: this.totalDataChannelMessagesReceived,
653
+ // totalDataChannelMessagesSent: this.totalDataChannelMessagesSent,
654
+ // totalInboundPacketsLost: this.totalInboundPacketsLost,
655
+ // totalInboundPacketsReceived: this.totalInboundPacketsReceived,
656
+ // totalOutboundPacketsSent: this.totalOutboundPacketsSent,
657
+ // totalReceivedAudioBytes: this.totalReceivedAudioBytes,
658
+ // totalReceivedVideoBytes: this.totalReceivedVideoBytes,
659
+ // totalSentAudioBytes: this.totalSentAudioBytes,
660
+ // totalSentVideoBytes: this.totalSentVideoBytes,
661
+ // totalSentBytes: this.totalSentBytes,
662
+ // totalReceivedBytes: this.totalReceivedBytes,
663
+
664
+ // numberOfIssues: this.totalNumberOfIssues,
1168
665
 
1169
- return result;
1170
- }
1171
- }
666
+ // totalScoreSum: this.totalScoreSum,
667
+ // numberOfScoreMeasurements: this.numberOfScoreMeasurements,
668
+ // };
669
+ // }
670
+ }