authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 17:54:08-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 17:54:08-07:00
log2dca7a1eddd27c823e4d9d0a316864c4ad9f95c5
tree572d533ca6496ff6d5d25060b21f6b992576097a
parent720e126b761ba0da112d8c68b589d20ab98a7eb0
signature Signed by PGP key B5690EEEBB952194

fix(client): explain possible network blocking for T3 Connect (#9783)


12 files changed, 211 insertions(+), 44 deletions(-)

apps/web/src/components/settings/ConnectionsSettings.tsx+3-1
......@@ -1524,7 +1524,9 @@ function SavedBackendListRow({
15241524 ) : null}
15251525 {environment.connection.error && !resumingServerUpdate ? (
15261526 <p className="flex min-w-0 items-center gap-2 text-destructive text-xs">
1527 <span className="truncate">{connectionStatusText(environment.connection)}</span>
1527 <span className="min-w-0 break-words">
1528 {connectionStatusText(environment.connection)}
1529 </span>
15281530 {errorTraceId ? (
15291531 <button
15301532 type="button"
packages/client-runtime/src/authorization/service.ts+4-3
......@@ -80,9 +80,10 @@ function mapDpopSocketError(error: RemoteEnvironmentAuthError | ConnectionAttemp
8080
8181const fetchDescriptor = Effect.fn("clientRuntime.connection.remote.fetchDescriptor")(function* (
8282 httpBaseUrl: string,
83 connectionMethod: ClientConnectionMethod,
8384) {
8485 return yield* fetchRemoteEnvironmentDescriptor({ httpBaseUrl }).pipe(
85 Effect.mapError(mapRemoteEnvironmentError),
86 Effect.mapError((error) => mapRemoteEnvironmentError(error, connectionMethod)),
8687 );
8788});
8889
......@@ -119,7 +120,7 @@ export const make = Effect.gen(function* () {
119120 cachedDescriptor.validatedAtEpochMs + BEARER_DESCRIPTOR_CACHE_TTL_MS > now;
120121 const descriptor = canReuseDescriptor
121122 ? cachedDescriptor.descriptor
122 : yield* fetchDescriptor(input.httpBaseUrl).pipe(
123 : yield* fetchDescriptor(input.httpBaseUrl, input.connectionMethod).pipe(
123124 Effect.provideService(HttpClient.HttpClient, httpClient),
124125 );
125126 if (descriptor.environmentId !== input.expectedEnvironmentId) {
......@@ -252,7 +253,7 @@ export const make = Effect.gen(function* () {
252253 "connection.remote_token_cache": "miss",
253254 });
254255 const bootstrap = yield* input.obtainBootstrap;
255 const descriptor = yield* fetchDescriptor(bootstrap.endpoint.httpBaseUrl).pipe(
256 const descriptor = yield* fetchDescriptor(bootstrap.endpoint.httpBaseUrl, "relay").pipe(
256257 Effect.provideService(HttpClient.HttpClient, httpClient),
257258 Effect.withSpan("environment.authorization.descriptor"),
258259 );
packages/client-runtime/src/connection/errors.test.ts+58-2
......@@ -1,12 +1,40 @@
11import { EnvironmentAuthInvalidError } from "@t3tools/contracts";
2import { RelayAuthInvalidError } from "@t3tools/contracts/relay";
2import {
3 RelayAuthInvalidError,
4 RelayEnvironmentEndpointTimedOutError,
5} from "@t3tools/contracts/relay";
36import { describe, expect, it } from "@effect/vitest";
47
5import { mapManagedRelayError, mapRemoteDpopEnvironmentError } from "./errors.ts";
8import {
9 mapManagedRelayError,
10 mapRemoteDpopEnvironmentError,
11 mapRemoteEnvironmentError,
12} from "./errors.ts";
613import { DPOP_RETRY_HINT, DPOP_UNKNOWN_HINT } from "../relay/errorPresentation.ts";
714import { ManagedRelayRequestFailedError } from "../relay/managedRelay.ts";
15import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
16import { RemoteEnvironmentAuthFetchError, RemoteEnvironmentAuthTimeoutError } from "../rpc/http.ts";
817
918describe("mapManagedRelayError", () => {
19 it("keeps a timeout reported by the relay distinct from a local network failure", () => {
20 const relayError = new RelayEnvironmentEndpointTimedOutError({
21 code: "environment_endpoint_timed_out",
22 traceId: "trace-server-timeout",
23 });
24 const mapped = mapManagedRelayError(
25 new ManagedRelayRequestFailedError({
26 action: "connect relay environment",
27 cause: relayError,
28 relayError,
29 }),
30 );
31 expect(mapped).toMatchObject({
32 reason: "timeout",
33 detail: "Relay timed out while contacting the environment endpoint.",
34 traceId: "trace-server-timeout",
35 });
36 });
37
1038 it("presents clock skew as one possible cause for a generic DPoP error", () => {
1139 const mapped = mapManagedRelayError(
1240 new ManagedRelayRequestFailedError({
......@@ -48,6 +76,34 @@ describe("mapManagedRelayError", () => {
4876});
4977
5078describe("mapRemoteDpopEnvironmentError", () => {
79 it("keeps relay descriptor auth failures distinct from DPoP proof failures", () => {
80 const error = new EnvironmentAuthInvalidError({
81 code: "auth_invalid",
82 reason: "invalid_credential",
83 traceId: "trace-descriptor",
84 });
85 expect(mapRemoteEnvironmentError(error, "relay").message).toBe(
86 "The environment credential is invalid.",
87 );
88 expect(mapRemoteDpopEnvironmentError(error).message).toBe(
89 `The environment credential is invalid. ${DPOP_UNKNOWN_HINT}`,
90 );
91 });
92
93 it.each([
94 new RemoteEnvironmentAuthFetchError({
95 message: "Failed to fetch remote environment endpoint.",
96 cause: new TypeError("Failed to fetch"),
97 }),
98 new RemoteEnvironmentAuthTimeoutError("https://environment.example.test", 10_000),
99 ])("suggests another network when the relay endpoint cannot be reached: $_tag", (error) => {
100 const mapped = mapRemoteDpopEnvironmentError(error);
101 expect(mapped).toMatchObject({
102 _tag: "ConnectionTransientError",
103 detail: `${error.message} ${NETWORK_BLOCKING_HINT}`,
104 });
105 });
106
51107 it("does not present a generic environment auth error as confirmed clock skew", () => {
52108 const mapped = mapRemoteDpopEnvironmentError(
53109 new EnvironmentAuthInvalidError({
packages/client-runtime/src/connection/errors.ts+7-4
......@@ -1,8 +1,9 @@
1import type { EnvironmentId } from "@t3tools/contracts";
1import type { ClientConnectionMethod, EnvironmentId } from "@t3tools/contracts";
22import type { RelayProtectedError } from "@t3tools/contracts/relay";
33import type { ManagedRelayClientError } from "../relay/managedRelay.ts";
44import { dpopFailureMessage, relayProtectedErrorMessage } from "../relay/errorPresentation.ts";
55import type { RemoteEnvironmentAuthError } from "../authorization/remote.ts";
6import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
67import {
78 ConnectionBlockedError,
89 type ConnectionAttemptError,
......@@ -113,7 +114,9 @@ export function mapManagedRelayError(error: ManagedRelayClientError): Connection
113114
114115export function mapRemoteEnvironmentError(
115116 error: RemoteEnvironmentAuthError,
117 connectionMethod: ClientConnectionMethod = "direct",
116118): ConnectionAttemptError {
119 const networkHint = connectionMethod === "relay" ? ` ${NETWORK_BLOCKING_HINT}` : "";
117120 switch (error._tag) {
118121 case "EnvironmentAuthInvalidError":
119122 return new ConnectionBlockedError({
......@@ -146,12 +149,12 @@ export function mapRemoteEnvironmentError(
146149 case "RemoteEnvironmentAuthTimeoutError":
147150 return new ConnectionTransientError({
148151 reason: "timeout",
149 detail: error.message,
152 detail: `${error.message}${networkHint}`,
150153 });
151154 case "RemoteEnvironmentAuthFetchError":
152155 return new ConnectionTransientError({
153156 reason: "network",
154 detail: error.message,
157 detail: `${error.message}${networkHint}`,
155158 });
156159 case "EnvironmentInternalError":
157160 return new ConnectionTransientError({
......@@ -185,5 +188,5 @@ export function mapRemoteDpopEnvironmentError(
185188 traceId: error.traceId,
186189 });
187190 }
188 return mapRemoteEnvironmentError(error);
191 return mapRemoteEnvironmentError(error, "relay");
189192}
packages/client-runtime/src/connection/supervisor.test.ts+31-1
......@@ -30,6 +30,7 @@ import {
3030import * as RpcSession from "../rpc/session.ts";
3131import * as EnvironmentSupervisor from "./supervisor.ts";
3232import * as ConnectionWakeups from "./wakeups.ts";
33import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
3334
3435const TARGET = new PrimaryConnectionTarget({
3536 environmentId: EnvironmentId.make("environment-1"),
......@@ -467,6 +468,35 @@ describe("EnvironmentSupervisor", () => {
467468 }).pipe(Effect.provide(TestClock.layer())),
468469 );
469470
471 it.effect(
472 "shows a network hint for a stalled relay connection and clears it after recovery",
473 () =>
474 Effect.gen(function* () {
475 const harness = yield* makeHarness({
476 prepare: (attempt) =>
477 attempt === 1 ? Effect.never : Effect.succeed(PREPARED_CONNECTION),
478 });
479 const supervisor = yield* EnvironmentSupervisor.make(RELAY_ENTRY, {
480 initiallyDesired: true,
481 }).pipe(Effect.provide(harness.dependencies));
482
483 yield* awaitState(supervisor.state, (state) => state.phase === "connecting");
484 yield* TestClock.adjust("15 seconds");
485 const failed = yield* awaitState(supervisor.state, (state) => state.phase === "backoff");
486 expect(failed.lastFailure?.message).toBe(
487 `Test environment did not respond during connection setup. ${NETWORK_BLOCKING_HINT}`,
488 );
489
490 yield* TestClock.adjust("3 seconds");
491 const recovered = yield* awaitState(
492 supervisor.state,
493 (state) => state.phase === "connected",
494 );
495 expect(recovered.lastFailure).toBeNull();
496 expect(yield* Ref.get(harness.prepareCount)).toBe(2);
497 }).pipe(Effect.provide(TestClock.layer())),
498 );
499
470500 it.effect("converts unexpected driver defects into retryable failures", () =>
471501 Effect.gen(function* () {
472502 const harness = yield* makeHarness({
......@@ -475,7 +505,7 @@ describe("EnvironmentSupervisor", () => {
475505 ? Effect.die(new Error("Native transport defect."))
476506 : Effect.succeed(PREPARED_CONNECTION),
477507 });
478 const supervisor = yield* EnvironmentSupervisor.make(TARGET_ENTRY, {
508 const supervisor = yield* EnvironmentSupervisor.make(RELAY_ENTRY, {
479509 initiallyDesired: true,
480510 }).pipe(Effect.provide(harness.dependencies));
481511
packages/client-runtime/src/connection/supervisor.ts+6-2
......@@ -28,6 +28,7 @@ import {
2828} from "./model.ts";
2929import * as RpcSession from "../rpc/session.ts";
3030import { safeErrorLogAttributes } from "../errors/safeLog.ts";
31import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
3132import * as ConnectionWakeups from "./wakeups.ts";
3233
3334const RETRY_DELAYS_MS = [3_000, 4_000, 8_000, 16_000] as const;
......@@ -241,6 +242,9 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* (
241242 | ConnectionWakeups.ConnectionWakeups
242243> {
243244 const target = entry.target;
245 const setupTimeoutDetail = `${target.label} did not respond during connection setup.${
246 target._tag === "RelayConnectionTarget" ? ` ${NETWORK_BLOCKING_HINT}` : ""
247 }`;
244248 yield* annotateTarget(target);
245249
246250 const connectivity = yield* Connectivity.Connectivity;
......@@ -626,7 +630,7 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* (
626630 } else {
627631 replacementError = new ConnectionTransientError({
628632 reason: "timeout",
629 detail: `${target.label} did not respond during connection setup.`,
633 detail: setupTimeoutDetail,
630634 });
631635 }
632636
......@@ -708,7 +712,7 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* (
708712 failure: {
709713 error: new ConnectionTransientError({
710714 reason: "timeout",
711 detail: `${target.label} did not respond during connection setup.`,
715 detail: setupTimeoutDetail,
712716 }),
713717 attemptSpan: Option.none(),
714718 },
packages/client-runtime/src/errors/network.ts created+4
......@@ -0,0 +1,4 @@
1// A failed request cannot distinguish filtering from an outage. Keep this a
2// possible cause, and suggest a way to check without changing server settings.
3export const NETWORK_BLOCKING_HINT =
4 "Your DNS or firewall may be blocking T3 Connect. Try another network, such as a phone hotspot.";
packages/client-runtime/src/relay/discovery.test.ts+2-1
......@@ -19,6 +19,7 @@ import * as Connectivity from "../connection/connectivity.ts";
1919import { ConnectionBlockedError, type NetworkStatus } from "../connection/model.ts";
2020import * as ConnectionWakeups from "../connection/wakeups.ts";
2121import * as RelayEnvironmentDiscovery from "./discovery.ts";
22import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
2223
2324const environments = [
2425 {
......@@ -305,7 +306,7 @@ describe("RelayEnvironmentDiscovery", () => {
305306 expect(Option.getOrThrow(state.error)).toMatchObject({
306307 _tag: "ConnectionTransientError",
307308 reason: "timeout",
308 message: "Relay environment listing timed out.",
309 message: `Relay environment listing timed out. ${NETWORK_BLOCKING_HINT}`,
309310 });
310311 }).pipe(Effect.provide(layer));
311312 }),
packages/client-runtime/src/relay/managedRelay.test.ts+39-1
......@@ -9,10 +9,15 @@ import * as Effect from "effect/Effect";
99import * as Fiber from "effect/Fiber";
1010import * as Layer from "effect/Layer";
1111import * as Tracer from "effect/Tracer";
12import * as Schema from "effect/Schema";
1213import * as TestClock from "effect/testing/TestClock";
1314
1415import * as ManagedRelay from "./managedRelay.ts";
1516import { remoteHttpClientLayer } from "../rpc/http.ts";
17import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
18
19const encodeRelayError = Schema.encodeEffect(ManagedRelay.ManagedRelayClientError);
20const decodeRelayError = Schema.decodeUnknownEffect(ManagedRelay.ManagedRelayClientError);
1621
1722function managedRelayTestLayer(
1823 fetchFn: typeof globalThis.fetch,
......@@ -529,11 +534,44 @@ describe("ManagedRelayClient", () => {
529534 _tag: "ManagedRelayRequestTimeoutError",
530535 activity: "Relay environment listing",
531536 timeoutMs: ManagedRelay.MANAGED_RELAY_REQUEST_TIMEOUT_MS,
532 message: "Relay environment listing timed out.",
537 message: `Relay environment listing timed out. ${NETWORK_BLOCKING_HINT}`,
533538 });
534539 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), managedRelayTestLayer(fetchFn))));
535540 });
536541
542 it.effect("suggests checking network filtering when fetch fails without a response", () => {
543 const fetchFn = (() =>
544 Promise.reject(new TypeError("Failed to fetch"))) satisfies typeof globalThis.fetch;
545 return Effect.gen(function* () {
546 const relayClient = yield* ManagedRelay.ManagedRelayClient;
547 const error = yield* relayClient
548 .listEnvironments({ clerkToken: "clerk-token" })
549 .pipe(Effect.flip);
550 expect(error).toMatchObject({
551 _tag: "ManagedRelayRequestFailedError",
552 transportFailed: true,
553 message: `Could not list relay-managed environments. ${NETWORK_BLOCKING_HINT}`,
554 });
555 const encoded = yield* encodeRelayError(error);
556 const decoded = yield* decodeRelayError(encoded);
557 expect(decoded.message).toBe(error.message);
558 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
559 });
560
561 it.effect("does not suggest network filtering for an HTTP server error", () => {
562 const fetchFn = (() =>
563 Promise.resolve(
564 new Response("Unavailable", { status: 503 }),
565 )) satisfies typeof globalThis.fetch;
566 return Effect.gen(function* () {
567 const relayClient = yield* ManagedRelay.ManagedRelayClient;
568 const error = yield* relayClient
569 .listEnvironments({ clerkToken: "clerk-token" })
570 .pipe(Effect.flip);
571 expect(error.message).toBe("Could not list relay-managed environments.");
572 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
573 });
574
537575 it.effect("preserves typed relay trace IDs on client errors", () => {
538576 const fetchFn = (() =>
539577 Promise.resolve(
packages/client-runtime/src/relay/managedRelay.ts+7-2
......@@ -44,6 +44,7 @@ import * as SynchronizedRef from "effect/SynchronizedRef";
4444import * as HttpClientError from "effect/unstable/http/HttpClientError";
4545import type * as HttpMethod from "effect/unstable/http/HttpMethod";
4646import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
47import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
4748
4849export interface ManagedRelayDpopProofInput {
4950 readonly method: HttpMethod.HttpMethod;
......@@ -126,7 +127,7 @@ export class ManagedRelayRequestTimeoutError extends Schema.TaggedErrorClass<Man
126127 },
127128) {
128129 override get message(): string {
129 return `${this.activity} timed out.`;
130 return `${this.activity} timed out. ${NETWORK_BLOCKING_HINT}`;
130131 }
131132}
132133
......@@ -145,13 +146,15 @@ export class ManagedRelayRequestFailedError extends Schema.TaggedErrorClass<Mana
145146 "ManagedRelayRequestFailedError",
146147 {
147148 action: ManagedRelayRequestAction,
149 transportFailed: Schema.optionalKey(Schema.Boolean),
148150 cause: Schema.Defect(),
149151 relayError: Schema.optional(RelayProtectedError),
150152 traceId: Schema.optional(Schema.String),
151153 },
152154) {
153155 override get message(): string {
154 return `Could not ${this.action}.`;
156 const message = `Could not ${this.action}.`;
157 return this.transportFailed ? `${message} ${NETWORK_BLOCKING_HINT}` : message;
155158 }
156159}
157160
......@@ -307,6 +310,8 @@ function relayRequestError(action: ManagedRelayRequestAction) {
307310 return (cause: RelayHttpRequestError): ManagedRelayClientError =>
308311 new ManagedRelayRequestFailedError({
309312 action,
313 transportFailed:
314 HttpClientError.isHttpClientError(cause) && cause.reason._tag === "TransportError",
310315 cause,
311316 ...(isRelayProtectedError(cause) ? { relayError: cause, traceId: cause.traceId } : {}),
312317 });
packages/client-runtime/src/rpc/session.test.ts+32-20
......@@ -30,6 +30,7 @@ import {
3030 ConnectionBlockedError,
3131 ConnectionTransientError,
3232 PrimaryConnectionTarget,
33 RelayConnectionTarget,
3334 type PreparedConnection,
3435} from "../connection/model.ts";
3536import * as EnvironmentSupervisor from "../connection/supervisor.ts";
......@@ -37,6 +38,7 @@ import * as Persistence from "../platform/persistence.ts";
3738import * as RpcSession from "./session.ts";
3839import { makeEnvironmentServerConfigState } from "../state/server.ts";
3940import { applyServerConfigProjection } from "../state/serverConfigProjection.ts";
41import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
4042
4143type SocketEventType = "open" | "message" | "close" | "error";
4244type SocketEvent = {
......@@ -1157,27 +1159,37 @@ describe("RpcSessionFactory", () => {
11571159 ),
11581160 );
11591161
1160 it.effect("fails readiness when the websocket never opens", () =>
1161 Effect.gen(function* () {
1162 const { factory, sockets } = yield* makeFactory();
1162 for (const relay of [false, true]) {
1163 it.effect(`fails readiness when the ${relay ? "relay" : "direct"} websocket never opens`, () =>
1164 Effect.gen(function* () {
1165 const { factory, sockets } = yield* makeFactory();
11631166
1164 const error = yield* Effect.scoped(
1165 Effect.gen(function* () {
1166 const session = yield* factory.connect(PREPARED);
1167 const readyFiber = yield* Effect.forkChild(Effect.flip(session.ready));
1168 yield* awaitSocket(sockets);
1167 const error = yield* Effect.scoped(
1168 Effect.gen(function* () {
1169 const session = yield* factory.connect({
1170 ...PREPARED,
1171 target: relay
1172 ? new RelayConnectionTarget({
1173 environmentId: TARGET.environmentId,
1174 label: TARGET.label,
1175 })
1176 : TARGET,
1177 });
1178 const readyFiber = yield* Effect.forkChild(Effect.flip(session.ready));
1179 yield* awaitSocket(sockets);
11691180
1170 yield* TestClock.adjust("15 seconds");
1171 return yield* Fiber.join(readyFiber);
1172 }),
1173 );
1181 yield* TestClock.adjust("15 seconds");
1182 return yield* Fiber.join(readyFiber);
1183 }),
1184 );
11741185
1175 expect(error).toBeInstanceOf(ConnectionTransientError);
1176 expect(error).toMatchObject({
1177 reason: "transport",
1178 message: "Test environment could not establish a WebSocket connection.",
1179 });
1180 expect(sockets[0]?.readyState).toBe(TestWebSocket.CLOSED);
1181 }).pipe(Effect.provide(TestClock.layer())),
1182 );
1186 expect(error).toBeInstanceOf(ConnectionTransientError);
1187 expect(error).toMatchObject({
1188 reason: "transport",
1189 message: `Test environment could not establish a WebSocket connection.${relay ? ` ${NETWORK_BLOCKING_HINT}` : ""}`,
1190 });
1191 expect(sockets[0]?.readyState).toBe(TestWebSocket.CLOSED);
1192 }).pipe(Effect.provide(TestClock.layer())),
1193 );
1194 }
11831195});
packages/client-runtime/src/rpc/session.ts+18-7
......@@ -15,6 +15,7 @@ import * as Option from "effect/Option";
1515import * as PubSub from "effect/PubSub";
1616import * as Ref from "effect/Ref";
1717import * as Schedule from "effect/Schedule";
18import * as Schema from "effect/Schema";
1819import type * as Scope from "effect/Scope";
1920import * as Stream from "effect/Stream";
2021import type * as Rpc from "effect/unstable/rpc/Rpc";
......@@ -24,6 +25,7 @@ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
2425import * as Socket from "effect/unstable/socket/Socket";
2526
2627import { makeWsRpcProtocolClient, type WsRpcProtocolClient } from "./protocol.ts";
28import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
2729import type {
2830 ConnectionAttemptError,
2931 ConnectionTransientError,
......@@ -117,8 +119,11 @@ function serverConfigReplayEvents(
117119 ];
118120}
119121
122const isSocketErrorReason = Schema.is(Socket.SocketErrorReason);
123
120124function mapSessionRpcError(
121125 error: InitialConfigError | ProbeError | ServerConfigSubscriptionError,
126 networkHint: string,
122127): ConnectionAttemptError {
123128 switch (error._tag) {
124129 case "EnvironmentAuthorizationError":
......@@ -135,7 +140,7 @@ function mapSessionRpcError(
135140 case "RpcClientError":
136141 return new ConnectionTransientErrorClass({
137142 reason: "transport",
138 detail: error.message,
143 detail: `${error.message}${isSocketErrorReason(error.reason) ? networkHint : ""}`,
139144 });
140145 }
141146}
......@@ -150,6 +155,10 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
150155 };
151156
152157 const connect = Effect.fnUntraced(function* (connection: PreparedConnection) {
158 const networkHint =
159 connection.target._tag === "RelayConnectionTarget" ? ` ${NETWORK_BLOCKING_HINT}` : "";
160 const mapRpcError = (error: Parameters<typeof mapSessionRpcError>[0]) =>
161 mapSessionRpcError(error, networkHint);
153162 yield* Effect.annotateCurrentSpan({
154163 "connection.environment.id": connection.environmentId,
155164 });
......@@ -164,9 +173,11 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
164173 disconnected,
165174 new ConnectionTransientErrorClass({
166175 reason: "transport",
167 detail: wasConnected
168 ? `${connection.label} disconnected.`
169 : `${connection.label} could not establish a WebSocket connection.`,
176 detail: `${
177 wasConnected
178 ? `${connection.label} disconnected.`
179 : `${connection.label} could not establish a WebSocket connection.`
180 }${networkHint}`,
170181 }),
171182 ),
172183 ),
......@@ -263,7 +274,7 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
263274 }
264275 return Effect.all([
265276 Deferred.failCause(serverConfigExit, exit.cause),
266 Deferred.failCause(configSubscriptionClosed, Cause.map(exit.cause, mapSessionRpcError)),
277 Deferred.failCause(configSubscriptionClosed, Cause.map(exit.cause, mapRpcError)),
267278 ]).pipe(Effect.asVoid);
268279 }),
269280 );
......@@ -271,7 +282,7 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
271282 const initialConfig = Effect.raceFirst(
272283 Deferred.await(initialConfigDeferred),
273284 Deferred.await(serverConfigExit).pipe(
274 Effect.mapError(mapSessionRpcError),
285 Effect.mapError(mapRpcError),
275286 Effect.flatMap(() => Effect.fail(configSubscriptionEndedError)),
276287 ),
277288 ).pipe(Effect.withSpan("environment.initialSync"));
......@@ -330,7 +341,7 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
330341 (config.environment.capabilities.connectionProbe === true
331342 ? protocolClient[WS_METHODS.serverProbe]({})
332343 : protocolClient[WS_METHODS.serverGetConfig]({})
333 ).pipe(Effect.mapError(mapSessionRpcError)),
344 ).pipe(Effect.mapError(mapRpcError)),
334345 ),
335346 Effect.asVoid,
336347 Effect.withSpan("clientRuntime.connection.rpcSession.probe"),