Files
orca/mobile/src/transport/socket-event-debug.ts
T
NeilandOrca 651f707ce0 [P1] fix(mobile): restore pairing self-heal and recover a wedged handshake (#11690)
* fix(mobile): restore pairing self-heal and recover a wedged handshake

readPairingKeychainItem threw when an Android presence record pointed at a
SecureStore entry that read back null. Android reports absent and undecryptable
identically, so the keystore fault the presence record was added to survive
latched every caller out of its own orphan cleanup: the pairing journal store
never reached its null-secret branch, stale winner-stamped metadata survived,
and every later QR scan failed with "mobile relay pairing recovery pending".
Report absent instead and drop the stale presence claim, still without falling
back to the superseded older generation.

The handshake-timeout path closed the socket with no handleSocketClosed
fallback, unlike the connect-timeout and activity-probe paths. When React
Native omits onclose for a wedged transport the client stayed in 'handshaking'
forever with no reconnect armed.

* fix(mobile): keep the presence pin when a recorded keychain item reads null

Clearing the presence record on the self-heal removed the only thing that
stops readPairingKeychainItem's generation walk, so the next read fell back to
the superseded value under an older generation -- exactly what #11430's
presence record exists to prevent, and reachable for host device tokens and
relay resume bundles after an Android encrypt rotation. Return null and leave
the record in place; the null return alone unlatches every caller's orphan
cleanup, and delete/re-pair already clear or re-stamp the record.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): date synthesized socket closes in transport diagnostics

Move the log-only close clocks behind handleSocketClosed's stale guard so a
synthesized close records them and a late onclose can't clobber the replacement.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): account for delayed synthesized closes

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 05:01:20 -07:00

43 lines
1.1 KiB
TypeScript

// Why: RN's WebSocket close/error events are loosely typed and vary per
// platform. Serialize them defensively (circular-safe, function-safe,
// truncated) so the [net] diagnostics can never crash mid-handler.
export function describeSocketEvent(event: unknown): { keys: string[]; json: string } {
let keys: string[] = []
try {
keys = event && typeof event === 'object' ? Object.keys(event as object) : []
} catch {
keys = []
}
let json = ''
try {
const seen = new WeakSet<object>()
json = JSON.stringify(
event,
(_k, v) => {
if (typeof v === 'object' && v !== null) {
if (seen.has(v as object)) {
return '[circular]'
}
seen.add(v as object)
}
if (typeof v === 'function') {
return '[fn]'
}
return v
},
0
).slice(0, 500)
} catch {
json = '[unstringifiable]'
}
return { keys, json }
}
export function redactSocketEndpoint(endpoint: string): string {
try {
return new URL(endpoint).host || 'unknown'
} catch {
return 'unknown'
}
}