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 19KB

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