@mikkel-ol/tunnelmole
Version:
Tunnelmole, an open source ngrok alternative. Instant public URLs for any http/https based application. Available as a command line application or as an NPM dependency for your code. Stable and maintained. Good test coverage. Works behind firewalls
101 lines (80 loc) • 3.54 kB
text/typescript
import { Options } from "../options.js";
import InitialiseMessage from "../messages/initialise-message.js";
import { initialise } from "../messages/types.js";
import { getClientId } from "../identity/client-id-service.js";
import { getApiKey } from "../identity/api-key-service.js";
import validator from "validator";
import { messageHandlers } from "../../message-handlers.js";
import HostipWebSocket from "./host-ip-websocket.js";
import config from "../../config.js";
import { getConnectionInfo } from "./connection-info-service.js";
import log from "../logging/log.js";
const connect = (options: Options): HostipWebSocket => {
const websocket = new HostipWebSocket(config.hostip.endpoint);
const websocketIsReady = websocket.readyState === 1;
const sendInitialiseMessage = async () => {
log("Sending initialise message");
// Give the server basic information on the Node version and if we are using CLI or not (in which case, tunnelmole is being run from JS code)
const connectionInfo = await getConnectionInfo();
const initialiseMessage: InitialiseMessage = {
type: initialise,
clientId: await getClientId(),
connectionInfo,
};
// Set api key if we have one available
const apiKey = await getApiKey();
if (typeof apiKey === "string") {
initialiseMessage.apiKey = apiKey;
}
// Handle passed subdomain param if present
let domain = options.domain ?? undefined;
if (typeof domain === "string") {
// Remove protocols in case they were passed by mistake as the "domain"
domain = domain.replace("http://", "");
domain = domain.replace("https://", "");
if (!validator.isURL(domain)) {
console.info(
"Invalid domain name passed, please use the format mydomain.tunnelmole.net",
);
return Promise.resolve();
}
const domainParts = domain.split(".");
const subdomain = domainParts[0];
initialiseMessage.subdomain = subdomain;
}
websocket.sendMessage(initialiseMessage);
};
// There seems to be a bug where on a second run, the websocket is re-used and is in a ready state
// Send initialise message now if this is the case, otherwise set the open event to trigger the initialise message
if (websocketIsReady) {
sendInitialiseMessage();
} else {
websocket.on("open", sendInitialiseMessage); // Could potentially improve this to websocket.on('once'), but it will need testing
}
websocket.on("message", (text: string) => {
const message = JSON.parse(text);
if (typeof message.type !== "string") {
console.error("Invalid message, type is missing or invalid");
}
// Errors should be handled in the handler itself. If it gets here it will be thrown.
if (typeof messageHandlers[message.type] !== "function") {
console.error("Handler not found for message type " + message.type);
}
const handler = messageHandlers[message.type];
handler(message, websocket, options);
});
// Log messages if debug is enabled
// Potential improvement: Combine with the other websocket.on('message') above. Requires testing, working well for now
websocket.on("message", (text: string) => {
const message = JSON.parse(text);
log(Date.now() + " Received " + message.type + " message:", "info");
log(message, "info");
});
// Log errors
websocket.on("error", (error) => {
log(Date.now() + "Caught an error:", "error");
console.error(error);
});
return websocket;
};
export { connect };