northbase 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/northbase.mjs +26 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "northbase",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Local-first CLI for reading and writing text files stored in Supabase",
5
5
  "type": "module",
6
6
  "bin": {
package/src/northbase.mjs CHANGED
@@ -82,12 +82,15 @@ async function doRefresh(supabase, stored) {
82
82
  });
83
83
  if (error) {
84
84
  const revoked = error.message?.includes("invalid_grant")
85
+ || error.message?.includes("Refresh Token Not Found")
86
+ || error.message?.includes("refresh_token_not_found")
85
87
  || error.error === "invalid_grant"
86
- || error.code === "invalid_grant";
88
+ || error.code === "invalid_grant"
89
+ || error.code === "refresh_token_not_found";
87
90
  if (revoked) {
88
- console.error("NORTHBASE session revoked — deleting session.json");
91
+ console.error("NORTHBASE session token invalid — deleting session.json");
89
92
  deleteSession();
90
- throw new Error("Session revoked. Run `northbase login`.");
93
+ throw new Error("Refresh token is no longer valid. Run `northbase login`.");
91
94
  }
92
95
  // Transient error (network, rate limit, etc.) — do NOT delete session.json
93
96
  throw new Error(`Session refresh failed (${error.message}) — session preserved, will retry next command.`);
@@ -122,13 +125,21 @@ async function getAuthenticatedClient() {
122
125
  if (needsRefresh) {
123
126
  await doRefresh(supabase, stored);
124
127
  } else {
125
- const { error } = await supabase.auth.setSession({
128
+ const { data: setData, error } = await supabase.auth.setSession({
126
129
  access_token: stored.access_token,
127
130
  refresh_token: stored.refresh_token,
128
131
  });
129
132
  if (error) {
130
133
  debug(`setSession failed (${error.message}) — falling back to refresh`);
131
134
  await doRefresh(supabase, stored);
135
+ } else if (setData?.session && setData.session.access_token !== stored.access_token) {
136
+ // setSession decoded the JWT, found it expired, and internally called _callRefreshToken().
137
+ // The new tokens are returned in setData.session but NOT written to disk
138
+ // (persistSession: false suppresses supabase-js's internal _saveSession).
139
+ // Persist them now — otherwise session.json keeps the consumed refresh_token
140
+ // and the next command fails with "Refresh Token Not Found".
141
+ console.error("NORTHBASE setSession triggered internal refresh — persisting new session");
142
+ saveSession(setData.session);
132
143
  }
133
144
  }
134
145
 
@@ -435,17 +446,28 @@ async function cmdAuthDebug() {
435
446
 
436
447
  console.log(`has_access_token=${!!stored.access_token}`);
437
448
  console.log(`has_refresh_token=${!!stored.refresh_token}`);
449
+ console.log(`refresh_token_prefix=${stored.refresh_token?.slice(0, 6) ?? "none"}`);
438
450
  console.log(`expires_at=${expiresAt}`);
439
451
  console.log(`seconds_remaining=${secsRemaining}`);
440
452
  console.log(`will_refresh_soon=${secsRemaining <= 60}`);
441
453
  console.log(`email=${stored.user?.email ?? "(unknown)"}`);
442
454
 
455
+ try {
456
+ const stat = fs.statSync(SESSION_PATH);
457
+ const mtimeSec = Math.floor(stat.mtimeMs / 1000);
458
+ console.log(`session_file_mtime=${mtimeSec}`);
459
+ console.log(`session_file_age_seconds=${nowSec - mtimeSec}`);
460
+ } catch { /* ignore */ }
461
+
443
462
  let refreshAttempted = false, refreshSucceeded = false, userFetchSucceeded = false;
444
463
  try {
445
464
  const supabase = await getAuthenticatedClient();
446
465
  const after = JSON.parse(fs.readFileSync(SESSION_PATH, "utf8"));
447
466
  refreshAttempted = secsRemaining <= 60 || after.expires_at !== expiresAt;
448
467
  refreshSucceeded = !!after.access_token;
468
+ console.log(`token_silently_refreshed=${after.access_token !== stored.access_token}`);
469
+ console.log(`refresh_token_rotated=${after.refresh_token?.slice(0,6) !== stored.refresh_token?.slice(0,6)}`);
470
+ console.log(`new_expires_at=${after.expires_at}`);
449
471
  const { data, error } = await supabase.auth.getUser();
450
472
  userFetchSucceeded = !error && !!data?.user;
451
473
  } catch (e) {