← back to Beverlyhillsvideos
social/poster/token-health.mjs
37 lines
#!/usr/bin/env node
// token-health.mjs — READ-ONLY check that the BHV Graph API token is alive + not near
// expiry. The 6-hour poster fails SILENTLY if the token dies (launchd doesn't alert),
// so this is the missing watchdog (Cody FIX-FIRST #2). Exit 0 healthy, 1 dead/expiring.
// Wire to a launchd job + George email on non-zero (gated install — see pending-approval).
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const env = fs.readFileSync(path.join(os.homedir(), 'Projects/secrets-manager/.env'), 'utf8');
const g = (k) => { let v = null; for (const l of env.split('\n')) { const m = l.match(/^([A-Z0-9_]+)=(.*)$/); if (m && m[1] === k) v = m[2]; } return v; };
const tok = g('BHV_IG_ACCESS_TOKEN'), ig = g('BHV_IG_USER_ID'), ver = g('BHV_IG_GRAPH_VERSION') || 'v21.0';
const WARN_DAYS = 10; // warn if expiry within this window
(async () => {
if (!tok || !ig) { console.log('DEAD: missing BHV_IG token/user id'); process.exit(1); }
try {
// 1) can the token still read the IG account?
const r = await fetch(`https://graph.facebook.com/${ver}/${ig}?fields=username&access_token=${tok}`);
const j = await r.json();
if (j.error) { console.log(`DEAD: ${JSON.stringify(j.error).slice(0, 160)}`); process.exit(1); }
// 2) how long until it expires?
const d = await (await fetch(`https://graph.facebook.com/${ver}/debug_token?input_token=${tok}&access_token=${tok}`)).json();
const exp = d?.data?.expires_at;
if (exp && exp > 0) {
const days = Math.round((exp * 1000 - Date.now()) / 864e5);
if (days <= 0) { console.log('DEAD: token expired'); process.exit(1); }
if (days <= WARN_DAYS) { console.log(`EXPIRING: @${j.username} token expires in ${days}d`); process.exit(1); }
console.log(`HEALTHY: @${j.username} token OK, expires in ${days}d`);
} else {
console.log(`HEALTHY: @${j.username} token OK (no expiry / long-lived)`);
}
process.exit(0);
} catch (e) { console.log(`DEAD: ${e.message}`); process.exit(1); }
})();