Stage 17 #2 fix: connection failures show only the spinner, never a toast

A dropped/reset/timed-out connection can surface as a Connect code other than
Unavailable (Canceled/DeadlineExceeded/Unknown/…) which fell through to the generic
'internal' -> a red 'something went wrong' toast appeared alongside the Connecting
spinner. Now toGatewayError (moved to the pure retry.ts, unit-tested) collapses every
transport-level code to 'unavailable' so it is retried + flips offline; and handleError
suppresses the toast for any connection code AND whenever the app is mid-reconnect
(!connection.online), covering the race where a unary error lands before the stream
reports the drop. Genuine server-internal / domain errors still toast while online.
This commit is contained in:
Ilia Denisov
2026-06-09 07:42:47 +02:00
parent efa1d0bd22
commit 84ecc85f51
4 changed files with 70 additions and 37 deletions
+2 -20
View File
@@ -5,35 +5,17 @@
// a thrown GatewayError. In dev the Vite proxy forwards the RPC path to the h2c
// gateway; in a packaged app VITE_GATEWAY_URL points at the real origin.
import { Code, ConnectError, createClient } from '@connectrpc/connect';
import { createClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { Gateway } from '../gen/edge/v1/edge_pb';
import { GatewayError, type GatewayClient } from './client';
import * as codec from './codec';
import { registerProbe, reportOffline, reportOnline } from './connection.svelte';
import { backoffMs, isConnectionCode, retryable } from './retry';
import { backoffMs, isConnectionCode, retryable, toGatewayError } from './retry';
const MAX_RETRIES = 6;
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
function toGatewayError(e: unknown): GatewayError {
if (e instanceof ConnectError) {
switch (e.code) {
case Code.Unauthenticated:
return new GatewayError('session_invalid', e.message);
case Code.ResourceExhausted:
return new GatewayError('rate_limited', e.message);
case Code.Unavailable:
return new GatewayError('unavailable', e.message);
case Code.NotFound:
return new GatewayError('not_found', e.message);
default:
return new GatewayError('internal', e.message);
}
}
return new GatewayError('unavailable', String(e));
}
export function createTransport(baseUrl: string): GatewayClient {
const origin = baseUrl || (typeof location !== 'undefined' ? location.origin : '');
const transport = createConnectTransport({ baseUrl: origin, useBinaryFormat: true });