diff --git a/bun.lockb b/bun.lockb index a4739f5..ec02b3d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 313092e..7ffc6c9 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ }, "dependencies": { "hono": "^4.6.9", - "id": "^0.0.0", + "ip": "^2.0.1", "zod": "^3.23.8" }, "devDependencies": { "@types/bun": "latest", "@types/ip": "^1.1.3" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 090c9ca..f3269cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,11 @@ import { Hono } from 'hono' -import {getConnInfo} from "hono/bun" -import connectToAddress from './utils/connect-to-address' +import { portRoute } from './routes/port' const app = new Hono() -app.get('/:port', async context => { - const port = Number(context.req.param("port")) - const info = getConnInfo(context) - const ipAddress = info.remote.address! - - const result = await connectToAddress(ipAddress, port) - - return context.json({ - isOpen: result.isOpen - }) -}) +app.route( + "/", + portRoute, +) export default app diff --git a/src/routes/port.ts b/src/routes/port.ts index 21f5536..543c790 100644 --- a/src/routes/port.ts +++ b/src/routes/port.ts @@ -7,8 +7,8 @@ import * as IP from "ip" export const portRoute = new Hono(); const schema = z.object({ - ip: z.string().refine(ip => !IP.isPublic(ip), "This IP address is not valid"), - port: z.coerce.number().min(1).max(2**16 - 1), + ip: z.string().refine(ip => IP.isPublic(ip), "This IP address is not valid"), + port: z.string().transform(Number).pipe(z.number().min(1).max(2**16 - 1)), }); portRoute.get("/:port", async context => { @@ -31,3 +31,23 @@ portRoute.get("/:port", async context => { isOpen: result.isOpen }) }); + +portRoute.get("/:ip/:port", async context => { + const rawData = { + ip: context.req.param("ip"), + port: context.req.param("port"), + }; + const parsedData = schema.safeParse(rawData); + + if (!parsedData.success) { + return context.json({ error: parsedData.error }, 401); + } + + const { ip, port } = parsedData.data; + + const result = await connectToAddress(ip, port) + + return context.json({ + isOpen: result.isOpen + }) +}) diff --git a/src/utils/connect-to-address.ts b/src/utils/connect-to-address.ts index 078df85..1434bbc 100644 --- a/src/utils/connect-to-address.ts +++ b/src/utils/connect-to-address.ts @@ -1,43 +1,54 @@ +import * as net from "net" + export interface ConnectionResult { isOpen: boolean } -const TIMEOUT = 5 +const TIMEOUT = 5_000 export default function connectToAddress(address: string, port: number): Promise { return new Promise(async (resolve) => { - const socket = await Bun.connect({ - hostname: address, - port: port, - - socket: { - open(socket) { - socket.end() - resolve({ - isOpen: true - }) - }, - data() { - }, - connectError() { - resolve({ - isOpen: false - }) - }, - error(socket) { - socket.end() - resolve({ - isOpen: false - }) - }, - timeout() { - resolve({ - isOpen: false - }) - }, + const onIsOpen = () => { + if (!socket.destroyed) { + socket.end() + socket.destroy() } - }) - socket.timeout(TIMEOUT) + resolve({ + isOpen: true + }) + } + const onIsClosed = () => { + if (!socket.destroyed) { + socket.end() + socket.destroy() + } + + resolve({ + isOpen: false + }) + } + + const socket = new net.Socket() + + // The `setTimeout` function from a socket does not work when connecting, + // so we need to use a custom timeout function ourselves. + setTimeout(() => { + if (!socket.destroyed) { + socket.end() + socket.destroy() + } + + resolve({ + isOpen: false + }) + }, TIMEOUT) + + socket.on("ready", onIsOpen) + socket.on("close", onIsClosed) + socket.on("error", onIsClosed) + socket.on("timeout", onIsClosed) + + socket.connect(port, address) }) } diff --git a/src/utils/timeout.ts b/src/utils/timeout.ts new file mode 100644 index 0000000..9ec9b47 --- /dev/null +++ b/src/utils/timeout.ts @@ -0,0 +1,4 @@ +export default function timeout(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} +