[object Object]

← back to Marketing Command Center

channels: Meta connect now upgrades short→long-lived token + re-caches all 80 Pages so the whole IG fleet comes back permanent (not 1-hour)

81c03cf86b16b03255943a396c32935de91cb1cf · 2026-07-15 16:00:47 -0700 · Steve Abrams

Files touched

Diff

commit 81c03cf86b16b03255943a396c32935de91cb1cf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 15 16:00:47 2026 -0700

    channels: Meta connect now upgrades short→long-lived token + re-caches all 80 Pages so the whole IG fleet comes back permanent (not 1-hour)
---
 modules/channels/index.js | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/modules/channels/index.js b/modules/channels/index.js
index b3c265c..e2b1106 100644
--- a/modules/channels/index.js
+++ b/modules/channels/index.js
@@ -203,6 +203,22 @@ async function exchangeCode(p, code) {
   return j; // {access_token, refresh_token?, expires_in?}
 }
 
+// Upgrade a short-lived Meta user token to a long-lived (~60-day) one. Page tokens
+// derived from a long-lived user token are PERMANENT (never expire) — this is the
+// single step that stops the whole fleet dying in an hour. Returns the long-lived
+// access_token, or throws (caller decides whether to fall back to the short one).
+async function exchangeLongLivedMeta(shortToken) {
+  const appId = env('META_APP_ID'), secret = env('META_APP_SECRET');
+  if (!appId || !secret) throw new Error('META_APP_ID / META_APP_SECRET not set');
+  const qs = new URLSearchParams({
+    grant_type: 'fb_exchange_token', client_id: appId, client_secret: secret, fb_exchange_token: shortToken,
+  });
+  const r = await fetch(`${GRAPH}/oauth/access_token?${qs}`);
+  const j = await r.json().catch(() => ({}));
+  if (!r.ok || j.error || !j.access_token) throw new Error(j.error?.message || 'long-lived exchange returned no token');
+  return j.access_token;
+}
+
 // ── Per-platform connection status (what's wired vs what Steve must authorize) ──
 function platformStatus() {
   const tk = readTokens();
@@ -540,7 +556,24 @@ module.exports = {
         all[p] = { ...tok, connectedAt: new Date().toISOString() };
         if (p === 'facebook' || p === 'instagram') { all.facebook = all[p]; all.instagram = all[p]; }
         writeTokens(all);
-        res.send(`<body style="font-family:system-ui;padding:50px;text-align:center"><h2>✓ ${p} connected</h2><p>You can close this tab and return to the Marketing Command Center.</p><a href="/#channels">← back to Channels</a></body>`);
+        // Meta short-lived → long-lived, then re-cache ALL Pages. A short-lived
+        // user token (and any Page tokens derived from it) dies in ~1-2h; Page
+        // tokens derived from a LONG-LIVED user token never expire. So we upgrade
+        // once here, persist it as META_ACCESS_TOKEN, and fetch /me/accounts so
+        // the whole 80-Page / IG fleet comes back permanent — not just for an hour.
+        let fleetNote = '';
+        if (p === 'facebook' || p === 'instagram') {
+          try {
+            const longLived = await exchangeLongLivedMeta(tok.access_token);
+            setEnvKeys({ META_ACCESS_TOKEN: longLived }); // process.env updated live
+            const cache = await fetchMetaPages();          // re-cache all Pages + IG links
+            const igCount = cache.pages.filter(x => x.igId).length;
+            fleetNote = `<p>Restored <b>${cache.pages.length} Pages</b> (${igCount} with Instagram) — long-lived, won’t expire.</p>`;
+          } catch (e) {
+            fleetNote = `<p style="color:#b00">Connected, but fleet refresh failed: ${e.message}. Click “↻ refresh Pages” on the Pages picker.</p>`;
+          }
+        }
+        res.send(`<body style="font-family:system-ui;padding:50px;text-align:center"><h2>✓ ${p} connected</h2>${fleetNote}<p>You can close this tab and return to the Marketing Command Center.</p><a href="/#channels">← back to Channels</a></body>`);
       } catch (e) { res.status(500).send(`Token exchange failed for ${p}: ${e.message}`); }
     });
 

← 9ef59f0 Add deploy/apply-cutover.sh — paste-safe one-command nginx c  ·  back to Marketing Command Center  ·  All Calendars module: backend (activation projection + shopi fea8d92 →