-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathbackendPort.ts
More file actions
83 lines (70 loc) · 2.46 KB
/
backendPort.ts
File metadata and controls
83 lines (70 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import * as Effect from "effect/Effect";
import { NetService } from "@t3tools/shared/Net";
export const DEFAULT_DESKTOP_BACKEND_PORT = 3773;
const MAX_TCP_PORT = 65_535;
export interface ResolveDesktopBackendPortOptions {
readonly host: string;
readonly startPort?: number;
readonly maxPort?: number;
readonly requiredHosts?: ReadonlyArray<string>;
readonly canListenOnHost?: (port: number, host: string) => Promise<boolean>;
}
const defaultCanListenOnHost = async (port: number, host: string): Promise<boolean> =>
Effect.service(NetService).pipe(
Effect.flatMap((net) => net.canListenOnHost(port, host)),
Effect.provide(NetService.layer),
Effect.runPromise,
);
const isValidPort = (port: number): boolean =>
Number.isInteger(port) && port >= 1 && port <= MAX_TCP_PORT;
const normalizeHosts = (
host: string,
requiredHosts: ReadonlyArray<string>,
): ReadonlyArray<string> =>
Array.from(
new Set(
[host, ...requiredHosts]
.map((candidate) => candidate.trim())
.filter((candidate) => candidate.length > 0),
),
);
async function canListenOnAllHosts(
port: number,
hosts: ReadonlyArray<string>,
canListenOnHost: (port: number, host: string) => Promise<boolean>,
): Promise<boolean> {
for (const candidateHost of hosts) {
if (!(await canListenOnHost(port, candidateHost))) {
return false;
}
}
return true;
}
export async function resolveDesktopBackendPort({
host,
startPort = DEFAULT_DESKTOP_BACKEND_PORT,
maxPort = MAX_TCP_PORT,
requiredHosts = [],
canListenOnHost = defaultCanListenOnHost,
}: ResolveDesktopBackendPortOptions): Promise<number> {
if (!isValidPort(startPort)) {
throw new Error(`Invalid desktop backend start port: ${startPort}`);
}
if (!isValidPort(maxPort)) {
throw new Error(`Invalid desktop backend max port: ${maxPort}`);
}
if (maxPort < startPort) {
throw new Error(`Desktop backend max port ${maxPort} is below start port ${startPort}`);
}
const hostsToCheck = normalizeHosts(host, requiredHosts);
// Keep desktop startup predictable across app restarts by probing upward from
// the same preferred port instead of picking a fresh ephemeral port.
for (let port = startPort; port <= maxPort; port += 1) {
if (await canListenOnAllHosts(port, hostsToCheck, canListenOnHost)) {
return port;
}
}
throw new Error(
`No desktop backend port is available on hosts ${hostsToCheck.join(", ")} between ${startPort} and ${maxPort}`,
);
}