Files
portal/app/resources/js/composables/usePolling.ts
T

74 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { onBeforeUnmount, onMounted } from 'vue';
import { POLLING_INTERVAL_MS } from '../constants/polling';
/**
* Polling-composable для авто-обновления view-данных.
*
* Используется в DealsView/KanbanView/AdminTenants/Billing/Incidents как
* замена manual reload-btn — каждые `intervalMs` миллисекунд вызывает
* переданный `loader()`. На MVP это покрывает «real-time» pattern до
* приезда SSE/WebSocket в production.
*
* Pause при скрытой вкладке (`document.hidden=true`) через Page Visibility
* API — не делаем лишних запросов когда пользователь смотрит другую
* вкладку. Resume на visibilitychange-event.
*
* Cleanup на onBeforeUnmount: clearInterval + removeEventListener.
*/
export interface PollingOptions {
/** Период polling в миллисекундах. По умолчанию POLLING_INTERVAL_MS (30 с). */
intervalMs?: number;
/** Если false — composable не стартует interval (для disable-флага). */
enabled?: boolean;
}
export function usePolling(loader: () => void | Promise<void>, options: PollingOptions = {}): void {
const intervalMs = options.intervalMs ?? POLLING_INTERVAL_MS;
const enabled = options.enabled ?? true;
if (!enabled) return;
let timerId: ReturnType<typeof setInterval> | null = null;
let visibilityListener: (() => void) | null = null;
function start() {
if (timerId !== null) return;
timerId = setInterval(() => {
// Skip если вкладка скрыта — экономим запросы.
if (typeof document !== 'undefined' && document.hidden) return;
void loader();
}, intervalMs);
}
function stop() {
if (timerId !== null) {
clearInterval(timerId);
timerId = null;
}
}
onMounted(() => {
start();
if (typeof document !== 'undefined') {
visibilityListener = () => {
if (document.hidden) {
stop();
} else {
start();
// Сразу подгрузить свежее на возврат во вкладку (не ждать interval).
void loader();
}
};
document.addEventListener('visibilitychange', visibilityListener);
}
});
onBeforeUnmount(() => {
stop();
if (visibilityListener !== null && typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', visibilityListener);
visibilityListener = null;
}
});
}