| 1 | import * as DateTime from "effect/DateTime"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Option from "effect/Option"; |
| 4 | import * as SubscriptionRef from "effect/SubscriptionRef"; |
| 5 | import { HttpClient } from "effect/http"; |
| 6 | |
| 7 | import { issueRemoteWebSocketTicket } from "../authorization/remote.ts"; |
| 8 | import * as EnvironmentSupervisor from "../connection/supervisor.ts"; |
| 9 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; |
| 10 | import { EnvironmentRpcUnavailableError } from "../rpc/client.ts"; |
| 11 | |
| 12 | export const authorizeHubSignedUrl = Effect.fn("clientRuntime.state.authorizeHubSignedUrl")( |
| 13 | function* <A extends { readonly relativeUrl: string; readonly expiresAt: number }>(result: A) { |
| 14 | const supervisor = yield* EnvironmentSupervisor.EnvironmentSupervisor; |
| 15 | const prepared = Option.getOrNull(yield* SubscriptionRef.get(supervisor.prepared)); |
| 16 | const target = prepared?.target ?? supervisor.target; |
| 17 | if (target._tag !== "BearerConnectionTarget" || !target.connectionId.startsWith("hub:")) { |
| 18 | return result; |
| 19 | } |
| 20 | |
| 21 | const unavailable = () => |
| 22 | new EnvironmentRpcUnavailableError({ |
| 23 | environmentId: target.environmentId, |
| 24 | message: "Reconnect to this server to access its files.", |
| 25 | }); |
| 26 | if (prepared?.httpAuthorization?._tag !== "Bearer") return yield* unavailable(); |
| 27 | |
| 28 | const client = yield* Effect.serviceOption(HttpClient.HttpClient); |
| 29 | if (Option.isNone(client)) return yield* unavailable(); |
| 30 | |
| 31 | const ticket = yield* issueRemoteWebSocketTicket({ |
| 32 | httpBaseUrl: prepared.httpBaseUrl, |
| 33 | bearerToken: prepared.httpAuthorization.token, |
| 34 | }).pipe( |
| 35 | Effect.provideService(HttpClient.HttpClient, client.value), |
| 36 | Effect.mapError(unavailable), |
| 37 | ); |
| 38 | const base = new URL(prepared.httpBaseUrl); |
| 39 | const url = new URL(environmentEndpointUrl(prepared.httpBaseUrl, result.relativeUrl)); |
| 40 | if (url.origin !== base.origin || !url.pathname.startsWith(base.pathname)) |
| 41 | return yield* unavailable(); |
| 42 | url.searchParams.set("wsTicket", ticket.ticket); |
| 43 | |
| 44 | return { |
| 45 | ...result, |
| 46 | relativeUrl: url.href, |
| 47 | expiresAt: Math.min(result.expiresAt, DateTime.toEpochMillis(ticket.expiresAt)), |
| 48 | }; |
| 49 | }, |
| 50 | ); |