Merge pull request #1 from CUBETIQ/ts

Reflecting from JavaScript to TypeScript
This commit is contained in:
Sambo Chea 2022-08-16 22:34:46 +07:00 committed by GitHub
commit 4fbcf1f82c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1451 additions and 216 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules/ node_modules/
npm-debug.log npm-debug.log
dist/

2
.npmignore Normal file
View File

@ -0,0 +1,2 @@
src/
.github/

View File

@ -1,3 +1,3 @@
#!/usr/bin/env node #!/usr/bin/env node
require('../client'); require('../dist/client');

156
lib.js
View File

@ -1,156 +0,0 @@
const stream = require("stream");
class TunnelRequest extends stream.Readable {
constructor({ socket, requestId }) {
super();
this._socket = socket;
this._requestId = requestId;
const onRequestPipe = (requestId, data) => {
if (this._requestId === requestId) {
this.push(data);
}
};
const onRequestPipes = (requestId, data) => {
if (this._requestId === requestId) {
data.forEach((chunk) => {
this.push(chunk);
});
}
};
const onRequestPipeError = (requestId, error) => {
if (this._requestId === requestId) {
this._socket.off("request-pipe", onRequestPipe);
this._socket.off("request-pipes", onRequestPipes);
this._socket.off("request-pipe-error", onRequestPipeError);
this._socket.off("request-pipe-end", onRequestPipeEnd);
this.destroy(new Error(error));
}
};
const onRequestPipeEnd = (requestId, data) => {
if (this._requestId === requestId) {
this._socket.off("request-pipe", onRequestPipe);
this._socket.off("request-pipes", onRequestPipes);
this._socket.off("request-pipe-error", onRequestPipeError);
this._socket.off("request-pipe-end", onRequestPipeEnd);
if (data) {
this.push(data);
}
this.push(null);
}
};
this._socket.on("request-pipe", onRequestPipe);
this._socket.on("request-pipes", onRequestPipes);
this._socket.on("request-pipe-error", onRequestPipeError);
this._socket.on("request-pipe-end", onRequestPipeEnd);
}
_read() { }
}
class TunnelResponse extends stream.Duplex {
constructor({ socket, responseId, duplex }) {
super();
this._socket = socket;
this._responseId = responseId;
if (duplex) {
// for websocket request bidirection
const onResponsePipe = (responseId, data) => {
if (this._responseId === responseId) {
this.push(data);
}
};
const onResponsePipes = (responseId, data) => {
if (this._responseId === responseId) {
data.forEach((chunk) => {
this.push(chunk);
});
}
};
const onResponsePipeError = (responseId, error) => {
if (this._responseId === responseId) {
this._socket.off("response-pipe", onResponsePipe);
this._socket.off("response-pipes", onResponsePipes);
this._socket.off("response-pipe-error", onResponsePipeError);
this._socket.off("response-pipe-end", onResponsePipeEnd);
this.destroy(new Error(error));
}
};
const onResponsePipeEnd = (responseId, data) => {
if (this._responseId === responseId) {
this._socket.off("response-pipe", onResponsePipe);
this._socket.off("response-pipes", onResponsePipes);
this._socket.off("response-pipe-error", onResponsePipeError);
this._socket.off("response-pipe-end", onResponsePipeEnd);
if (data) {
this.push(data);
}
this.push(null);
}
};
this._socket.on("response-pipe", onResponsePipe);
this._socket.on("response-pipes", onResponsePipes);
this._socket.on("response-pipe-error", onResponsePipeError);
this._socket.on("response-pipe-end", onResponsePipeEnd);
}
}
_write(chunk, encoding, callback) {
this._socket.emit("response-pipe", this._responseId, chunk);
this._socket.io.engine.once("drain", () => {
callback();
});
}
_writev(chunks, callback) {
this._socket.emit("response-pipes", this._responseId, chunks);
this._socket.io.engine.once("drain", () => {
callback();
});
}
_final(callback) {
this._socket.emit("response-pipe-end", this._responseId);
this._socket.io.engine.once("drain", () => {
callback();
});
}
_destroy(e, callback) {
if (e) {
this._socket.emit(
"response-pipe-error",
this._responseId,
e && e.message
);
this._socket.io.engine.once("drain", () => {
callback();
});
return;
}
callback();
}
writeHead(statusCode, statusMessage, headers, httpVersion) {
this._socket.emit("response", this._responseId, {
statusCode,
statusMessage,
headers,
httpVersion,
});
}
_read(size) { }
}
exports.TunnelRequest = TunnelRequest;
exports.TunnelResponse = TunnelResponse;

