@skxng/shp.it 1.0.2
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/bin/shpthis.js +18 -0
- package/package.json +26 -0
- package/src/client.js +124 -0
package/bin/shpthis.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const startTunnel = require('../src/client.js');
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
let port = 3000;
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
if (args[i] === '--port' || args[i] === '-p') {
|
|
9
|
+
port = parseInt(args[i + 1], 10);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (isNaN(port)) {
|
|
14
|
+
console.error("shpit: invalid port specified");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
startTunnel(port);
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skxng/shp.it",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Share your local dev server with anyone, instantly.",
|
|
5
|
+
"main": "src/client.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shpthis": "./bin/shpthis.js",
|
|
8
|
+
"shpit": "./bin/shpthis.js",
|
|
9
|
+
"shp.it": "./bin/shpthis.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "node ./bin/shpthis.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"ws": "^8.19.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"tunnel",
|
|
19
|
+
"localhost",
|
|
20
|
+
"ngrok",
|
|
21
|
+
"share",
|
|
22
|
+
"port"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC"
|
|
26
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
// For local testing of the Node.js Relay Server, we use localhost:8081
|
|
6
|
+
// In production, this connects to the live Render web service
|
|
7
|
+
const RELAY_HOST = process.env.RELAY_HOST || 'shpit-com.onrender.com';
|
|
8
|
+
const RELAY_URL = process.env.RELAY_SECURE === 'false' || RELAY_HOST.includes('127.0.0.1') ? `ws://${RELAY_HOST}` : `wss://${RELAY_HOST}`;
|
|
9
|
+
|
|
10
|
+
function generateId() {
|
|
11
|
+
return Math.random().toString(36).substring(2, 8);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function startTunnel(port) {
|
|
15
|
+
const tunnelId = generateId();
|
|
16
|
+
console.log(`\nš shpit CLI`);
|
|
17
|
+
console.log(`\n āø Exposing: http://127.0.0.1:${port}`);
|
|
18
|
+
console.log(` āø Connecting to relay...`);
|
|
19
|
+
|
|
20
|
+
const ws = new WebSocket(`${RELAY_URL}/register?id=${tunnelId}`);
|
|
21
|
+
|
|
22
|
+
ws.on('open', () => {
|
|
23
|
+
console.log(` ā Tunnel active!`);
|
|
24
|
+
|
|
25
|
+
const isLocal = RELAY_HOST.includes('127.0.0.1') || RELAY_HOST.includes('localhost');
|
|
26
|
+
const protocol = process.env.RELAY_SECURE || !isLocal ? 'https' : 'http';
|
|
27
|
+
|
|
28
|
+
// Host-based URL for production, or path-based fallback
|
|
29
|
+
const rawUrl = isLocal
|
|
30
|
+
? `http://127.0.0.1:8081/proxy/${tunnelId}/`
|
|
31
|
+
: `https://${RELAY_HOST}/proxy/${tunnelId}/`;
|
|
32
|
+
|
|
33
|
+
if (isLocal) {
|
|
34
|
+
console.log(`\n š Public URL: ${rawUrl}\n`);
|
|
35
|
+
} else {
|
|
36
|
+
console.log(` āø Generating short link...`);
|
|
37
|
+
https.get(`https://is.gd/create.php?format=simple&url=${encodeURIComponent(rawUrl)}`, (res) => {
|
|
38
|
+
let shortUrl = '';
|
|
39
|
+
res.on('data', chunk => shortUrl += chunk);
|
|
40
|
+
res.on('end', () => {
|
|
41
|
+
console.log(`\n š Public URL: ${shortUrl.trim()}`);
|
|
42
|
+
console.log(` (Traffic secured via shortener)`);
|
|
43
|
+
console.log(`\n [Logs]`);
|
|
44
|
+
});
|
|
45
|
+
}).on('error', () => {
|
|
46
|
+
console.log(`\n š Public URL: ${rawUrl}\n`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
ws.on('message', async (data) => {
|
|
52
|
+
try {
|
|
53
|
+
const payload = JSON.parse(data.toString());
|
|
54
|
+
|
|
55
|
+
if (payload.type === 'request') {
|
|
56
|
+
const { id, method, url, headers } = payload;
|
|
57
|
+
|
|
58
|
+
// Exclude headers that might mess up local requests
|
|
59
|
+
const cleanHeaders = { ...headers };
|
|
60
|
+
delete cleanHeaders['host'];
|
|
61
|
+
delete cleanHeaders['cf-connecting-ip'];
|
|
62
|
+
delete cleanHeaders['x-forwarded-for'];
|
|
63
|
+
delete cleanHeaders['x-forwarded-proto'];
|
|
64
|
+
|
|
65
|
+
const localReqOpts = {
|
|
66
|
+
hostname: '127.0.0.1',
|
|
67
|
+
port: port,
|
|
68
|
+
path: url,
|
|
69
|
+
method: method,
|
|
70
|
+
headers: cleanHeaders,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const localReq = http.request(localReqOpts, (localRes) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
localRes.on('data', chunk => chunks.push(chunk));
|
|
76
|
+
localRes.on('end', () => {
|
|
77
|
+
const bodyBuffer = Buffer.concat(chunks);
|
|
78
|
+
const base64Body = bodyBuffer.toString('base64');
|
|
79
|
+
|
|
80
|
+
const responsePayload = {
|
|
81
|
+
type: 'response',
|
|
82
|
+
id: id,
|
|
83
|
+
status: localRes.statusCode,
|
|
84
|
+
headers: localRes.headers,
|
|
85
|
+
body: base64Body
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
ws.send(JSON.stringify(responsePayload));
|
|
89
|
+
console.log(` [${new Date().toLocaleTimeString()}] ${method} ${url} - ${localRes.statusCode}`);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
localReq.on('error', (err) => {
|
|
94
|
+
console.error(` [!] Error connecting to local port ${port}:`, err.message);
|
|
95
|
+
ws.send(JSON.stringify({
|
|
96
|
+
type: 'response',
|
|
97
|
+
id: id,
|
|
98
|
+
status: 502,
|
|
99
|
+
headers: { 'content-type': 'text/plain' },
|
|
100
|
+
body: Buffer.from(`Bad Gateway: Could not reach 127.0.0.1:${port}`).toString('base64')
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// If the incoming request had a body, we would write it here.
|
|
105
|
+
// For simplicity in this v1, we only handle GET/HEAD perfectly.
|
|
106
|
+
// To handle POST, we'd need to receive the body from the worker.
|
|
107
|
+
localReq.end();
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error('Failed to process message from relay:', err);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
ws.on('close', () => {
|
|
115
|
+
console.log(`\n ā¹ Relay connection closed.`);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
ws.on('error', (err) => {
|
|
120
|
+
console.error(`\n [!] WebSocket error:`, err.message);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = startTunnel;
|