1import { expect, it } from "@effect/vitest";
2import { NodeWS } from "@effect/platform-node/NodeSocket";
3import * as Context from "effect/Context";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpRouter, HttpServer } from "effect/http";
7import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
8import {
9 AuthOrchestrationOperateScope,
10 AuthSessionId,
11 LOCAL_DEVICE_HOST_ID,
12} from "@t3tools/contracts";
13import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
14import * as DeviceService from "./DeviceService.ts";
15import * as DeviceHubProxy from "./DeviceHubProxy.ts";
16
17it.effect("preserves binary input and admission frames through the real socket proxy", () =>
18 Effect.gen(function* () {
19 const hub = yield* Effect.acquireRelease(
20 Effect.sync(() => new NodeWS.WebSocketServer({ port: 0 })),
21 (server) =>
22 Effect.promise(
23 () =>
24 new Promise<void>((resolve) => {
25 for (const client of server.clients) client.terminate();
26 server.close(() => resolve());
27 }),
28 ),
29 );
30 if (!hub.address())
31 yield* Effect.promise(() => new Promise<void>((resolve) => hub.once("listening", resolve)));
32 const address = hub.address();
33 if (typeof address !== "object" || !address) throw new Error("Missing hub port");
34 hub.on("connection", (socket) => {
35 socket.send(Uint8Array.of(0x83));
36 socket.on("message", (data, binary) => socket.send(data, { binary }));
37 });
38 const services = yield* Layer.build(
39 HttpRouter.serve(DeviceHubProxy.layer, { disableListenLog: true }).pipe(
40 Layer.provideMerge(NodeHttpServer.layerTest),
41 Layer.provide(
42 Layer.succeed(EnvironmentAuth.EnvironmentAuth, {
43 authenticateWebSocketUpgrade: () =>
44 Effect.succeed({
45 sessionId: AuthSessionId.make("test"),
46 subject: "test",
47 method: "bearer-access-token",
48 scopes: [AuthOrchestrationOperateScope],
49 }),
50 } as unknown as EnvironmentAuth.EnvironmentAuth["Service"]),
51 ),
52 Layer.provide(
53 Layer.succeed(DeviceService.DeviceService, {
54 currentReadiness: () =>
55 Effect.succeed({
56 hostId: LOCAL_DEVICE_HOST_ID,
57 hub: { origin: `http://127.0.0.1:${address.port}` },
58 }),
59 } as DeviceService.DeviceService["Service"]),
60 ),
61 ),
62 );
63 const origin = HttpServer.formatAddress(
64 Context.get(services, HttpServer.HttpServer).address,
65 ).replace(/^http/, "ws");
66 const client = yield* Effect.acquireRelease(
67 Effect.sync(
68 () =>
69 new WebSocket(
70 `${origin}/api/device-hub/vendor/serve-sim/helper/ws?device=test&wsTicket=test`,
71 ),
72 ),
73 (socket) => Effect.sync(() => socket.close()),
74 );
75 client.binaryType = "arraybuffer";
76 const frames: number[][] = [];
77 const received = Promise.withResolvers<void>();
78 client.addEventListener("error", () => received.reject(new Error("Proxy failed")));
79 client.addEventListener("message", ({ data }) => {
80 frames.push(Array.from(new Uint8Array(data as ArrayBuffer)));
81 if (frames.length === 1) {
82 client.send(Uint8Array.of(0x03, 1));
83 client.send(Uint8Array.of(0x03, 2));
84 } else if (frames.length === 3) received.resolve();
85 });
86 yield* Effect.promise(() => received.promise);
87 expect(frames).toEqual([[0x83], [0x03, 1], [0x03, 2]]);
88 }),
89);