0178f8f5cc
iOS Safari reuses a keep-alive socket the server closed while the login
form sat idle during typing, so the first Sign In / Create account POST
dies instantly with NSURLErrorNetworkConnectionLost ("The network
connection was lost"). That rejects the server-action call, hitting the
client-side catch in LoginForm/SignupForm and showing "An unexpected
error occurred"; the second tap lands on a fresh connection and works.
Add lib/retryAction.ts: retryOnTransportError() retries the action once
only when the call throws. A returned { error } (bad password, rate
limit) is a real result and passes straight through. A lost-on-a-stale-
socket POST never reached the server, so retrying it once is safe.
26 lines
1.0 KiB
TypeScript
26 lines
1.0 KiB
TypeScript
/**
|
|
* Run a server action, retrying it ONCE if the call rejects at the
|
|
* transport layer.
|
|
*
|
|
* iOS Safari (and Safari generally) frequently drops the first POST sent
|
|
* on a keep-alive socket that the server closed while the connection sat
|
|
* idle — e.g. while the user typed their credentials. The request fails
|
|
* instantly with `NSURLErrorNetworkConnectionLost` ("The network
|
|
* connection was lost", -1005); a retry lands on a fresh connection and
|
|
* succeeds. This is why a first login/signup tap shows "An unexpected
|
|
* error occurred" and the second tap works.
|
|
*
|
|
* Only a *thrown* rejection is retried. A server action that returns a
|
|
* value — including an application-level `{ error }` ("Invalid email or
|
|
* password", a rate-limit message) — is a real result and passes
|
|
* straight through untouched. A lost-on-a-stale-socket POST never
|
|
* reached the server, so retrying it once is safe.
|
|
*/
|
|
export async function retryOnTransportError<T>(fn: () => Promise<T>): Promise<T> {
|
|
try {
|
|
return await fn();
|
|
} catch {
|
|
return await fn();
|
|
}
|
|
}
|