1import { Credentials } from "@distilled.cloud/fly-io/Credentials";
2import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
3import * as NodeHttpServerRequest from "@effect/platform-node/NodeHttpServerRequest";
4import * as Effect from "effect/Effect";
5import * as Redacted from "effect/Redacted";
6import * as Ref from "effect/Ref";
7import * as HttpClient from "effect/http/HttpClient";
8import * as HttpClientRequest from "effect/http/HttpClientRequest";
9import { HttpServerRequest } from "effect/http/HttpServerRequest";
10import * as HttpServerResponse from "effect/http/HttpServerResponse";
11import { createServer } from "node:http";
12
13export const dropCompletedCreate = Effect.fn(function* (appName: string) {
14 const resolveCredentials = yield* Credentials;
15 const config = yield* resolveCredentials;
16 const client = yield* HttpClient.HttpClient;
17 const forwarded = yield* Ref.make(0);
18 const completedStatus = yield* Ref.make<number | undefined>(undefined);
19 const route = `/v1/apps/${appName}/machines`;
20 const authorization = `Bearer ${Redacted.value(config.apiKey)}`;
21 const raw = yield* Effect.sync(() => createServer());
22 const server = yield* NodeHttpServer.make(() => raw, {
23 host: "127.0.0.1",
24 port: 0,
25 gracefulShutdownTimeout: "1 second",
26 });
27 if (server.address._tag !== "InetAddressV4") {
28 return yield* Effect.fail(new Error("Probe proxy must bind IPv4 loopback"));
29 }
30 yield* server.serve(
31 Effect.gen(function* () {
32 const request = yield* HttpServerRequest;
33 if (
34 request.method !== "POST" ||
35 request.url !== route ||
36 request.headers.authorization !== authorization
37 ) {
38 yield* Effect.sync(() =>
39 NodeHttpServerRequest.toIncomingMessage(request).socket.destroy(),
40 );
41 return HttpServerResponse.empty();
42 }
43 const body = yield* request.text;
44 yield* Ref.update(forwarded, (count) => count + 1);
45 const response = yield* client.execute(
46 HttpClientRequest.post(`${config.apiBaseUrl}${route}`).pipe(
47 HttpClientRequest.setHeader("authorization", authorization),
48 HttpClientRequest.bodyText(body, "application/json"),
49 ),
50 );
51 const actualBody = yield* response.text;
52 yield* Ref.set(completedStatus, response.status);
53 if (response.status >= 200 && response.status < 300) {
54 // Drop the actual completed response, not a synthesized API failure.
55 yield* Effect.sync(() =>
56 NodeHttpServerRequest.toIncomingMessage(request).socket.destroy(),
57 );
58 return HttpServerResponse.empty();
59 }
60 return HttpServerResponse.text(actualBody, {
61 status: response.status,
62 contentType: response.headers["content-type"] ?? "application/json",
63 });
64 }),
65 );
66 return {
67 credentials: Effect.succeed({
68 ...config,
69 apiBaseUrl: `http://127.0.0.1:${server.address.port}`,
70 }),
71 forwarded: Ref.get(forwarded),
72 completedStatus: Ref.get(completedStatus),
73 };
74});