northbase 0.1.4 → 0.1.5
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.
- package/package.json +1 -1
- package/src/northbase.mjs +80 -22
package/package.json
CHANGED
package/src/northbase.mjs
CHANGED
|
@@ -16,6 +16,11 @@ const INDEX_PATH = path.join(NORTHBASE_DIR, "index.json");
|
|
|
16
16
|
const SESSION_PATH = path.join(NORTHBASE_DIR, "session.json");
|
|
17
17
|
const MAX_BYTES = 500_000;
|
|
18
18
|
|
|
19
|
+
const DEBUG = !!process.env.NORTHBASE_DEBUG;
|
|
20
|
+
function debug(...args) {
|
|
21
|
+
if (DEBUG) console.error("NORTHBASE DEBUG", ...args);
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
// ── directory / index helpers ─────────────────────────────────────────────────
|
|
20
25
|
|
|
21
26
|
function ensureDir(p) {
|
|
@@ -37,6 +42,13 @@ function saveIndex(idx) {
|
|
|
37
42
|
|
|
38
43
|
// ── session helpers ───────────────────────────────────────────────────────────
|
|
39
44
|
|
|
45
|
+
function normalizeSession(s) {
|
|
46
|
+
let expiresAt = s.expires_at;
|
|
47
|
+
if (expiresAt && expiresAt > 1e12) expiresAt = Math.floor(expiresAt / 1000); // ms → s
|
|
48
|
+
if (!expiresAt && s.expires_in) expiresAt = Math.floor(Date.now() / 1000) + s.expires_in;
|
|
49
|
+
return { ...s, expires_at: expiresAt ?? 0 };
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
function loadSession() {
|
|
41
53
|
try {
|
|
42
54
|
const s = JSON.parse(fs.readFileSync(SESSION_PATH, "utf8"));
|
|
@@ -49,7 +61,11 @@ function loadSession() {
|
|
|
49
61
|
|
|
50
62
|
function saveSession(session) {
|
|
51
63
|
ensureDir(NORTHBASE_DIR);
|
|
52
|
-
|
|
64
|
+
const normalized = normalizeSession(session);
|
|
65
|
+
const tmp = SESSION_PATH + ".tmp";
|
|
66
|
+
fs.writeFileSync(tmp, JSON.stringify(normalized, null, 2), { mode: 0o600 });
|
|
67
|
+
fs.renameSync(tmp, SESSION_PATH);
|
|
68
|
+
debug("session.json written expires_at=" + normalized.expires_at);
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
function deleteSession() {
|
|
@@ -58,34 +74,58 @@ function deleteSession() {
|
|
|
58
74
|
|
|
59
75
|
// ── authenticated supabase client ─────────────────────────────────────────────
|
|
60
76
|
|
|
77
|
+
async function doRefresh(supabase, stored) {
|
|
78
|
+
debug("refresh starting");
|
|
79
|
+
console.error("NORTHBASE session refreshing");
|
|
80
|
+
const { data, error } = await supabase.auth.refreshSession({
|
|
81
|
+
refresh_token: stored.refresh_token,
|
|
82
|
+
});
|
|
83
|
+
if (error) {
|
|
84
|
+
const revoked = error.message?.includes("invalid_grant") || error.status === 400;
|
|
85
|
+
if (revoked) deleteSession();
|
|
86
|
+
throw new Error(revoked
|
|
87
|
+
? "Session revoked. Run `northbase login`."
|
|
88
|
+
: `Session refresh failed (${error.message}). Run \`northbase login\`.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const newSession = data.session;
|
|
92
|
+
if (!newSession?.access_token || !newSession?.refresh_token) {
|
|
93
|
+
throw new Error("Refresh returned incomplete session. Run `northbase login`.");
|
|
94
|
+
}
|
|
95
|
+
debug(`refresh ok refresh_token_rotated=${newSession.refresh_token !== stored.refresh_token}`);
|
|
96
|
+
saveSession(newSession);
|
|
97
|
+
const { error: setErr } = await supabase.auth.setSession({
|
|
98
|
+
access_token: newSession.access_token,
|
|
99
|
+
refresh_token: newSession.refresh_token,
|
|
100
|
+
});
|
|
101
|
+
if (setErr) throw setErr;
|
|
102
|
+
}
|
|
103
|
+
|
|
61
104
|
async function getAuthenticatedClient() {
|
|
62
|
-
const stored = loadSession();
|
|
105
|
+
const stored = loadSession();
|
|
106
|
+
debug(`session loaded expires_at=${stored.expires_at}`);
|
|
63
107
|
|
|
64
108
|
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
|
|
65
109
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
66
110
|
});
|
|
67
111
|
|
|
68
|
-
const nowSec
|
|
69
|
-
const
|
|
112
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
113
|
+
const expiresAt = stored.expires_at ?? 0;
|
|
114
|
+
const secsRemaining = expiresAt - nowSec;
|
|
115
|
+
const needsRefresh = expiresAt === 0 || secsRemaining <= 60;
|
|
116
|
+
debug(`seconds_remaining=${secsRemaining} needs_refresh=${needsRefresh}`);
|
|
70
117
|
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
const { data, error } = await supabase.auth.refreshSession({
|
|
74
|
-
refresh_token: stored.refresh_token,
|
|
75
|
-
});
|
|
76
|
-
if (error) throw new Error("Session expired. Run `northbase login` again.");
|
|
77
|
-
saveSession(data.session);
|
|
78
|
-
const { error: setErr } = await supabase.auth.setSession({
|
|
79
|
-
access_token: data.session.access_token,
|
|
80
|
-
refresh_token: data.session.refresh_token,
|
|
81
|
-
});
|
|
82
|
-
if (setErr) throw setErr;
|
|
118
|
+
if (needsRefresh) {
|
|
119
|
+
await doRefresh(supabase, stored);
|
|
83
120
|
} else {
|
|
84
121
|
const { error } = await supabase.auth.setSession({
|
|
85
122
|
access_token: stored.access_token,
|
|
86
123
|
refresh_token: stored.refresh_token,
|
|
87
124
|
});
|
|
88
|
-
if (error)
|
|
125
|
+
if (error) {
|
|
126
|
+
debug(`setSession failed (${error.message}) — falling back to refresh`);
|
|
127
|
+
await doRefresh(supabase, stored);
|
|
128
|
+
}
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
return supabase;
|
|
@@ -356,16 +396,33 @@ async function cmdWhoami() {
|
|
|
356
396
|
console.log(`Logged in as ${email} (${id})`);
|
|
357
397
|
}
|
|
358
398
|
|
|
399
|
+
async function cmdSession() {
|
|
400
|
+
let stored;
|
|
401
|
+
try { stored = loadSession(); } catch {
|
|
402
|
+
console.log("Not logged in.");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
406
|
+
const expiresAt = stored.expires_at ?? 0;
|
|
407
|
+
const secsRemaining = expiresAt - nowSec;
|
|
408
|
+
console.log(`now=${nowSec}`);
|
|
409
|
+
console.log(`expires_at=${expiresAt}`);
|
|
410
|
+
console.log(`seconds_remaining=${secsRemaining}`);
|
|
411
|
+
console.log(`will_refresh_soon=${secsRemaining <= 60}`);
|
|
412
|
+
console.log(`email=${stored.user?.email ?? "(unknown)"}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
359
415
|
// ── main ──────────────────────────────────────────────────────────────────────
|
|
360
416
|
|
|
361
417
|
async function main() {
|
|
362
418
|
const [cmd, ...args] = process.argv.slice(2);
|
|
363
419
|
|
|
364
|
-
if (cmd === "login")
|
|
365
|
-
if (cmd === "logout")
|
|
366
|
-
if (cmd === "whoami")
|
|
367
|
-
if (cmd === "
|
|
368
|
-
if (cmd === "
|
|
420
|
+
if (cmd === "login") { await cmdLogin(); return; }
|
|
421
|
+
if (cmd === "logout") { await cmdLogout(); return; }
|
|
422
|
+
if (cmd === "whoami") { await cmdWhoami(); return; }
|
|
423
|
+
if (cmd === "session") { await cmdSession(); return; }
|
|
424
|
+
if (cmd === "list") { await cmdList(args[0]); return; }
|
|
425
|
+
if (cmd === "pull") { await cmdPull(args[0]); return; }
|
|
369
426
|
|
|
370
427
|
if (cmd === "get") {
|
|
371
428
|
const rel = args[0];
|
|
@@ -390,6 +447,7 @@ async function main() {
|
|
|
390
447
|
console.log(" northbase login");
|
|
391
448
|
console.log(" northbase logout");
|
|
392
449
|
console.log(" northbase whoami");
|
|
450
|
+
console.log(" northbase session");
|
|
393
451
|
console.log(" northbase list [prefix]");
|
|
394
452
|
console.log(" northbase pull [prefix]");
|
|
395
453
|
console.log(" northbase get <path>");
|