-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathnet.ts
More file actions
244 lines (218 loc) · 6.85 KB
/
net.ts
File metadata and controls
244 lines (218 loc) · 6.85 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import chalk from "chalk";
import getPort from "get-port";
import net from "net";
import ora from "ora";
import waitOn from "wait-on";
import { confirmChooseRandomPort } from "../prompts";
import { logger } from "./logger";
const VALID_PORT_MIN = 1024;
const VALID_PORT_MAX = 65535;
/**
* Check if a given remote address and port are accepting TCP connections.
* @param Object host and port of the server to check.
* @returns The resolved port number if the given server does not accept TCP connections. 0 if the port is already taken.
*/
export async function isAcceptingTcpConnections({ host, port }: { host?: string; port?: number }): Promise<number> {
port = Number(port) as number;
logger.silly(`Checking if ${host}:${port} is accepting TCP connections...`);
// Check if current port is beyond the MAX valid port range
if (port > VALID_PORT_MAX) {
logger.silly(`Port ${port} is beyond the valid port range (${VALID_PORT_MAX}).`);
return 0;
}
return new Promise((resolve) => {
const socket = net.createConnection(port as number, host);
socket
.once("error", () => {
socket.end();
resolve(port as number);
})
.once("connect", async () => {
resolve(0);
socket.end();
});
});
}
/**
* Ask if the user wants to use a new port number, and if yes return the new port number.
* @returns A new port number if the user accepts or 0 if he refuses.
*/
export async function askNewPort(): Promise<number> {
const confirm = await confirmChooseRandomPort(true);
return confirm ? getPort() : 0;
}
/**
* Check if a given URL string is a valid URL.
* @param url The URL string to check.
* @returns True if the URL string is a valid URL. False otherwise.
*/
export function isHttpUrl(url: string | undefined) {
if (!url) {
return false;
}
try {
const uri = new URL(url);
return uri.protocol.startsWith("http") || uri.protocol.startsWith("ws");
} catch {
return false;
}
}
/**
* Check if a given URL string is a valid https URL.
* @param url The URL string to check.
* @returns True if the URL string is a valid https URL. False otherwise.
*/
export function isHttpsUrl(url: string | undefined) {
if (!url) {
return false;
}
try {
const uri = new URL(url);
return uri.protocol.startsWith("https");
} catch {
return false;
}
}
/**
* Checks if a given server is up and accepting connection.
* @param url An HTTP URL.
* @param timeout Maximum time in ms to wait before exiting with failure (1) code,
default Infinity.
*/
export async function validateDevServerConfig(url: string | undefined, timeout: number | undefined) {
logger.silly(`Validating dev server config:`);
logger.silly({
url,
timeout,
});
let { hostname, port } = parseUrl(url);
try {
const resolvedPortNumber = await isAcceptingTcpConnections({ port, host: hostname });
if (resolvedPortNumber !== 0) {
const spinner = ora();
const waitOnOneOfResources = hostname === "localhost" ? [`tcp:127.0.0.1:${port}`, `tcp:[::1]:${port}`] : [`tcp:${hostname}:${port}`];
spinner.start(`Waiting for ${chalk.green(url)} to be ready`);
let promises = waitOnOneOfResources.map((resource) => {
return waitOn({
resources: [resource],
delay: 1000, // initial delay in ms, default 0
interval: 100, // poll interval in ms, default 250ms
simultaneous: 1, // limit to 1 connection per resource at a time
timeout: timeout ? timeout * 1000 : timeout, // timeout in ms, default Infinity
tcpTimeout: 1000, // tcp timeout in ms, default 300ms
window: 1000, // stabilization time in ms, default 750ms
strictSSL: false,
verbose: false, // force disable verbose logs even if SWA_CLI_DEBUG is enabled
})
.then(() => {
logger.silly(`Connected to ${resource} successfully`);
return resource;
})
.catch((err) => {
logger.silly(`Could not connect to ${resource}`);
throw err;
});
});
try {
await Promise.any(promises);
spinner.succeed(`Connected to ${url} successfully`);
spinner.clear();
} catch {
spinner.fail();
logger.error(`Could not connect to "${url}". Is the server up and running?`, true);
}
}
} catch (err) {
if ((err as any).message.includes("EACCES")) {
logger.error(
`Port "${port}" cannot be used. You might need elevated or admin privileges. Or, use a valid port from ${VALID_PORT_MIN} to ${VALID_PORT_MAX}.`,
true
);
} else {
logger.error((err as any).message, true);
}
}
}
/**
* Parse a given URL and return its protocol, port, host and hostname.
* @param url The URL string to check.
* @returns Protocol, port, host and hostname extracted from the URL.
*/
export function parseUrl(url: string | undefined) {
if (!url) {
throw new Error(`Address: ${url} is malformed!`);
}
let { protocol, port, host, hostname } = new URL(url);
if (port === "") {
switch (protocol) {
case "http:":
port = "80";
break;
case "https:":
port = "443";
break;
}
}
return {
protocol,
port: Number(port),
host,
hostname,
};
}
/**
* Construct a valid URL string from a host, port and protocol.
* @param host A host address.
* @param port (optional) A host port.
* @param protocol (optional) A host protocol.
* @throws {Error} if the URL is malformed.
* @returns
*/
export function address(host: string | undefined, port: number | string = 80, protocol = `http`) {
if (!host) {
throw new Error(`Host value is not set`);
}
let url = port === 80 ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
if (isHttpUrl(url)) {
return url;
} else {
throw new Error(`Address: ${url} is malformed!`);
}
}
export function response({ status, headers, cookies, body = "" }: ResponseOptions) {
if (typeof status !== "number") {
throw Error("TypeError: status code must be a number.");
}
body = body || null;
const res = {
status,
cookies,
headers: {
status,
"Content-Type": "application/json",
...headers,
},
body,
};
return res;
}
export function parsePort(port: string) {
const portNumber = parseInt(port, 10);
if (isNaN(portNumber)) {
logger.error(`Port "${port}" is not a number.`, true);
} else {
if (portNumber < VALID_PORT_MIN || portNumber > VALID_PORT_MAX) {
logger.error(`Port "${port}" is out of range. Valid ports are from ${VALID_PORT_MIN} to ${VALID_PORT_MAX}.`, true);
}
}
return portNumber;
}
export function hostnameToIpAdress(hostnameOrIpAddress: string | undefined) {
if (hostnameOrIpAddress === "localhost") {
return "127.0.0.1";
}
return hostnameOrIpAddress;
}
export function isValidIpAddress(ip: string) {
return net.isIP(ip);
}