1import type { DesktopBridge, DesktopTerminalConnection } from "@t3tools/contracts";
2import * as Deferred from "effect/Deferred";
3import * as Effect from "effect/Effect";
4import * as Queue from "effect/Queue";
5import * as Socket from "effect/socket/Socket";
6import type { PreparedConnection } from "../connection/model.ts";
7
8export function desktopTerminalSocket(
9 bridge: Pick<DesktopBridge, "supportsLocalTerminal" | "openLocalTerminal"> | undefined,
10 prepared: PreparedConnection,
11): Socket.Socket | undefined {
12 if (!bridge?.openLocalTerminal || bridge.supportsLocalTerminal?.(prepared) !== true) return;
13 const open = bridge.openLocalTerminal;
14 const connected = Deferred.makeUnsafe<DesktopTerminalConnection>();
15 const closed = () =>
16 new Socket.SocketError({ reason: new Socket.SocketCloseError({ code: 1001 }) });
17 return {
18 [Socket.TypeId]: Socket.TypeId,
19 reader: Effect.gen(function* () {
20 const queue = yield* Queue.unbounded<string, Socket.SocketError>();
21 const run = Effect.runForkWith(yield* Effect.context<never>());
22 yield* Effect.acquireRelease(
23 Effect.sync(() => {
24 const connection = open(
25 { environmentId: prepared.environmentId, url: prepared.socketUrl },
26 (data) => {
27 run(Queue.offer(queue, data));
28 },
29 () => {
30 run(Queue.fail(queue, closed()));
31 },
32 );
33 run(Deferred.succeed(connected, connection));
34 return connection;
35 }),
36 (connection) =>
37 Effect.sync(connection.close).pipe(Effect.andThen(Queue.fail(queue, closed()))),
38 );
39 return {
40 pull: Queue.take(queue).pipe(Effect.map((data) => [data] as const)),
41 upgrade: () =>
42 Effect.fail(new Socket.SocketError({ reason: new Socket.SocketUpgradeError({}) })),
43 };
44 }),
45 writer: Effect.succeed({
46 write: (data) =>
47 Deferred.await(connected).pipe(
48 Effect.flatMap((connection) =>
49 Effect.sync(() => {
50 if (Socket.isCloseEvent(data)) connection.close();
51 else
52 connection.send(typeof data === "string" ? data : new TextDecoder().decode(data));
53 }),
54 ),
55 ),
56 writeAll: (chunks) =>
57 Deferred.await(connected).pipe(
58 Effect.flatMap((connection) =>
59 Effect.sync(() => {
60 for (const data of chunks)
61 connection.send(typeof data === "string" ? data : new TextDecoder().decode(data));
62 }),
63 ),
64 ),
65 }),
66 };
67}