use-editing-session-heartbeat.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
  2. import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
  3. import { EditorType } from '@/features/ide-react/editor/types/editor-type'
  4. import { putJSON } from '@/infrastructure/fetch-json'
  5. import { debugConsole } from '@/utils/debugging'
  6. import { useCallback, useEffect, useRef } from 'react'
  7. import useEventListener from '@/shared/hooks/use-event-listener'
  8. import useDomEventListener from '@/shared/hooks/use-dom-event-listener'
  9. import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
  10. import {
  11. IdeLayout,
  12. IdeView,
  13. useLayoutContext,
  14. } from '@/shared/context/layout-context'
  15. import {
  16. RailTabKey,
  17. useRailContext,
  18. } from '@/features/ide-redesign/contexts/rail-context'
  19. function createEditingSessionHeartbeatData(
  20. editorType: EditorType,
  21. newEditor: boolean,
  22. view: IdeView | null,
  23. layout: IdeLayout,
  24. railOpen: boolean,
  25. railTab: RailTabKey,
  26. hasDetachedPdf: boolean
  27. ) {
  28. const newEditorSegmentation = newEditor ? { railOpen, railTab } : {}
  29. return {
  30. editorType,
  31. editorRedesign: newEditor,
  32. editorView: view,
  33. editorLayout: layout,
  34. hasDetachedPdf,
  35. ...newEditorSegmentation,
  36. }
  37. }
  38. function sendEditingSessionHeartbeat(
  39. projectId: string,
  40. segmentation: Record<string, unknown>
  41. ) {
  42. putJSON(`/editingSession/${projectId}`, {
  43. body: { segmentation },
  44. }).catch(debugConsole.error)
  45. }
  46. export function useEditingSessionHeartbeat() {
  47. const { projectId } = useIdeReactContext()
  48. const { getEditorType } = useEditorManagerContext()
  49. const newEditor = useIsNewEditorEnabled()
  50. const { view, pdfLayout: layout, detachIsLinked } = useLayoutContext()
  51. const { isOpen: railIsOpen, selectedTab: selectedRailTab } = useRailContext()
  52. // Keep track of how many heartbeats we've sent so that we can calculate how
  53. // long to wait until the next one
  54. const heartBeatsSentRef = useRef(0)
  55. const heartBeatSentRecentlyRef = useRef(false)
  56. const heartBeatResetTimerRef = useRef<number>()
  57. useEffect(() => {
  58. return () => {
  59. window.clearTimeout(heartBeatResetTimerRef.current)
  60. }
  61. }, [])
  62. const editingSessionHeartbeat = useCallback(() => {
  63. debugConsole.log('[Event] heartbeat trigger')
  64. const editorType = getEditorType()
  65. if (editorType === null) return
  66. // Heartbeat already sent recently
  67. if (heartBeatSentRecentlyRef.current) return
  68. heartBeatSentRecentlyRef.current = true
  69. const segmentation = createEditingSessionHeartbeatData(
  70. editorType,
  71. newEditor,
  72. view,
  73. layout,
  74. railIsOpen,
  75. selectedRailTab,
  76. detachIsLinked
  77. )
  78. debugConsole.log('[Event] send heartbeat request', segmentation)
  79. sendEditingSessionHeartbeat(projectId, segmentation)
  80. const heartbeatsSent = heartBeatsSentRef.current
  81. heartBeatsSentRef.current++
  82. // Send two first heartbeats at 0 and 30s then increase the backoff time
  83. // 1min per call until we reach 5 min
  84. const backoffSecs =
  85. heartbeatsSent <= 2
  86. ? 30
  87. : heartbeatsSent <= 6
  88. ? (heartbeatsSent - 2) * 60
  89. : 300
  90. heartBeatResetTimerRef.current = window.setTimeout(() => {
  91. heartBeatSentRecentlyRef.current = false
  92. }, backoffSecs * 1000)
  93. }, [
  94. getEditorType,
  95. projectId,
  96. newEditor,
  97. view,
  98. layout,
  99. railIsOpen,
  100. selectedRailTab,
  101. detachIsLinked,
  102. ])
  103. // Hook the heartbeat up to editor events
  104. useEventListener('cursor:editor:update', editingSessionHeartbeat)
  105. useEventListener('scroll:editor:update', editingSessionHeartbeat)
  106. useDomEventListener(document, 'click', editingSessionHeartbeat)
  107. }