| 1 | import { expect, it } from "@effect/vitest"; |
| 2 | import { NodeWS } from "@effect/platform-node/NodeSocket"; |
| 3 | import * as Context from "effect/Context"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; |
| 6 | import { HttpRouter, HttpServer } from "effect/http"; |
| 7 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 8 | import { |
| 9 | AuthOrchestrationOperateScope, |
| 10 | AuthSessionId, |
| 11 | LOCAL_DEVICE_HOST_ID, |
| 12 | } from "@t3tools/contracts"; |
| 13 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 14 | import * as DeviceService from "./DeviceService.ts"; |
| 15 | import * as DeviceHubProxy from "./DeviceHubProxy.ts"; |
| 16 | |
| 17 | it.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 | ); |