1import * as DateTime from "effect/DateTime";
2import * as Effect from "effect/Effect";
3import * as Option from "effect/Option";
4import * as SubscriptionRef from "effect/SubscriptionRef";
5import { HttpClient } from "effect/http";
6
7import { issueRemoteWebSocketTicket } from "../authorization/remote.ts";
8import * as EnvironmentSupervisor from "../connection/supervisor.ts";
9import { environmentEndpointUrl } from "../environment/endpoint.ts";
10import { EnvironmentRpcUnavailableError } from "../rpc/client.ts";
11
12export 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);