Skip to content

Commit 9e12fbe

Browse files
authored
feat: add unix domain socket utils (#110)
1 parent 20d20ec commit 9e12fbe

File tree

4 files changed

+114
-0
lines changed

4 files changed

+114
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
checkPort,
2323
getRandomPort,
2424
waitForPort,
25+
getSocketAddress,
26+
isSocketSupported,
27+
cleanSocket
2528
} from "get-port-please";
2629

2730
// CommonJS
@@ -30,6 +33,9 @@ const {
3033
checkPort,
3134
getRandomPort,
3235
waitForPort,
36+
getSocketAddress,
37+
isSocketSupported,
38+
cleanSocket
3339
} = require("get-port-please");
3440
```
3541

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./types";
22
export * from "./get-port";
33
export * from "./unsafe-ports";
4+
export * from "./socket";

src/socket.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { tmpdir } from "node:os";
2+
import { join } from "node:path";
3+
import { Server } from "node:net";
4+
import { rm } from "node:fs/promises";
5+
6+
export interface GetSocketOptions {
7+
/* Human readable prefix for the socket name */
8+
name: string;
9+
10+
/**
11+
* Use process ID in the socket name.
12+
*/
13+
pid?: boolean;
14+
15+
/**
16+
* Use a random number in the socket name.
17+
*
18+
*/
19+
random?: boolean;
20+
}
21+
22+
let _nodeMajorVersion: number | undefined;
23+
let _isSocketSupported: boolean | undefined;
24+
25+
/**
26+
* Generates a socket address based on the provided options.
27+
*/
28+
export function getSocketAddress(opts: GetSocketOptions): string {
29+
const parts = [
30+
opts.name,
31+
opts.pid ? process.pid : undefined,
32+
opts.random ? Math.round(Math.random() * 10_000) : undefined,
33+
].filter(Boolean);
34+
35+
const socketName = `${parts.join("-")}.sock`;
36+
37+
// Windows: pipe
38+
if (process.platform === "win32") {
39+
return join(String.raw`\\.\pipe`, socketName);
40+
}
41+
42+
// Linux: abstract namespace
43+
// Fallback to a regular file socket on older Node.js versions to avoid issues
44+
if (process.platform === "linux") {
45+
if (_nodeMajorVersion === undefined) {
46+
_nodeMajorVersion = +process.versions.node.split(".")[0];
47+
}
48+
if (_nodeMajorVersion >= 20) {
49+
return `\0${socketName}`;
50+
}
51+
}
52+
53+
// Unix socket
54+
return join(tmpdir(), socketName);
55+
}
56+
57+
/**
58+
* Test if the current environment supports sockets.
59+
*/
60+
export async function isSocketSupported(): Promise<boolean> {
61+
if (_isSocketSupported !== undefined) {
62+
return _isSocketSupported;
63+
}
64+
if (globalThis.process?.versions?.webcontainer) {
65+
// Seems broken: https://stackblitz.com/edit/stackblitz-starters-1y68uhvu
66+
return false;
67+
}
68+
69+
const socketAddress = getSocketAddress({ name: "get-port", random: true });
70+
const server = new Server();
71+
try {
72+
await new Promise<void>((resolve, reject) => {
73+
server.on("error", reject);
74+
server.listen(socketAddress, resolve);
75+
});
76+
_isSocketSupported = true;
77+
return true;
78+
} catch {
79+
_isSocketSupported = false;
80+
return false;
81+
} finally {
82+
if (server.listening) {
83+
server.close();
84+
await cleanSocket(socketAddress);
85+
}
86+
}
87+
}
88+
89+
export async function cleanSocket(path: string): Promise<void> {
90+
if (!path || path[0] === "\0" || path.startsWith(String.raw`\\.\pipe`)) {
91+
// Abstract namespace sockets or invalid paths
92+
return;
93+
}
94+
return rm(path, { force: true, recursive: true }).catch(console.error);
95+
}

test/socket.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe, test, expect } from "vitest";
2+
import { isSocketSupported, getSocketAddress } from "../src";
3+
4+
describe("socket", () => {
5+
test("isSocketSupported", async () => {
6+
expect(await isSocketSupported()).toBe(true);
7+
});
8+
test("getSocketAddress", async () => {
9+
const addr = getSocketAddress({ name: "test", pid: true, random: true });
10+
expect(addr).toMatch(/test-\d+-\d+\.sock/);
11+
});
12+
});

0 commit comments

Comments
 (0)