useQueryPolling.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. // Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. import {
  3. ref,
  4. type Ref,
  5. type ComputedRef,
  6. onScopeDispose,
  7. toRef,
  8. watch,
  9. toValue,
  10. } from 'vue'
  11. import type { QueryHandler } from '#shared/server/apollo/handler'
  12. import type { OperationQueryResult } from '#shared/types/server/apollo/handler'
  13. import type { OperationVariables } from '@apollo/client/core'
  14. export type QueryPollingOptions = {
  15. enabled?: boolean // Enable polling, default is true.
  16. randomize?: boolean // Randomize the interval (1000 milliseconds). Useful to prevent request at the same time.
  17. }
  18. export const useQueryPolling = <
  19. TResult extends OperationQueryResult = OperationQueryResult,
  20. TVariables extends OperationVariables = OperationVariables,
  21. >(
  22. query: QueryHandler<TResult, TVariables>,
  23. interval: number | Ref<number> | ComputedRef<number> | (() => number),
  24. variables?:
  25. | Ref<Partial<TVariables>>
  26. | ComputedRef<Partial<TVariables>>
  27. | (() => Partial<TVariables>),
  28. options?:
  29. | QueryPollingOptions
  30. | Ref<QueryPollingOptions>
  31. | ComputedRef<QueryPollingOptions>
  32. | (() => QueryPollingOptions),
  33. ) => {
  34. const isPolling = ref(false)
  35. let pollTimer: ReturnType<typeof setTimeout>
  36. // Only randomize up to +1000ms to avoid requests happening at the same time
  37. const randomizeInterval = toValue(options)?.randomize
  38. ? Math.floor(Math.random() * 1000)
  39. : 0
  40. const intervalRef = toRef(interval)
  41. const startPolling = () => {
  42. if (
  43. isPolling.value ||
  44. (toValue(options)?.enabled !== undefined && !toValue(options)?.enabled)
  45. )
  46. return
  47. isPolling.value = true
  48. const poll = async () => {
  49. const pollVariables =
  50. typeof variables === 'function' ? variables() : variables?.value
  51. await query.refetch(pollVariables as TVariables)
  52. // Only schedule next poll after current one completes
  53. if (isPolling.value) {
  54. pollTimer = setTimeout(poll, intervalRef.value + randomizeInterval)
  55. }
  56. }
  57. // Schedule first poll after interval instead of running immediately
  58. pollTimer = setTimeout(poll, intervalRef.value + randomizeInterval)
  59. }
  60. const stopPolling = () => {
  61. if (!isPolling.value) return
  62. clearTimeout(pollTimer)
  63. isPolling.value = false
  64. }
  65. watch(
  66. () => toValue(options)?.enabled,
  67. (newValue) => {
  68. if (newValue) {
  69. startPolling()
  70. return
  71. }
  72. stopPolling()
  73. },
  74. )
  75. // Automatically stop polling when scope is disposed
  76. onScopeDispose(() => {
  77. stopPolling()
  78. })
  79. return {
  80. isPolling,
  81. startPolling,
  82. stopPolling,
  83. }
  84. }