Skip to content

Commit a3ecbd9

Browse files
atapifireclaude
andcommitted
Fix key removal bug and remaining client vs serviceClient issues
Edge Function fixes: - Fix two more places where 'client' was used instead of 'serviceClient' in runAgentGeneration (heartbeat update at line 1414, model info update at 1612) - This fixes "Job Failed: Client Not found" error in production Client-side fixes: - settings.js: Use clearServerKey() function which handles device-specific AND legacy storage keys (was only removing legacy key) - settings.js: Clear useBackgroundJobs setting on removal - job-queue.js: clearServerKey() now also clears serverApiKey and sessionExpiresAt Tests: - Add 4 more tests for key removal edge cases: - Clear both device-specific and legacy keys - Clear useBackgroundJobs setting - Clear all session state (serverApiKey, sessionExpiresAt) - Verify key not visible after removal + page reload Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dd13358 commit a3ecbd9

4 files changed

Lines changed: 81 additions & 4 deletions

File tree

job-queue.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,8 @@ export function clearServerKey() {
15171517
localStorage.removeItem(storageKey);
15181518
localStorage.removeItem('simplesim_share_b'); // Also clear legacy key
15191519
state.sessionUnlocked = false;
1520+
state.serverApiKey = null;
1521+
state.sessionExpiresAt = null;
15201522
devLog('Server key cleared for device:', getDeviceId());
15211523
}
15221524

settings.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
unlockSession,
1313
checkSessionStatus,
1414
isPasskeySupported,
15-
getApiKey
15+
getApiKey,
16+
clearServerKey
1617
} from './job-queue.js';
1718
import { devLog, devError } from './thinking.js';
1819

@@ -322,9 +323,12 @@ function setupListeners() {
322323
d.btnUnlockSession?.addEventListener('click', () => startPinFlow('unlock-session'));
323324
d.btnRemoveServerKey?.addEventListener('click', async () => {
324325
if (confirm("Remove server key? You'll need to set it up again to use background jobs.")) {
325-
localStorage.removeItem('simplesim_share_b');
326+
// Use clearServerKey() which handles both device-specific and legacy storage
327+
clearServerKey();
326328
state.settings.hasServerKey = false;
329+
state.settings.useBackgroundJobs = false;
327330
state.sessionUnlocked = false;
331+
state.serverApiKey = null;
328332
saveSettings();
329333
updateUI();
330334
showToast("Server key removed");

supabase/functions/process-job/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,7 @@ async function runAgentGeneration(
14111411
});
14121412

14131413
// Update heartbeat
1414-
await client
1414+
await serviceClient
14151415
.from('jobs')
14161416
.update({ last_heartbeat: new Date().toISOString() })
14171417
.eq('id', jobData.id);
@@ -1609,7 +1609,7 @@ async function runAgentGeneration(
16091609
});
16101610

16111611
// Store model info for analytics
1612-
await client
1612+
await serviceClient
16131613
.from('jobs')
16141614
.update({
16151615
model_info: {

tests/key-flow-integration.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,56 @@ describe('Server-Side Key Flows (Background Jobs)', () => {
340340
expect(mockState.serverApiKey).toBeNull();
341341
});
342342

343+
it('should clear BOTH device-specific AND legacy keys on removal', () => {
344+
const deviceId = 'device-abc123';
345+
const deviceStorageKey = `simplesim_share_b_${deviceId}`;
346+
const legacyStorageKey = 'simplesim_share_b';
347+
348+
// Set both device-specific and legacy keys
349+
localStorageMock.setItem(deviceStorageKey, JSON.stringify({ data: 'device-share-b' }));
350+
localStorageMock.setItem(legacyStorageKey, JSON.stringify({ data: 'legacy-share-b' }));
351+
mockState.settings.hasServerKey = true;
352+
mockState.settings.useBackgroundJobs = true;
353+
354+
// Proper removal should clear BOTH
355+
localStorageMock.removeItem(deviceStorageKey);
356+
localStorageMock.removeItem(legacyStorageKey);
357+
mockState.settings.hasServerKey = false;
358+
mockState.settings.useBackgroundJobs = false;
359+
360+
expect(localStorageMock.getItem(deviceStorageKey)).toBeNull();
361+
expect(localStorageMock.getItem(legacyStorageKey)).toBeNull();
362+
expect(mockState.settings.hasServerKey).toBe(false);
363+
expect(mockState.settings.useBackgroundJobs).toBe(false);
364+
});
365+
366+
it('should clear useBackgroundJobs setting on removal', () => {
367+
mockState.settings.hasServerKey = true;
368+
mockState.settings.useBackgroundJobs = true;
369+
370+
// Simulate proper removal (must clear useBackgroundJobs too)
371+
mockState.settings.hasServerKey = false;
372+
mockState.settings.useBackgroundJobs = false;
373+
374+
expect(mockState.settings.hasServerKey).toBe(false);
375+
expect(mockState.settings.useBackgroundJobs).toBe(false);
376+
});
377+
378+
it('should clear all session state on removal', () => {
379+
mockState.sessionUnlocked = true;
380+
mockState.serverApiKey = 'sk-or-key';
381+
mockState.sessionExpiresAt = Date.now() + 7200000;
382+
383+
// Proper removal clears ALL session state
384+
mockState.sessionUnlocked = false;
385+
mockState.serverApiKey = null;
386+
mockState.sessionExpiresAt = null;
387+
388+
expect(mockState.sessionUnlocked).toBe(false);
389+
expect(mockState.serverApiKey).toBeNull();
390+
expect(mockState.sessionExpiresAt).toBeNull();
391+
});
392+
343393
it('should allow re-setup of server key after removal', () => {
344394
const deviceId = 'device-abc123';
345395

@@ -356,6 +406,27 @@ describe('Server-Side Key Flows (Background Jobs)', () => {
356406
expect(localStorageMock.getItem(`simplesim_share_b_${deviceId}`)).toBeTruthy();
357407
expect(mockState.settings.hasServerKey).toBe(true);
358408
});
409+
410+
it('should not see old key after removal even with page reload', () => {
411+
const deviceId = 'device-abc123';
412+
const deviceStorageKey = `simplesim_share_b_${deviceId}`;
413+
414+
// User has key configured
415+
localStorageMock.setItem(deviceStorageKey, JSON.stringify({ data: 'share-b' }));
416+
mockState.settings.hasServerKey = true;
417+
418+
// User removes key
419+
localStorageMock.removeItem(deviceStorageKey);
420+
localStorageMock.removeItem('simplesim_share_b');
421+
mockState.settings.hasServerKey = false;
422+
423+
// Simulate page reload - check hasServerKey logic
424+
const hasDeviceKey = !!localStorageMock.getItem(deviceStorageKey);
425+
const hasLegacyKey = !!localStorageMock.getItem('simplesim_share_b');
426+
const hasKey = hasDeviceKey || hasLegacyKey;
427+
428+
expect(hasKey).toBe(false);
429+
});
359430
});
360431
});
361432

0 commit comments

Comments
 (0)