From 762b2df9e381f4da3d660e8197c66f5a522b7d8c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 8 Jun 2026 16:07:29 +0000 Subject: [PATCH 1/3] Make relay helpers more idiomatic Effect Co-authored-by: Julius Marminge --- .../src/managedRelayState.test.ts | 41 ++++++++ .../client-runtime/src/managedRelayState.ts | 34 ++++--- packages/shared/src/relayJwt.test.ts | 98 +++++++++++++++++++ packages/shared/src/relayJwt.ts | 43 ++++++-- 4 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 packages/shared/src/relayJwt.test.ts diff --git a/packages/client-runtime/src/managedRelayState.test.ts b/packages/client-runtime/src/managedRelayState.test.ts index ce58241e796..0b12879ec9a 100644 --- a/packages/client-runtime/src/managedRelayState.test.ts +++ b/packages/client-runtime/src/managedRelayState.test.ts @@ -132,6 +132,47 @@ describe("createManagedRelayQueryManager", () => { }); }); + it("uses schema encoded status keys for equivalent environment inputs", async () => { + const getEnvironmentStatus = vi.fn(() => + Effect.succeed({ + environmentId: environment.environmentId, + endpoint: environment.endpoint, + status: "online" as const, + checkedAt: "2026-06-01T00:00:00.000Z", + }), + ); + const manager = createManager({ getEnvironmentStatus }); + const reorderedEnvironment = { + linkedAt: environment.linkedAt, + endpoint: { + providerKind: environment.endpoint.providerKind, + wsBaseUrl: environment.endpoint.wsBaseUrl, + httpBaseUrl: environment.endpoint.httpBaseUrl, + }, + label: environment.label, + environmentId: environment.environmentId, + } satisfies RelayClientEnvironmentRecord; + setSession(); + + const atom = manager.environmentStatusAtom({ accountId: "account-1", environment }); + registry.get(atom); + await vi.waitFor(() => expect(getEnvironmentStatus).toHaveBeenCalledTimes(1)); + + registry.get( + manager.environmentStatusAtom({ + accountId: "account-1", + environment: reorderedEnvironment, + }), + ); + expect(getEnvironmentStatus).toHaveBeenCalledTimes(1); + + manager.refreshEnvironmentStatus(registry, { + accountId: "account-1", + environment: reorderedEnvironment, + }); + await vi.waitFor(() => expect(getEnvironmentStatus).toHaveBeenCalledTimes(2)); + }); + it("rejects status responses for a different environment", async () => { const mismatchedStatus = { environmentId: EnvironmentId.make("environment-2"), diff --git a/packages/client-runtime/src/managedRelayState.ts b/packages/client-runtime/src/managedRelayState.ts index 7d50f0fdb7c..b60ab136b7c 100644 --- a/packages/client-runtime/src/managedRelayState.ts +++ b/packages/client-runtime/src/managedRelayState.ts @@ -1,15 +1,17 @@ import type { - RelayClientEnvironmentRecord, RelayEnvironmentStatusResponse, } from "@t3tools/contracts/relay"; import { + RelayClientEnvironmentRecord, RelayEnvironmentConnectScope, RelayEnvironmentStatusScope, + type RelayClientEnvironmentRecord as RelayClientEnvironmentRecordType, } from "@t3tools/contracts/relay"; import * as Cause from "effect/Cause"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { AsyncResult, Atom, type AtomRegistry } from "effect/unstable/reactivity"; import { ManagedRelayClient } from "./managedRelay.ts"; @@ -37,6 +39,16 @@ export class ManagedRelaySnapshotError extends Data.TaggedError("ManagedRelaySna readonly message: string; }> {} +const EnvironmentStatusKey = Schema.Struct({ + accountId: Schema.String, + environment: RelayClientEnvironmentRecord, +}); +type EnvironmentStatusKey = typeof EnvironmentStatusKey.Type; + +const EnvironmentStatusKeyJson = Schema.fromJsonString(EnvironmentStatusKey); +const encodeEnvironmentStatusKey = Schema.encodeSync(EnvironmentStatusKeyJson); +const decodeEnvironmentStatusKey = Schema.decodeSync(EnvironmentStatusKeyJson); + export const managedRelaySessionAtom = Atom.make(null).pipe( Atom.keepAlive, Atom.withLabel("managed-relay:session"), @@ -130,19 +142,13 @@ function requireClerkToken( function statusKey(input: { readonly accountId: string; - readonly environment: RelayClientEnvironmentRecord; + readonly environment: RelayClientEnvironmentRecordType; }): string { - return JSON.stringify(input); + return encodeEnvironmentStatusKey(input); } -function parseStatusKey(key: string): { - readonly accountId: string; - readonly environment: RelayClientEnvironmentRecord; -} { - return JSON.parse(key) as { - readonly accountId: string; - readonly environment: RelayClientEnvironmentRecord; - }; +function parseStatusKey(key: string): EnvironmentStatusKey { + return decodeEnvironmentStatusKey(key); } function endpointMatches( @@ -157,7 +163,7 @@ function endpointMatches( } function validateEnvironmentStatus( - environment: RelayClientEnvironmentRecord, + environment: RelayClientEnvironmentRecordType, status: RelayEnvironmentStatusResponse, ): Effect.Effect { if (status.environmentId !== environment.environmentId) { @@ -268,7 +274,7 @@ export function createManagedRelayQueryManager( devicesAtom, environmentStatusAtom: (input: { readonly accountId: string; - readonly environment: RelayClientEnvironmentRecord; + readonly environment: RelayClientEnvironmentRecordType; }) => environmentStatusAtom(statusKey(input)), refreshEnvironments(registry: AtomRegistry.AtomRegistry, accountId: string): void { registry.refresh(environmentsAtom(accountId)); @@ -280,7 +286,7 @@ export function createManagedRelayQueryManager( registry: AtomRegistry.AtomRegistry, input: { readonly accountId: string; - readonly environment: RelayClientEnvironmentRecord; + readonly environment: RelayClientEnvironmentRecordType; }, ): void { registry.refresh(environmentStatusAtom(statusKey(input))); diff --git a/packages/shared/src/relayJwt.test.ts b/packages/shared/src/relayJwt.test.ts new file mode 100644 index 00000000000..7edc1d35a17 --- /dev/null +++ b/packages/shared/src/relayJwt.test.ts @@ -0,0 +1,98 @@ +import * as NodeCrypto from "node:crypto"; + +import { assert, describe, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; + +import { + RelayJwtSignError, + RelayJwtVerifyError, + signRelayJwt, + verifyRelayJwt, +} from "./relayJwt.ts"; + +const keyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, +}); + +const isRelayJwtSignError = Schema.is(RelayJwtSignError); +const isRelayJwtVerifyError = Schema.is(RelayJwtVerifyError); + +describe("relayJwt", () => { + it.effect("signs and verifies relay JWTs", () => + Effect.gen(function* () { + const token = yield* signRelayJwt({ + privateKey: keyPair.privateKey, + typ: "test+jwt", + payload: { + iss: "https://relay.example.test", + aud: "https://relay.example.test", + sub: "user_123", + jti: "nonce-1", + iat: 100, + exp: 200, + }, + }); + + const payload = yield* verifyRelayJwt({ + publicKey: keyPair.publicKey, + token, + typ: "test+jwt", + issuer: "https://relay.example.test", + audience: "https://relay.example.test", + nowEpochSeconds: 150, + }); + + assert.equal(payload.sub, "user_123"); + assert.equal(payload.jti, "nonce-1"); + }), + ); + + it.effect("returns a structured sign error for invalid private key material", () => + Effect.gen(function* () { + const error = yield* signRelayJwt({ + privateKey: "not a pem", + typ: "test+jwt", + payload: {}, + }).pipe(Effect.flip); + + assert.equal(error._tag, "RelayJwtSignError"); + assert.equal(error.typ, "test+jwt"); + assert.equal(error.message, "Failed to sign relay JWT (test+jwt)."); + assert.equal(isRelayJwtSignError(error), true); + }), + ); + + it.effect("returns a structured verify error with expected claim context", () => + Effect.gen(function* () { + const token = yield* signRelayJwt({ + privateKey: keyPair.privateKey, + typ: "test+jwt", + payload: { + iss: "https://relay.example.test", + aud: "https://relay.example.test", + sub: "user_123", + iat: 100, + exp: 200, + }, + }); + + const error = yield* verifyRelayJwt({ + publicKey: keyPair.publicKey, + token, + typ: "other+jwt", + issuer: "https://relay.example.test", + audience: "https://relay.example.test", + nowEpochSeconds: 150, + }).pipe(Effect.flip); + + assert.equal(error._tag, "RelayJwtVerifyError"); + assert.equal(error.typ, "other+jwt"); + assert.equal(error.issuer, "https://relay.example.test"); + assert.equal(error.audience, "https://relay.example.test"); + assert.equal(error.message, "Failed to verify relay JWT (other+jwt)."); + assert.equal(isRelayJwtVerifyError(error), true); + }), + ); +}); diff --git a/packages/shared/src/relayJwt.ts b/packages/shared/src/relayJwt.ts index bd00023e8fb..2140a5d8ea5 100644 --- a/packages/shared/src/relayJwt.ts +++ b/packages/shared/src/relayJwt.ts @@ -1,7 +1,7 @@ import { decodeJwt, importPKCS8, importSPKI, jwtVerify, SignJWT, type JWTPayload } from "jose"; -import * as Data from "effect/Data"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; export const RELAY_LINK_PROOF_TYP = "t3-env-link+jwt"; export const RELAY_MINT_REQUEST_TYP = "t3-cloud-mint+jwt"; @@ -10,9 +10,34 @@ export const RELAY_MINT_RESPONSE_TYP = "t3-env-mint+jwt"; export const RELAY_HEALTH_RESPONSE_TYP = "t3-env-health+jwt"; export const RELAY_ACTIVITY_PUBLISH_TYP = "t3-env-activity+jwt"; -export class RelayJwtError extends Data.TaggedError("RelayJwtError")<{ - readonly cause: unknown; -}> {} +export class RelayJwtSignError extends Schema.TaggedErrorClass()( + "RelayJwtSignError", + { + typ: Schema.String, + cause: Schema.Defect(), + }, +) { + override get message(): string { + return `Failed to sign relay JWT (${this.typ}).`; + } +} + +export class RelayJwtVerifyError extends Schema.TaggedErrorClass()( + "RelayJwtVerifyError", + { + typ: Schema.String, + issuer: Schema.String, + audience: Schema.String, + cause: Schema.Defect(), + }, +) { + override get message(): string { + return `Failed to verify relay JWT (${this.typ}).`; + } +} + +export const RelayJwtError = Schema.Union([RelayJwtSignError, RelayJwtVerifyError]); +export type RelayJwtError = typeof RelayJwtError.Type; export function normalizeRelayIssuer(value: string): string { return value.trim().replace(/\/+$/gu, ""); @@ -38,7 +63,7 @@ export function signRelayJwt(input: { .setProtectedHeader({ alg: "EdDSA", typ: input.typ }) .sign(key); }, - catch: (cause) => new RelayJwtError({ cause }), + catch: (cause) => new RelayJwtSignError({ typ: input.typ, cause }), }); } @@ -64,6 +89,12 @@ export function verifyRelayJwt(input: { }); return verified.payload; }, - catch: (cause) => new RelayJwtError({ cause }), + catch: (cause) => + new RelayJwtVerifyError({ + typ: input.typ, + issuer: input.issuer, + audience: input.audience, + cause, + }), }); } From c0f91ed832d0dde0831f53e1199e6d308a5a4c62 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 8 Jun 2026 16:10:19 +0000 Subject: [PATCH 2/3] Format managed relay state import Co-authored-by: Julius Marminge --- packages/client-runtime/src/managedRelayState.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/client-runtime/src/managedRelayState.ts b/packages/client-runtime/src/managedRelayState.ts index b60ab136b7c..4559667c455 100644 --- a/packages/client-runtime/src/managedRelayState.ts +++ b/packages/client-runtime/src/managedRelayState.ts @@ -1,6 +1,4 @@ -import type { - RelayEnvironmentStatusResponse, -} from "@t3tools/contracts/relay"; +import type { RelayEnvironmentStatusResponse } from "@t3tools/contracts/relay"; import { RelayClientEnvironmentRecord, RelayEnvironmentConnectScope, From cbc057a2096a39d5936861227878ae4cefadecef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 8 Jun 2026 16:11:35 +0000 Subject: [PATCH 3/3] Narrow relay JWT verify error test Co-authored-by: Julius Marminge --- packages/shared/src/relayJwt.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/shared/src/relayJwt.test.ts b/packages/shared/src/relayJwt.test.ts index 7edc1d35a17..1cc496955fa 100644 --- a/packages/shared/src/relayJwt.test.ts +++ b/packages/shared/src/relayJwt.test.ts @@ -88,6 +88,9 @@ describe("relayJwt", () => { }).pipe(Effect.flip); assert.equal(error._tag, "RelayJwtVerifyError"); + if (error._tag !== "RelayJwtVerifyError") { + assert.fail(`Expected RelayJwtVerifyError, got ${error._tag}`); + } assert.equal(error.typ, "other+jwt"); assert.equal(error.issuer, "https://relay.example.test"); assert.equal(error.audience, "https://relay.example.test");