diff --git a/packages/ide/src/fill/net.ts b/packages/ide/src/fill/net.ts index 7a8e5b7a..4f3a41b4 100644 --- a/packages/ide/src/fill/net.ts +++ b/packages/ide/src/fill/net.ts @@ -97,9 +97,7 @@ class Server extends CallbackEmitter implements net.Server { const { maybeCallback, bindSocket, createUniqueEval } = __non_webpack_require__("@coder/ide/src/fill/evaluation") as typeof import("@coder/ide/src/fill/evaluation"); let connectionId = 0; - let server = new net.Server(options, maybeCallback(ae, callbackId)); const sockets = new Map(); - const storeSocket = (socket: net.Socket): number => { const socketId = connectionId++; sockets.set(socketId, socket); @@ -112,13 +110,18 @@ class Server extends CallbackEmitter implements net.Server { return socketId; }; + const callback = maybeCallback(ae, callbackId); + let server = new net.Server(options, typeof callback !== "undefined" ? (socket): void => { + callback(storeSocket(socket)); + } : undefined); + server.on("close", () => ae.emit("close")); server.on("connection", (socket) => ae.emit("connection", storeSocket(socket))); server.on("error", (error) => ae.emit("error", error)); server.on("listening", () => ae.emit("listening")); - ae.on("close", (callbackId) => server.close(maybeCallback(ae, callbackId))); - ae.on("listen", (handle) => server.listen(handle)); + ae.on("close", (callbackId: number) => server.close(maybeCallback(ae, callbackId))); + ae.on("listen", (handle?: net.ListenOptions | number | string) => server.listen(handle)); ae.on("ref", () => server.ref()); ae.on("unref", () => server.unref()); @@ -136,7 +139,7 @@ class Server extends CallbackEmitter implements net.Server { sockets.clear(); }, }; - }, options, this.storeCallback(connectionListener)); + }, options || {}, this.storeCallback(connectionListener)); this.ae.on("close", () => { this._listening = false; diff --git a/packages/ide/test/net.test.ts b/packages/ide/test/net.test.ts new file mode 100644 index 00000000..fd208831 --- /dev/null +++ b/packages/ide/test/net.test.ts @@ -0,0 +1,130 @@ +import * as fs from "fs"; +import * as nativeNet from "net"; +import * as os from "os"; +import * as path from "path"; +import * as util from "util"; +import * as rimraf from "rimraf"; +import { createClient } from "@coder/protocol/test"; + +const client = createClient(); +jest.mock("../src/fill/client", () => ({ client })); +const net = require("../src/fill/net") as typeof import("net"); + +describe("net", () => { + let i = 0; + const coderDir = path.join(os.tmpdir(), "coder"); + const tmpFile = (): string => path.join(coderDir, `socket.${i++}`); + + beforeAll(async () => { + await util.promisify(rimraf)(coderDir); + await util.promisify(fs.mkdir)(coderDir); + }); + + describe("Socket", () => { + const socketPath = tmpFile(); + let server: nativeNet.Server; + + beforeAll(async () => { + await new Promise((r): void => { + server = nativeNet.createServer().listen(socketPath, r); + }); + }); + + afterAll(() => { + server.close(); + }); + + it("should connect", async () => { + await new Promise((resolve): void => { + const socket = net.createConnection(socketPath, () => { + socket.end(); + socket.addListener("close", () => { + resolve(); + }); + }); + }); + + await new Promise((resolve): void => { + const socket = new net.Socket(); + socket.connect(socketPath, () => { + socket.end(); + socket.addListener("close", () => { + resolve(); + }); + }); + }); + }); + + it("should get data", (done) => { + server.once("connection", (socket: nativeNet.Socket) => { + socket.write("hi how r u"); + }); + + const socket = net.createConnection(socketPath); + + socket.addListener("data", (data) => { + expect(data.toString()).toEqual("hi how r u"); + socket.end(); + socket.addListener("close", () => { + done(); + }); + }); + }); + + it("should send data", (done) => { + const clientSocket = net.createConnection(socketPath); + clientSocket.write(Buffer.from("bananas")); + server.once("connection", (socket: nativeNet.Socket) => { + socket.addListener("data", (data) => { + expect(data.toString()).toEqual("bananas"); + socket.end(); + clientSocket.addListener("end", () => { + done(); + }); + }); + }); + }); + }); + + describe("Server", () => { + it("should listen", (done) => { + const s = net.createServer(); + s.on("listening", () => s.close()); + s.on("close", () => done()); + s.listen(tmpFile()); + }); + + it("should get connection", async () => { + let constructorListener: (() => void) | undefined; + const s = net.createServer(() => { + if (constructorListener) { + constructorListener(); + } + }); + + const socketPath = tmpFile(); + s.listen(socketPath); + + const makeConnection = async (): Promise => { + net.createConnection(socketPath); + await Promise.all([ + new Promise((resolve): void => { + constructorListener = resolve; + }), + new Promise((resolve): void => { + s.once("connection", (socket) => { + socket.destroy(); + resolve(); + }); + }), + ]); + }; + + await makeConnection(); + await makeConnection(); + + s.close(); + await new Promise((r): nativeNet.Server => s.on("close", r)); + }); + }); +});