edsger 0.72.2 → 0.72.3
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.
|
@@ -19,5 +19,16 @@ export declare function getSupabase(): SupabaseClient;
|
|
|
19
19
|
* usable for direct-SDK calls.
|
|
20
20
|
*/
|
|
21
21
|
export declare function hasSupabaseSession(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Wait for the most recent setSession to finish applying, then report whether
|
|
24
|
+
* the client actually holds a usable user session.
|
|
25
|
+
*
|
|
26
|
+
* Returns false when the synced token was stale/expired and supabase-js
|
|
27
|
+
* dropped the session — in that state REST calls run as `anon` (auth.uid() is
|
|
28
|
+
* NULL) and any insert into an RLS table with `WITH CHECK (auth.uid() =
|
|
29
|
+
* user_id)` is rejected. Writers should gate the direct-SDK path on this and
|
|
30
|
+
* fall back to the MCP edge function (service-role) when it returns false.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureSupabaseSession(): Promise<boolean>;
|
|
22
33
|
/** Reset module state. Test-only. */
|
|
23
34
|
export declare function resetSupabaseClient(): void;
|
package/dist/supabase/client.js
CHANGED
|
@@ -14,6 +14,8 @@ const AUTH_FILE = join(homedir(), '.edsger', 'auth.json');
|
|
|
14
14
|
let _client = null;
|
|
15
15
|
let _watcherInstalled = false;
|
|
16
16
|
let _lastAppliedAccessToken;
|
|
17
|
+
/** The in-flight setSession promise, awaited by ensureSupabaseSession(). */
|
|
18
|
+
let _sessionReady = null;
|
|
17
19
|
/**
|
|
18
20
|
* Get (or lazily create) the shared SupabaseClient.
|
|
19
21
|
*
|
|
@@ -39,7 +41,11 @@ export function getSupabase() {
|
|
|
39
41
|
detectSessionInUrl: false,
|
|
40
42
|
},
|
|
41
43
|
});
|
|
42
|
-
|
|
44
|
+
// Capture the setSession promise so writers can await it (via
|
|
45
|
+
// ensureSupabaseSession) before issuing inserts. Without this, the first
|
|
46
|
+
// REST call can race ahead of setSession and go out as `anon` (no user
|
|
47
|
+
// JWT), tripping the `auth.uid() = user_id` RLS check.
|
|
48
|
+
_sessionReady = _client.auth.setSession({
|
|
43
49
|
access_token: accessToken,
|
|
44
50
|
refresh_token: refreshToken ?? '',
|
|
45
51
|
});
|
|
@@ -54,6 +60,32 @@ export function getSupabase() {
|
|
|
54
60
|
export function hasSupabaseSession() {
|
|
55
61
|
return Boolean(getSupabaseUrl() && getSupabaseAnonKey() && getAccessToken());
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Wait for the most recent setSession to finish applying, then report whether
|
|
65
|
+
* the client actually holds a usable user session.
|
|
66
|
+
*
|
|
67
|
+
* Returns false when the synced token was stale/expired and supabase-js
|
|
68
|
+
* dropped the session — in that state REST calls run as `anon` (auth.uid() is
|
|
69
|
+
* NULL) and any insert into an RLS table with `WITH CHECK (auth.uid() =
|
|
70
|
+
* user_id)` is rejected. Writers should gate the direct-SDK path on this and
|
|
71
|
+
* fall back to the MCP edge function (service-role) when it returns false.
|
|
72
|
+
*/
|
|
73
|
+
export async function ensureSupabaseSession() {
|
|
74
|
+
if (!hasSupabaseSession()) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const client = getSupabase();
|
|
79
|
+
if (_sessionReady) {
|
|
80
|
+
await _sessionReady;
|
|
81
|
+
}
|
|
82
|
+
const { data } = await client.auth.getSession();
|
|
83
|
+
return Boolean(data.session?.access_token);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
57
89
|
function installAuthWatcher() {
|
|
58
90
|
if (_watcherInstalled) {
|
|
59
91
|
return;
|
|
@@ -70,7 +102,7 @@ function installAuthWatcher() {
|
|
|
70
102
|
return;
|
|
71
103
|
}
|
|
72
104
|
_lastAppliedAccessToken = nextAccess;
|
|
73
|
-
|
|
105
|
+
_sessionReady = _client.auth.setSession({
|
|
74
106
|
access_token: nextAccess,
|
|
75
107
|
refresh_token: nextRefresh ?? '',
|
|
76
108
|
});
|
|
@@ -87,4 +119,5 @@ export function resetSupabaseClient() {
|
|
|
87
119
|
_client = null;
|
|
88
120
|
_watcherInstalled = false;
|
|
89
121
|
_lastAppliedAccessToken = undefined;
|
|
122
|
+
_sessionReady = null;
|
|
90
123
|
}
|
|
@@ -10,7 +10,7 @@ import { hostname } from 'os';
|
|
|
10
10
|
import { callMcpEndpoint } from '../api/mcp-client.js';
|
|
11
11
|
import { getUserId } from '../auth/auth-store.js';
|
|
12
12
|
import { getVersion } from '../constants.js';
|
|
13
|
-
import { getSupabase, hasSupabaseSession } from '../supabase/client.js';
|
|
13
|
+
import { ensureSupabaseSession, getSupabase, hasSupabaseSession, } from '../supabase/client.js';
|
|
14
14
|
import { initLogSync, logInfo, logWarning, stopLogSync, } from '../utils/logger.js';
|
|
15
15
|
let currentSessionId = null;
|
|
16
16
|
let heartbeatTimer;
|
|
@@ -63,7 +63,13 @@ export async function registerSession(options) {
|
|
|
63
63
|
const invocation = process.argv.slice(2).join(' ') || undefined;
|
|
64
64
|
try {
|
|
65
65
|
const userId = getUserId();
|
|
66
|
-
|
|
66
|
+
// ensureSupabaseSession() awaits the in-flight setSession and confirms the
|
|
67
|
+
// client holds a live user session — so a token that hasn't applied yet
|
|
68
|
+
// (race) or was dropped (stale → anon) doesn't silently write as anon and
|
|
69
|
+
// trip the cli_sessions RLS check. The desktop refreshes the token into
|
|
70
|
+
// auth.json before spawning the CLI, so this should hold for app-spawned
|
|
71
|
+
// runs; the MCP branch stays for the legacy no-Supabase-session path.
|
|
72
|
+
if (userId && (await ensureSupabaseSession())) {
|
|
67
73
|
const row = {
|
|
68
74
|
session_id: sessionId,
|
|
69
75
|
user_id: userId,
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { callMcpEndpoint } from '../api/mcp-client.js';
|
|
2
2
|
import { getUserId } from '../auth/auth-store.js';
|
|
3
3
|
import { getVersion } from '../constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ensureSupabaseSession, getSupabase } from '../supabase/client.js';
|
|
5
5
|
export const colors = {
|
|
6
6
|
reset: '\x1b[0m',
|
|
7
7
|
bright: '\x1b[1m',
|
|
@@ -49,13 +49,16 @@ export async function flushLogs() {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
const batch = _logBuffer.splice(0);
|
|
52
|
+
const sessionId = _logSyncSessionId;
|
|
52
53
|
try {
|
|
53
54
|
const userId = getUserId();
|
|
54
|
-
if (
|
|
55
|
+
if (userId && (await ensureSupabaseSession())) {
|
|
55
56
|
// Direct-SDK path. user_id is set explicitly per row to satisfy the
|
|
56
|
-
// cli_logs RLS check (auth.uid() = user_id).
|
|
57
|
+
// cli_logs RLS check (auth.uid() = user_id). Gating on a confirmed
|
|
58
|
+
// session keeps a stale token (→ anon) from writing as anon and tripping
|
|
59
|
+
// RLS — desktop refreshes the token into auth.json before spawning.
|
|
57
60
|
const rows = batch.slice(0, 200).map((log) => ({
|
|
58
|
-
session_id:
|
|
61
|
+
session_id: sessionId,
|
|
59
62
|
user_id: userId,
|
|
60
63
|
level: log.level,
|
|
61
64
|
message: log.message,
|
|
@@ -68,7 +71,7 @@ export async function flushLogs() {
|
|
|
68
71
|
}
|
|
69
72
|
else {
|
|
70
73
|
await callMcpEndpoint('cli_logs/batch', {
|
|
71
|
-
session_id:
|
|
74
|
+
session_id: sessionId,
|
|
72
75
|
logs: batch,
|
|
73
76
|
});
|
|
74
77
|
}
|