import { httpRequest } from '@utils'; import { ApplicationController } from './application_controller'; const DEFAULT_POLL_INTERVAL = 3000; const DEFAULT_MAX_CHECKS = 5; // Periodically check the state of a URL. // // Each time the given URL is requested, a turbo-stream is rendered, causing the state to be refreshed. // // This is used mainly to refresh attachments during the anti-virus check, // but also to refresh the state of a pending spreadsheet export. export class TurboPollController extends ApplicationController { static values = { url: String, maxChecks: { type: Number, default: DEFAULT_MAX_CHECKS }, interval: { type: Number, default: DEFAULT_POLL_INTERVAL } }; declare readonly urlValue: string; declare readonly intervalValue: number; declare readonly maxChecksValue: number; #timer?: ReturnType; #abortController?: AbortController; connect(): void { const state = this.nextState(); if (state) { this.schedule(state); } } disconnect(): void { this.cancel(); } refresh() { this.cancel(); this.#abortController = new AbortController(); httpRequest(this.urlValue, { signal: this.#abortController.signal }) .turbo() .catch(() => null); } private schedule(state: PollState): void { this.cancel(); this.#timer = setTimeout(() => { this.refresh(); }, state.interval); } private cancel(): void { clearTimeout(this.#timer); this.#abortController?.abort(); this.#abortController = window.AbortController ? new AbortController() : undefined; } private nextState(): PollState | false { const state = pollers.get(this.urlValue); if (!state) { return this.resetState(); } state.interval *= 1.5; state.checks += 1; if (state.checks <= this.maxChecksValue) { return state; } else { this.resetState(); return false; } } private resetState(): PollState { const state = { interval: this.intervalValue, checks: 0 }; pollers.set(this.urlValue, state); return state; } } type PollState = { interval: number; checks: number; }; // We keep a global state of the pollers. It will be reset on every page change. const pollers = new Map();