@wonderwhy-er/desktop-commander
Version:
MCP server for terminal operations and file editing
97 lines (80 loc) • 3.61 kB
JavaScript
/**
* Blocking script to update device status to offline
* Runs synchronously during shutdown to ensure DB update completes
*
* Usage: node blocking-offline-update.js <deviceId> <supabaseUrl> <supabaseKey> <accessToken> [refreshToken]
* Without a refreshToken only a still-valid accessToken can be used.
*/
import { createClient } from '@supabase/supabase-js';
// Parse command line arguments
const [deviceId, supabaseUrl, supabaseKey, accessToken, refreshToken] = process.argv.slice(2);
if (!deviceId || !supabaseUrl || !supabaseKey || !accessToken) {
console.error('❌ Missing required arguments');
console.error('Usage: node blocking-offline-update.js <deviceId> <supabaseUrl> <supabaseKey> <accessToken> [refreshToken]');
process.exit(1);
}
// Set timeout for entire operation
const TIMEOUT_MS = 3000;
const timeoutHandle = setTimeout(() => {
console.error('⏱️ Timeout: Update took too long');
process.exit(2); // Exit code 2 for timeout
}, TIMEOUT_MS);
const auth = { persistSession: false, autoRefreshToken: false };
// Update device status to offline, stamping the exact shutdown moment so
// "last seen X ago" is precise for clean shutdowns (the periodic
// bookkeeping write only runs on the slow capable cadence).
// The token goes straight to PostgREST as a Bearer header: setSession()
// would first spend a GoTrue round trip of the budget re-validating it.
const markOffline = (token) => createClient(supabaseUrl, supabaseKey, {
auth,
global: { headers: { Authorization: `Bearer ${token}` } }
})
.from('mcp_devices')
.update({ status: 'offline', last_seen: new Date().toISOString() })
.eq('id', deviceId);
function tokenLooksLive(token) {
try {
const { exp } = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
return typeof exp === 'number' && exp * 1000 > Date.now() + 5000;
} catch {
return false;
}
}
// refreshSession(), not setSession(): setSession() validates a token that
// looks live on this machine's clock via /user instead of refreshing it.
async function refreshedToken() {
if (!refreshToken) {
console.error('❌ Auth error: access token not usable and no refresh token');
clearTimeout(timeoutHandle);
process.exit(3); // Exit code 3 for auth error
}
const client = createClient(supabaseUrl, supabaseKey, { auth });
const { data, error: authError } = await client.auth.refreshSession({ refresh_token: refreshToken });
if (authError || !data.session) {
console.error('❌ Auth error:', authError?.message ?? 'no session returned');
clearTimeout(timeoutHandle);
process.exit(3); // Exit code 3 for auth error
}
return data.session.access_token;
}
try {
// An expired token (e.g. the machine just woke) is refreshed first. A 401
// means it only looked live on this machine's clock: refresh and retry once.
let result = tokenLooksLive(accessToken) ? await markOffline(accessToken) : null;
if (!result || result.status === 401) {
result = await markOffline(await refreshedToken());
}
const { error } = result;
clearTimeout(timeoutHandle);
if (error) {
console.error('❌ DB update error:', error.message);
process.exit(4); // Exit code 4 for DB error
}
console.log('✓ Device marked as offline');
process.exit(0); // Success
} catch (error) {
clearTimeout(timeoutHandle);
console.error('❌ Unexpected error:', error.message);
process.exit(5); // Exit code 5 for unexpected error
}