@dainprotocol/tunnel 1.0.0 → 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/dist/client/index.d.ts +5 -2
- package/dist/client/index.js +49 -7
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +81 -32
- package/dist/server/start.js +5 -2
- package/package.json +37 -2
package/dist/client/index.d.ts
CHANGED
|
@@ -7,9 +7,12 @@ declare class DainTunnel extends EventEmitter {
|
|
|
7
7
|
private reconnectAttempts;
|
|
8
8
|
private maxReconnectAttempts;
|
|
9
9
|
private reconnectDelay;
|
|
10
|
-
|
|
10
|
+
private auth;
|
|
11
|
+
private tunnelId;
|
|
12
|
+
constructor(serverUrl: string, apiKey: string);
|
|
11
13
|
start(port: number): Promise<string>;
|
|
12
14
|
private connect;
|
|
15
|
+
private requestChallenge;
|
|
13
16
|
private handleMessage;
|
|
14
17
|
private handleRequest;
|
|
15
18
|
private forwardRequest;
|
|
@@ -19,6 +22,6 @@ declare class DainTunnel extends EventEmitter {
|
|
|
19
22
|
}
|
|
20
23
|
export { DainTunnel };
|
|
21
24
|
declare const _default: {
|
|
22
|
-
createTunnel: (serverUrl: string) => DainTunnel;
|
|
25
|
+
createTunnel: (serverUrl: string, apiKey: string) => DainTunnel;
|
|
23
26
|
};
|
|
24
27
|
export default _default;
|
package/dist/client/index.js
CHANGED
|
@@ -7,8 +7,10 @@ exports.DainTunnel = void 0;
|
|
|
7
7
|
const ws_1 = __importDefault(require("ws"));
|
|
8
8
|
const http_1 = __importDefault(require("http"));
|
|
9
9
|
const events_1 = require("events");
|
|
10
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
11
|
+
const client_1 = require("@dainprotocol/service-sdk/client");
|
|
10
12
|
class DainTunnel extends events_1.EventEmitter {
|
|
11
|
-
constructor(serverUrl) {
|
|
13
|
+
constructor(serverUrl, apiKey) {
|
|
12
14
|
super();
|
|
13
15
|
this.serverUrl = serverUrl;
|
|
14
16
|
this.ws = null;
|
|
@@ -17,6 +19,8 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
17
19
|
this.reconnectAttempts = 0;
|
|
18
20
|
this.maxReconnectAttempts = 5;
|
|
19
21
|
this.reconnectDelay = 5000;
|
|
22
|
+
this.auth = new client_1.DainClientAuth({ apiKey });
|
|
23
|
+
this.tunnelId = bs58_1.default.encode(this.auth.getPublicKey());
|
|
20
24
|
}
|
|
21
25
|
async start(port) {
|
|
22
26
|
this.port = port;
|
|
@@ -25,9 +29,17 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
25
29
|
async connect() {
|
|
26
30
|
return new Promise((resolve, reject) => {
|
|
27
31
|
this.ws = new ws_1.default(this.serverUrl);
|
|
28
|
-
this.ws.on("open", () => {
|
|
32
|
+
this.ws.on("open", async () => {
|
|
29
33
|
this.reconnectAttempts = 0;
|
|
30
|
-
|
|
34
|
+
const challenge = await this.requestChallenge();
|
|
35
|
+
const signature = this.auth.signMessage(challenge);
|
|
36
|
+
this.sendMessage({
|
|
37
|
+
type: "start",
|
|
38
|
+
port: this.port,
|
|
39
|
+
challenge,
|
|
40
|
+
signature,
|
|
41
|
+
tunnelId: this.tunnelId
|
|
42
|
+
});
|
|
31
43
|
this.emit("connected");
|
|
32
44
|
});
|
|
33
45
|
this.ws.on("message", (data) => {
|
|
@@ -51,6 +63,32 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
51
63
|
}, 5000);
|
|
52
64
|
});
|
|
53
65
|
}
|
|
66
|
+
async requestChallenge() {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
if (!this.ws) {
|
|
69
|
+
reject(new Error("WebSocket is not connected"));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.ws.send(JSON.stringify({ type: "challenge_request" }));
|
|
73
|
+
const challengeHandler = (message) => {
|
|
74
|
+
const data = JSON.parse(message);
|
|
75
|
+
if (data.type === "challenge") {
|
|
76
|
+
if (this.ws) {
|
|
77
|
+
this.ws.removeListener("message", challengeHandler);
|
|
78
|
+
}
|
|
79
|
+
resolve(data.challenge);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
this.ws.on("message", challengeHandler);
|
|
83
|
+
// Add a timeout for the challenge request
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
if (this.ws) {
|
|
86
|
+
this.ws.removeListener("message", challengeHandler);
|
|
87
|
+
}
|
|
88
|
+
reject(new Error("Challenge request timeout"));
|
|
89
|
+
}, 5000);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
54
92
|
handleMessage(message, resolve) {
|
|
55
93
|
switch (message.type) {
|
|
56
94
|
case "tunnelUrl":
|
|
@@ -75,10 +113,14 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
75
113
|
}
|
|
76
114
|
forwardRequest(request) {
|
|
77
115
|
return new Promise((resolve, reject) => {
|
|
78
|
-
const
|
|
116
|
+
const options = {
|
|
117
|
+
hostname: 'localhost',
|
|
118
|
+
port: this.port,
|
|
119
|
+
path: request.path,
|
|
79
120
|
method: request.method,
|
|
80
121
|
headers: request.headers,
|
|
81
|
-
}
|
|
122
|
+
};
|
|
123
|
+
const req = http_1.default.request(options, (res) => {
|
|
82
124
|
let body = Buffer.from([]);
|
|
83
125
|
res.on('data', (chunk) => {
|
|
84
126
|
body = Buffer.concat([body, chunk]);
|
|
@@ -97,7 +139,7 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
97
139
|
});
|
|
98
140
|
});
|
|
99
141
|
req.on('error', reject);
|
|
100
|
-
if (request.body) {
|
|
142
|
+
if (request.body && request.method !== 'GET') {
|
|
101
143
|
req.write(Buffer.from(request.body, 'base64'));
|
|
102
144
|
}
|
|
103
145
|
req.end();
|
|
@@ -129,5 +171,5 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
129
171
|
}
|
|
130
172
|
exports.DainTunnel = DainTunnel;
|
|
131
173
|
exports.default = {
|
|
132
|
-
createTunnel: (serverUrl) => new DainTunnel(serverUrl),
|
|
174
|
+
createTunnel: (serverUrl, apiKey) => new DainTunnel(serverUrl, apiKey),
|
|
133
175
|
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -6,13 +6,14 @@ declare class DainTunnelServer {
|
|
|
6
6
|
private wss;
|
|
7
7
|
private tunnels;
|
|
8
8
|
private pendingRequests;
|
|
9
|
+
private challenges;
|
|
9
10
|
constructor(hostname: string, port: number);
|
|
10
11
|
private setupExpressRoutes;
|
|
11
12
|
private setupWebSocketServer;
|
|
13
|
+
private handleChallengeRequest;
|
|
12
14
|
private handleStartMessage;
|
|
13
15
|
private handleResponseMessage;
|
|
14
16
|
private handleHttpRequest;
|
|
15
|
-
private getRequestBody;
|
|
16
17
|
private removeTunnel;
|
|
17
18
|
start(): Promise<void>;
|
|
18
19
|
stop(): Promise<void>;
|
package/dist/server/index.js
CHANGED
|
@@ -7,43 +7,92 @@ const express_1 = __importDefault(require("express"));
|
|
|
7
7
|
const http_1 = __importDefault(require("http"));
|
|
8
8
|
const ws_1 = __importDefault(require("ws"));
|
|
9
9
|
const uuid_1 = require("uuid");
|
|
10
|
+
const body_parser_1 = __importDefault(require("body-parser"));
|
|
11
|
+
const cors_1 = __importDefault(require("cors")); // Add this import
|
|
12
|
+
const client_1 = require("@dainprotocol/service-sdk/client");
|
|
13
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
10
14
|
class DainTunnelServer {
|
|
11
15
|
constructor(hostname, port) {
|
|
12
16
|
this.hostname = hostname;
|
|
13
17
|
this.port = port;
|
|
14
18
|
this.tunnels = new Map();
|
|
15
19
|
this.pendingRequests = new Map();
|
|
20
|
+
this.challenges = new Map();
|
|
16
21
|
this.app = (0, express_1.default)();
|
|
17
22
|
this.server = http_1.default.createServer(this.app);
|
|
18
23
|
this.wss = new ws_1.default.Server({ server: this.server });
|
|
24
|
+
// Use cors middleware
|
|
25
|
+
this.app.use((0, cors_1.default)());
|
|
26
|
+
// Update CORS middleware
|
|
27
|
+
this.app.use((req, res, next) => {
|
|
28
|
+
res.header("Access-Control-Allow-Origin", "*");
|
|
29
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
|
30
|
+
res.header("Access-Control-Allow-Headers", 'X-DAIN-SIGNATURE, X-DAIN-SMART-ACCOUNT-PDA, X-DAIN-AGENT-ID, X-DAIN-ORG-ID, X-DAIN-ADDRESS, X-DAIN-TIMESTAMP, Content-Type, Authorization, Accept, Origin, X-Requested-With');
|
|
31
|
+
if (req.method === "OPTIONS") {
|
|
32
|
+
return res.sendStatus(200);
|
|
33
|
+
}
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
36
|
+
// Add body-parser middleware
|
|
37
|
+
this.app.use(body_parser_1.default.raw({ type: "*/*", limit: "100mb" }));
|
|
19
38
|
this.setupExpressRoutes();
|
|
20
39
|
this.setupWebSocketServer();
|
|
21
40
|
}
|
|
22
41
|
setupExpressRoutes() {
|
|
23
|
-
this.app.use(
|
|
42
|
+
this.app.use("/:tunnelId", this.handleHttpRequest.bind(this));
|
|
24
43
|
}
|
|
25
44
|
setupWebSocketServer() {
|
|
26
|
-
this.wss.on(
|
|
27
|
-
console.log(
|
|
28
|
-
ws.on(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
this.wss.on("connection", (ws) => {
|
|
46
|
+
console.log("New WebSocket connection");
|
|
47
|
+
ws.on("message", (message) => {
|
|
48
|
+
try {
|
|
49
|
+
const data = JSON.parse(message);
|
|
50
|
+
if (data.type === "challenge_request") {
|
|
51
|
+
this.handleChallengeRequest(ws);
|
|
52
|
+
}
|
|
53
|
+
else if (data.type === "start") {
|
|
54
|
+
this.handleStartMessage(ws, data);
|
|
55
|
+
}
|
|
56
|
+
else if (data.type === "response") {
|
|
57
|
+
this.handleResponseMessage(data);
|
|
58
|
+
}
|
|
32
59
|
}
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("Error processing message:", error);
|
|
62
|
+
ws.close(1008, "Invalid message");
|
|
35
63
|
}
|
|
36
64
|
});
|
|
37
|
-
ws.on(
|
|
65
|
+
ws.on("close", () => {
|
|
38
66
|
this.removeTunnel(ws);
|
|
39
67
|
});
|
|
40
68
|
});
|
|
41
69
|
}
|
|
70
|
+
handleChallengeRequest(ws) {
|
|
71
|
+
const challenge = (0, uuid_1.v4)();
|
|
72
|
+
const challengeObj = { ws, challenge };
|
|
73
|
+
this.challenges.set(challenge, challengeObj);
|
|
74
|
+
ws.send(JSON.stringify({ type: "challenge", challenge }));
|
|
75
|
+
}
|
|
42
76
|
handleStartMessage(ws, data) {
|
|
43
|
-
const tunnelId =
|
|
77
|
+
const { challenge, signature, tunnelId } = data;
|
|
78
|
+
const challengeObj = this.challenges.get(challenge);
|
|
79
|
+
if (!challengeObj || challengeObj.ws !== ws) {
|
|
80
|
+
ws.close(1008, "Invalid challenge");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.challenges.delete(challenge);
|
|
84
|
+
const publicKey = bs58_1.default.decode(tunnelId);
|
|
85
|
+
if (!client_1.DainClientAuth.verifyMessage(challenge, signature, publicKey)) {
|
|
86
|
+
ws.close(1008, "Invalid signature");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
44
89
|
this.tunnels.set(tunnelId, { id: tunnelId, ws });
|
|
45
|
-
|
|
46
|
-
|
|
90
|
+
let tunnelUrl = `${this.hostname}`;
|
|
91
|
+
if (process.env.SKIP_PORT !== "true") {
|
|
92
|
+
tunnelUrl += `:${this.port}`;
|
|
93
|
+
}
|
|
94
|
+
tunnelUrl += `/${tunnelId}`;
|
|
95
|
+
ws.send(JSON.stringify({ type: "tunnelUrl", url: tunnelUrl }));
|
|
47
96
|
console.log(`New tunnel created: ${tunnelUrl}`);
|
|
48
97
|
}
|
|
49
98
|
handleResponseMessage(data) {
|
|
@@ -52,12 +101,13 @@ class DainTunnelServer {
|
|
|
52
101
|
const { res, startTime } = pendingRequest;
|
|
53
102
|
const endTime = Date.now();
|
|
54
103
|
const headers = { ...data.headers };
|
|
55
|
-
delete headers[
|
|
56
|
-
delete headers[
|
|
57
|
-
const bodyBuffer = Buffer.from(data.body,
|
|
58
|
-
res
|
|
104
|
+
delete headers["transfer-encoding"];
|
|
105
|
+
delete headers["content-length"];
|
|
106
|
+
const bodyBuffer = Buffer.from(data.body, "base64");
|
|
107
|
+
res
|
|
108
|
+
.status(data.status)
|
|
59
109
|
.set(headers)
|
|
60
|
-
.set(
|
|
110
|
+
.set("Content-Length", bodyBuffer.length.toString())
|
|
61
111
|
.send(bodyBuffer);
|
|
62
112
|
console.log(`Request handled: ${data.requestId}, Duration: ${endTime - startTime}ms`);
|
|
63
113
|
this.pendingRequests.delete(data.requestId);
|
|
@@ -67,33 +117,25 @@ class DainTunnelServer {
|
|
|
67
117
|
const tunnelId = req.params.tunnelId;
|
|
68
118
|
const tunnel = this.tunnels.get(tunnelId);
|
|
69
119
|
if (!tunnel) {
|
|
70
|
-
|
|
120
|
+
console.log(`Tunnel not found: ${tunnelId}`);
|
|
121
|
+
return res.status(404).send("Tunnel not found");
|
|
71
122
|
}
|
|
72
123
|
const requestId = (0, uuid_1.v4)();
|
|
73
124
|
const startTime = Date.now();
|
|
74
125
|
this.pendingRequests.set(requestId, { res, startTime });
|
|
75
126
|
const requestMessage = {
|
|
76
|
-
type:
|
|
127
|
+
type: "request",
|
|
77
128
|
id: requestId,
|
|
78
129
|
method: req.method,
|
|
79
130
|
path: req.url,
|
|
80
131
|
headers: req.headers,
|
|
81
|
-
body:
|
|
132
|
+
body: req.method !== "GET" && req.body
|
|
133
|
+
? req.body.toString("base64")
|
|
134
|
+
: undefined,
|
|
82
135
|
};
|
|
83
136
|
tunnel.ws.send(JSON.stringify(requestMessage));
|
|
84
137
|
console.log(`Request forwarded: ${requestId}, Method: ${req.method}, Path: ${req.url}`);
|
|
85
138
|
}
|
|
86
|
-
getRequestBody(req) {
|
|
87
|
-
return new Promise((resolve) => {
|
|
88
|
-
let body = '';
|
|
89
|
-
req.on('data', chunk => {
|
|
90
|
-
body += chunk.toString();
|
|
91
|
-
});
|
|
92
|
-
req.on('end', () => {
|
|
93
|
-
resolve(body);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
139
|
removeTunnel(ws) {
|
|
98
140
|
for (const [id, tunnel] of this.tunnels.entries()) {
|
|
99
141
|
if (tunnel.ws === ws) {
|
|
@@ -102,6 +144,13 @@ class DainTunnelServer {
|
|
|
102
144
|
break;
|
|
103
145
|
}
|
|
104
146
|
}
|
|
147
|
+
// Also remove any pending challenges for this WebSocket
|
|
148
|
+
for (const [challenge, challengeObj] of this.challenges.entries()) {
|
|
149
|
+
if (challengeObj.ws === ws) {
|
|
150
|
+
this.challenges.delete(challenge);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
105
154
|
}
|
|
106
155
|
async start() {
|
|
107
156
|
return new Promise((resolve) => {
|
package/dist/server/start.js
CHANGED
|
@@ -4,12 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const index_1 = __importDefault(require("./index"));
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
// Load environment variables from .env file
|
|
9
|
+
dotenv_1.default.config();
|
|
7
10
|
const port = parseInt(process.env.PORT || '3000', 10);
|
|
8
|
-
const hostname = process.env.HOSTNAME || '
|
|
11
|
+
const hostname = process.env.HOSTNAME || 'localhost';
|
|
9
12
|
const server = new index_1.default(hostname, port);
|
|
10
13
|
server.start()
|
|
11
14
|
.then(() => {
|
|
12
|
-
console.log(`DainTunnel Server started on
|
|
15
|
+
console.log(`DainTunnel Server started on http://${hostname}:${port}`);
|
|
13
16
|
})
|
|
14
17
|
.catch((error) => {
|
|
15
18
|
console.error('Failed to start DainTunnel Server:', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dainprotocol/tunnel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -15,23 +15,58 @@
|
|
|
15
15
|
"author": "Ryan",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@dainprotocol/service-sdk": "^1.0.11",
|
|
19
|
+
"@types/body-parser": "^1.19.5",
|
|
20
|
+
"@types/cors": "^2.8.17",
|
|
18
21
|
"@types/express": "^4.17.21",
|
|
19
22
|
"@types/jest": "^29.5.12",
|
|
20
23
|
"@types/node": "^22.5.4",
|
|
21
24
|
"@types/uuid": "^10.0.0",
|
|
22
25
|
"@types/ws": "^8.5.12",
|
|
26
|
+
"body-parser": "^1.20.2",
|
|
27
|
+
"bs58": "^6.0.0",
|
|
28
|
+
"cors": "^2.8.5",
|
|
29
|
+
"dotenv": "^16.4.5",
|
|
23
30
|
"express": "^4.19.2",
|
|
24
31
|
"fetch-mock": "^11.1.3",
|
|
25
32
|
"jest": "^29.7.0",
|
|
26
33
|
"ts-jest": "^29.2.5",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
27
35
|
"typescript": "^5.5.4",
|
|
28
36
|
"uuid": "^10.0.0",
|
|
29
37
|
"ws": "^8.18.0"
|
|
30
|
-
},
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
31
40
|
"dist",
|
|
32
41
|
"README.md"
|
|
33
42
|
],
|
|
34
43
|
"engines": {
|
|
35
44
|
"node": "20.7.0"
|
|
45
|
+
},
|
|
46
|
+
"exports": {
|
|
47
|
+
".": "./dist/index.js",
|
|
48
|
+
"./client": "./dist/client/index.js",
|
|
49
|
+
"./server": "./dist/server/index.js",
|
|
50
|
+
"./client/*": "./dist/client/*.js",
|
|
51
|
+
"./server/*": "./dist/server/*.js"
|
|
52
|
+
},
|
|
53
|
+
"typesVersions": {
|
|
54
|
+
"*": {
|
|
55
|
+
".": [
|
|
56
|
+
"./dist/index.d.ts"
|
|
57
|
+
],
|
|
58
|
+
"client": [
|
|
59
|
+
"./dist/client/index.d.ts"
|
|
60
|
+
],
|
|
61
|
+
"server": [
|
|
62
|
+
"./dist/server/index.d.ts"
|
|
63
|
+
],
|
|
64
|
+
"client/*": [
|
|
65
|
+
"./dist/client/*.d.ts"
|
|
66
|
+
],
|
|
67
|
+
"server/*": [
|
|
68
|
+
"./dist/server/*.d.ts"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
36
71
|
}
|
|
37
72
|
}
|