Skip to content

Commit 07172d7

Browse files
MotionPeakclaude
andcommitted
feat(web): Open sign-in window button for remote captcha pension sign-in
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e9ba454 commit 07172d7

2 files changed

Lines changed: 39 additions & 6 deletions

File tree

web/src/accounts/AccountsView.test.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,3 +1359,24 @@ describe('AccountsView — restores an in-flight sync after remount', () => {
13591359
});
13601360
});
13611361
});
1362+
1363+
describe('AccountsView — remote sign-in window', () => {
1364+
it('shows "Open sign-in window" linking to /vnc with the ticket while a run needs it', async () => {
1365+
const activeRun = {
1366+
runId: 'run-9', connectionId: 'c-pen-1', status: 'running' as const,
1367+
message: 'Waiting for you to finish signing in…', accountsCount: 0,
1368+
transactionsCount: 0, startedAt: '2026-06-24T10:00:00.000Z',
1369+
needsRemoteSignin: true, vncTicket: 'tkt-123',
1370+
};
1371+
const penConn = { ...CONNECTIONS.connections[3]!, lastStatus: 'running', activeRun };
1372+
installFetchMock({
1373+
...FULL,
1374+
'GET /api/connections': () => ({ connections: [penConn] }),
1375+
'GET /api/scrape/run-9': () => ({ run: activeRun }),
1376+
});
1377+
render(<AccountsView />);
1378+
const link = await screen.findByRole('link', { name: /open sign-in window/i });
1379+
expect(link).toHaveAttribute('href', expect.stringContaining('/vnc/vnc_lite.html'));
1380+
expect(link).toHaveAttribute('href', expect.stringContaining('ticket=tkt-123'));
1381+
});
1382+
});

web/src/accounts/AccountsView.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ interface RunStatus {
7373
type SyncState =
7474
| { kind: 'idle' }
7575
| { kind: 'starting' }
76-
| { kind: 'running'; runId: string; message: string }
77-
| { kind: 'needs-otp'; runId: string; message: string }
76+
| { kind: 'running'; runId: string; message: string; signin?: { ticket: string } }
77+
| { kind: 'needs-otp'; runId: string; message: string; signin?: { ticket: string } }
7878
| { kind: 'success'; accountsCount: number; transactionsCount: number }
7979
| { kind: 'error'; message: string };
8080

@@ -279,11 +279,12 @@ export function AccountsView() {
279279
const pollRun = useCallback(async (connectionId: string, runId: string) => {
280280
try {
281281
const { run } = await api<{ run: RunStatus }>(`/scrape/${encodeURIComponent(runId)}`);
282+
const signin = run.needsRemoteSignin && run.vncTicket ? { ticket: run.vncTicket } : undefined;
282283
if (run.status === 'running') {
283-
setSyncForConnection(connectionId, { kind: 'running', runId, message: run.message });
284+
setSyncForConnection(connectionId, { kind: 'running', runId, message: run.message, signin });
284285
schedule(connectionId, () => void pollRun(connectionId, runId), SCRAPE_POLL_INTERVAL_MS);
285286
} else if (run.status === 'needs-otp') {
286-
setSyncForConnection(connectionId, { kind: 'needs-otp', runId, message: run.message });
287+
setSyncForConnection(connectionId, { kind: 'needs-otp', runId, message: run.message, signin });
287288
// Keep polling — the OTP modal posts the code, then the run goes
288289
// back to running and eventually success/error.
289290
schedule(connectionId, () => void pollRun(connectionId, runId), SCRAPE_POLL_INTERVAL_MS);
@@ -382,11 +383,12 @@ export function AccountsView() {
382383
|| ((cur.kind === 'running' || cur.kind === 'needs-otp') && cur.runId === run.runId)
383384
);
384385
if (alreadyTracking) continue;
386+
const signin = run.needsRemoteSignin && run.vncTicket ? { ticket: run.vncTicket } : undefined;
385387
setSyncForConnection(
386388
conn.id,
387389
run.status === 'needs-otp'
388-
? { kind: 'needs-otp', runId: run.runId, message: run.message }
389-
: { kind: 'running', runId: run.runId, message: run.message },
390+
? { kind: 'needs-otp', runId: run.runId, message: run.message, signin }
391+
: { kind: 'running', runId: run.runId, message: run.message, signin },
390392
);
391393
void pollRun(conn.id, run.runId);
392394
}
@@ -859,6 +861,16 @@ function ConnectionCard({ connection, company, accounts, callbacks, showHistory
859861
{syncing ? 'Syncing…' : 'Sync'}
860862
</button>
861863
)}
864+
{(syncState.kind === 'running' || syncState.kind === 'needs-otp') && syncState.signin && (
865+
<a
866+
className="mini primary"
867+
href={`/vnc/vnc_lite.html?path=${encodeURIComponent(`vnc/websockify?ticket=${syncState.signin.ticket}`)}&ticket=${encodeURIComponent(syncState.signin.ticket)}&autoconnect=true&resize=remote`}
868+
target="_blank"
869+
rel="noreferrer"
870+
>
871+
Open sign-in window
872+
</a>
873+
)}
862874
{connection.hasCredentials && showHistory && (
863875
<span className="conn-history-label">
864876
<span className="conn-history-text">History</span>

0 commit comments

Comments
 (0)