ncc-client-poc 0.1.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/README.md +91 -0
- package/dist/assets/index-B6UyNgvq.css +1 -0
- package/dist/assets/index-CcCz_74v.js +251 -0
- package/dist/assets/pure-3kflLNsq.js +1 -0
- package/dist/index.html +16 -0
- package/package.json +78 -0
- package/scripts/check-bridge.js +35 -0
- package/scripts/check-local-relay.js +87 -0
- package/scripts/probe-3jlk.js +45 -0
- package/scripts/scan-onion-ports.js +51 -0
- package/scripts/test-relay-alive.js +25 -0
- package/scripts/test-tor-http.js +29 -0
- package/scripts/test-tor-plumbing.js +32 -0
- package/scripts/tor-bridge.js +144 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{s as a,b as n,a as o}from"./index-CcCz_74v.js";var i=Symbol("verified"),l=r=>r instanceof Object;function y(r){if(!l(r)||typeof r.kind!="number"||typeof r.content!="string"||typeof r.created_at!="number"||typeof r.pubkey!="string"||!r.pubkey.match(/^[a-f0-9]{64}$/)||!Array.isArray(r.tags))return!1;for(let t=0;t<r.tags.length;t++){let e=r.tags[t];if(!Array.isArray(e))return!1;for(let s=0;s<e.length;s++)if(typeof e[s]!="string")return!1}return!0}new TextDecoder("utf-8");var c=new TextEncoder,g=class{generateSecretKey(){return a.utils.randomPrivateKey()}getPublicKey(r){return n(a.getPublicKey(r))}finalizeEvent(r,t){const e=r;return e.pubkey=n(a.getPublicKey(t)),e.id=u(e),e.sig=n(a.sign(u(e),t)),e[i]=!0,e}verifyEvent(r){if(typeof r[i]=="boolean")return r[i];const t=u(r);if(t!==r.id)return r[i]=!1,!1;try{const e=a.verify(r.sig,t,r.pubkey);return r[i]=e,e}catch{return r[i]=!1,!1}}};function b(r){if(!y(r))throw new Error("can't serialize event with wrong or missing properties");return JSON.stringify([0,r.pubkey,r.created_at,r.kind,r.tags,r.content])}function u(r){let t=o(c.encode(b(r)));return n(t)}var f=new g;f.generateSecretKey;f.getPublicKey;var p=f.finalizeEvent;f.verifyEvent;export{p as finalizeEvent,u as getEventHash,b as serializeEvent,y as validateEvent,i as verifiedSymbol};
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<!-- Security: Mitigate side-channel and XSS attacks -->
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' ws: wss: http: https:;">
|
|
9
|
+
<title>NCC Client PoC</title>
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CcCz_74v.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B6UyNgvq.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ncc-client-poc",
|
|
3
|
+
"description": "Proof of Concept client for Nostr Community Conventions (NCC). Implements decentralized infrastructure management and discovery without DNS.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.html",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"scripts",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/mattthomson/ncc-client.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"nostr",
|
|
21
|
+
"ncc",
|
|
22
|
+
"decentralized",
|
|
23
|
+
"dns-less",
|
|
24
|
+
"infrastructure",
|
|
25
|
+
"tor",
|
|
26
|
+
"onion"
|
|
27
|
+
],
|
|
28
|
+
"author": "Matt Thomson",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "concurrently \"vite --host\" \"node scripts/tor-bridge.js\"",
|
|
32
|
+
"build": "tsc && vite build",
|
|
33
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
34
|
+
"preview": "vite preview",
|
|
35
|
+
"test": "vitest",
|
|
36
|
+
"bridge": "node scripts/tor-bridge.js",
|
|
37
|
+
"check-bridge": "node scripts/check-bridge.js",
|
|
38
|
+
"check-local": "node scripts/check-local-relay.js"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"buffer": "^6.0.3",
|
|
42
|
+
"clsx": "^2.1.0",
|
|
43
|
+
"lucide-react": "^0.344.0",
|
|
44
|
+
"ncc-02-js": "file:../ncc-02/ncc-02-js",
|
|
45
|
+
"ncc-05-js": "file:../ncc-05/ncc-05-js",
|
|
46
|
+
"nostr-tools": "^2.1.5",
|
|
47
|
+
"qrcode.react": "^4.2.0",
|
|
48
|
+
"react": "^18.2.0",
|
|
49
|
+
"react-dom": "^18.2.0",
|
|
50
|
+
"react-router-dom": "^6.22.1",
|
|
51
|
+
"socks-proxy-agent": "^8.0.5",
|
|
52
|
+
"tailwind-merge": "^2.2.1",
|
|
53
|
+
"vite-plugin-node-polyfills": "^0.24.0",
|
|
54
|
+
"ws": "^8.18.3"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
58
|
+
"@testing-library/react": "^16.3.1",
|
|
59
|
+
"@testing-library/user-event": "^14.6.1",
|
|
60
|
+
"@types/react": "^18.2.56",
|
|
61
|
+
"@types/react-dom": "^18.2.19",
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
|
63
|
+
"@typescript-eslint/parser": "^7.0.2",
|
|
64
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
65
|
+
"autoprefixer": "^10.4.17",
|
|
66
|
+
"concurrently": "^9.2.1",
|
|
67
|
+
"daisyui": "^4.7.2",
|
|
68
|
+
"eslint": "^8.56.0",
|
|
69
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
70
|
+
"eslint-plugin-react-refresh": "^0.4.5",
|
|
71
|
+
"jsdom": "^27.4.0",
|
|
72
|
+
"postcss": "^8.4.35",
|
|
73
|
+
"tailwindcss": "^3.4.1",
|
|
74
|
+
"typescript": "^5.2.2",
|
|
75
|
+
"vite": "^5.1.4",
|
|
76
|
+
"vitest": "^4.0.16"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
|
|
3
|
+
const BRIDGE_URL = 'ws://localhost:3001?target=ws://rk3v46dbyw3ns3rtuy6mw4hlgpaf2ot6xtcaqbqmhd4xje7hluh7loqd.onion:80';
|
|
4
|
+
|
|
5
|
+
console.log('🔍 Checking Tor Bridge on localhost:3001...');
|
|
6
|
+
|
|
7
|
+
const ws = new WebSocket(BRIDGE_URL);
|
|
8
|
+
|
|
9
|
+
let timeout = setTimeout(() => {
|
|
10
|
+
console.error('❌ Timeout: Bridge did not respond. Is it running? (npm run dev or npm run bridge)');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}, 5000);
|
|
13
|
+
|
|
14
|
+
ws.on('open', () => {
|
|
15
|
+
console.log('✅ Bridge Connection Open!');
|
|
16
|
+
console.log(' Note: This only confirms the bridge is reachable.');
|
|
17
|
+
console.log(' If the onion relay is down or Tor is off, you will see a Remote Error soon.');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
ws.on('message', (data) => {
|
|
21
|
+
console.log('📥 Received data from bridge:', data.toString());
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
ws.on('error', (err) => {
|
|
25
|
+
console.error('❌ Bridge Error:', err.message);
|
|
26
|
+
if (err.message.includes('ECONNREFUSED')) {
|
|
27
|
+
console.error(' Hint: The bridge server is not running.');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
ws.on('close', () => {
|
|
32
|
+
console.log('🚪 Bridge Connection Closed.');
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
|
|
4
|
+
const LOCAL_RELAY_WS = 'ws://localhost:8081';
|
|
5
|
+
const LOCAL_RELAY_HTTP = 'http://localhost:8081';
|
|
6
|
+
|
|
7
|
+
console.log(`🔍 Checking Local Relay...`);
|
|
8
|
+
console.log(`🌐 WebSocket: ${LOCAL_RELAY_WS}`);
|
|
9
|
+
console.log(`📄 HTTP Info: ${LOCAL_RELAY_HTTP}`);
|
|
10
|
+
|
|
11
|
+
// 1. Check HTTP / NIP-11
|
|
12
|
+
const checkHttp = () => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const options = {
|
|
15
|
+
headers: { 'Accept': 'application/nostr+json' },
|
|
16
|
+
timeout: 2000
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const req = http.get(LOCAL_RELAY_HTTP, options, (res) => {
|
|
20
|
+
console.log(`✅ HTTP Status: ${res.statusCode}`);
|
|
21
|
+
let body = '';
|
|
22
|
+
res.on('data', chunk => body += chunk);
|
|
23
|
+
res.on('end', () => {
|
|
24
|
+
if (res.headers['content-type']?.includes('application/nostr+json')) {
|
|
25
|
+
console.log(`✅ NIP-11 Supported!`);
|
|
26
|
+
try {
|
|
27
|
+
const info = JSON.parse(body);
|
|
28
|
+
console.log(` Name: ${info.name || 'Unknown'}`);
|
|
29
|
+
console.log(` Software: ${info.software || 'Unknown'}`);
|
|
30
|
+
} catch(e) {}
|
|
31
|
+
}
|
|
32
|
+
resolve(true);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
req.on('error', (err) => {
|
|
37
|
+
console.error(`❌ HTTP Error: ${err.message}`);
|
|
38
|
+
resolve(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
req.on('timeout', () => {
|
|
42
|
+
console.error(`❌ HTTP Timeout`);
|
|
43
|
+
req.destroy();
|
|
44
|
+
resolve(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 2. Check WebSocket
|
|
50
|
+
const checkWs = () => {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const ws = new WebSocket(LOCAL_RELAY_WS);
|
|
53
|
+
|
|
54
|
+
const timeout = setTimeout(() => {
|
|
55
|
+
console.error('❌ WebSocket Timeout');
|
|
56
|
+
ws.terminate();
|
|
57
|
+
resolve(false);
|
|
58
|
+
}, 3000);
|
|
59
|
+
|
|
60
|
+
ws.on('open', () => {
|
|
61
|
+
console.log('✅ WebSocket Connected!');
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
ws.close();
|
|
64
|
+
resolve(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
ws.on('error', (err) => {
|
|
68
|
+
console.error(`❌ WebSocket Error: ${err.message}`);
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
resolve(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
async function run() {
|
|
76
|
+
const httpOk = await checkHttp();
|
|
77
|
+
const wsOk = await checkWs();
|
|
78
|
+
|
|
79
|
+
if (httpOk && wsOk) {
|
|
80
|
+
console.log('\n🌟 Local Relay is HEALTHY');
|
|
81
|
+
} else {
|
|
82
|
+
console.log('\n⚠️ Local Relay has ISSUES');
|
|
83
|
+
}
|
|
84
|
+
process.exit(httpOk && wsOk ? 0 : 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
run();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
|
|
4
|
+
const ONION_HOST = '3jlkvosxtic7q3ek3de276zwattupyoohn6u2fyr6asbc2djq6k7ehqd.onion';
|
|
5
|
+
const PORTS = [80, 443, 8080, 8081];
|
|
6
|
+
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
|
7
|
+
|
|
8
|
+
console.log(`🔎 Probing ports on ${ONION_HOST}...`);
|
|
9
|
+
|
|
10
|
+
const agent = new SocksProxyAgent(TOR_PROXY);
|
|
11
|
+
|
|
12
|
+
async function checkPort(port) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const url = `ws://${ONION_HOST}:${port}`;
|
|
15
|
+
console.log(` Trying port ${port}...`);
|
|
16
|
+
|
|
17
|
+
const ws = new WebSocket(url, { agent, handshakeTimeout: 15000 });
|
|
18
|
+
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
ws.terminate();
|
|
21
|
+
resolve({ port, status: 'TIMEOUT' });
|
|
22
|
+
}, 17000);
|
|
23
|
+
|
|
24
|
+
ws.on('open', () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
ws.close();
|
|
27
|
+
resolve({ port, status: 'OPEN' });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
ws.on('error', (err) => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
resolve({ port, status: 'ERROR', error: err.message });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function run() {
|
|
38
|
+
for (const port of PORTS) {
|
|
39
|
+
const res = await checkPort(port);
|
|
40
|
+
console.log(` ${res.status === 'OPEN' ? '✅' : '❌'} Port ${res.port}: ${res.status} ${res.error ? '(' + res.error + ')' : ''}`);
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
run();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
|
|
4
|
+
const ONION_HOST = 'rk3v46dbyw3ns3rtuy6mw4hlgpaf2ot6xtcaqbqmhd4xje7hluh7loqd.onion';
|
|
5
|
+
const PORTS = [80, 8080, 8081, 443];
|
|
6
|
+
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
|
7
|
+
|
|
8
|
+
console.log(`🔎 Scanning common ports on ${ONION_HOST}...`);
|
|
9
|
+
|
|
10
|
+
const agent = new SocksProxyAgent(TOR_PROXY);
|
|
11
|
+
|
|
12
|
+
async function checkPort(port) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const url = `ws://${ONION_HOST}:${port}`;
|
|
15
|
+
console.log(` Trying port ${port}...`);
|
|
16
|
+
|
|
17
|
+
const ws = new WebSocket(url, { agent, handshakeTimeout: 10000 });
|
|
18
|
+
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
ws.terminate();
|
|
21
|
+
resolve({ port, status: 'TIMEOUT' });
|
|
22
|
+
}, 12000);
|
|
23
|
+
|
|
24
|
+
ws.on('open', () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
ws.close();
|
|
27
|
+
resolve({ port, status: 'OPEN' });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
ws.on('error', (err) => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
resolve({ port, status: 'CLOSED', error: err.message });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function run() {
|
|
38
|
+
const results = [];
|
|
39
|
+
for (const port of PORTS) {
|
|
40
|
+
results.push(await checkPort(port));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n📊 Scan Results:');
|
|
44
|
+
results.forEach(r => {
|
|
45
|
+
const icon = r.status === 'OPEN' ? '✅' : '❌';
|
|
46
|
+
console.log(`${icon} Port ${r.port}: ${r.status} ${r.error ? '(' + r.error + ')' : ''}`);
|
|
47
|
+
});
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
run();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
|
|
4
|
+
const RELAY_ONION = 'http://rk3v46dbyw3ns3rtuy6mw4hlgpaf2ot6xtcaqbqmhd4xje7hluh7loqd.onion/';
|
|
5
|
+
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
|
6
|
+
|
|
7
|
+
console.log(`🕵️ Checking if Relay is alive: ${RELAY_ONION}`);
|
|
8
|
+
|
|
9
|
+
const agent = new SocksProxyAgent(TOR_PROXY);
|
|
10
|
+
|
|
11
|
+
const req = http.get(RELAY_ONION, { agent, timeout: 30000 }, (res) => {
|
|
12
|
+
console.log(`✅ Status: ${res.statusCode}`);
|
|
13
|
+
process.exit(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
req.on('error', (err) => {
|
|
17
|
+
console.error(`❌ Relay Unreachable:`, err.message);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
req.on('timeout', () => {
|
|
22
|
+
console.error(`⌛ Timeout: Relay did not respond within 30s.`);
|
|
23
|
+
req.destroy();
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
|
|
4
|
+
const ONION_URL = 'http://7gwqlr2fu4uu77qnymfxkjtmfc6ddbz7riy2s7n3naavv5ouymx5ivqd.onion/';
|
|
5
|
+
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
|
6
|
+
|
|
7
|
+
console.log(`🌐 Testing Tor Connectivity...`);
|
|
8
|
+
console.log(`🔗 Target: ${ONION_URL}`);
|
|
9
|
+
console.log(`🕵️ Proxy: ${TOR_PROXY}`);
|
|
10
|
+
|
|
11
|
+
const agent = new SocksProxyAgent(TOR_PROXY);
|
|
12
|
+
|
|
13
|
+
http.get(ONION_URL, { agent }, (res) => {
|
|
14
|
+
console.log(`✅ Success! Response Code: ${res.statusCode}`);
|
|
15
|
+
console.log(`📝 Headers:`, res.headers['server'] || 'No server header');
|
|
16
|
+
|
|
17
|
+
let data = '';
|
|
18
|
+
res.on('data', (chunk) => data += chunk);
|
|
19
|
+
res.on('end', () => {
|
|
20
|
+
console.log(`📄 Content length received: ${data.length} bytes`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
}).on('error', (err) => {
|
|
24
|
+
console.error(`❌ Connection Failed:`, err.message);
|
|
25
|
+
if (err.message.includes('ECONNREFUSED')) {
|
|
26
|
+
console.error(` Hint: Is your system Tor service running on port 9050?`);
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
|
|
4
|
+
const PUBLIC_RELAY = 'wss://relay.damus.io';
|
|
5
|
+
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
|
6
|
+
|
|
7
|
+
console.log(`🕵️ Testing Bridge-to-Proxy plumbing...`);
|
|
8
|
+
console.log(`🔗 Target: ${PUBLIC_RELAY} (via Tor)`);
|
|
9
|
+
|
|
10
|
+
const agent = new SocksProxyAgent(TOR_PROXY);
|
|
11
|
+
|
|
12
|
+
const ws = new WebSocket(PUBLIC_RELAY, { agent, handshakeTimeout: 20000 });
|
|
13
|
+
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
console.error('❌ Timeout: No response from relay via Tor.');
|
|
16
|
+
ws.terminate();
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}, 25000);
|
|
19
|
+
|
|
20
|
+
ws.on('open', () => {
|
|
21
|
+
console.log('✅ Success! Bridge can reach public relays via Tor proxy.');
|
|
22
|
+
console.log(' This confirms the connection to port 9050 is working.');
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
ws.close();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
ws.on('error', (err) => {
|
|
29
|
+
console.error(`❌ Connection Failed:`, err.message);
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
|
|
5
|
+
// Configuration
|
|
6
|
+
const BRIDGE_PORT = 3001;
|
|
7
|
+
const BRIDGE_HOST = '127.0.0.1';
|
|
8
|
+
const TOR_SOCKS_PORT = 9050; // Standard Tor port
|
|
9
|
+
const TOR_HOST = '127.0.0.1';
|
|
10
|
+
|
|
11
|
+
console.log(`\n Onion NCC Tor Bridge starting...`);
|
|
12
|
+
console.log(` Listening at: ws://${BRIDGE_HOST}:${BRIDGE_PORT}`);
|
|
13
|
+
console.log(` Tor SOCKS: socks5h://${TOR_HOST}:${TOR_SOCKS_PORT}`);
|
|
14
|
+
|
|
15
|
+
// Create HTTP server to upgrade requests
|
|
16
|
+
const server = http.createServer();
|
|
17
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
18
|
+
|
|
19
|
+
// SOCKS Agent - Use socks5h to ensure remote DNS resolution for .onion
|
|
20
|
+
const agent = new SocksProxyAgent(`socks5h://${TOR_HOST}:${TOR_SOCKS_PORT}`);
|
|
21
|
+
|
|
22
|
+
server.on('upgrade', (request, socket, head) => {
|
|
23
|
+
const reqUrl = new URL(request.url || '', `http://${request.headers.host}`);
|
|
24
|
+
const target = reqUrl.searchParams.get('target');
|
|
25
|
+
|
|
26
|
+
if (!target) {
|
|
27
|
+
console.error(`[Bridge] Upgrade failed: Missing 'target' query parameter.`);
|
|
28
|
+
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
|
|
29
|
+
socket.destroy();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
34
|
+
wss.emit('connection', ws, request);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
wss.on('connection', (clientWs, req) => {
|
|
39
|
+
const reqUrl = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
|
|
40
|
+
const targetUrl = reqUrl.searchParams.get('target');
|
|
41
|
+
|
|
42
|
+
if (targetUrl === 'internal-ping') {
|
|
43
|
+
clientWs.send('pong');
|
|
44
|
+
clientWs.close();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!targetUrl) {
|
|
49
|
+
console.error("[Bridge] Connection failed: No target URL provided.");
|
|
50
|
+
clientWs.close();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let isClosed = false;
|
|
55
|
+
let remoteOpen = false;
|
|
56
|
+
const messageBuffer = [];
|
|
57
|
+
|
|
58
|
+
let parsedTarget;
|
|
59
|
+
try {
|
|
60
|
+
parsedTarget = new URL(targetUrl);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(`[Bridge] Invalid target URL: ${targetUrl}`);
|
|
63
|
+
clientWs.close();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`[Bridge] [${new Date().toLocaleTimeString()}] Tunneling: Client -> ${targetUrl}`);
|
|
68
|
+
|
|
69
|
+
// Connect to the Onion Relay via SOCKS Agent
|
|
70
|
+
const remoteWs = new WebSocket(targetUrl, {
|
|
71
|
+
agent,
|
|
72
|
+
handshakeTimeout: 60000,
|
|
73
|
+
headers: {
|
|
74
|
+
'Host': parsedTarget.host,
|
|
75
|
+
'User-Agent': 'NCC-Tor-Bridge/1.0',
|
|
76
|
+
'Origin': 'http://' + parsedTarget.host // Match the host to satisfy origin checks
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(`[Bridge] Handshake initiated...`);
|
|
81
|
+
|
|
82
|
+
// Handle data from Onion Relay -> Browser
|
|
83
|
+
remoteWs.on('message', (data, isBinary) => {
|
|
84
|
+
if (clientWs.readyState === WebSocket.OPEN) {
|
|
85
|
+
clientWs.send(data, { binary: isBinary });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
remoteWs.on('open', () => {
|
|
90
|
+
remoteOpen = true;
|
|
91
|
+
console.log(`[Bridge] ✅ Tunnel Established to ${targetUrl}`);
|
|
92
|
+
|
|
93
|
+
// Flush buffered messages
|
|
94
|
+
console.log(`[Bridge] 📤 Flushed ${messageBuffer.length} buffered messages to remote.`);
|
|
95
|
+
while (messageBuffer.length > 0) {
|
|
96
|
+
const msg = messageBuffer.shift();
|
|
97
|
+
remoteWs.send(msg.data, { binary: msg.isBinary });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
remoteWs.on('close', (code, reason) => {
|
|
102
|
+
if (!isClosed) {
|
|
103
|
+
console.log(`[Bridge] 🔌 Remote Closed (Code: ${code}, Reason: ${reason || 'none'})`);
|
|
104
|
+
clientWs.close();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
remoteWs.on('error', (err) => {
|
|
109
|
+
console.error(`[Bridge] ❌ Remote Error:`, err.message);
|
|
110
|
+
if (clientWs.readyState === WebSocket.OPEN) {
|
|
111
|
+
clientWs.close();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Handle data from Browser -> Onion Relay
|
|
116
|
+
clientWs.on('message', (data, isBinary) => {
|
|
117
|
+
const msgStr = data.toString();
|
|
118
|
+
console.log(`[Bridge] << Client Message: ${msgStr.slice(0, 100)}${msgStr.length > 100 ? '...' : ''}`);
|
|
119
|
+
|
|
120
|
+
if (remoteOpen && remoteWs.readyState === WebSocket.OPEN) {
|
|
121
|
+
remoteWs.send(data, { binary: isBinary });
|
|
122
|
+
} else {
|
|
123
|
+
// Buffer the message until the remote is ready
|
|
124
|
+
console.log(`[Bridge] 📥 Buffering client message...`);
|
|
125
|
+
messageBuffer.push({ data, isBinary });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
clientWs.on('close', () => {
|
|
130
|
+
isClosed = true;
|
|
131
|
+
console.log(`[Bridge] 👤 Client Disconnected.`);
|
|
132
|
+
remoteWs.close();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
clientWs.on('error', (err) => {
|
|
136
|
+
console.error(`[Bridge] ❌ Client Error:`, err.message);
|
|
137
|
+
remoteWs.close();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
server.listen(BRIDGE_PORT, BRIDGE_HOST, () => {
|
|
142
|
+
console.log(`✅ Bridge Ready! Use 'ws://${BRIDGE_HOST}:${BRIDGE_PORT}?target=ws://...onion'\n`);
|
|
143
|
+
});
|
|
144
|
+
|