Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/client-runtime/src/connection/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EnvironmentAuthInvalidError } from "@t3tools/contracts";
import {
RelayAuthInvalidError,
RelayEnvironmentConnectNotAuthorizedError,
RelayEnvironmentEndpointTimedOutError,
} from "@t3tools/contracts/relay";
import { describe, expect, it } from "@effect/vitest";
Expand Down Expand Up @@ -35,6 +36,38 @@ describe("mapManagedRelayError", () => {
});
});

it("retries a tunnel that is still being set up, and blocks other refusals", () => {
const mapConnectRefusal = (reason: RelayEnvironmentConnectNotAuthorizedError["reason"]) => {
const relayError = new RelayEnvironmentConnectNotAuthorizedError({
code: "environment_connect_not_authorized",
reason,
traceId: "trace-connect-refused",
});
return mapManagedRelayError(
new ManagedRelayRequestFailedError({
action: "connect relay environment",
cause: relayError,
relayError,
}),
);
};

expect(mapConnectRefusal("managed_endpoint_allocation_not_ready")).toMatchObject({
_tag: "ConnectionTransientError",
reason: "endpoint-unavailable",
detail: "The environment's T3 Connect tunnel is still being set up.",
traceId: "trace-connect-refused",
});
expect(mapConnectRefusal("endpoint_provider_not_managed")).toMatchObject({
_tag: "ConnectionBlockedError",
reason: "permission",
});
expect(mapConnectRefusal(undefined)).toMatchObject({
_tag: "ConnectionBlockedError",
reason: "permission",
});
});

it("presents clock skew as one possible cause for a generic DPoP error", () => {
const mapped = mapManagedRelayError(
new ManagedRelayRequestFailedError({
Expand Down
14 changes: 14 additions & 0 deletions packages/client-runtime/src/connection/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ function relayProtectedError(error: RelayProtectedError): ConnectionAttemptError
traceId: error.traceId,
});
case "RelayEnvironmentConnectNotAuthorizedError":
// A host swapping in a new tunnel leaves its allocation unready until
// the tunnel is up, and nothing on the client changes when it is.
if (error.reason === "managed_endpoint_allocation_not_ready") {
return new ConnectionTransientError({
reason: "endpoint-unavailable",
detail: relayProtectedErrorMessage(error),
traceId: error.traceId,
});
}
return new ConnectionBlockedError({
reason: "permission",
detail: relayProtectedErrorMessage(error),
traceId: error.traceId,
});
case "RelayEnvironmentLinkProofInvalidError":
case "RelayEnvironmentLinkLimitExceededError":
return new ConnectionBlockedError({
Expand Down
3 changes: 3 additions & 0 deletions packages/client-runtime/src/relay/errorPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export function relayProtectedErrorMessage(error: RelayProtectedError): string {
if (error.reason === "environment_link_not_found") {
return "Relay has no active link for this environment. The environment server may not have re-established its link yet.";
}
if (error.reason === "managed_endpoint_allocation_not_ready") {
return "The environment's T3 Connect tunnel is still being set up.";
}
return error.reason
? `Relay rejected the environment connection request (${error.reason}).`
: "Relay rejected the environment connection request.";
Expand Down
Loading