@builder.io/ai-utils 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -1
- package/src/connectivity/browser.d.ts +7 -0
- package/src/connectivity/browser.js +5 -0
- package/src/connectivity/checks/dns-check.d.ts +8 -0
- package/src/connectivity/checks/dns-check.js +96 -0
- package/src/connectivity/checks/http-check.d.ts +8 -0
- package/src/connectivity/checks/http-check.js +81 -0
- package/src/connectivity/checks/ssh-check.d.ts +9 -0
- package/src/connectivity/checks/ssh-check.js +108 -0
- package/src/connectivity/checks/tcp-check.d.ts +9 -0
- package/src/connectivity/checks/tcp-check.js +80 -0
- package/src/connectivity/checks/tls-check.d.ts +9 -0
- package/src/connectivity/checks/tls-check.js +113 -0
- package/src/connectivity/environment.d.ts +6 -0
- package/src/connectivity/environment.js +27 -0
- package/src/connectivity/error-codes.d.ts +21 -0
- package/src/connectivity/error-codes.js +167 -0
- package/src/connectivity/node.d.ts +15 -0
- package/src/connectivity/node.js +9 -0
- package/src/connectivity/run-checks.browser.d.ts +2 -0
- package/src/connectivity/run-checks.browser.js +103 -0
- package/src/connectivity/run-checks.d.ts +2 -0
- package/src/connectivity/run-checks.js +134 -0
- package/src/connectivity/targets.d.ts +6 -0
- package/src/connectivity/targets.js +52 -0
- package/src/connectivity/types.d.ts +66 -0
- package/src/connectivity/types.js +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@builder.io/ai-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Builder.io AI utils",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./src/index.js",
|
|
11
11
|
"types": "./src/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./connectivity/node": {
|
|
14
|
+
"import": "./src/connectivity/node.js",
|
|
15
|
+
"types": "./src/connectivity/node.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./connectivity/browser": {
|
|
18
|
+
"import": "./src/connectivity/browser.js",
|
|
19
|
+
"types": "./src/connectivity/browser.d.ts"
|
|
12
20
|
}
|
|
13
21
|
},
|
|
14
22
|
"files": [
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { Source, TestId, Test, RunChecksInput, ProgressEvent, CheckResult, CheckReport, ConnectivityErrorCode, CheckType, Recommendation, LikelyCause, ConnectivityStatus, AnalysisResult, AnalyzeConnectivityInput, } from "./types.js";
|
|
2
|
+
export { runChecks } from "./run-checks.browser.js";
|
|
3
|
+
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
4
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
5
|
+
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
6
|
+
export { httpCheck } from "./checks/http-check.js";
|
|
7
|
+
export type { HttpCheckOptions } from "./checks/http-check.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { runChecks } from "./run-checks.browser.js";
|
|
2
|
+
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
3
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
4
|
+
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
5
|
+
export { httpCheck } from "./checks/http-check.js";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import dns from "dns";
|
|
2
|
+
import { mapNodeErrorToConnectivityCode } from "../error-codes.js";
|
|
3
|
+
const { resolve4, resolve6 } = dns.promises;
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
5
|
+
export async function dnsCheck(options) {
|
|
6
|
+
const { hostname, source, testId, timeout = DEFAULT_TIMEOUT_MS } = options;
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
let timeoutId;
|
|
9
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
10
|
+
timeoutId = setTimeout(() => {
|
|
11
|
+
reject(new Error("DNS resolution timed out"));
|
|
12
|
+
}, timeout);
|
|
13
|
+
});
|
|
14
|
+
try {
|
|
15
|
+
let addresses = [];
|
|
16
|
+
let ipVersion = "IPv4";
|
|
17
|
+
try {
|
|
18
|
+
addresses = await Promise.race([resolve4(hostname), timeoutPromise]);
|
|
19
|
+
}
|
|
20
|
+
catch (ipv4Error) {
|
|
21
|
+
// If IPv4 fails with ENODATA/ENOTFOUND, try IPv6 before giving up
|
|
22
|
+
const err = ipv4Error;
|
|
23
|
+
if (err.code === "ENODATA" || err.code === "ENOTFOUND") {
|
|
24
|
+
try {
|
|
25
|
+
addresses = await Promise.race([resolve6(hostname), timeoutPromise]);
|
|
26
|
+
ipVersion = "IPv6";
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
throw ipv4Error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw ipv4Error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
const durationMs = Date.now() - startTime;
|
|
38
|
+
if (addresses.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
source,
|
|
41
|
+
testId,
|
|
42
|
+
target: hostname,
|
|
43
|
+
passed: false,
|
|
44
|
+
errorCode: "dns_resolution_failed",
|
|
45
|
+
durationMs,
|
|
46
|
+
metadata: {
|
|
47
|
+
error: "No addresses resolved",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
source,
|
|
53
|
+
testId,
|
|
54
|
+
target: hostname,
|
|
55
|
+
passed: true,
|
|
56
|
+
durationMs,
|
|
57
|
+
metadata: {
|
|
58
|
+
addresses,
|
|
59
|
+
ipVersion,
|
|
60
|
+
addressCount: addresses.length,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
const durationMs = Date.now() - startTime;
|
|
67
|
+
const err = error;
|
|
68
|
+
if (err.message === "DNS resolution timed out") {
|
|
69
|
+
return {
|
|
70
|
+
source,
|
|
71
|
+
testId,
|
|
72
|
+
target: hostname,
|
|
73
|
+
passed: false,
|
|
74
|
+
errorCode: "dns_timeout",
|
|
75
|
+
durationMs,
|
|
76
|
+
metadata: {
|
|
77
|
+
error: err.message,
|
|
78
|
+
timeoutMs: timeout,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const errorCode = mapNodeErrorToConnectivityCode(err);
|
|
83
|
+
return {
|
|
84
|
+
source,
|
|
85
|
+
testId,
|
|
86
|
+
target: hostname,
|
|
87
|
+
passed: false,
|
|
88
|
+
errorCode,
|
|
89
|
+
durationMs,
|
|
90
|
+
metadata: {
|
|
91
|
+
error: err.message,
|
|
92
|
+
nodeErrorCode: err.code,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, } from "../error-codes.js";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
3
|
+
const LATENCY_THRESHOLD_MS = 5000;
|
|
4
|
+
export async function httpCheck(options) {
|
|
5
|
+
const { target, source, testId, timeout = DEFAULT_TIMEOUT_MS } = options;
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(target, {
|
|
11
|
+
method: "HEAD",
|
|
12
|
+
signal: controller.signal,
|
|
13
|
+
redirect: "follow",
|
|
14
|
+
});
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
const durationMs = Date.now() - startTime;
|
|
17
|
+
const errorCode = mapHttpStatusToErrorCode(response.status);
|
|
18
|
+
const hasHighLatency = durationMs > LATENCY_THRESHOLD_MS;
|
|
19
|
+
if (errorCode) {
|
|
20
|
+
return {
|
|
21
|
+
source,
|
|
22
|
+
testId,
|
|
23
|
+
target,
|
|
24
|
+
passed: false,
|
|
25
|
+
errorCode,
|
|
26
|
+
durationMs,
|
|
27
|
+
metadata: {
|
|
28
|
+
statusCode: response.status,
|
|
29
|
+
statusText: response.statusText,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
source,
|
|
35
|
+
testId,
|
|
36
|
+
target,
|
|
37
|
+
passed: true,
|
|
38
|
+
durationMs,
|
|
39
|
+
errorCode: hasHighLatency ? "latency_high" : undefined,
|
|
40
|
+
metadata: {
|
|
41
|
+
statusCode: response.status,
|
|
42
|
+
statusText: response.statusText,
|
|
43
|
+
latencyHigh: hasHighLatency,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
const durationMs = Date.now() - startTime;
|
|
50
|
+
const err = error;
|
|
51
|
+
if (err.name === "AbortError") {
|
|
52
|
+
return {
|
|
53
|
+
source,
|
|
54
|
+
testId,
|
|
55
|
+
target,
|
|
56
|
+
passed: false,
|
|
57
|
+
errorCode: "tcp_connection_timeout",
|
|
58
|
+
durationMs,
|
|
59
|
+
metadata: {
|
|
60
|
+
error: "Request timed out",
|
|
61
|
+
timeoutMs: timeout,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const errorCode = mapFetchErrorToConnectivityCode(err);
|
|
66
|
+
return {
|
|
67
|
+
source,
|
|
68
|
+
testId,
|
|
69
|
+
target,
|
|
70
|
+
passed: false,
|
|
71
|
+
errorCode,
|
|
72
|
+
durationMs,
|
|
73
|
+
metadata: {
|
|
74
|
+
error: err.message,
|
|
75
|
+
...(err.cause && {
|
|
76
|
+
causeCode: err.cause.code,
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CheckResult, Source, TestId } from "../types.js";
|
|
2
|
+
export interface SshCheckOptions {
|
|
3
|
+
hostname: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
source: Source;
|
|
6
|
+
testId: TestId;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function sshCheck(options: SshCheckOptions): Promise<CheckResult>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import net from "net";
|
|
2
|
+
import { mapNodeErrorToConnectivityCode } from "../error-codes.js";
|
|
3
|
+
const DEFAULT_PORT = 22;
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
5
|
+
const SSH_BANNER_PREFIX = "SSH-";
|
|
6
|
+
export async function sshCheck(options) {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const { hostname, port = DEFAULT_PORT, source, testId, timeout = DEFAULT_TIMEOUT_MS, } = options;
|
|
9
|
+
const target = `${hostname}:${port}`;
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
const result = await new Promise((resolve) => {
|
|
13
|
+
const socket = new net.Socket();
|
|
14
|
+
let banner = "";
|
|
15
|
+
socket.setTimeout(timeout);
|
|
16
|
+
socket.on("connect", () => {
|
|
17
|
+
// Wait for SSH banner data before closing
|
|
18
|
+
});
|
|
19
|
+
socket.on("data", (data) => {
|
|
20
|
+
banner += data.toString();
|
|
21
|
+
if (banner.includes("\n") || banner.length > 256) {
|
|
22
|
+
socket.destroy();
|
|
23
|
+
banner = banner.split("\n")[0].trim();
|
|
24
|
+
if (banner.startsWith(SSH_BANNER_PREFIX)) {
|
|
25
|
+
resolve({ success: true, banner });
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
resolve({
|
|
29
|
+
success: false,
|
|
30
|
+
error: new Error(`Unexpected response: not an SSH server`),
|
|
31
|
+
banner,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
socket.on("timeout", () => {
|
|
37
|
+
socket.destroy();
|
|
38
|
+
const error = new Error("SSH connection timed out");
|
|
39
|
+
error.code = "ETIMEDOUT";
|
|
40
|
+
resolve({ success: false, error });
|
|
41
|
+
});
|
|
42
|
+
socket.on("error", (err) => {
|
|
43
|
+
socket.destroy();
|
|
44
|
+
resolve({ success: false, error: err });
|
|
45
|
+
});
|
|
46
|
+
socket.on("end", () => {
|
|
47
|
+
if (!banner) {
|
|
48
|
+
resolve({
|
|
49
|
+
success: false,
|
|
50
|
+
error: new Error("Connection closed without receiving SSH banner"),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
socket.connect(port, hostname);
|
|
55
|
+
});
|
|
56
|
+
const durationMs = Date.now() - startTime;
|
|
57
|
+
if (result.success) {
|
|
58
|
+
return {
|
|
59
|
+
source,
|
|
60
|
+
testId,
|
|
61
|
+
target,
|
|
62
|
+
passed: true,
|
|
63
|
+
durationMs,
|
|
64
|
+
metadata: {
|
|
65
|
+
hostname,
|
|
66
|
+
port,
|
|
67
|
+
banner: result.banner,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const errorCode = result.error
|
|
72
|
+
? mapNodeErrorToConnectivityCode(result.error)
|
|
73
|
+
: "tcp_connection_refused";
|
|
74
|
+
return {
|
|
75
|
+
source,
|
|
76
|
+
testId,
|
|
77
|
+
target,
|
|
78
|
+
passed: false,
|
|
79
|
+
errorCode,
|
|
80
|
+
durationMs,
|
|
81
|
+
metadata: {
|
|
82
|
+
hostname,
|
|
83
|
+
port,
|
|
84
|
+
error: (_a = result.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
85
|
+
nodeErrorCode: (_b = result.error) === null || _b === void 0 ? void 0 : _b.code,
|
|
86
|
+
banner: result.banner,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const durationMs = Date.now() - startTime;
|
|
92
|
+
const err = error;
|
|
93
|
+
return {
|
|
94
|
+
source,
|
|
95
|
+
testId,
|
|
96
|
+
target,
|
|
97
|
+
passed: false,
|
|
98
|
+
errorCode: mapNodeErrorToConnectivityCode(err),
|
|
99
|
+
durationMs,
|
|
100
|
+
metadata: {
|
|
101
|
+
hostname,
|
|
102
|
+
port,
|
|
103
|
+
error: err.message,
|
|
104
|
+
nodeErrorCode: err.code,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CheckResult, Source, TestId } from "../types.js";
|
|
2
|
+
export interface TcpCheckOptions {
|
|
3
|
+
hostname: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
source: Source;
|
|
6
|
+
testId: TestId;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function tcpCheck(options: TcpCheckOptions): Promise<CheckResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import net from "net";
|
|
2
|
+
import { mapNodeErrorToConnectivityCode } from "../error-codes.js";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
4
|
+
const DEFAULT_PORT = 443;
|
|
5
|
+
export async function tcpCheck(options) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
const { hostname, port = DEFAULT_PORT, source, testId, timeout = DEFAULT_TIMEOUT_MS, } = options;
|
|
8
|
+
const target = `${hostname}:${port}`;
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
try {
|
|
11
|
+
const result = await new Promise((resolve) => {
|
|
12
|
+
const socket = new net.Socket();
|
|
13
|
+
socket.setTimeout(timeout);
|
|
14
|
+
socket.on("connect", () => {
|
|
15
|
+
socket.destroy();
|
|
16
|
+
resolve({ success: true });
|
|
17
|
+
});
|
|
18
|
+
socket.on("timeout", () => {
|
|
19
|
+
socket.destroy();
|
|
20
|
+
const error = new Error("Connection timed out");
|
|
21
|
+
error.code = "ETIMEDOUT";
|
|
22
|
+
resolve({ success: false, error });
|
|
23
|
+
});
|
|
24
|
+
socket.on("error", (err) => {
|
|
25
|
+
socket.destroy();
|
|
26
|
+
resolve({ success: false, error: err });
|
|
27
|
+
});
|
|
28
|
+
socket.connect(port, hostname);
|
|
29
|
+
});
|
|
30
|
+
const durationMs = Date.now() - startTime;
|
|
31
|
+
if (result.success) {
|
|
32
|
+
return {
|
|
33
|
+
source,
|
|
34
|
+
testId,
|
|
35
|
+
target,
|
|
36
|
+
passed: true,
|
|
37
|
+
durationMs,
|
|
38
|
+
metadata: {
|
|
39
|
+
hostname,
|
|
40
|
+
port,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const errorCode = result.error
|
|
45
|
+
? mapNodeErrorToConnectivityCode(result.error)
|
|
46
|
+
: "tcp_connection_timeout";
|
|
47
|
+
return {
|
|
48
|
+
source,
|
|
49
|
+
testId,
|
|
50
|
+
target,
|
|
51
|
+
passed: false,
|
|
52
|
+
errorCode,
|
|
53
|
+
durationMs,
|
|
54
|
+
metadata: {
|
|
55
|
+
hostname,
|
|
56
|
+
port,
|
|
57
|
+
error: (_a = result.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
58
|
+
nodeErrorCode: (_b = result.error) === null || _b === void 0 ? void 0 : _b.code,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const durationMs = Date.now() - startTime;
|
|
64
|
+
const err = error;
|
|
65
|
+
return {
|
|
66
|
+
source,
|
|
67
|
+
testId,
|
|
68
|
+
target,
|
|
69
|
+
passed: false,
|
|
70
|
+
errorCode: mapNodeErrorToConnectivityCode(err),
|
|
71
|
+
durationMs,
|
|
72
|
+
metadata: {
|
|
73
|
+
hostname,
|
|
74
|
+
port,
|
|
75
|
+
error: err.message,
|
|
76
|
+
nodeErrorCode: err.code,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CheckResult, Source, TestId } from "../types.js";
|
|
2
|
+
export interface TlsCheckOptions {
|
|
3
|
+
hostname: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
source: Source;
|
|
6
|
+
testId: TestId;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function tlsCheck(options: TlsCheckOptions): Promise<CheckResult>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import tls from "tls";
|
|
2
|
+
import { mapNodeErrorToConnectivityCode } from "../error-codes.js";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
4
|
+
const DEFAULT_PORT = 443;
|
|
5
|
+
export async function tlsCheck(options) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
const { hostname, port = DEFAULT_PORT, source, testId, timeout = DEFAULT_TIMEOUT_MS, } = options;
|
|
8
|
+
const target = `${hostname}:${port}`;
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
try {
|
|
11
|
+
const result = await new Promise((resolve) => {
|
|
12
|
+
const socket = tls.connect({
|
|
13
|
+
host: hostname,
|
|
14
|
+
port,
|
|
15
|
+
servername: hostname, // SNI required for virtual hosts
|
|
16
|
+
rejectUnauthorized: true,
|
|
17
|
+
timeout,
|
|
18
|
+
}, () => {
|
|
19
|
+
const cert = socket.getPeerCertificate();
|
|
20
|
+
let certInfo;
|
|
21
|
+
if (cert && Object.keys(cert).length > 0) {
|
|
22
|
+
certInfo = {
|
|
23
|
+
subject: formatCertName(cert.subject),
|
|
24
|
+
issuer: formatCertName(cert.issuer),
|
|
25
|
+
validFrom: cert.valid_from,
|
|
26
|
+
validTo: cert.valid_to,
|
|
27
|
+
fingerprint: cert.fingerprint,
|
|
28
|
+
serialNumber: cert.serialNumber,
|
|
29
|
+
subjectAltNames: cert.subjectaltname
|
|
30
|
+
? cert.subjectaltname.split(", ")
|
|
31
|
+
: undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
socket.end();
|
|
35
|
+
resolve({ success: true, certInfo });
|
|
36
|
+
});
|
|
37
|
+
socket.on("timeout", () => {
|
|
38
|
+
socket.destroy();
|
|
39
|
+
const error = new Error("TLS connection timed out");
|
|
40
|
+
error.code = "ETIMEDOUT";
|
|
41
|
+
resolve({ success: false, error });
|
|
42
|
+
});
|
|
43
|
+
socket.on("error", (err) => {
|
|
44
|
+
socket.destroy();
|
|
45
|
+
resolve({ success: false, error: err });
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
const durationMs = Date.now() - startTime;
|
|
49
|
+
if (result.success) {
|
|
50
|
+
return {
|
|
51
|
+
source,
|
|
52
|
+
testId,
|
|
53
|
+
target,
|
|
54
|
+
passed: true,
|
|
55
|
+
durationMs,
|
|
56
|
+
metadata: {
|
|
57
|
+
hostname,
|
|
58
|
+
port,
|
|
59
|
+
certificate: result.certInfo,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const errorCode = result.error
|
|
64
|
+
? mapNodeErrorToConnectivityCode(result.error)
|
|
65
|
+
: "tls_handshake_failed";
|
|
66
|
+
return {
|
|
67
|
+
source,
|
|
68
|
+
testId,
|
|
69
|
+
target,
|
|
70
|
+
passed: false,
|
|
71
|
+
errorCode,
|
|
72
|
+
durationMs,
|
|
73
|
+
metadata: {
|
|
74
|
+
hostname,
|
|
75
|
+
port,
|
|
76
|
+
error: (_a = result.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
77
|
+
nodeErrorCode: (_b = result.error) === null || _b === void 0 ? void 0 : _b.code,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const durationMs = Date.now() - startTime;
|
|
83
|
+
const err = error;
|
|
84
|
+
return {
|
|
85
|
+
source,
|
|
86
|
+
testId,
|
|
87
|
+
target,
|
|
88
|
+
passed: false,
|
|
89
|
+
errorCode: mapNodeErrorToConnectivityCode(err),
|
|
90
|
+
durationMs,
|
|
91
|
+
metadata: {
|
|
92
|
+
hostname,
|
|
93
|
+
port,
|
|
94
|
+
error: err.message,
|
|
95
|
+
nodeErrorCode: err.code,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function formatCertName(name) {
|
|
101
|
+
if (!name) {
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
const parts = [];
|
|
105
|
+
const fields = ["CN", "O", "OU", "L", "ST", "C"];
|
|
106
|
+
for (const field of fields) {
|
|
107
|
+
const value = name[field];
|
|
108
|
+
if (value) {
|
|
109
|
+
parts.push(`${field}=${value}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return parts.join(", ");
|
|
113
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TestId, CheckType } from "./types.js";
|
|
2
|
+
export declare function isBrowser(): boolean;
|
|
3
|
+
export declare function isNode(): boolean;
|
|
4
|
+
export declare function getCheckTypeForTestId(testId: TestId): CheckType;
|
|
5
|
+
export declare function isCheckAvailable(checkType: CheckType): boolean;
|
|
6
|
+
export declare function getUnavailabilityReason(checkType: CheckType): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function isBrowser() {
|
|
2
|
+
return (typeof window !== "undefined" && typeof window.document !== "undefined");
|
|
3
|
+
}
|
|
4
|
+
export function isNode() {
|
|
5
|
+
return (typeof process !== "undefined" &&
|
|
6
|
+
process.versions != null &&
|
|
7
|
+
process.versions.node != null);
|
|
8
|
+
}
|
|
9
|
+
export function getCheckTypeForTestId(testId) {
|
|
10
|
+
if (testId.startsWith("git-host:")) {
|
|
11
|
+
return testId.replace("git-host:", "");
|
|
12
|
+
}
|
|
13
|
+
return "http";
|
|
14
|
+
}
|
|
15
|
+
export function isCheckAvailable(checkType) {
|
|
16
|
+
if (checkType === "http") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// DNS, TCP, TLS, and SSH checks require Node.js modules
|
|
20
|
+
return isNode();
|
|
21
|
+
}
|
|
22
|
+
export function getUnavailabilityReason(checkType) {
|
|
23
|
+
if (isBrowser()) {
|
|
24
|
+
return `${checkType.toUpperCase()} checks are not available in browser environments. Only HTTP checks can be performed from the browser.`;
|
|
25
|
+
}
|
|
26
|
+
return `${checkType.toUpperCase()} checks are not available in this environment.`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ConnectivityErrorCode } from "./types.js";
|
|
2
|
+
export declare const SELF_SIGNED_CERT_ERRORS: Set<string>;
|
|
3
|
+
export declare const CERT_EXPIRED_ERRORS: Set<string>;
|
|
4
|
+
export declare const CERT_NOT_YET_VALID_ERRORS: Set<string>;
|
|
5
|
+
export declare const CERT_INVALID_ERRORS: Set<string>;
|
|
6
|
+
export declare const CERT_HOSTNAME_MISMATCH_ERRORS: Set<string>;
|
|
7
|
+
export declare const SSL_PROTOCOL_ERRORS: Set<string>;
|
|
8
|
+
export declare const SSL_HANDSHAKE_ERRORS: Set<string>;
|
|
9
|
+
export declare const NETWORK_UNREACHABLE_ERRORS: Set<string>;
|
|
10
|
+
export declare const TIMEOUT_ERRORS: Set<string>;
|
|
11
|
+
export declare const PROXY_ERRORS: Set<string>;
|
|
12
|
+
export declare const DNS_ERRORS: Set<string>;
|
|
13
|
+
export declare function mapNodeErrorToConnectivityCode(error: Error & {
|
|
14
|
+
code?: string;
|
|
15
|
+
}): ConnectivityErrorCode;
|
|
16
|
+
export declare function mapHttpStatusToErrorCode(status: number): ConnectivityErrorCode | undefined;
|
|
17
|
+
export declare function mapFetchErrorToConnectivityCode(error: Error & {
|
|
18
|
+
cause?: Error & {
|
|
19
|
+
code?: string;
|
|
20
|
+
};
|
|
21
|
+
}): ConnectivityErrorCode;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export const SELF_SIGNED_CERT_ERRORS = new Set([
|
|
2
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
3
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
4
|
+
]);
|
|
5
|
+
export const CERT_EXPIRED_ERRORS = new Set([
|
|
6
|
+
"CERT_HAS_EXPIRED",
|
|
7
|
+
"ERROR_IN_CERT_NOT_AFTER_FIELD",
|
|
8
|
+
]);
|
|
9
|
+
// Most commonly caused by incorrect system clock settings
|
|
10
|
+
export const CERT_NOT_YET_VALID_ERRORS = new Set([
|
|
11
|
+
"CERT_NOT_YET_VALID",
|
|
12
|
+
"ERROR_IN_CERT_NOT_BEFORE_FIELD",
|
|
13
|
+
]);
|
|
14
|
+
export const CERT_INVALID_ERRORS = new Set([
|
|
15
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
16
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
17
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
18
|
+
"INVALID_CA",
|
|
19
|
+
"CERT_SIGNATURE_FAILURE",
|
|
20
|
+
"CERT_REVOKED",
|
|
21
|
+
"CERT_REJECTED",
|
|
22
|
+
"CERT_UNTRUSTED",
|
|
23
|
+
"CERT_CHAIN_TOO_LONG",
|
|
24
|
+
"PATH_LENGTH_EXCEEDED",
|
|
25
|
+
"INVALID_PURPOSE",
|
|
26
|
+
"UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
|
|
27
|
+
"UNABLE_TO_DECRYPT_CERT_SIGNATURE",
|
|
28
|
+
]);
|
|
29
|
+
export const CERT_HOSTNAME_MISMATCH_ERRORS = new Set([
|
|
30
|
+
"HOSTNAME_MISMATCH",
|
|
31
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
32
|
+
]);
|
|
33
|
+
export const SSL_PROTOCOL_ERRORS = new Set([
|
|
34
|
+
"ERR_TLS_INVALID_PROTOCOL_VERSION",
|
|
35
|
+
"ERR_TLS_INVALID_PROTOCOL_METHOD",
|
|
36
|
+
"ERR_TLS_PROTOCOL_VERSION_CONFLICT",
|
|
37
|
+
"ERR_TLS_INVALID_STATE",
|
|
38
|
+
"ERR_TLS_INVALID_CONTEXT",
|
|
39
|
+
"ERR_TLS_RENEGOTIATION_DISABLED",
|
|
40
|
+
"ERR_TLS_REQUIRED_SERVER_NAME",
|
|
41
|
+
"ERR_TLS_SESSION_ATTACK",
|
|
42
|
+
"ERR_TLS_SNI_FROM_SERVER",
|
|
43
|
+
"ERR_TLS_DH_PARAM_SIZE",
|
|
44
|
+
"ERR_SSL_WRONG_VERSION_NUMBER",
|
|
45
|
+
"EPROTO",
|
|
46
|
+
]);
|
|
47
|
+
export const SSL_HANDSHAKE_ERRORS = new Set([
|
|
48
|
+
"ERR_TLS_HANDSHAKE_TIMEOUT",
|
|
49
|
+
"ERR_SSL_HANDSHAKE_FAILURE",
|
|
50
|
+
]);
|
|
51
|
+
export const NETWORK_UNREACHABLE_ERRORS = new Set([
|
|
52
|
+
"ENETUNREACH",
|
|
53
|
+
"EHOSTUNREACH",
|
|
54
|
+
"ENETDOWN",
|
|
55
|
+
"ENONET",
|
|
56
|
+
]);
|
|
57
|
+
export const TIMEOUT_ERRORS = new Set([
|
|
58
|
+
"ETIMEDOUT",
|
|
59
|
+
"ESOCKETTIMEDOUT",
|
|
60
|
+
"ERR_SOCKET_CONNECTION_TIMEOUT",
|
|
61
|
+
"ERR_HTTP_REQUEST_TIMEOUT",
|
|
62
|
+
]);
|
|
63
|
+
export const PROXY_ERRORS = new Set([
|
|
64
|
+
"ERR_PROXY_INVALID_CONFIG",
|
|
65
|
+
"ERR_PROXY_TUNNEL",
|
|
66
|
+
"ERR_TUNNEL_CONNECTION_FAILED",
|
|
67
|
+
]);
|
|
68
|
+
export const DNS_ERRORS = new Set(["ENOTFOUND", "EAI_AGAIN", "ENODATA"]);
|
|
69
|
+
export function mapNodeErrorToConnectivityCode(error) {
|
|
70
|
+
const errorCode = error.code;
|
|
71
|
+
if (!errorCode) {
|
|
72
|
+
return "unknown_error";
|
|
73
|
+
}
|
|
74
|
+
if (DNS_ERRORS.has(errorCode)) {
|
|
75
|
+
return "dns_resolution_failed";
|
|
76
|
+
}
|
|
77
|
+
if (errorCode === "ECONNREFUSED") {
|
|
78
|
+
return "tcp_connection_refused";
|
|
79
|
+
}
|
|
80
|
+
if (errorCode === "ECONNRESET" || errorCode === "EPIPE") {
|
|
81
|
+
return "tcp_connection_reset";
|
|
82
|
+
}
|
|
83
|
+
if (TIMEOUT_ERRORS.has(errorCode)) {
|
|
84
|
+
return "tcp_connection_timeout";
|
|
85
|
+
}
|
|
86
|
+
if (errorCode === "ENETUNREACH" || errorCode === "ENETDOWN") {
|
|
87
|
+
return "tcp_network_unreachable";
|
|
88
|
+
}
|
|
89
|
+
if (errorCode === "EHOSTUNREACH") {
|
|
90
|
+
return "tcp_host_unreachable";
|
|
91
|
+
}
|
|
92
|
+
if (SELF_SIGNED_CERT_ERRORS.has(errorCode)) {
|
|
93
|
+
return "tls_self_signed_cert";
|
|
94
|
+
}
|
|
95
|
+
if (CERT_EXPIRED_ERRORS.has(errorCode)) {
|
|
96
|
+
return "tls_cert_expired";
|
|
97
|
+
}
|
|
98
|
+
if (CERT_NOT_YET_VALID_ERRORS.has(errorCode)) {
|
|
99
|
+
return "tls_cert_not_yet_valid";
|
|
100
|
+
}
|
|
101
|
+
if (CERT_HOSTNAME_MISMATCH_ERRORS.has(errorCode)) {
|
|
102
|
+
return "tls_cert_hostname_mismatch";
|
|
103
|
+
}
|
|
104
|
+
if (CERT_INVALID_ERRORS.has(errorCode)) {
|
|
105
|
+
return "tls_cert_invalid";
|
|
106
|
+
}
|
|
107
|
+
if (SSL_HANDSHAKE_ERRORS.has(errorCode)) {
|
|
108
|
+
return "tls_handshake_failed";
|
|
109
|
+
}
|
|
110
|
+
if (SSL_PROTOCOL_ERRORS.has(errorCode)) {
|
|
111
|
+
return "tls_protocol_error";
|
|
112
|
+
}
|
|
113
|
+
if (PROXY_ERRORS.has(errorCode)) {
|
|
114
|
+
return "proxy_tunnel_failed";
|
|
115
|
+
}
|
|
116
|
+
return "unknown_error";
|
|
117
|
+
}
|
|
118
|
+
export function mapHttpStatusToErrorCode(status) {
|
|
119
|
+
if (status >= 200 && status < 400) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
if (status === 407) {
|
|
123
|
+
return "proxy_auth_required";
|
|
124
|
+
}
|
|
125
|
+
if (status === 401) {
|
|
126
|
+
return "http_unauthorized";
|
|
127
|
+
}
|
|
128
|
+
if (status === 403) {
|
|
129
|
+
return "http_forbidden";
|
|
130
|
+
}
|
|
131
|
+
if (status === 404) {
|
|
132
|
+
return "http_not_found";
|
|
133
|
+
}
|
|
134
|
+
if (status === 503) {
|
|
135
|
+
return "http_service_unavailable";
|
|
136
|
+
}
|
|
137
|
+
if (status >= 500) {
|
|
138
|
+
return "http_server_error";
|
|
139
|
+
}
|
|
140
|
+
return "unknown_error";
|
|
141
|
+
}
|
|
142
|
+
// Fetch errors don't have Node.js-style error codes, so we inspect the message
|
|
143
|
+
export function mapFetchErrorToConnectivityCode(error) {
|
|
144
|
+
if (error.cause && "code" in error.cause) {
|
|
145
|
+
return mapNodeErrorToConnectivityCode(error.cause);
|
|
146
|
+
}
|
|
147
|
+
const message = error.message.toLowerCase();
|
|
148
|
+
if (message.includes("failed to fetch") || message.includes("network")) {
|
|
149
|
+
return "tcp_connection_refused";
|
|
150
|
+
}
|
|
151
|
+
if (message.includes("timeout") || message.includes("timed out")) {
|
|
152
|
+
return "tcp_connection_timeout";
|
|
153
|
+
}
|
|
154
|
+
if (message.includes("dns") || message.includes("not found")) {
|
|
155
|
+
return "dns_resolution_failed";
|
|
156
|
+
}
|
|
157
|
+
if (message.includes("certificate") || message.includes("ssl")) {
|
|
158
|
+
if (message.includes("expired")) {
|
|
159
|
+
return "tls_cert_expired";
|
|
160
|
+
}
|
|
161
|
+
if (message.includes("self-signed") || message.includes("self signed")) {
|
|
162
|
+
return "tls_self_signed_cert";
|
|
163
|
+
}
|
|
164
|
+
return "tls_cert_invalid";
|
|
165
|
+
}
|
|
166
|
+
return "unknown_error";
|
|
167
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { Source, TestId, Test, RunChecksInput, ProgressEvent, CheckResult, CheckReport, ConnectivityErrorCode, CheckType, Recommendation, LikelyCause, ConnectivityStatus, AnalysisResult, AnalyzeConnectivityInput, } from "./types.js";
|
|
2
|
+
export { runChecks } from "./run-checks.js";
|
|
3
|
+
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
4
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
5
|
+
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
6
|
+
export { httpCheck } from "./checks/http-check.js";
|
|
7
|
+
export type { HttpCheckOptions } from "./checks/http-check.js";
|
|
8
|
+
export { dnsCheck } from "./checks/dns-check.js";
|
|
9
|
+
export type { DnsCheckOptions } from "./checks/dns-check.js";
|
|
10
|
+
export { tcpCheck } from "./checks/tcp-check.js";
|
|
11
|
+
export type { TcpCheckOptions } from "./checks/tcp-check.js";
|
|
12
|
+
export { tlsCheck } from "./checks/tls-check.js";
|
|
13
|
+
export type { TlsCheckOptions } from "./checks/tls-check.js";
|
|
14
|
+
export { sshCheck } from "./checks/ssh-check.js";
|
|
15
|
+
export type { SshCheckOptions } from "./checks/ssh-check.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { runChecks } from "./run-checks.js";
|
|
2
|
+
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
3
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
4
|
+
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
5
|
+
export { httpCheck } from "./checks/http-check.js";
|
|
6
|
+
export { dnsCheck } from "./checks/dns-check.js";
|
|
7
|
+
export { tcpCheck } from "./checks/tcp-check.js";
|
|
8
|
+
export { tlsCheck } from "./checks/tls-check.js";
|
|
9
|
+
export { sshCheck } from "./checks/ssh-check.js";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { resolveTarget } from "./targets.js";
|
|
2
|
+
import { getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
3
|
+
import { httpCheck } from "./checks/http-check.js";
|
|
4
|
+
export async function runChecks(input) {
|
|
5
|
+
const { tests, gitHost, onProgress } = input;
|
|
6
|
+
const results = [];
|
|
7
|
+
const total = tests.length;
|
|
8
|
+
for (let index = 0; index < tests.length; index++) {
|
|
9
|
+
const test = tests[index];
|
|
10
|
+
emitProgress(onProgress, {
|
|
11
|
+
type: "test:start",
|
|
12
|
+
test,
|
|
13
|
+
index,
|
|
14
|
+
total,
|
|
15
|
+
});
|
|
16
|
+
const result = await runSingleCheck(test, gitHost);
|
|
17
|
+
results.push(result);
|
|
18
|
+
emitProgress(onProgress, {
|
|
19
|
+
type: "test:complete",
|
|
20
|
+
result,
|
|
21
|
+
index,
|
|
22
|
+
total,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
emitProgress(onProgress, {
|
|
26
|
+
type: "batch:complete",
|
|
27
|
+
results,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
timestamp: new Date().toISOString(),
|
|
31
|
+
gitHost,
|
|
32
|
+
results,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function runSingleCheck(test, gitHost) {
|
|
36
|
+
const { source, testId } = test;
|
|
37
|
+
const checkType = getCheckTypeForTestId(testId);
|
|
38
|
+
if (!isCheckAvailable(checkType)) {
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
let target;
|
|
41
|
+
try {
|
|
42
|
+
target = resolveTarget(testId, gitHost);
|
|
43
|
+
}
|
|
44
|
+
catch (_a) {
|
|
45
|
+
target = gitHost || testId;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
source,
|
|
49
|
+
testId,
|
|
50
|
+
target,
|
|
51
|
+
passed: false,
|
|
52
|
+
errorCode: "check_unavailable",
|
|
53
|
+
durationMs: Date.now() - startTime,
|
|
54
|
+
metadata: {
|
|
55
|
+
reason: getUnavailabilityReason(checkType),
|
|
56
|
+
checkType,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
let target;
|
|
61
|
+
try {
|
|
62
|
+
target = resolveTarget(testId, gitHost);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
source,
|
|
67
|
+
testId,
|
|
68
|
+
target: gitHost || testId,
|
|
69
|
+
passed: false,
|
|
70
|
+
errorCode: "unknown_error",
|
|
71
|
+
durationMs: 0,
|
|
72
|
+
metadata: {
|
|
73
|
+
error: error.message,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
switch (checkType) {
|
|
78
|
+
case "http":
|
|
79
|
+
return httpCheck({ target, source, testId });
|
|
80
|
+
default:
|
|
81
|
+
return {
|
|
82
|
+
source,
|
|
83
|
+
testId,
|
|
84
|
+
target,
|
|
85
|
+
passed: false,
|
|
86
|
+
errorCode: "check_unavailable",
|
|
87
|
+
durationMs: 0,
|
|
88
|
+
metadata: {
|
|
89
|
+
reason: `Check type "${checkType}" is not available in browser environments`,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function emitProgress(onProgress, event) {
|
|
95
|
+
if (onProgress) {
|
|
96
|
+
try {
|
|
97
|
+
onProgress(event);
|
|
98
|
+
}
|
|
99
|
+
catch (_a) {
|
|
100
|
+
// Ignore errors in progress callback to prevent breaking the check flow
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { resolveTarget, extractHostname, extractPort } from "./targets.js";
|
|
2
|
+
import { getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
3
|
+
import { httpCheck } from "./checks/http-check.js";
|
|
4
|
+
import { dnsCheck } from "./checks/dns-check.js";
|
|
5
|
+
import { tcpCheck } from "./checks/tcp-check.js";
|
|
6
|
+
import { tlsCheck } from "./checks/tls-check.js";
|
|
7
|
+
import { sshCheck } from "./checks/ssh-check.js";
|
|
8
|
+
export async function runChecks(input) {
|
|
9
|
+
const { tests, gitHost, onProgress } = input;
|
|
10
|
+
const results = [];
|
|
11
|
+
const total = tests.length;
|
|
12
|
+
for (let index = 0; index < tests.length; index++) {
|
|
13
|
+
const test = tests[index];
|
|
14
|
+
emitProgress(onProgress, {
|
|
15
|
+
type: "test:start",
|
|
16
|
+
test,
|
|
17
|
+
index,
|
|
18
|
+
total,
|
|
19
|
+
});
|
|
20
|
+
const result = await runSingleCheck(test, gitHost);
|
|
21
|
+
results.push(result);
|
|
22
|
+
emitProgress(onProgress, {
|
|
23
|
+
type: "test:complete",
|
|
24
|
+
result,
|
|
25
|
+
index,
|
|
26
|
+
total,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
emitProgress(onProgress, {
|
|
30
|
+
type: "batch:complete",
|
|
31
|
+
results,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
gitHost,
|
|
36
|
+
results,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function runSingleCheck(test, gitHost) {
|
|
40
|
+
const { source, testId } = test;
|
|
41
|
+
const checkType = getCheckTypeForTestId(testId);
|
|
42
|
+
if (!isCheckAvailable(checkType)) {
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
let target;
|
|
45
|
+
try {
|
|
46
|
+
target = resolveTarget(testId, gitHost);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
target = gitHost || testId;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
source,
|
|
53
|
+
testId,
|
|
54
|
+
target,
|
|
55
|
+
passed: false,
|
|
56
|
+
errorCode: "check_unavailable",
|
|
57
|
+
durationMs: Date.now() - startTime,
|
|
58
|
+
metadata: {
|
|
59
|
+
reason: getUnavailabilityReason(checkType),
|
|
60
|
+
checkType,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
let target;
|
|
65
|
+
try {
|
|
66
|
+
target = resolveTarget(testId, gitHost);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
source,
|
|
71
|
+
testId,
|
|
72
|
+
target: gitHost || testId,
|
|
73
|
+
passed: false,
|
|
74
|
+
errorCode: "unknown_error",
|
|
75
|
+
durationMs: 0,
|
|
76
|
+
metadata: {
|
|
77
|
+
error: error.message,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
switch (checkType) {
|
|
82
|
+
case "http":
|
|
83
|
+
return httpCheck({ target, source, testId });
|
|
84
|
+
case "dns":
|
|
85
|
+
return dnsCheck({
|
|
86
|
+
hostname: extractHostname(target),
|
|
87
|
+
source,
|
|
88
|
+
testId,
|
|
89
|
+
});
|
|
90
|
+
case "tcp":
|
|
91
|
+
return tcpCheck({
|
|
92
|
+
hostname: extractHostname(target),
|
|
93
|
+
port: extractPort(target, 443),
|
|
94
|
+
source,
|
|
95
|
+
testId,
|
|
96
|
+
});
|
|
97
|
+
case "tls":
|
|
98
|
+
return tlsCheck({
|
|
99
|
+
hostname: extractHostname(target),
|
|
100
|
+
port: extractPort(target, 443),
|
|
101
|
+
source,
|
|
102
|
+
testId,
|
|
103
|
+
});
|
|
104
|
+
case "ssh":
|
|
105
|
+
return sshCheck({
|
|
106
|
+
hostname: extractHostname(target),
|
|
107
|
+
port: 22,
|
|
108
|
+
source,
|
|
109
|
+
testId,
|
|
110
|
+
});
|
|
111
|
+
default:
|
|
112
|
+
return {
|
|
113
|
+
source,
|
|
114
|
+
testId,
|
|
115
|
+
target,
|
|
116
|
+
passed: false,
|
|
117
|
+
errorCode: "unknown_error",
|
|
118
|
+
durationMs: 0,
|
|
119
|
+
metadata: {
|
|
120
|
+
error: `Unknown check type: ${checkType}`,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function emitProgress(onProgress, event) {
|
|
126
|
+
if (onProgress) {
|
|
127
|
+
try {
|
|
128
|
+
onProgress(event);
|
|
129
|
+
}
|
|
130
|
+
catch (_a) {
|
|
131
|
+
// Ignore errors in progress callback to prevent breaking the check flow
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TestId } from "./types.js";
|
|
2
|
+
export declare const BUILDER_TARGETS: Record<string, string>;
|
|
3
|
+
export declare const DEFAULT_PORTS: Record<string, number>;
|
|
4
|
+
export declare function resolveTarget(testId: TestId, gitHost?: string): string;
|
|
5
|
+
export declare function extractHostname(target: string): string;
|
|
6
|
+
export declare function extractPort(target: string, defaultPort?: number): number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const BUILDER_TARGETS = {
|
|
2
|
+
"builder.io": "https://www.builder.io",
|
|
3
|
+
"builder.codes": "https://test.projects.builder.codes/proxy-health",
|
|
4
|
+
"api.builder.io": "https://api.builder.io/codegen/health",
|
|
5
|
+
"cdn.builder.io": "https://cdn.builder.io/api/v1/image/assets/TEMP/75a212ab82b6175c9862b125e0e23db8d369a58a?width=100",
|
|
6
|
+
"builderio.xyz": "https://builderio.xyz/health",
|
|
7
|
+
"fly.dev": "https://fly.dev",
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_PORTS = {
|
|
10
|
+
http: 443,
|
|
11
|
+
https: 443,
|
|
12
|
+
ssh: 22,
|
|
13
|
+
tcp: 443,
|
|
14
|
+
tls: 443,
|
|
15
|
+
};
|
|
16
|
+
export function resolveTarget(testId, gitHost) {
|
|
17
|
+
if (testId.startsWith("git-host:")) {
|
|
18
|
+
if (!gitHost) {
|
|
19
|
+
throw new Error(`gitHost parameter is required for test "${testId}"`);
|
|
20
|
+
}
|
|
21
|
+
return gitHost;
|
|
22
|
+
}
|
|
23
|
+
const target = BUILDER_TARGETS[testId];
|
|
24
|
+
if (!target) {
|
|
25
|
+
throw new Error(`Unknown testId: ${testId}`);
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
export function extractHostname(target) {
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(target);
|
|
32
|
+
return url.hostname;
|
|
33
|
+
}
|
|
34
|
+
catch (_a) {
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function extractPort(target, defaultPort = 443) {
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(target);
|
|
41
|
+
if (url.port) {
|
|
42
|
+
return parseInt(url.port, 10);
|
|
43
|
+
}
|
|
44
|
+
if (url.protocol === "http:") {
|
|
45
|
+
return 80;
|
|
46
|
+
}
|
|
47
|
+
return defaultPort;
|
|
48
|
+
}
|
|
49
|
+
catch (_a) {
|
|
50
|
+
return defaultPort;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type Source = "local" | "cloud" | "static-ip";
|
|
2
|
+
export type TestId = "builder.io" | "builder.codes" | "api.builder.io" | "cdn.builder.io" | "builderio.xyz" | "fly.dev" | "git-host:http" | "git-host:dns" | "git-host:tcp" | "git-host:tls" | "git-host:ssh";
|
|
3
|
+
export interface Test {
|
|
4
|
+
source: Source;
|
|
5
|
+
testId: TestId;
|
|
6
|
+
}
|
|
7
|
+
export interface RunChecksInput {
|
|
8
|
+
tests: Test[];
|
|
9
|
+
gitHost?: string;
|
|
10
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
11
|
+
}
|
|
12
|
+
export type ProgressEvent = {
|
|
13
|
+
type: "test:start";
|
|
14
|
+
test: Test;
|
|
15
|
+
index: number;
|
|
16
|
+
total: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: "test:complete";
|
|
19
|
+
result: CheckResult;
|
|
20
|
+
index: number;
|
|
21
|
+
total: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: "batch:complete";
|
|
24
|
+
results: CheckResult[];
|
|
25
|
+
};
|
|
26
|
+
export interface CheckResult {
|
|
27
|
+
source: Source;
|
|
28
|
+
testId: TestId;
|
|
29
|
+
target: string;
|
|
30
|
+
passed: boolean;
|
|
31
|
+
errorCode?: ConnectivityErrorCode;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface CheckReport {
|
|
36
|
+
timestamp: string;
|
|
37
|
+
gitHost?: string;
|
|
38
|
+
results: CheckResult[];
|
|
39
|
+
}
|
|
40
|
+
export type ConnectivityErrorCode = "dns_resolution_failed" | "dns_timeout" | "dns_wrong_ip" | "tcp_connection_refused" | "tcp_connection_timeout" | "tcp_connection_reset" | "tcp_host_unreachable" | "tcp_network_unreachable" | "tls_self_signed_cert" | "tls_cert_expired" | "tls_cert_not_yet_valid" | "tls_cert_invalid" | "tls_cert_hostname_mismatch" | "tls_handshake_failed" | "tls_protocol_error" | "proxy_auth_required" | "proxy_connection_failed" | "proxy_tunnel_failed" | "http_unauthorized" | "http_forbidden" | "http_not_found" | "http_server_error" | "http_service_unavailable" | "latency_high" | "check_unavailable" | "unknown_error";
|
|
41
|
+
export type CheckType = "http" | "dns" | "tcp" | "tls" | "ssh";
|
|
42
|
+
export type Recommendation = "ready_for_cloud_dev" | "enable_static_ip_proxy" | "whitelist_static_ip" | "fix_local_dns" | "fix_local_tls_certs" | "fix_local_firewall" | "use_local_development";
|
|
43
|
+
export type LikelyCause = "ip_whitelisting_required" | "vpn_blocking" | "corporate_proxy_required" | "self_signed_certificate" | "dns_misconfiguration" | "firewall_blocking" | "server_unavailable";
|
|
44
|
+
export type ConnectivityStatus = "pass" | "fail" | "unknown";
|
|
45
|
+
export interface AnalysisResult {
|
|
46
|
+
recommendation: Recommendation;
|
|
47
|
+
reason: string;
|
|
48
|
+
steps: string[];
|
|
49
|
+
fallback?: Recommendation;
|
|
50
|
+
likelyCause?: LikelyCause;
|
|
51
|
+
summary: {
|
|
52
|
+
localToBuilder: ConnectivityStatus;
|
|
53
|
+
localToGitHost: ConnectivityStatus;
|
|
54
|
+
cloudToGitHost: ConnectivityStatus;
|
|
55
|
+
staticIpToGitHost: ConnectivityStatus;
|
|
56
|
+
};
|
|
57
|
+
allResults: CheckResult[];
|
|
58
|
+
}
|
|
59
|
+
export interface AnalyzeConnectivityInput {
|
|
60
|
+
localResults: CheckResult[];
|
|
61
|
+
serverResults: CheckResult[];
|
|
62
|
+
spaceSettings: {
|
|
63
|
+
staticProxyEnabled: boolean;
|
|
64
|
+
};
|
|
65
|
+
gitHost: string;
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED