Renew before expiry

Refresh a token, lease, or session a few minutes before it expires. Worked examples for Google and Slack v2 OAuth (with refresh-token rotation); the same shape applies to GitHub Apps, presigned URLs, and any time-bound credential.

Use this to
  • Refresh a Google OAuth access token 5 minutes before its expiry_date
  • Refresh a Slack v2 OAuth token before the 12-hour access window closes (refresh tokens always rotate)
  • Renew a GitHub App installation token before its 1-hour TTL expires
  • Re-sign a presigned S3 upload URL ahead of its expiration
Code
Google OAuth: schedule a renewal before expiry_date
// after first authorizing, schedule a renewal 5 minutes before expiry
await dk.schedule("refresh-google", {
  key: account.id,
  at: new Date(tokens.expiry_date - 5 * 60 * 1000),
});

// handler refreshes, persists, reschedules.
// Google may rotate the refresh_token. fall back to the stored
// one if the refresh response omits it (common case).
dk.handle("refresh-google", async ({ key, reschedule }) => {
  const stored = await db.googleTokens.find(key);
  oauth2Client.setCredentials({ refresh_token: stored.refresh_token });

  const { credentials } = await oauth2Client.refreshAccessToken();

  await db.googleTokens.update(key, {
    access_token: credentials.access_token,
    refresh_token: credentials.refresh_token ?? stored.refresh_token,
    expiry_date: credentials.expiry_date,
  });

  reschedule({ at: new Date(credentials.expiry_date - 5 * 60 * 1000) });
});
Slack v2: refresh_token rotates on every refresh
// Slack v2 with token rotation enabled. the refresh_token is
// single-use and rotates on every call, so persist both tokens
// or the next renewal will fail.
dk.handle("refresh-slack", async ({ key, reschedule }) => {
  const stored = await db.slackTokens.find(key);

  const res = await fetch("https://slack.com/api/oauth.v2.access", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: stored.refresh_token,
      client_id: process.env.SLACK_CLIENT_ID!,
      client_secret: process.env.SLACK_CLIENT_SECRET!,
    }),
  });
  const data = await res.json();

  await db.slackTokens.update(key, {
    access_token: data.access_token,
    refresh_token: data.refresh_token, // mandatory rotation
  });

  // renew at 90% of the new access token's lifetime
  reschedule({ delay: `${Math.floor(data.expires_in * 0.9)}s` });
});
npm install delaykit
bun add delaykit