You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AnalyticsEvents.ts 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /**
  2. * This class exports constants and factory methods related to the analytics
  3. * API provided by AnalyticsAdapter. In order for entries in a database to be
  4. * somewhat easily traceable back to the code which produced them, events sent
  5. * through analytics should be defined here.
  6. *
  7. * Since the AnalyticsAdapter API can be used in different ways, for some events
  8. * it is more convenient to just define the event name as a constant. For other
  9. * events a factory function is easier.
  10. *
  11. * A general approach for adding a new event:
  12. * 1. Determine the event type: track, UI, page, or operational. If in doubt use
  13. * operational.
  14. * 2. Determine whether the event is related to other existing events, and
  15. * which fields are desired to be set: name, action, actionSubject, source.
  16. * 3. If the name is sufficient (the other fields are not important), use a
  17. * constant. Otherwise use a factory function.
  18. *
  19. * Note that the AnalyticsAdapter uses the events passed to its functions for
  20. * its own purposes, and might modify them. Because of this, factory functions
  21. * should create new objects.
  22. *
  23. */
  24. export enum AnalyticsEvents {
  25. /**
  26. * The "action" value for Jingle events which indicates that the Jingle session
  27. * was restarted (TODO: verify/fix the documentation)
  28. */
  29. ACTION_JINGLE_RESTART = 'restart',
  30. /**
  31. * The "action" value for Jingle events which indicates that a session-accept
  32. * timed out (TODO: verify/fix the documentation)
  33. */
  34. ACTION_JINGLE_SA_TIMEOUT = 'session-accept.timeout',
  35. /**
  36. * The "action" value for Jingle events which indicates that a session-initiate
  37. * was received.
  38. */
  39. ACTION_JINGLE_SI_RECEIVED = 'session-initiate.received',
  40. /**
  41. * The "action" value for Jingle events which indicates that a session-initiate
  42. * not arrived within a timeout (the value is specified in
  43. * the {@link JingleSessionPC}.
  44. */
  45. ACTION_JINGLE_SI_TIMEOUT = 'session-initiate.timeout',
  46. /**
  47. * A constant for the "terminate" action for Jingle events. TODO: verify/fix
  48. * the documentation)
  49. */
  50. ACTION_JINGLE_TERMINATE = 'terminate',
  51. /**
  52. * The "action" value for JVB events which indicates that the ICE connection has failed after 3 restart attempts
  53. */
  54. ACTION_JVB_ICE_FAILED = 'jvb.ice.failed',
  55. /**
  56. * The "action" value for P2P events which indicates that P2P session initiate message has been rejected by the
  57. * client because the mandatory requirements were not met.
  58. */
  59. ACTION_P2P_DECLINED = 'decline',
  60. /**
  61. * The "action" value for P2P events which indicates that a connection was
  62. * established (TODO: verify/fix the documentation)
  63. */
  64. ACTION_P2P_ESTABLISHED = 'established',
  65. /**
  66. * The "action" value for P2P events which indicates that something failed.
  67. */
  68. ACTION_P2P_FAILED = 'failed',
  69. /**
  70. * The "action" value for P2P events which indicates that a switch to
  71. * jitsi-videobridge happened.
  72. */
  73. ACTION_P2P_SWITCH_TO_JVB = 'switch.to.jvb',
  74. /**
  75. * The name of an event which indicates an available device. We send one such
  76. * event per available device once when the available devices are first known,
  77. * and every time that they change
  78. *
  79. * Properties:
  80. * audio_input_device_count: the number of audio input devices available at
  81. * the time the event was sent.
  82. * audio_output_device_count: the number of audio output devices available
  83. * at the time the event was sent.
  84. * video_input_device_count: the number of video input devices available at
  85. * the time the event was sent.
  86. * video_output_device_count: the number of video output devices available
  87. * at the time the event was sent.
  88. * device_id: an identifier of the device described in this event.
  89. * device_group_id:
  90. * device_kind: one of 'audioinput', 'audiooutput', 'videoinput' or
  91. * 'videooutput'.
  92. * device_label: a string which describes the device.
  93. */
  94. AVAILABLE_DEVICE = 'available.device',
  95. /**
  96. * This appears to be fired only in certain cases when the XMPP connection
  97. * disconnects (and it was intentional?). It is currently never observed to
  98. * fire in production.
  99. *
  100. * TODO: document
  101. *
  102. * Properties:
  103. * message: an error message
  104. */
  105. CONNECTION_DISCONNECTED = 'connection.disconnected',
  106. /**
  107. * Indicates that the user of the application provided feedback in terms of a
  108. * rating (an integer from 1 to 5) and an optional comment.
  109. * Properties:
  110. * value: the user's rating (an integer from 1 to 5)
  111. * comment: the user's comment
  112. */
  113. FEEDBACK = 'feedback',
  114. /**
  115. * Indicates the duration of a particular phase of the ICE connectivity
  116. * establishment.
  117. *
  118. * Properties:
  119. * phase: the ICE phase (e.g. 'gathering', 'checking', 'establishment')
  120. * value: the duration in milliseconds.
  121. * p2p: whether the associated ICE connection is p2p or towards a
  122. * jitsi-videobridge
  123. * initiator: whether the local Jingle peer is the initiator or responder
  124. * in the Jingle session. XXX we probably actually care about the ICE
  125. * role (controlling vs controlled), and we assume that this correlates
  126. * with the Jingle initiator.
  127. */
  128. ICE_DURATION = 'ice.duration',
  129. /**
  130. * Indicates the difference in milliseconds between the ICE establishment time
  131. * for the P2P and JVB connections (e.g. a value of 10 would indicate that the
  132. * P2P connection took 10ms more than JVB connection to establish).
  133. *
  134. * Properties:
  135. * value: the difference in establishment durations in milliseconds.
  136. *
  137. */
  138. ICE_ESTABLISHMENT_DURATION_DIFF = 'ice.establishment.duration.diff',
  139. /**
  140. * Indicates that the ICE state has changed.
  141. *
  142. * Properties:
  143. * state: the ICE state which was entered (e.g. 'checking', 'connected',
  144. * 'completed', etc).
  145. * value: the time in milliseconds (as reported by
  146. * window.performance.now()) that the state change occurred.
  147. * p2p: whether the associated ICE connection is p2p or towards a
  148. * jitsi-videobridge
  149. * signalingState: The signaling state of the associated PeerConnection
  150. * reconnect: whether the associated Jingle session is in the process of
  151. * reconnecting (or is it ICE? TODO: verify/fix the documentation)
  152. */
  153. ICE_STATE_CHANGED = 'ice.state.changed',
  154. /**
  155. * Indicates that no bytes have been sent for the track.
  156. *
  157. * Properties:
  158. * mediaType: the media type of the local track ('audio' or 'video').
  159. */
  160. NO_BYTES_SENT = 'track.no-bytes-sent',
  161. /**
  162. * Indicates that a track was unmuted (?).
  163. *
  164. * Properties:
  165. * mediaType: the media type of the local track ('audio' or 'video').
  166. * trackType: the type of the track ('local' or 'remote').
  167. * value: TODO: document
  168. */
  169. TRACK_UNMUTED = 'track.unmuted',
  170. /**
  171. * The constant which identifies an event of type "operational".
  172. */
  173. TYPE_OPERATIONAL = 'operational',
  174. /**
  175. * The constant which identifies an event of type "page".
  176. */
  177. TYPE_PAGE = 'page',
  178. /**
  179. * The constant which identifies an event of type "track".
  180. */
  181. TYPE_TRACK = 'track',
  182. /**
  183. * The constant which identifies an event of type "ui".
  184. */
  185. TYPE_UI = 'ui',
  186. /**
  187. * Indicates that the video codec changed for a local track.
  188. *
  189. * Properties:
  190. * value: the video codec mimeType.
  191. * videoType: the videoType of local track, whether its 'camera' or 'desktop'.
  192. */
  193. VIDEO_CODEC_CHANGED = 'quality.video-codec-changed',
  194. }
  195. // exported for backward compatibility
  196. export const TYPE_OPERATIONAL = AnalyticsEvents.TYPE_OPERATIONAL;
  197. export const TYPE_PAGE = AnalyticsEvents.TYPE_PAGE;
  198. export const TYPE_TRACK = AnalyticsEvents.TYPE_TRACK;
  199. export const TYPE_UI = AnalyticsEvents.TYPE_UI;
  200. export const ACTION_JINGLE_RESTART = AnalyticsEvents.ACTION_JINGLE_RESTART;
  201. export const ACTION_JINGLE_SA_TIMEOUT = AnalyticsEvents.ACTION_JINGLE_SA_TIMEOUT;
  202. export const ACTION_JINGLE_SI_RECEIVED = AnalyticsEvents.ACTION_JINGLE_SI_RECEIVED;
  203. export const ACTION_JINGLE_SI_TIMEOUT = AnalyticsEvents.ACTION_JINGLE_SI_TIMEOUT;
  204. export const ACTION_JINGLE_TERMINATE = AnalyticsEvents.ACTION_JINGLE_TERMINATE;
  205. export const ACTION_JVB_ICE_FAILED = AnalyticsEvents.ACTION_JVB_ICE_FAILED;
  206. export const ACTION_P2P_DECLINED = AnalyticsEvents.ACTION_P2P_DECLINED;
  207. export const ACTION_P2P_ESTABLISHED = AnalyticsEvents.ACTION_P2P_ESTABLISHED;
  208. export const ACTION_P2P_FAILED = AnalyticsEvents.ACTION_P2P_FAILED;
  209. export const ACTION_P2P_SWITCH_TO_JVB = AnalyticsEvents.ACTION_P2P_SWITCH_TO_JVB;
  210. export const AVAILABLE_DEVICE = AnalyticsEvents.AVAILABLE_DEVICE;
  211. export const CONNECTION_DISCONNECTED = AnalyticsEvents.CONNECTION_DISCONNECTED;
  212. export const FEEDBACK = AnalyticsEvents.FEEDBACK;
  213. export const ICE_DURATION = AnalyticsEvents.ICE_DURATION;
  214. export const ICE_ESTABLISHMENT_DURATION_DIFF = AnalyticsEvents.ICE_ESTABLISHMENT_DURATION_DIFF;
  215. export const ICE_STATE_CHANGED = AnalyticsEvents.ICE_STATE_CHANGED;
  216. export const NO_BYTES_SENT = AnalyticsEvents.NO_BYTES_SENT;
  217. export const TRACK_UNMUTED = AnalyticsEvents.TRACK_UNMUTED;
  218. export const VIDEO_CODEC_CHANGED = AnalyticsEvents.VIDEO_CODEC_CHANGED;
  219. /**
  220. * Creates an operational event which indicates that we have received a
  221. * "bridge down" event from jicofo.
  222. */
  223. export const createBridgeDownEvent = () => {
  224. return {
  225. action: 'bridge.down',
  226. actionSubject: 'bridge.down',
  227. type: TYPE_OPERATIONAL
  228. };
  229. };
  230. /**
  231. * Creates an event which indicates that the XMPP connection failed
  232. * @param errorType TODO
  233. * @param errorMessage TODO
  234. * @param detail connection failed details.
  235. */
  236. export const createConnectionFailedEvent = (errorType: unknown, errorMessage: unknown, details: object) => {
  237. return {
  238. type: AnalyticsEvents.TYPE_OPERATIONAL,
  239. action: 'connection.failed',
  240. attributes: {
  241. 'error_type': errorType,
  242. 'error_message': errorMessage,
  243. ...details
  244. }
  245. };
  246. };
  247. /**
  248. * Creates a conference event.
  249. *
  250. * @param action - The action of the event.
  251. * @param attributes - The attributes to be added to the event.
  252. */
  253. export const createConferenceEvent = (action: string, attributes: object) => {
  254. return {
  255. action,
  256. attributes,
  257. source: 'conference',
  258. type: AnalyticsEvents.TYPE_OPERATIONAL
  259. };
  260. };
  261. /**
  262. * Creates an operational event which indicates that a particular connection
  263. * stage was reached (i.e. the XMPP connection transitioned to the "connected"
  264. * state).
  265. *
  266. * @param stage the stage which was reached
  267. * @param attributes additional attributes for the event. This should be an
  268. * object with a "value" property indicating a timestamp in milliseconds
  269. * relative to the beginning of the document's lifetime.
  270. *
  271. */
  272. export const createConnectionStageReachedEvent = (stage: unknown, attributes: object) => {
  273. return {
  274. action: 'connection.stage.reached',
  275. actionSubject: stage,
  276. attributes,
  277. source: 'connection.stage.reached',
  278. type: AnalyticsEvents.TYPE_OPERATIONAL
  279. };
  280. };
  281. /**
  282. * Creates an operational event for the end-to-end round trip time to a
  283. * specific remote participant.
  284. * @param participantId the ID of the remote participant.
  285. * @param region the region of the remote participant
  286. * @param rtt the rtt
  287. */
  288. export const createE2eRttEvent = (participantId: unknown, region: unknown, rtt: unknown) => {
  289. return {
  290. attributes: {
  291. 'participant_id': participantId,
  292. region,
  293. rtt
  294. },
  295. name: 'e2e_rtt',
  296. type: AnalyticsEvents.TYPE_OPERATIONAL
  297. };
  298. };
  299. /**
  300. * Creates an event which indicates that the focus has left the MUC.
  301. */
  302. export const createFocusLeftEvent = () => {
  303. return {
  304. action: 'focus.left',
  305. actionSubject: 'focus.left',
  306. type: AnalyticsEvents.TYPE_OPERATIONAL
  307. };
  308. };
  309. /**
  310. * Creates an event related to a getUserMedia call.
  311. *
  312. * @param action the type of the result that the event represents: 'error',
  313. * 'success', 'warning', etc.
  314. * @param attributes the attributes to attach to the event.
  315. */
  316. export const createGetUserMediaEvent = (action: 'error' | 'success' | 'warning' | string, attributes: object = {}) => {
  317. return {
  318. type: AnalyticsEvents.TYPE_OPERATIONAL,
  319. source: 'get.user.media',
  320. action,
  321. attributes
  322. };
  323. };
  324. /**
  325. * Creates an event which indicates that the JVB ICE connection has failed event after 3 retries.
  326. *
  327. * @param action - The action type of the event.
  328. * @param attributes - The attributes to be added to the event.
  329. * @returns - The event object.
  330. */
  331. export const createJvbIceFailedEvent = (action: unknown, attributes: object = {}) => {
  332. return {
  333. action,
  334. attributes,
  335. type: AnalyticsEvents.TYPE_OPERATIONAL,
  336. };
  337. };
  338. /**
  339. * Creates an event related to remote participant connection status changes.
  340. *
  341. * @param attributes the attributes to attach to the event.
  342. */
  343. export const createParticipantConnectionStatusEvent = (attributes: object = {}) => {
  344. return {
  345. type: AnalyticsEvents.TYPE_OPERATIONAL,
  346. source: 'peer.conn.status',
  347. action: 'duration',
  348. attributes
  349. };
  350. };
  351. /**
  352. * Creates an event related to remote track streaming status changes.
  353. *
  354. * @param attributes the attributes to attach to the event.
  355. */
  356. export const createTrackStreamingStatusEvent = (attributes: object = {}) => {
  357. return {
  358. type: AnalyticsEvents.TYPE_OPERATIONAL,
  359. source: 'track.streaming.status',
  360. action: 'duration',
  361. attributes
  362. };
  363. };
  364. /**
  365. * Creates an event for a Jingle-related event.
  366. * @param action the action of the event
  367. * @param attributes attributes to add to the event.
  368. */
  369. export const createJingleEvent = (action: unknown, attributes: object = {}) => {
  370. return {
  371. type: AnalyticsEvents.TYPE_OPERATIONAL,
  372. action,
  373. source: 'jingle',
  374. attributes
  375. };
  376. };
  377. /**
  378. * Creates an event which indicates that a local track was not able to read
  379. * data from its source (a camera or a microphone).
  380. *
  381. * @param mediaType the media type of the local track ('audio' or
  382. * 'video').
  383. */
  384. export const createNoDataFromSourceEvent = (mediaType: 'audio' | 'video' | string, value: unknown) => {
  385. return {
  386. attributes: {
  387. 'media_type': mediaType,
  388. value
  389. },
  390. action: 'track.no.data.from.source',
  391. type: AnalyticsEvents.TYPE_OPERATIONAL
  392. };
  393. };
  394. /**
  395. * Creates an event for a p2p-related event.
  396. * @param action the action of the event
  397. * @param attributes attributes to add to the event.
  398. */
  399. export const createP2PEvent = (action: unknown, attributes: object = {}) => {
  400. return {
  401. type: AnalyticsEvents.TYPE_OPERATIONAL,
  402. action,
  403. source: 'p2p',
  404. attributes
  405. };
  406. };
  407. /**
  408. * Indicates that we received a remote command to mute.
  409. */
  410. export const createRemotelyMutedEvent = (mediaType: unknown) => {
  411. return {
  412. type: AnalyticsEvents.TYPE_OPERATIONAL,
  413. action: 'remotely.muted',
  414. mediaType
  415. };
  416. };
  417. /**
  418. * Creates an event which contains RTP statistics such as RTT and packet loss.
  419. *
  420. * All average RTP stats are currently reported under 1 event name, but with
  421. * different properties that allows to distinguish between a P2P call, a
  422. * call relayed through TURN or the JVB, and multiparty vs 1:1.
  423. *
  424. * The structure of the event is:
  425. *
  426. * {
  427. * p2p: true,
  428. * conferenceSize: 2,
  429. * localCandidateType: "relay",
  430. * remoteCandidateType: "relay",
  431. * transportType: "udp",
  432. *
  433. * // Average RTT of 200ms
  434. * "rtt.avg": 200,
  435. * "rtt.samples": "[100, 200, 300]",
  436. *
  437. * // Average packet loss of 10%
  438. * "packet.loss.avg": 10,
  439. * "packet.loss.samples": '[5, 10, 15]'
  440. *
  441. * // Difference in milliseconds in the end-to-end RTT between p2p and jvb.
  442. * // The e2e RTT through jvb is 15ms shorter:
  443. * "rtt.diff": 15,
  444. *
  445. * // End-to-end RTT through JVB is ms.
  446. * "end2end.rtt.avg" = 100
  447. * }
  448. *
  449. * Note that the value of the "samples" properties are (JSON encoded) strings,
  450. * and not JSON arrays, as events' attributes can not be nested. The samples are
  451. * currently included for debug purposes only and can be removed anytime soon
  452. * from the structure.
  453. *
  454. * Also note that not all of values are present in each event, as values are
  455. * obtained and calculated as part of different process/event pipe. For example
  456. * {@link ConnectionAvgStats} instances are doing the reports for each
  457. * {@link TraceablePeerConnection} and work independently from the main stats
  458. * pipe.
  459. */
  460. export const createRtpStatsEvent = (attributes: object) => {
  461. return {
  462. type: AnalyticsEvents.TYPE_OPERATIONAL,
  463. action: 'rtp.stats',
  464. attributes
  465. };
  466. };
  467. /**
  468. * Creates an event which contains the round trip time (RTT) to a set of
  469. * regions.
  470. *
  471. * @param attributes
  472. */
  473. export const createRttByRegionEvent = (attributes: object) => {
  474. return {
  475. type: AnalyticsEvents.TYPE_OPERATIONAL,
  476. action: 'rtt.by.region',
  477. attributes
  478. };
  479. };
  480. /**
  481. * Creates an event which contains the local and remote ICE candidate types
  482. * for the transport that is currently selected.
  483. *
  484. * @param attributes
  485. */
  486. export const createTransportStatsEvent = (attributes: object) => {
  487. return {
  488. type: AnalyticsEvents.TYPE_OPERATIONAL,
  489. action: 'transport.stats',
  490. attributes
  491. };
  492. };
  493. /**
  494. * Creates an event which contains information about the audio output problem (the user id of the affected participant,
  495. * the local audio levels and the remote audio levels that triggered the event).
  496. *
  497. * @param userID - The user id of the affected participant.
  498. * @param localAudioLevels - The local audio levels.
  499. * @param remoteAudioLevels - The audio levels received from the participant.
  500. */
  501. export const createAudioOutputProblemEvent
  502. = (userID: string, localAudioLevels: unknown, remoteAudioLevels: unknown) => {
  503. return {
  504. type: AnalyticsEvents.TYPE_OPERATIONAL,
  505. action: 'audio.output.problem',
  506. attributes: {
  507. userID,
  508. localAudioLevels,
  509. remoteAudioLevels
  510. }
  511. };
  512. };
  513. /**
  514. * Creates an event which contains an information related to the bridge channel close event.
  515. *
  516. * @param code - A code from {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
  517. * @param reason - A string which describes the reason for closing the bridge channel.
  518. */
  519. export const createBridgeChannelClosedEvent = (code: string, reason: string) => {
  520. return {
  521. type: AnalyticsEvents.TYPE_OPERATIONAL,
  522. action: 'bridge-channel.error',
  523. attributes: {
  524. code,
  525. reason
  526. }
  527. };
  528. };
  529. /**
  530. * Creates an event which indicates the Time To First Media (TTFM).
  531. * It is measured in milliseconds relative to the beginning of the document's
  532. * lifetime (i.e. the origin used by window.performance.now()), and it excludes
  533. * the following:
  534. * 1. The delay due to getUserMedia()
  535. * 2. The period between the MUC being joined and the reception of the Jingle
  536. * session-initiate from jicofo. This is because jicofo will not start a Jingle
  537. * session until there are at least 2 participants in the room.
  538. *
  539. * @param attributes the attributes to add to the event. Currently used fields:
  540. * mediaType: the media type of the local track ('audio' or 'video').
  541. * muted: whether the track has ever been muted (?)
  542. * value: the TTMF in milliseconds.
  543. */
  544. export const createTtfmEvent = (attributes: object) => createConnectionStageReachedEvent('ttfm', attributes);