fix(idaa): add Novi fetch timeout and auto-retry on network errors
- Wrap Novi API fetch in AbortController with 12s hard timeout — prevents verify_in_flight from getting stuck if Novi's server hangs with no response - Auto-retry once (after 3s) on network errors (TypeError: Failed to fetch) and timeouts (AbortError) — these are almost always transient cellular/WiFi blips and previously hard-failed with no second chance - Rate-limit retries (429) already had a 10s wait; network retry is separate - Update status message to "Connection issue — retrying..." during network retry - Update error panel hint to suggest closing/reopening the Novi page as last resort - Update Access Denied hint with same guidance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -271,7 +271,14 @@ $effect(() => {
|
||||
* Verifies a Novi UUID against the Novi API and sets permissions accordingly.
|
||||
* "All or nothing" — if no API key is configured or the call fails, access is denied.
|
||||
* Called from within untrack(), so store writes here will not trigger reactive loops.
|
||||
* On a 429 rate-limit response, waits 10 seconds and retries once before failing.
|
||||
*
|
||||
* Retry policy:
|
||||
* 429 (rate limited) → wait 10s, retry once.
|
||||
* Network error / timeout → wait 3s, retry once.
|
||||
* Anything else (4xx, 5xx, empty 200) → fail immediately.
|
||||
*
|
||||
* The Novi fetch is wrapped in an AbortController with a 12s hard timeout.
|
||||
* Without it, a hung Novi server leaves verify_in_flight=true indefinitely.
|
||||
*/
|
||||
async function verify_novi_uuid(
|
||||
uuid: string,
|
||||
@@ -302,10 +309,21 @@ async function verify_novi_uuid(
|
||||
try {
|
||||
const headers = new Headers();
|
||||
headers.append('Authorization', `Basic ${api_key}`);
|
||||
const response = await fetch(`${api_root_url}/customers/${uuid}`, {
|
||||
method: 'GET',
|
||||
headers
|
||||
});
|
||||
|
||||
// Hard timeout: abort if Novi doesn't respond within 12 seconds.
|
||||
// Prevents verify_in_flight from getting stuck on a hung server.
|
||||
const abort_ctrl = new AbortController();
|
||||
const abort_timeout_id = setTimeout(() => abort_ctrl.abort(), 12_000);
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${api_root_url}/customers/${uuid}`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
signal: abort_ctrl.signal
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(abort_timeout_id);
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
if (is_retry) {
|
||||
@@ -393,6 +411,23 @@ async function verify_novi_uuid(
|
||||
$idaa_loc.bb.qry__hidden = 'not_hidden';
|
||||
$idaa_loc.bb.qry__enabled = 'enabled';
|
||||
} catch (error) {
|
||||
// Network errors (TypeError: Failed to fetch) and AbortError (our 12s timeout) get one
|
||||
// automatic retry after a short wait — they are almost always transient (cellular drop,
|
||||
// hotel WiFi hiccup, momentary Novi server lag).
|
||||
const is_network_or_timeout =
|
||||
error instanceof TypeError ||
|
||||
(error instanceof Error && error.name === 'AbortError');
|
||||
if (is_network_or_timeout && !is_retry) {
|
||||
const reason = error instanceof Error && error.name === 'AbortError'
|
||||
? 'timed out (no response in 12s)'
|
||||
: 'network error';
|
||||
console.warn(`IDAA Layout: Novi verification ${reason} for ${uuid}. Retrying in 3s...`);
|
||||
verifying_status_msg = 'Connection issue — retrying...';
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 3_000));
|
||||
await verify_novi_uuid(uuid, api_key, api_root_url, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verification failed — all-or-nothing means deny access.
|
||||
console.error(
|
||||
`IDAA Layout: Novi UUID verification failed for ${uuid}:`,
|
||||
@@ -488,11 +523,12 @@ function handle_verify_retry() {
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-sm">
|
||||
We were unable to connect to the membership directory. This is likely a temporary issue — it may not be a problem with your membership or access.
|
||||
We were unable to connect to the membership directory. This is usually a temporary network issue — it is not a problem with your membership or access.
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-xs italic text-gray-500">
|
||||
If this keeps happening, try "Clear Cache & Reload" or contact technical support.
|
||||
Try "Try Again" first. If the problem persists, use "Clear Cache & Reload".
|
||||
If nothing works, close this page and reopen IDAA from the menu on idaa.org.
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center justify-center gap-2">
|
||||
<!-- Try Again: unlatches verify_failed_for_uuid and re-runs Effect 2 — no reload needed -->
|
||||
@@ -591,7 +627,8 @@ function handle_verify_retry() {
|
||||
|
||||
{#if $ae_loc.iframe}
|
||||
<p class="text-xs italic text-gray-500">
|
||||
If you just opened this page, try reloading. If the problem persists, try "Clear Cache & Reload".
|
||||
If you just opened this page, try reloading. If the problem persists, try
|
||||
"Clear Cache & Reload", or close this page and reopen IDAA from the menu on idaa.org.
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center justify-center gap-2">
|
||||
<!-- WHY: In iframe mode the Novi UUID is passed via URL param on first load.
|
||||
|
||||
Reference in New Issue
Block a user