private-connect 0.3.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/dist/index.d.ts +11 -0
- package/dist/index.js +261 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Private Connect CLI
|
|
5
|
+
*
|
|
6
|
+
* Zero-friction connectivity testing. No signup required.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx private-connect test vault.internal:8200
|
|
10
|
+
* npx private-connect test https://api.example.com
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const net = require("net");
|
|
14
|
+
const tls = require("tls");
|
|
15
|
+
const https = require("https");
|
|
16
|
+
const http = require("http");
|
|
17
|
+
const url_1 = require("url");
|
|
18
|
+
// Colors (no dependencies)
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
gray: '\x1b[90m',
|
|
28
|
+
};
|
|
29
|
+
const ok = `${c.green}✓${c.reset}`;
|
|
30
|
+
const fail = `${c.red}✗${c.reset}`;
|
|
31
|
+
const warn = `${c.yellow}⚠${c.reset}`;
|
|
32
|
+
async function testTcp(host, port, timeout = 5000) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
const socket = new net.Socket();
|
|
36
|
+
socket.setTimeout(timeout);
|
|
37
|
+
socket.on('connect', () => {
|
|
38
|
+
const latency = Date.now() - start;
|
|
39
|
+
socket.destroy();
|
|
40
|
+
resolve({ ok: true, latency });
|
|
41
|
+
});
|
|
42
|
+
socket.on('timeout', () => {
|
|
43
|
+
socket.destroy();
|
|
44
|
+
resolve({ ok: false, error: 'Connection timeout' });
|
|
45
|
+
});
|
|
46
|
+
socket.on('error', (err) => {
|
|
47
|
+
resolve({ ok: false, error: err.message });
|
|
48
|
+
});
|
|
49
|
+
socket.connect(port, host);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function testTls(host, port, timeout = 5000) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const socket = tls.connect({
|
|
55
|
+
host,
|
|
56
|
+
port,
|
|
57
|
+
timeout,
|
|
58
|
+
rejectUnauthorized: false, // We want to check even self-signed certs
|
|
59
|
+
});
|
|
60
|
+
socket.on('secureConnect', () => {
|
|
61
|
+
const cert = socket.getPeerCertificate();
|
|
62
|
+
socket.destroy();
|
|
63
|
+
if (cert && cert.issuer) {
|
|
64
|
+
resolve({
|
|
65
|
+
ok: true,
|
|
66
|
+
issuer: cert.issuer.O || cert.issuer.CN || 'Unknown',
|
|
67
|
+
expiry: cert.valid_to,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
resolve({ ok: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
socket.on('timeout', () => {
|
|
75
|
+
socket.destroy();
|
|
76
|
+
resolve({ ok: false, error: 'TLS timeout' });
|
|
77
|
+
});
|
|
78
|
+
socket.on('error', (err) => {
|
|
79
|
+
// Not all services support TLS
|
|
80
|
+
if (err.message.includes('wrong version') || err.message.includes('ECONNRESET')) {
|
|
81
|
+
resolve({ ok: false, error: 'Not TLS' });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
resolve({ ok: false, error: err.message });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function testHttp(url, timeout = 5000) {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
const parsedUrl = new url_1.URL(url);
|
|
93
|
+
const client = parsedUrl.protocol === 'https:' ? https : http;
|
|
94
|
+
const req = client.request(url, {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
timeout,
|
|
97
|
+
rejectUnauthorized: false,
|
|
98
|
+
}, (res) => {
|
|
99
|
+
const latency = Date.now() - start;
|
|
100
|
+
res.destroy();
|
|
101
|
+
resolve({
|
|
102
|
+
ok: res.statusCode !== undefined && res.statusCode < 500,
|
|
103
|
+
status: res.statusCode,
|
|
104
|
+
latency,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
req.on('timeout', () => {
|
|
108
|
+
req.destroy();
|
|
109
|
+
resolve({ ok: false, error: 'HTTP timeout' });
|
|
110
|
+
});
|
|
111
|
+
req.on('error', (err) => {
|
|
112
|
+
resolve({ ok: false, error: err.message });
|
|
113
|
+
});
|
|
114
|
+
req.end();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function parseTarget(target) {
|
|
118
|
+
// Handle URLs
|
|
119
|
+
if (target.startsWith('http://') || target.startsWith('https://')) {
|
|
120
|
+
const url = new url_1.URL(target);
|
|
121
|
+
const port = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80);
|
|
122
|
+
return { host: url.hostname, port, isHttp: true, url: target };
|
|
123
|
+
}
|
|
124
|
+
// Handle host:port
|
|
125
|
+
const parts = target.split(':');
|
|
126
|
+
if (parts.length === 2) {
|
|
127
|
+
return { host: parts[0], port: parseInt(parts[1], 10), isHttp: false };
|
|
128
|
+
}
|
|
129
|
+
// Default to port 443
|
|
130
|
+
return { host: target, port: 443, isHttp: false };
|
|
131
|
+
}
|
|
132
|
+
async function runTest(target) {
|
|
133
|
+
const { host, port, isHttp, url } = parseTarget(target);
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(`${c.bold}Testing ${c.cyan}${target}${c.reset}`);
|
|
136
|
+
console.log(`${c.gray}────────────────────────────────────${c.reset}`);
|
|
137
|
+
console.log();
|
|
138
|
+
// TCP test
|
|
139
|
+
process.stdout.write(` TCP `);
|
|
140
|
+
const tcpResult = await testTcp(host, port);
|
|
141
|
+
if (tcpResult.ok) {
|
|
142
|
+
console.log(`${ok} ${c.dim}${tcpResult.latency}ms${c.reset}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(`${fail} ${c.red}${tcpResult.error}${c.reset}`);
|
|
146
|
+
// Can't continue if TCP fails
|
|
147
|
+
printCta(false);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// TLS test (only for likely TLS ports or explicit https)
|
|
151
|
+
const tlsPorts = [443, 8443, 5432, 6379, 27017, 9200];
|
|
152
|
+
const shouldTestTls = isHttp || tlsPorts.includes(port) || port > 1024;
|
|
153
|
+
let tlsResult = null;
|
|
154
|
+
if (shouldTestTls) {
|
|
155
|
+
process.stdout.write(` TLS `);
|
|
156
|
+
tlsResult = await testTls(host, port);
|
|
157
|
+
if (tlsResult.ok) {
|
|
158
|
+
const extra = tlsResult.issuer ? `${c.dim}(${tlsResult.issuer})${c.reset}` : '';
|
|
159
|
+
console.log(`${ok} ${extra}`);
|
|
160
|
+
}
|
|
161
|
+
else if (tlsResult.error === 'Not TLS') {
|
|
162
|
+
console.log(`${c.gray}– No TLS${c.reset}`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(`${warn} ${c.yellow}${tlsResult.error}${c.reset}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// HTTP test (if it looks like HTTP)
|
|
169
|
+
let httpResult = null;
|
|
170
|
+
const httpUrl = url || (tlsResult?.ok ? `https://${host}:${port}` : `http://${host}:${port}`);
|
|
171
|
+
if (isHttp || [80, 443, 8080, 8443, 3000, 5000, 8000].includes(port)) {
|
|
172
|
+
process.stdout.write(` HTTP `);
|
|
173
|
+
httpResult = await testHttp(httpUrl);
|
|
174
|
+
if (httpResult.ok) {
|
|
175
|
+
console.log(`${ok} ${c.dim}${httpResult.status} (${httpResult.latency}ms)${c.reset}`);
|
|
176
|
+
}
|
|
177
|
+
else if (httpResult.error) {
|
|
178
|
+
console.log(`${c.gray}– ${httpResult.error}${c.reset}`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(`${warn} ${c.yellow}${httpResult.status}${c.reset}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Latency summary
|
|
185
|
+
const latency = tcpResult.latency || httpResult?.latency;
|
|
186
|
+
if (latency) {
|
|
187
|
+
console.log();
|
|
188
|
+
console.log(` Latency ${c.bold}${latency}ms${c.reset}`);
|
|
189
|
+
}
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(`${c.gray}────────────────────────────────────${c.reset}`);
|
|
192
|
+
const allOk = tcpResult.ok && (tlsResult === null || tlsResult.ok || tlsResult.error === 'Not TLS');
|
|
193
|
+
if (allOk) {
|
|
194
|
+
console.log(` ${c.green}${c.bold}REACHABLE${c.reset}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(` ${c.yellow}${c.bold}ISSUES DETECTED${c.reset}`);
|
|
198
|
+
}
|
|
199
|
+
printCta(allOk);
|
|
200
|
+
}
|
|
201
|
+
function printCta(success) {
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(`${c.gray}────────────────────────────────────${c.reset}`);
|
|
204
|
+
console.log();
|
|
205
|
+
if (success) {
|
|
206
|
+
console.log(` ${c.bold}Want to share this securely?${c.reset}`);
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(` ${c.cyan}curl -fsSL https://privateconnect.co/install.sh | bash${c.reset}`);
|
|
209
|
+
console.log(` ${c.cyan}connect up${c.reset}`);
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(` Then: ${c.bold}connect <target> --share${c.reset}`);
|
|
212
|
+
console.log(` → Get a shareable link anyone can use`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.log(` ${c.bold}Need help debugging?${c.reset}`);
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(` ${c.cyan}https://privateconnect.co${c.reset}`);
|
|
218
|
+
}
|
|
219
|
+
console.log();
|
|
220
|
+
}
|
|
221
|
+
function printHelp() {
|
|
222
|
+
console.log(`
|
|
223
|
+
${c.bold}Private Connect${c.reset} - Test connectivity to any service
|
|
224
|
+
|
|
225
|
+
${c.bold}Usage:${c.reset}
|
|
226
|
+
npx private-connect test <target>
|
|
227
|
+
|
|
228
|
+
${c.bold}Examples:${c.reset}
|
|
229
|
+
npx private-connect test vault.internal:8200
|
|
230
|
+
npx private-connect test https://api.example.com
|
|
231
|
+
npx private-connect test postgres.prod:5432
|
|
232
|
+
|
|
233
|
+
${c.bold}What it checks:${c.reset}
|
|
234
|
+
• TCP reachability
|
|
235
|
+
• TLS validation
|
|
236
|
+
• HTTP response (if applicable)
|
|
237
|
+
• Latency
|
|
238
|
+
|
|
239
|
+
No signup. No account. Just diagnostics.
|
|
240
|
+
|
|
241
|
+
${c.dim}For full features: https://privateconnect.co${c.reset}
|
|
242
|
+
`);
|
|
243
|
+
}
|
|
244
|
+
// Main
|
|
245
|
+
const args = process.argv.slice(2);
|
|
246
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
247
|
+
printHelp();
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
if (args[0] === 'test') {
|
|
251
|
+
if (!args[1]) {
|
|
252
|
+
console.error(`${c.red}Error: Target required${c.reset}`);
|
|
253
|
+
console.error(`Usage: npx private-connect test <host:port>`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
runTest(args[1]).catch(console.error);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// Default to test if just a target is provided
|
|
260
|
+
runTest(args[0]).catch(console.error);
|
|
261
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "private-connect",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Test connectivity to any service. No signup required.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"private-connect": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc -w"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"connectivity",
|
|
18
|
+
"diagnostics",
|
|
19
|
+
"network",
|
|
20
|
+
"tunnel",
|
|
21
|
+
"private-connect"
|
|
22
|
+
],
|
|
23
|
+
"license": "FSL-1.1-MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/treadiehq/private-connect.git",
|
|
27
|
+
"directory": "packages/cli"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|