| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Redacted from "effect/Redacted"; |
| 3 | import * as HttpClient from "effect/http/HttpClient"; |
| 4 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; |
| 5 | import { makeFetchRpcStub } from "../../Rpc.ts"; |
| 6 | |
| 7 | /** |
| 8 | * A running MicroVM's connection details, as returned by the {@link RunMicrovm} |
| 9 | * and {@link CreateAuthToken} bindings. |
| 10 | */ |
| 11 | export interface MicrovmConnection { |
| 12 | /** |
| 13 | * The MicroVM endpoint hostname (no scheme), e.g. |
| 14 | * `<id>.lambda-microvm.<region>.on.aws` — from `RunMicrovm`'s response. |
| 15 | */ |
| 16 | endpoint: string; |
| 17 | /** |
| 18 | * The auth token from `CreateAuthToken` — a map of header name → value that |
| 19 | * authorizes requests to the MicroVM endpoint (the AWS proxy validates them). |
| 20 | * Values may be {@link Redacted.Redacted}. |
| 21 | */ |
| 22 | authToken: Record<string, string | Redacted.Redacted<string> | undefined>; |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * Flatten a MicroVM `authToken` (from {@link CreateAuthToken}) into a plain |
| 27 | * header map, unwrapping any {@link Redacted.Redacted} values. Send these as |
| 28 | * request headers when calling the MicroVM endpoint. |
| 29 | */ |
| 30 | export const microvmAuthHeaders = ( |
| 31 | authToken: MicrovmConnection["authToken"], |
| 32 | ): Record<string, string> => { |
| 33 | const headers: Record<string, string> = {}; |
| 34 | for (const [key, value] of Object.entries(authToken)) { |
| 35 | if (value === undefined) continue; |
| 36 | headers[key] = Redacted.isRedacted(value) ? Redacted.value(value) : value; |
| 37 | } |
| 38 | return headers; |
| 39 | }; |
| 40 | |
| 41 | /** |
| 42 | * Connect to a running MicroVM's in-VM RPC server and return a typed client |
| 43 | * stub. The stub mirrors the image's tagged RPC `Shape`: value methods |
| 44 | * `yield*` as `Effect`s, streaming methods pipe as `Stream`s. Each call is a |
| 45 | * `POST https://<endpoint>/__rpc__/<method>` with the auth-token entries set as |
| 46 | * request headers — the mirror of {@link serveRpc} running inside the MicroVM. |
| 47 | * |
| 48 | * @example |
| 49 | * ```typescript |
| 50 | * const vm = yield* runMicrovm({}); |
| 51 | * const { authToken } = yield* createAuthToken({ |
| 52 | * microvmIdentifier: vm.microvmId, |
| 53 | * expirationInMinutes: 5, |
| 54 | * allowedPorts: [{ port: 8080 }], |
| 55 | * }); |
| 56 | * const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, { |
| 57 | * endpoint: vm.endpoint, |
| 58 | * authToken, |
| 59 | * }); |
| 60 | * const reply = yield* sandbox.hello("world"); |
| 61 | * ``` |
| 62 | */ |
| 63 | export const connectMicrovm = <S>( |
| 64 | _image: abstract new (_: never) => S, |
| 65 | connection: MicrovmConnection, |
| 66 | ): Effect.Effect<S, never, HttpClient.HttpClient> => |
| 67 | Effect.gen(function* () { |
| 68 | const client = yield* HttpClient.HttpClient; |
| 69 | const headers = microvmAuthHeaders(connection.authToken); |
| 70 | return makeFetchRpcStub<S>({ |
| 71 | baseUrl: `https://${connection.endpoint}`, |
| 72 | fetch: (request) => |
| 73 | client.execute(request.pipe(HttpClientRequest.setHeaders(headers))), |
| 74 | }); |
| 75 | }); |