1219
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
{ {
"name": "@cubetiq/hlt", "name": "@cubetiq/hlt",
"version": "0.0.9", "version": "0.1.0",
"description": "A lightweight http tunnel client using nodejs and socket.io client", "description": "A lightweight http tunnel client using nodejs and socket.io client",
"main": "client.js", "main": "dist/client.js",
"bin": { "bin": {
"hlt": "bin/hlt" "hlt": "bin/hlt"
}, },
"scripts": { "scripts": {
"start": "node client.js" "start": "ts-node-dev --respawn --transpile-only src/client.ts",
"build": "tsc"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -27,5 +28,10 @@
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
},
"devDependencies": {
"@types/node": "^18.0.3",
"ts-node-dev": "^2.0.0",
"typescript": "^4.7.4"
} }
} }

View File

@ -1,16 +1,17 @@
const os = require("os"); import * as os from "os";
const fs = require("fs"); import * as fs from "fs";
const path = require("path"); import * as path from "path";
const http = require("http"); import * as http from "http";
const { io } = require("socket.io-client"); import { io } from "socket.io-client";
const HttpsProxyAgent = require("https-proxy-agent"); import { HttpsProxyAgent } from "https-proxy-agent";
const { program, InvalidArgumentError, Argument } = require("commander"); import { program, InvalidArgumentError, Argument } from "commander";
const { TunnelRequest, TunnelResponse } = require("./lib"); import { TunnelRequest, TunnelResponse } from "./lib";
const { generateUUID, addPrefixOnHttpSchema } = require("./util"); import { generateUUID, addPrefixOnHttpSchema } from "./util";
import { Socket } from 'socket.io-client';
const sdk = require("./sdk"); import { getTokenFree } from './sdk'
const packageInfo = require("./package.json"); const packageInfo = require("../package.json");
// constants // constants
const PROFILE_DEFAULT = "default"; const PROFILE_DEFAULT = "default";
@ -19,7 +20,7 @@ const SERVER_DEFAULT_URL = "https://lt.ctdn.net";
const TOKEN_FREE = "FREE"; const TOKEN_FREE = "FREE";
// create socket instance // create socket instance
let socket = null; let socket: Socket | null = null;
function keepAlive() { function keepAlive() {
setTimeout(() => { setTimeout(() => {
@ -30,7 +31,7 @@ function keepAlive() {
}, 5000); }, 5000);
} }
function initClient(options) { function initClient(options: any) {
// Please change this if your domain goes wrong here // Please change this if your domain goes wrong here
// Current style using sub-domain: https://{{clientId}}-tunnel.myhostingdomain.com // Current style using sub-domain: https://{{clientId}}-tunnel.myhostingdomain.com
// (Original server: https://tunnel.myhostingdomain.com) // (Original server: https://tunnel.myhostingdomain.com)
@ -66,7 +67,7 @@ function initClient(options) {
release: os.release(), release: os.release(),
}; };
const initParams = { const initParams: any = {
path: "/$cubetiq_http_tunnel", path: "/$cubetiq_http_tunnel",
transports: ["websocket"], transports: ["websocket"],
auth: { auth: {
@ -91,7 +92,7 @@ function initClient(options) {
const clientLogPrefix = `client: ${clientId} on profile: ${profile}`; const clientLogPrefix = `client: ${clientId} on profile: ${profile}`;
socket.on("connect", () => { socket.on("connect", () => {
if (socket.connected) { if (socket!.connected) {
console.log(`${clientLogPrefix} is connected to server successfully!`); console.log(`${clientLogPrefix} is connected to server successfully!`);
} }
}); });
@ -112,7 +113,7 @@ function initClient(options) {
socket.on("disconnect_exit", (reason) => { socket.on("disconnect_exit", (reason) => {
console.log(`${clientLogPrefix} disconnected and exited ${reason}!`); console.log(`${clientLogPrefix} disconnected and exited ${reason}!`);
socket.disconnect(); socket?.disconnect();
process.exit(1); process.exit(1);
}); });
@ -126,15 +127,12 @@ function initClient(options) {
request.headers.host = options.origin; request.headers.host = options.origin;
} }
const tunnelRequest = new TunnelRequest({ const tunnelRequest = new TunnelRequest(socket!, requestId);
requestId,
socket: socket,
});
const localReq = http.request(request); const localReq = http.request(request);
tunnelRequest.pipe(localReq); tunnelRequest.pipe(localReq);
const onTunnelRequestError = (e) => { const onTunnelRequestError = (e: any) => {
tunnelRequest.off("end", onTunnelRequestEnd); tunnelRequest.off("end", onTunnelRequestEnd);
localReq.destroy(e); localReq.destroy(e);
}; };
@ -146,17 +144,14 @@ function initClient(options) {
tunnelRequest.once("error", onTunnelRequestError); tunnelRequest.once("error", onTunnelRequestError);
tunnelRequest.once("end", onTunnelRequestEnd); tunnelRequest.once("end", onTunnelRequestEnd);
const onLocalResponse = (localRes) => { const onLocalResponse = (localRes: any) => {
localReq.off("error", onLocalError); localReq.off("error", onLocalError);
if (isWebSocket && localRes.upgrade) { if (isWebSocket && localRes.upgrade) {
return; return;
} }
const tunnelResponse = new TunnelResponse({ const tunnelResponse = new TunnelResponse(socket!, requestId);
responseId: requestId,
socket: socket,
});
tunnelResponse.writeHead( tunnelResponse.writeHead(
localRes.statusCode, localRes.statusCode,
@ -168,23 +163,18 @@ function initClient(options) {
localRes.pipe(tunnelResponse); localRes.pipe(tunnelResponse);
}; };
const onLocalError = (error) => { const onLocalError = (error: any) => {
console.log(error); console.log(error);
localReq.off("response", onLocalResponse); localReq.off("response", onLocalResponse);
socket.emit("request-error", requestId, error && error.message); socket?.emit("request-error", requestId, error && error.message);
tunnelRequest.destroy(error); tunnelRequest.destroy(error);
}; };
const onUpgrade = (localRes, localSocket, localHead) => { const onUpgrade = (localRes: any, localSocket: any, localHead: any) => {
// localSocket.once('error', onTunnelRequestError); // localSocket.once('error', onTunnelRequestError);
if (localHead && localHead.length) localSocket.unshift(localHead); if (localHead && localHead.length) localSocket.unshift(localHead);
const tunnelResponse = new TunnelResponse({ const tunnelResponse = new TunnelResponse(socket!, requestId, true);
responseId: requestId,
socket: socket,
duplex: true,
});
tunnelResponse.writeHead(null, null, localRes.headers); tunnelResponse.writeHead(null, null, localRes.headers);
localSocket.pipe(tunnelResponse).pipe(localSocket); localSocket.pipe(tunnelResponse).pipe(localSocket);
}; };
@ -200,7 +190,7 @@ function initClient(options) {
// reconnect manually // reconnect manually
const tryReconnect = () => { const tryReconnect = () => {
setTimeout(() => { setTimeout(() => {
socket.io.open((err) => { socket!.io.open((err) => {
if (err) { if (err) {
tryReconnect(); tryReconnect();
} }
@ -245,7 +235,7 @@ program
console.log(`config file ${configDir} was created`); console.log(`config file ${configDir} was created`);
} }
let config = {}; let config: any = {};
const configFilename = `${options.profile}.json`; const configFilename = `${options.profile}.json`;
const configFilePath = path.resolve(configDir, configFilename); const configFilePath = path.resolve(configDir, configFilename);
@ -275,9 +265,8 @@ program
if (!config.token) { if (!config.token) {
console.log("Generating token..."); console.log("Generating token...");
await sdk await getTokenFree(config.server)
.getTokenFree(config.server) .then((resp: any) => {
.then((resp) => {
if (resp.data?.token) { if (resp.data?.token) {
config.token = resp.data?.token; config.token = resp.data?.token;
} else { } else {
@ -285,7 +274,7 @@ program
return; return;
} }
}) })
.catch((err) => { .catch((err: any) => {
console.error("cannot get free token from server", err); console.error("cannot get free token from server", err);
return; return;
}); });
@ -327,7 +316,7 @@ program
fs.mkdirSync(configDir); fs.mkdirSync(configDir);
} }
let config = {}; let config: any = {};
const configFilename = `${options.profile}.json`; const configFilename = `${options.profile}.json`;
const configFilePath = path.resolve(configDir, configFilename); const configFilePath = path.resolve(configDir, configFilename);
@ -399,7 +388,7 @@ program
console.log(`config file ${configDir} was created`); console.log(`config file ${configDir} was created`);
} }
let config = {}; let config: any = {};
const configFilename = `${options.profile}.json`; const configFilename = `${options.profile}.json`;
const configFilePath = path.resolve(configDir, configFilename); const configFilePath = path.resolve(configDir, configFilename);
@ -429,9 +418,8 @@ program
// FREE // FREE
if (config.access === TOKEN_FREE) { if (config.access === TOKEN_FREE) {
await sdk await getTokenFree(config.server)
.getTokenFree(config.server) .then((resp: any) => {
.then((resp) => {
if (resp.data?.token) { if (resp.data?.token) {
config.token = resp.data?.token; config.token = resp.data?.token;
} else { } else {
@ -439,7 +427,7 @@ program
return; return;
} }
}) })
.catch((err) => { .catch((err: any) => {
console.error("cannot get free token from server", err); console.error("cannot get free token from server", err);
return; return;
}); });
@ -480,7 +468,7 @@ program
return; return;
} }
let config = {}; let config: any = {};
const configFilename = `${options.profile}.json`; const configFilename = `${options.profile}.json`;
const configFilePath = path.resolve(configDir, configFilename); const configFilePath = path.resolve(configDir, configFilename);

165
src/lib.ts Normal file
View File

@ -0,0 +1,165 @@
import * as stream from "stream";
import { Socket } from 'socket.io-client';
class TunnelRequest extends stream.Readable {
constructor(private socket: Socket, private requestId: string) {
super();
const onRequestPipe = (requestId: string, data: any) => {
if (this.requestId === requestId) {
this.push(data);
}
};
const onRequestPipes = (requestId: string, data: any) => {
if (!data) return;
if (this.requestId === requestId) {
data.forEach((chunk: any) => {
this.push(chunk);
});
}
};
const onRequestPipeError = (requestId: string, error?: any) => {
if (this.requestId === requestId) {
this.socket.off("request-pipe", onRequestPipe);
this.socket.off("request-pipes", onRequestPipes);
this.socket.off("request-pipe-error", onRequestPipeError);
this.socket.off("request-pipe-end", onRequestPipeEnd);
this.destroy(new Error(error));
}
};
const onRequestPipeEnd = (requestId: string, data: any) => {
if (this.requestId === requestId) {
this.socket.off("request-pipe", onRequestPipe);
this.socket.off("request-pipes", onRequestPipes);
this.socket.off("request-pipe-error", onRequestPipeError);
this.socket.off("request-pipe-end", onRequestPipeEnd);
if (data) {
this.push(data);
}
this.push(null);
}
};
this.socket.on("request-pipe", onRequestPipe);
this.socket.on("request-pipes", onRequestPipes);
this.socket.on("request-pipe-error", onRequestPipeError);
this.socket.on("request-pipe-end", onRequestPipeEnd);
}
_read() { }
}
class TunnelResponse extends stream.Duplex {
constructor(private socket: Socket, private responseId: string, duplex?: boolean) {
super();
if (duplex) {
// for websocket request bidirection
const onResponsePipe = (responseId: string, data: any) => {
if (this.responseId === responseId) {
this.push(data);
}
};
const onResponsePipes = (responseId: string, data: any) => {
if (this.responseId === responseId) {
data.forEach((chunk: any) => {
this.push(chunk);
});
}
};
const onResponsePipeError = (responseId: string, error?: any) => {
if (this.responseId === responseId) {
this.socket.off("response-pipe", onResponsePipe);
this.socket.off("response-pipes", onResponsePipes);
this.socket.off("response-pipe-error", onResponsePipeError);
this.socket.off("response-pipe-end", onResponsePipeEnd);
this.destroy(new Error(error));
}
};
const onResponsePipeEnd = (responseId: string, data: any) => {
if (this.responseId === responseId) {
this.socket.off("response-pipe", onResponsePipe);
this.socket.off("response-pipes", onResponsePipes);
this.socket.off("response-pipe-error", onResponsePipeError);
this.socket.off("response-pipe-end", onResponsePipeEnd);
if (data) {
this.push(data);
}
this.push(null);
}
};
this.socket.on("response-pipe", onResponsePipe);
this.socket.on("response-pipes", onResponsePipes);
this.socket.on("response-pipe-error", onResponsePipeError);
this.socket.on("response-pipe-end", onResponsePipeEnd);
}
}
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
this.socket.emit("response-pipe", this.responseId, chunk);
this.socket.io.engine.once("drain", () => {
callback && callback();
});
}
_writev(
chunks: Array<{
chunk: any;
encoding: BufferEncoding;
}>,
callback: (error?: Error | null) => void) {
this.socket.emit("response-pipes", this.responseId, chunks);
this.socket.io.engine.once("drain", () => {
callback();
});
}
_final(callback: (error?: Error | null) => void) {
this.socket.emit("response-pipe-end", this.responseId);
this.socket.io.engine.once("drain", () => {
callback();
});
}
_destroy(error: Error | null, callback: (error: Error | null) => void) {
if (error) {
this.socket.emit(
"response-pipe-error",
this.responseId,
error && error.message
);
this.socket.io.engine.once("drain", () => {
callback(error);
});
return;
}
callback(null);
}
writeHead(statusCode: any, statusMessage?: any, headers?: any, httpVersion?: any) {
this.socket.emit("response", this.responseId, {
statusCode,
statusMessage,
headers,
httpVersion,
});
}
_read(size: number) { }
}
export {
TunnelRequest,
TunnelResponse
}

View File

@ -1,6 +1,6 @@
const axios = require("axios").default; const axios = require("axios").default;
const getTokenFree = async (baseUrl, data = {}) => { const getTokenFree = async (baseUrl: string, data: any = {}) => {
const url = `${baseUrl}/__free__/api/get_token`; const url = `${baseUrl}/__free__/api/get_token`;
return axios({ return axios({
method: "POST", method: "POST",
@ -14,6 +14,6 @@ const getTokenFree = async (baseUrl, data = {}) => {
}); });
}; };
module.exports = { export {
getTokenFree, getTokenFree,
}; };

View File

@ -1,6 +1,6 @@
const crypto = require("crypto"); import * as crypto from "crypto";
const addPrefixOnHttpSchema = (url, prefixDomain) => { const addPrefixOnHttpSchema = (url: string, prefixDomain: string) => {
let prefixSubDomain = prefixDomain; let prefixSubDomain = prefixDomain;
const prefixSchema = url.substring(0, url.indexOf("://") + 3); const prefixSchema = url.substring(0, url.indexOf("://") + 3);
const splitDomain = url.substring(url.indexOf("://") + 3); const splitDomain = url.substring(url.indexOf("://") + 3);
@ -16,4 +16,4 @@ const generateUUID = () => {
return crypto.randomUUID(); return crypto.randomUUID();
}; };
module.exports = { addPrefixOnHttpSchema, generateUUID }; export { addPrefixOnHttpSchema, generateUUID };

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"lib": ["ES2015"],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": false,
"outDir": "dist",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}