trigger.dev
Version:
A Command-Line Interface for Trigger.dev projects
84 lines • 3.14 kB
JavaScript
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