UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

84 lines • 3.14 kB
import { RUN_TOKEN_REFRESH_BEFORE_EXPIRY_SECONDS, } from "@trigger.dev/core/v3/workers"; /** * Tracks the run token installed in one attempt's task process. It reports the installed expiry on * heartbeats so the server can decide when a replacement is due, and installs replacements with an * acknowledgement from the task process. Failed installs keep the old expiry, so the next * heartbeat asks again. */ export class RunTokenRefresher { opts; expiresAt; installing = false; pendingReport; constructor(opts) { this.opts = opts; this.expiresAt = opts.initialToken ? claimsOf(opts.initialToken)?.exp : undefined; } /** Undefined when this attempt has no run token, or while a replacement is being installed. */ report(trigger) { if (this.expiresAt === undefined || this.installing) return undefined; return { expiresAt: this.expiresAt, trigger, installation: this.pendingReport }; } get isDue() { return (this.expiresAt !== undefined && this.expiresAt - this.nowSeconds() <= RUN_TOKEN_REFRESH_BEFORE_EXPIRY_SECONDS); } /** Call after the heartbeat that carried `sent` succeeded, with any replacement it returned. */ async handleHeartbeatResponse(sent, replacement) { if (!sent || this.installing) return undefined; if (sent.installation === this.pendingReport) this.pendingReport = undefined; if (!replacement) return undefined; const claims = claimsOf(replacement); let outcome; if (!claims || claims.run_id !== this.opts.runId || claims.attempt !== this.opts.attemptNumber || claims.exp === undefined) { outcome = { result: "failure", reason: "invalid_token" }; } else { this.installing = true; try { outcome = await this.opts.install({ runId: this.opts.runId, attemptNumber: this.opts.attemptNumber, token: replacement, }); } finally { this.installing = false; } if (outcome.result === "success") this.expiresAt = claims.exp; } this.pendingReport = { ...outcome, trigger: sent.trigger }; return outcome; } nowSeconds() { return (this.opts.now?.() ?? Date.now()) / 1_000; } } // Not verification: the token came from the control plane, this only reads timing and identity. function claimsOf(token) { const payload = token.split(".")[1]; if (!payload) return undefined; try { const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")); if (!claims || typeof claims !== "object") return undefined; return { exp: typeof claims.exp === "number" ? claims.exp : undefined, run_id: claims.run_id, attempt: claims.attempt, }; } catch { return undefined; } } //# sourceMappingURL=runTokenRefresh.js.map