| 1 | import { |
| 2 | RelayManagedEndpointOrigin, |
| 3 | RelayManagedEndpointRuntimeConfig, |
| 4 | } from "@t3tools/contracts/relay"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Option from "effect/Option"; |
| 7 | import * as Schema from "effect/Schema"; |
| 8 | |
| 9 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 10 | |
| 11 | export const CLOUD_MINT_PUBLIC_KEY = "cloud-mint-ed25519-public-key"; |
| 12 | export const CLOUD_ENDPOINT_RUNTIME_CONFIG = "cloud-endpoint-runtime-config"; |
| 13 | export const CLOUD_ENDPOINT_CONFIRMED_ORIGIN = "cloud-endpoint-confirmed-origin"; |
| 14 | export const CLOUD_LINKED_USER_ID = "cloud-linked-user-id"; |
| 15 | export const RELAY_URL_SECRET = "cloud-relay-url"; |
| 16 | export const RELAY_ISSUER_SECRET = "cloud-relay-issuer"; |
| 17 | export const RELAY_ENVIRONMENT_CREDENTIAL_SECRET = "cloud-relay-environment-credential"; |
| 18 | export const PUBLISH_AGENT_ACTIVITY_SECRET = "cloud-publish-agent-activity"; |
| 19 | export const HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET = "cloud-hold-webhooks-while-offline"; |
| 20 | // Set when the relay rejects this environment's credential (401), as after the |
| 21 | // environment is deregistered from T3 Connect. The link stays stored; the mark |
| 22 | // tells `t3 connect status` and clients to offer a relink. Linking, unlinking, |
| 23 | // or a later successful tunnel registration clears it. |
| 24 | export const CLOUD_LINK_REVOKED = "cloud-link-revoked"; |
| 25 | |
| 26 | export const encodeEndpointRuntimeConfigJson = Schema.encodeEffect( |
| 27 | Schema.fromJsonString(RelayManagedEndpointRuntimeConfig), |
| 28 | ); |
| 29 | |
| 30 | export const decodeRuntimeConfig = Schema.decodeUnknownOption( |
| 31 | Schema.fromJsonString(RelayManagedEndpointRuntimeConfig), |
| 32 | ); |
| 33 | |
| 34 | export const ManagedEndpointConfirmedOrigin = Schema.Struct({ |
| 35 | config: RelayManagedEndpointRuntimeConfig, |
| 36 | origin: RelayManagedEndpointOrigin, |
| 37 | }); |
| 38 | |
| 39 | export const encodeConfirmedOriginJson = Schema.encodeEffect( |
| 40 | Schema.fromJsonString(ManagedEndpointConfirmedOrigin), |
| 41 | ); |
| 42 | |
| 43 | export const decodeConfirmedOrigin = Schema.decodeUnknownOption( |
| 44 | Schema.fromJsonString(ManagedEndpointConfirmedOrigin), |
| 45 | ); |
| 46 | |
| 47 | export function isAgentActivityPublishingEnabledValue(value: string | null): boolean { |
| 48 | return value === "true"; |
| 49 | } |
| 50 | |
| 51 | /** Whether agent-activity publishes currently leave this environment: the |
| 52 | publish opt-in secret is enabled and the relay link credentials exist. |
| 53 | Mirrors the per-publish gate in AgentAwarenessRelay, so the descriptor |
| 54 | capability never advertises publishing that the publisher would skip. */ |
| 55 | export const readAgentActivityPublishingActive = ( |
| 56 | secrets: ServerSecretStore.ServerSecretStore["Service"], |
| 57 | ): Effect.Effect<boolean> => |
| 58 | Effect.gen(function* () { |
| 59 | const readSecretString = (name: string) => |
| 60 | secrets |
| 61 | .get(name) |
| 62 | .pipe( |
| 63 | Effect.map((bytes) => |
| 64 | Option.isSome(bytes) ? new TextDecoder().decode(bytes.value) : null, |
| 65 | ), |
| 66 | ); |
| 67 | const [enabled, url, environmentCredential] = yield* Effect.all([ |
| 68 | readSecretString(PUBLISH_AGENT_ACTIVITY_SECRET), |
| 69 | readSecretString(RELAY_URL_SECRET), |
| 70 | readSecretString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), |
| 71 | ]); |
| 72 | // Empty strings are as unconfigured as missing files: the publisher's |
| 73 | // truthiness gate skips them, so the capability must too. |
| 74 | return ( |
| 75 | isAgentActivityPublishingEnabledValue(enabled) && |
| 76 | url !== null && |
| 77 | url !== "" && |
| 78 | environmentCredential !== null && |
| 79 | environmentCredential !== "" |
| 80 | ); |
| 81 | }).pipe(Effect.orElseSucceed(() => false)); |
| 82 | |
| 83 | /** A non-empty secret as text, or null when it is missing, empty or unreadable. */ |
| 84 | const readSecretString = (name: string) => |
| 85 | ServerSecretStore.ServerSecretStore.pipe( |
| 86 | Effect.flatMap((secrets) => secrets.get(name)), |
| 87 | Effect.map((bytes) => |
| 88 | Option.isSome(bytes) && bytes.value.length > 0 ? new TextDecoder().decode(bytes.value) : null, |
| 89 | ), |
| 90 | Effect.orElseSucceed(() => null), |
| 91 | ); |
| 92 | |
| 93 | /** The relay URL and environment credential, or null when not linked to T3 Connect. */ |
| 94 | export const readRelayConnection = Effect.all([ |
| 95 | readSecretString(RELAY_URL_SECRET), |
| 96 | readSecretString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), |
| 97 | ]).pipe( |
| 98 | Effect.map(([url, environmentCredential]) => |
| 99 | url && environmentCredential ? { url, environmentCredential } : null, |
| 100 | ), |
| 101 | ); |
| 102 | |
| 103 | /** Whether this environment opted in to T3 Connect holding webhooks while it is offline. */ |
| 104 | export const readHoldWebhooksWhileOffline = readSecretString( |
| 105 | HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET, |
| 106 | ).pipe(Effect.map((value) => value === "true")); |