-
Notifications
You must be signed in to change notification settings - Fork 211
fix: resolve issue from losing connection mid proof verifcation #1627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughPreserve cached Changes
Sequence Diagram(s)sequenceDiagram
participant UI as "Prover / UI"
participant ProvingState as "ProvingState"
participant SelfClient as "SelfClient"
participant TEE_WS as "TEE WebSocket (TEE)"
UI->>ProvingState: startProving()
ProvingState->>ProvingState: check get().wsConnection (activeWsConnection)
alt ws is OPEN
ProvingState->>TEE_WS: send payload
TEE_WS-->>ProvingState: response / ack
ProvingState-->>UI: proceed with proof
else ws not OPEN
ProvingState->>ProvingState: call _reconnectTeeWebSocket(SelfClient)
ProvingState->>SelfClient: build circuit params & WS URL
ProvingState->>TEE_WS: attempt open (exponential backoff)
alt open success
ProvingState->>ProvingState: reset wsReconnectAttempts
ProvingState->>TEE_WS: send payload
TEE_WS-->>ProvingState: response / ack
ProvingState-->>UI: proceed with proof
else reconnect failed (exhausted)
ProvingState-->>UI: emit PROVE_ERROR / abort
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/mobile-sdk-alpha/src/proving/provingMachine.ts`:
- Around line 913-940: The Promise in the reconnection logic can resolve both on
timeout and later on a late "open" event; fix by introducing a local resolved
flag (e.g., let settled = false) or a timeout id and ensure only a single
resolution path runs: in the timeout handler, if not settled set settled=true,
remove the ws event listeners (ws.removeEventListener for
wsHandlers.message/open/error/close), optionally close the ws, and then
resolve(false); in the wsHandlers.open handler, first check if settled is true
(or clear the timeout and set settled=true) before calling set({
wsReconnectAttempts: 0 }) and resolve(true); ensure cleanup of listeners in the
open path as well to prevent late events from firing. Use the existing symbols
ws, wsHandlers, RECONNECT_TIMEOUT_MS, selfClient, set, and get to implement
this.
♻️ Duplicate comments (1)
packages/mobile-sdk-alpha/src/stores/selfAppStore.tsx (1)
112-119: StaleselfAppcan persist across different sessions.When an intentional disconnect (
io server disconnect) occurs,socketandsessionIdare cleared butselfAppis preserved. IfstartAppListeneris later called with a different session ID, the condition on line 68 (currentSocket && get().sessionId !== sessionId) evaluates to false becausecurrentSocketis null. The staleselfAppfrom the previous session remains until the newself_appevent arrives.Consider clearing
selfAppon intentional disconnects, or adding an additional guard instartAppListenerto clearselfAppwhen the storedselfApp.sessionIddoesn't match the newsessionId.
| return new Promise(resolve => { | ||
| const ws = new WebSocket(wsRpcUrl); | ||
| const RECONNECT_TIMEOUT_MS = 15000; | ||
|
|
||
| const wsHandlers: WsHandlers = { | ||
| message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient), | ||
| open: () => { | ||
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | ||
| set({ wsReconnectAttempts: 0 }); | ||
| resolve(true); | ||
| }, | ||
| error: (error: Event) => get()._handleWsError(error, selfClient), | ||
| close: (event: CloseEvent) => get()._handleWsClose(event, selfClient), | ||
| }; | ||
|
|
||
| set({ wsConnection: ws, wsHandlers }); | ||
| ws.addEventListener('message', wsHandlers.message); | ||
| ws.addEventListener('open', wsHandlers.open); | ||
| ws.addEventListener('error', wsHandlers.error); | ||
| ws.addEventListener('close', wsHandlers.close); | ||
|
|
||
| setTimeout(() => { | ||
| if (ws.readyState !== WebSocket.OPEN) { | ||
| selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context); | ||
| resolve(false); | ||
| } | ||
| }, RECONNECT_TIMEOUT_MS); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition between timeout and successful connection.
If the WebSocket opens after the 15-second timeout fires, both code paths execute:
- Timeout fires →
resolve(false) - WebSocket opens →
set({ wsReconnectAttempts: 0 })runs, thenresolve(true)(no-op since already resolved)
The wsReconnectAttempts counter gets incorrectly reset to 0 even though the reconnection was considered failed. Additionally, the event listeners remain attached and could fire unexpectedly on the "late" connection.
🔧 Suggested fix: track resolution state and clean up on timeout
return new Promise(resolve => {
const ws = new WebSocket(wsRpcUrl);
const RECONNECT_TIMEOUT_MS = 15000;
+ let resolved = false;
const wsHandlers: WsHandlers = {
message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient),
open: () => {
+ if (resolved) return;
+ resolved = true;
selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context);
set({ wsReconnectAttempts: 0 });
resolve(true);
},
error: (error: Event) => get()._handleWsError(error, selfClient),
close: (event: CloseEvent) => get()._handleWsClose(event, selfClient),
};
set({ wsConnection: ws, wsHandlers });
ws.addEventListener('message', wsHandlers.message);
ws.addEventListener('open', wsHandlers.open);
ws.addEventListener('error', wsHandlers.error);
ws.addEventListener('close', wsHandlers.close);
setTimeout(() => {
- if (ws.readyState !== WebSocket.OPEN) {
+ if (!resolved && ws.readyState !== WebSocket.OPEN) {
+ resolved = true;
selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context);
+ // Clean up the failed connection
+ ws.removeEventListener('message', wsHandlers.message);
+ ws.removeEventListener('open', wsHandlers.open);
+ ws.removeEventListener('error', wsHandlers.error);
+ ws.removeEventListener('close', wsHandlers.close);
+ ws.close();
+ set({ wsConnection: null, wsHandlers: null });
resolve(false);
}
}, RECONNECT_TIMEOUT_MS);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return new Promise(resolve => { | |
| const ws = new WebSocket(wsRpcUrl); | |
| const RECONNECT_TIMEOUT_MS = 15000; | |
| const wsHandlers: WsHandlers = { | |
| message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient), | |
| open: () => { | |
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | |
| set({ wsReconnectAttempts: 0 }); | |
| resolve(true); | |
| }, | |
| error: (error: Event) => get()._handleWsError(error, selfClient), | |
| close: (event: CloseEvent) => get()._handleWsClose(event, selfClient), | |
| }; | |
| set({ wsConnection: ws, wsHandlers }); | |
| ws.addEventListener('message', wsHandlers.message); | |
| ws.addEventListener('open', wsHandlers.open); | |
| ws.addEventListener('error', wsHandlers.error); | |
| ws.addEventListener('close', wsHandlers.close); | |
| setTimeout(() => { | |
| if (ws.readyState !== WebSocket.OPEN) { | |
| selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context); | |
| resolve(false); | |
| } | |
| }, RECONNECT_TIMEOUT_MS); | |
| }); | |
| return new Promise(resolve => { | |
| const ws = new WebSocket(wsRpcUrl); | |
| const RECONNECT_TIMEOUT_MS = 15000; | |
| let resolved = false; | |
| const wsHandlers: WsHandlers = { | |
| message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient), | |
| open: () => { | |
| if (resolved) return; | |
| resolved = true; | |
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | |
| set({ wsReconnectAttempts: 0 }); | |
| resolve(true); | |
| }, | |
| error: (error: Event) => get()._handleWsError(error, selfClient), | |
| close: (event: CloseEvent) => get()._handleWsClose(event, selfClient), | |
| }; | |
| set({ wsConnection: ws, wsHandlers }); | |
| ws.addEventListener('message', wsHandlers.message); | |
| ws.addEventListener('open', wsHandlers.open); | |
| ws.addEventListener('error', wsHandlers.error); | |
| ws.addEventListener('close', wsHandlers.close); | |
| setTimeout(() => { | |
| if (!resolved && ws.readyState !== WebSocket.OPEN) { | |
| resolved = true; | |
| selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context); | |
| // Clean up the failed connection | |
| ws.removeEventListener('message', wsHandlers.message); | |
| ws.removeEventListener('open', wsHandlers.open); | |
| ws.removeEventListener('error', wsHandlers.error); | |
| ws.removeEventListener('close', wsHandlers.close); | |
| ws.close(); | |
| set({ wsConnection: null, wsHandlers: null }); | |
| resolve(false); | |
| } | |
| }, RECONNECT_TIMEOUT_MS); | |
| }); |
🤖 Prompt for AI Agents
In `@packages/mobile-sdk-alpha/src/proving/provingMachine.ts` around lines 913 -
940, The Promise in the reconnection logic can resolve both on timeout and later
on a late "open" event; fix by introducing a local resolved flag (e.g., let
settled = false) or a timeout id and ensure only a single resolution path runs:
in the timeout handler, if not settled set settled=true, remove the ws event
listeners (ws.removeEventListener for wsHandlers.message/open/error/close),
optionally close the ws, and then resolve(false); in the wsHandlers.open
handler, first check if settled is true (or clear the timeout and set
settled=true) before calling set({ wsReconnectAttempts: 0 }) and resolve(true);
ensure cleanup of listeners in the open path as well to prevent late events from
firing. Use the existing symbols ws, wsHandlers, RECONNECT_TIMEOUT_MS,
selfClient, set, and get to implement this.
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | ||
| set({ wsReconnectAttempts: 0 }); | ||
| resolve(true); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WebSocket reconnection skips required hello handshake protocol
High Severity
The _reconnectTeeWebSocket function's open handler just logs and resolves true without calling _handleWsOpen. The original connection flow in initTeeConnection uses open: () => get()._handleWsOpen(selfClient) which sends the required openpassport_hello message with the client's public key, receives the server's attestation, and derives the shared encryption key. Without this handshake, the reconnected WebSocket has no valid session with the server, and subsequent proof requests will fail because the server doesn't recognize the connection or the preserved UUID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/screens/verification/ProveScreen.tsx (1)
171-187: Prevent stale timeout from marking scroll complete for the wrong sessionThe new
setTimeout(0)can run after a session switch/unmount and sethasScrolledToBottomusing stale measurements, potentially enabling verification without scrolling. Add cleanup to cancel pending timeouts.✅ Suggested fix (cleanup pending timeout)
useEffect(() => { if (!isFocused || !selectedApp) { return; } + let resetTimeout: ReturnType<typeof setTimeout> | undefined; + // Reset scroll state tracking for new session if (selectedAppRef.current?.sessionId !== selectedApp.sessionId) { hasInitializedScrollStateRef.current = false; setHasScrolledToBottom(false); // After state reset, check if content is short using current measurements. // Use setTimeout(0) to ensure we read values AFTER React processes the reset, // without adding measurements to dependencies (which causes race conditions). - setTimeout(() => { + resetTimeout = setTimeout(() => { const hasMeasurements = scrollViewContentHeight > 0 && scrollViewHeight > 0; const isShort = scrollViewContentHeight <= scrollViewHeight + 50; if (hasMeasurements && isShort) { setHasScrolledToBottom(true); hasInitializedScrollStateRef.current = true; } }, 0); } setDefaultDocumentTypeIfNeeded(); ... - }, [selectedApp?.sessionId, isFocused, selfClient]); + return () => { + if (resetTimeout) { + clearTimeout(resetTimeout); + } + }; + }, [selectedApp?.sessionId, isFocused, selfClient]);
🤖 Fix all issues with AI agents
In `@packages/mobile-sdk-alpha/src/proving/provingMachine.ts`:
- Around line 845-863: The reconnection logic allows overlapping attempts
between _handleWsClose's backoff schedule and startProving, so add a single
in‑flight reconnect guard (e.g., a private field like reconnectPromise or
isReconnecting) and use it inside _reconnectTeeWebSocket, _handleWsClose and
startProving to ensure only one reconnect runs: when scheduling a backoff in the
ready_to_prove branch set the guard before calling setTimeout, have
_reconnectTeeWebSocket return/reuse the same Promise if the guard is set, have
startProving check/await the guard instead of spawning a new connection, and
clear the guard on success/final failure while still incrementing
wsReconnectAttempts and updating wsConnection as before.
| setHasScrolledToBottom(true); | ||
| hasInitializedScrollStateRef.current = true; | ||
| } | ||
| }, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale closure uses old measurements for new session
Medium Severity
The setTimeout(0) callback captures scrollViewContentHeight and scrollViewHeight from the closure when the effect runs, not when the callback executes. When switching sessions, if the previous session had short content and the new session has long content, the timeout uses the stale (short) measurements to set hasScrolledToBottom=true and hasInitializedScrollStateRef.current=true. This prevents the other useEffect (which checks hasInitializedScrollStateRef.current) from properly re-evaluating with new measurements, potentially allowing users to skip scrolling through required disclosures.
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | ||
| set({ wsReconnectAttempts: 0 }); | ||
| resolve(true); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconnect counter reset bypasses max attempts limit
Medium Severity
The open handler in _reconnectTeeWebSocket resets wsReconnectAttempts to 0 whenever the WebSocket opens, while _handleWsClose increments the counter to enforce MAX_RECONNECT_ATTEMPTS. If a connection opens but immediately closes (which will happen due to the missing hello handshake), the counter resets to 0 before _handleWsClose runs again. This creates an infinite loop of reconnection attempts, effectively bypassing the 3-attempt limit meant to prevent endless retries.
Additional Locations (1)
7e35e08 to
39678e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/mobile-sdk-alpha/src/proving/provingMachine.ts`:
- Around line 884-941: The reconnect logic in _reconnectTeeWebSocket currently
resolves on WebSocket 'open' without running the full TEE handshake; update the
wsHandlers.open to call get()._handleWsOpen(...) (passing the open event and
selfClient) and only resolve true when the handshake completes (e.g., when the
prover state or a CONNECT_SUCCESS event/flag is observed), otherwise wait until
the RECONNECT_TIMEOUT_MS then resolve false; ensure wsHandlers and set({
wsConnection, wsHandlers }) remain consistent and preserve existing error/close
handling so startProving uses the fresh sharedKey/uuid after a successful
_handleWsOpen-driven handshake.
🧹 Nitpick comments (1)
packages/mobile-sdk-alpha/src/proving/provingMachine.ts (1)
845-877: Potential concurrent reconnection attempts remain possible.The backoff reconnection in
_handleWsCloseschedules viasetTimeout, whilestartProvingcan trigger an immediate reconnection. IfstartProvingis invoked during the backoff delay, both paths may spawn concurrent WebSocket connections, racing the store state.Consider adding an in-flight promise guard to ensure only one reconnection attempt runs at a time:
🔧 Suggested approach
+let wsReconnectInFlight: Promise<boolean> | null = null; // In _handleWsClose backoff path: setTimeout(() => { if (get().currentState === 'ready_to_prove') { - get()._reconnectTeeWebSocket(selfClient); + if (!wsReconnectInFlight) { + wsReconnectInFlight = get()._reconnectTeeWebSocket(selfClient).finally(() => { + wsReconnectInFlight = null; + }); + } } }, backoffMs); // In startProving reconnection path: -const reconnected = await get()._reconnectTeeWebSocket(selfClient); +if (!wsReconnectInFlight) { + wsReconnectInFlight = get()._reconnectTeeWebSocket(selfClient).finally(() => { + wsReconnectInFlight = null; + }); +} +const reconnected = await wsReconnectInFlight;
| /** | ||
| * Re-establishes the TEE WebSocket connection using stored circuit parameters. | ||
| * Called automatically when connection is lost in ready_to_prove state. | ||
| */ | ||
| _reconnectTeeWebSocket: async (selfClient: SelfClient): Promise<boolean> => { | ||
| const context = createProofContext(selfClient, '_reconnectTeeWebSocket'); | ||
| const { passportData, circuitType } = get(); | ||
|
|
||
| if (!passportData || !circuitType) { | ||
| selfClient.logProofEvent('error', 'Reconnect failed: missing prerequisites', context); | ||
| return false; | ||
| } | ||
|
|
||
| const typedCircuitType = circuitType as 'disclose' | 'register' | 'dsc'; | ||
| const circuitName = | ||
| typedCircuitType === 'disclose' | ||
| ? passportData.documentCategory === 'aadhaar' | ||
| ? 'disclose_aadhaar' | ||
| : 'disclose' | ||
| : getCircuitNameFromPassportData(passportData, typedCircuitType as 'register' | 'dsc'); | ||
|
|
||
| const wsRpcUrl = resolveWebSocketUrl(selfClient, typedCircuitType, passportData as PassportData, circuitName); | ||
| if (!wsRpcUrl) { | ||
| selfClient.logProofEvent('error', 'Reconnect failed: no WebSocket URL', context); | ||
| return false; | ||
| } | ||
|
|
||
| selfClient.logProofEvent('info', 'TEE WebSocket reconnection started', context); | ||
|
|
||
| return new Promise(resolve => { | ||
| const ws = new WebSocket(wsRpcUrl); | ||
| const RECONNECT_TIMEOUT_MS = 15000; | ||
|
|
||
| const wsHandlers: WsHandlers = { | ||
| message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient), | ||
| open: () => { | ||
| selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context); | ||
| set({ wsReconnectAttempts: 0 }); | ||
| resolve(true); | ||
| }, | ||
| error: (error: Event) => get()._handleWsError(error, selfClient), | ||
| close: (event: CloseEvent) => get()._handleWsClose(event, selfClient), | ||
| }; | ||
|
|
||
| set({ wsConnection: ws, wsHandlers }); | ||
| ws.addEventListener('message', wsHandlers.message); | ||
| ws.addEventListener('open', wsHandlers.open); | ||
| ws.addEventListener('error', wsHandlers.error); | ||
| ws.addEventListener('close', wsHandlers.close); | ||
|
|
||
| setTimeout(() => { | ||
| if (ws.readyState !== WebSocket.OPEN) { | ||
| selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context); | ||
| resolve(false); | ||
| } | ||
| }, RECONNECT_TIMEOUT_MS); | ||
| }); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Reconnection does not re-establish TEE session (hello + attestation).
The open handler in _reconnectTeeWebSocket only resets the counter and resolves—it doesn't invoke _handleWsOpen, which is responsible for sending the hello message and initiating the attestation/key-derivation flow.
After reconnection:
- WebSocket opens →
resolve(true) startProvingproceeds using the oldsharedKeyanduuid- Server hasn't received hello, so it doesn't recognize this client
- Payload encryption/decryption will fail or be rejected
The reconnected WebSocket must complete the full handshake before the connection is considered ready.
🔧 Suggested fix: Invoke _handleWsOpen and wait for CONNECT_SUCCESS
_reconnectTeeWebSocket: async (selfClient: SelfClient): Promise<boolean> => {
const context = createProofContext(selfClient, '_reconnectTeeWebSocket');
const { passportData, circuitType } = get();
if (!passportData || !circuitType) {
selfClient.logProofEvent('error', 'Reconnect failed: missing prerequisites', context);
return false;
}
// ... URL resolution code ...
selfClient.logProofEvent('info', 'TEE WebSocket reconnection started', context);
return new Promise(resolve => {
const ws = new WebSocket(wsRpcUrl);
const RECONNECT_TIMEOUT_MS = 15000;
+ let resolved = false;
const wsHandlers: WsHandlers = {
message: (event: MessageEvent) => get()._handleWebSocketMessage(event, selfClient),
- open: () => {
- selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context);
- set({ wsReconnectAttempts: 0 });
- resolve(true);
- },
+ open: () => get()._handleWsOpen(selfClient),
error: (error: Event) => get()._handleWsError(error, selfClient),
close: (event: CloseEvent) => get()._handleWsClose(event, selfClient),
};
set({ wsConnection: ws, wsHandlers });
ws.addEventListener('message', wsHandlers.message);
ws.addEventListener('open', wsHandlers.open);
ws.addEventListener('error', wsHandlers.error);
ws.addEventListener('close', wsHandlers.close);
+ // Subscribe to actor state to detect successful reconnection
+ if (actor) {
+ const unsubscribe = actor.subscribe(state => {
+ if (resolved) return;
+ if (state.matches('ready_to_prove')) {
+ resolved = true;
+ selfClient.logProofEvent('info', 'TEE WebSocket reconnected', context);
+ set({ wsReconnectAttempts: 0 });
+ unsubscribe.unsubscribe();
+ resolve(true);
+ } else if (state.matches('error')) {
+ resolved = true;
+ unsubscribe.unsubscribe();
+ resolve(false);
+ }
+ });
+ }
setTimeout(() => {
- if (ws.readyState !== WebSocket.OPEN) {
+ if (!resolved) {
+ resolved = true;
selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context);
+ // Cleanup failed connection
+ ws.removeEventListener('message', wsHandlers.message);
+ ws.removeEventListener('open', wsHandlers.open);
+ ws.removeEventListener('error', wsHandlers.error);
+ ws.removeEventListener('close', wsHandlers.close);
+ ws.close();
+ set({ wsConnection: null, wsHandlers: null });
resolve(false);
}
}, RECONNECT_TIMEOUT_MS);
});
},🤖 Prompt for AI Agents
In `@packages/mobile-sdk-alpha/src/proving/provingMachine.ts` around lines 884 -
941, The reconnect logic in _reconnectTeeWebSocket currently resolves on
WebSocket 'open' without running the full TEE handshake; update the
wsHandlers.open to call get()._handleWsOpen(...) (passing the open event and
selfClient) and only resolve true when the handshake completes (e.g., when the
prover state or a CONNECT_SUCCESS event/flag is observed), otherwise wait until
the RECONNECT_TIMEOUT_MS then resolve false; ensure wsHandlers and set({
wsConnection, wsHandlers }) remain consistent and preserve existing error/close
handling so startProving uses the fresh sharedKey/uuid after a successful
_handleWsOpen-driven handshake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| selfClient.logProofEvent('warn', 'TEE WebSocket reconnection timeout', context); | ||
| resolve(false); | ||
| } | ||
| }, RECONNECT_TIMEOUT_MS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconnection timeout leaks WebSocket without cleanup
Medium Severity
When the 15-second reconnection timeout fires and the WebSocket is not yet open, the code calls resolve(false) but does not close the WebSocket or remove its event handlers. Since set({ wsConnection: ws, wsHandlers }) was already called at line 928, the WebSocket remains in state. If the WebSocket later connects (slow network), the open handler fires and sets wsReconnectAttempts to 0, causing inconsistent state even though the reconnection was deemed failed.
Description
Previously internet issues could cause websocket disconnection, which would lead to the selfApp state being cleared which could cause the app to hang in 'Waiting for app...'. Ensured disconnect does not clear the state.
Tested
Used local mobile build on my Android device. After selecting my document during a verification proof flow, I turned on airplane mode, then turned back on. Before the fix, it would be stalled in 'Waiting for app...'. After, it allowed me to click the button to hold to verify and continue.
How to QA
Build mobile app with fix and follow steps I did in the test.
Note
Improves resilience of the proof flow to temporary network loss and reduces stalls.
wsReconnectAttemptstracking,_reconnectTeeWebSocketwith backoff/timeout, and auto-retries onready_to_prove;startProvingnow ensures an open WS (reconnects if needed) before sending payload; resets attempts on successselfAppStoreno longer clearsselfAppon transient errors/disconnects; only clears on intentional client/server disconnectsProveScreenauto-enables verify state when content is short after session change to avoid unnecessary scrollingWritten by Cursor Bugbot for commit 39678e3. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes
Style
✏️ Tip: You can customize this high-level summary in your review settings.