@ngrok/ngrok
Version:
The ngrok agent in library form, suitable for integrating directly into your NodeJS application.
725 lines (680 loc) • 21.9 kB
JavaScript
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
const lddPath = require('child_process').execSync('which ldd').toString().trim()
return readFileSync(lddPath, 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'ngrok.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.android-arm64.node')
} else {
nativeBinding = require('@ngrok/ngrok-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'ngrok.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.android-arm-eabi.node')
} else {
nativeBinding = require('@ngrok/ngrok-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'ngrok.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.win32-x64-msvc.node')
} else {
nativeBinding = require('@ngrok/ngrok-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'ngrok.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.win32-ia32-msvc.node')
} else {
nativeBinding = require('@ngrok/ngrok-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'ngrok.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.win32-arm64-msvc.node')
} else {
nativeBinding = require('@ngrok/ngrok-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
localFileExisted = existsSync(join(__dirname, 'ngrok.darwin-universal.node'))
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.darwin-universal.node')
} else {
nativeBinding = require('@ngrok/ngrok-darwin-universal')
}
break
} catch {}
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'ngrok.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.darwin-x64.node')
} else {
nativeBinding = require('@ngrok/ngrok-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'ngrok.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.darwin-arm64.node')
} else {
nativeBinding = require('@ngrok/ngrok-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'ngrok.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.freebsd-x64.node')
} else {
nativeBinding = require('@ngrok/ngrok-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-x64-musl.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-x64-gnu.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-arm64-musl.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-arm64-gnu.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-arm-musleabihf.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-riscv64-musl.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-riscv64-gnu.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'ngrok.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./ngrok.linux-s390x-gnu.node')
} else {
nativeBinding = require('@ngrok/ngrok-linux-s390x-gnu')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { connect, forward, disconnect, kill, Listener, listeners, getListener, getListenerByUrl, HttpListenerBuilder, TcpListenerBuilder, TlsListenerBuilder, LabeledListenerBuilder, loggingCallback, authtoken, SessionBuilder, Session, UpdateRequest } = nativeBinding
module.exports.connect = connect
module.exports.forward = forward
module.exports.disconnect = disconnect
module.exports.kill = kill
module.exports.Listener = Listener
module.exports.listeners = listeners
module.exports.getListener = getListener
module.exports.getListenerByUrl = getListenerByUrl
module.exports.HttpListenerBuilder = HttpListenerBuilder
module.exports.TcpListenerBuilder = TcpListenerBuilder
module.exports.TlsListenerBuilder = TlsListenerBuilder
module.exports.LabeledListenerBuilder = LabeledListenerBuilder
module.exports.loggingCallback = loggingCallback
module.exports.authtoken = authtoken
module.exports.SessionBuilder = SessionBuilder
module.exports.Session = Session
module.exports.UpdateRequest = UpdateRequest
//
// javascript trailer
//
const net = require("net");
const fs = require("fs");
const os = require("os");
const path = require("path");
// wrap connect with the code for extending exception with error code
SessionBuilder.prototype._connect = SessionBuilder.prototype.connect;
SessionBuilder.prototype.connect = ngrokSessionConnect;
// wrap listen with the bind code for passing to net.Server.listen()
HttpListenerBuilder.prototype._listen = HttpListenerBuilder.prototype.listen;
TcpListenerBuilder.prototype._listen = TcpListenerBuilder.prototype.listen;
TlsListenerBuilder.prototype._listen = TlsListenerBuilder.prototype.listen;
LabeledListenerBuilder.prototype._listen = LabeledListenerBuilder.prototype.listen;
HttpListenerBuilder.prototype.listen = ngrokBind;
TcpListenerBuilder.prototype.listen = ngrokBind;
TlsListenerBuilder.prototype.listen = ngrokBind;
LabeledListenerBuilder.prototype.listen = ngrokBind;
HttpListenerBuilder.prototype.listenAndServe = listenAndServe;
TcpListenerBuilder.prototype.listenAndServe = listenAndServe;
TlsListenerBuilder.prototype.listenAndServe = listenAndServe;
LabeledListenerBuilder.prototype.listenAndServe = listenAndServe;
// Wrap session connect to fill in exception's errorCode
async function ngrokSessionConnect() {
try {
return await this._connect();
} catch (err) {
populateErrorCode(err);
throw err;
}
}
// Begin listening for new connections on this listener,
// and bind to a local socket so this listener can be
// passed into net.Server.listen().
async function ngrokBind(bind) {
try {
const listener = await this._listen();
if (bind !== false) {
const socket = await randomTcpSocket();
listener.socket = socket;
defineListenerHandle(listener, socket);
}
return listener;
} catch (err) {
populateErrorCode(err);
throw err;
}
}
/// Begin listening for new connections on this listener and forwarding them to the given server.
async function listenAndServe(server) {
const listener = await this._listen();
listener.socket = await ngrokListen(server, listener);
return listener;
}
function populateErrorCode(err) {
if (err.message) {
const regex = /error_code: (ERR_NGROK_\d+)$/;
const errorCode = err.message.match(regex);
if (errorCode && errorCode.length > 1) {
err.errorCode = errorCode[1];
}
}
}
// add a 'handle' getter to the listener so it can be
// passed into net.Server.listen().
function defineListenerHandle(listener, socket) {
// NodeJS net.Server asks passed-in object for 'handle',
// Return the native TCP object so the pre-existing socket is used.
Object.defineProperty(listener, "handle", {
get: function () {
// turn on forwarding now that it has been requested
listener.forward("localhost:" + socket.address().port);
return socket._handle;
},
});
}
// generate a net.Server listening to a random port
async function randomTcpSocket() {
return await asyncListen(new net.Server(), { host: "localhost", port: 0 });
}
// NodeJS has not promisified 'net': https://github.com/nodejs/node/issues/21482
function asyncListen(server, options) {
return new Promise((resolve, reject) => {
const socket = server.listen(options);
socket
.once("listening", () => {
resolve(socket);
})
.once("error", (err) => {
reject(err);
});
});
}
// Make a session using NGROK_AUTHTOKEN from the environment,
// and then return a listening HTTP listener.
async function defaultListener(bind) {
// set up a default session and listener
var builder = new SessionBuilder();
builder.authtokenFromEnv();
var session = await builder.connect();
var listener = await session.httpEndpoint().listen(bind);
listener.session = session; // surface to caller
return listener;
}
// Get a listenable ngrok listener, suitable for passing to net.Server.listen().
// Uses the NGROK_AUTHTOKEN environment variable to authenticate.
async function listenable() {
return await defaultListener();
}
// Bind a server to a new ngrok listener, optionally passing in a pre-existing listener instead.
// Uses the NGROK_AUTHTOKEN environment variable to authenticate if a new listener is created.
async function ngrokListen(server, listener) {
if (listener && listener.socket) {
// close the default bound port
listener.socket.close();
}
if (!listener) {
// turn off automatic bind
listener = await defaultListener(false);
}
// attempt pipe socket
try {
socket = await ngrokLinkPipe(listener, server);
} catch (err) {
console.debug("Using TCP socket. " + err);
// fallback to tcp socket
socket = await ngrokLinkTcp(listener, server);
}
registerCleanup(listener, socket);
server.listener = listener; // surface to caller
socket.listener = listener; // surface to caller
// return the newly created net.Server, which will be different in the express case
return socket;
}
async function ngrokLinkTcp(listener, server) {
// random local port
const socket = await asyncListen(server, { host: "localhost", port: 0 });
// forward to socket
listener.forward("localhost:" + socket.address().port);
return socket;
}
function generatePipeFilename(listener, server) {
var proposed = "tun-" + listener.id() + ".sock";
// windows leaves little choice
if (platform == "win32") {
return "\\\\.\\pipe\\" + proposed;
}
// try to make a directory in the current working directory
const dir = ".ngrok";
try {
fs.mkdirSync(dir);
} catch (err) {
// move on
}
try {
fs.accessSync(dir, fs.constants.W_OK);
return dir + path.sep + proposed;
} catch (err) {
// move on
}
// try the OS temp directory, being careful not to exceed the maximum path length for unix sockets
// https://linux.die.net/man/7/unix
// https://unix.stackexchange.com/a/367012
if (os.tmpdir().length < 90) {
try {
fs.accessSync(os.tmpdir(), fs.constants.W_OK);
filepath = os.tmpdir() + path.sep + proposed;
if (filepath.length > 100) {
// truncate
filepath = filepath.substring(0, 100);
}
return filepath;
} catch (err) {
// move on
}
}
// fallback to current working directory. allow any exception to propagate
fs.accessSync(process.cwd(), fs.constants.W_OK);
return proposed;
}
async function ngrokLinkPipe(listener, server) {
var filename = generatePipeFilename(listener);
// begin listening
const socket = await asyncListen(server, { path: filename });
// tighten permissions
try {
if (platform != "win32") {
fs.chmodSync(filename, fs.constants.S_IRWXU);
}
} catch (err) {
console.debug("Cannot change permissions of file: " + filename);
}
// forward listener
var addr = "unix:" + filename;
if (platform == "win32") {
// convert pipe path to url
addr = "pipe:" + filename.replace("\\\\.\\pipe\\", "//./");
}
listener.forward(addr);
socket.path = filename; // surface to caller
return socket;
}
// protect against multiple calls, for instance from npm
var sigHandlerRan = false;
function registerCleanup(listener, socket) {
for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, function () {
if (process.listenerCount(signal) > 1) {
// user has registered a handler, abort this one
return;
}
if (sigHandlerRan) return;
sigHandlerRan = true;
// close listener
if (listener) {
listener.close().catch((err) => {
console.error(`Error closing listener: ${err}`);
});
}
// close webserver's socket
if (socket) socket.close();
// unregister any logging callback
loggingCallback();
});
}
}
function consoleLog(level) {
loggingCallback((level, target, message) => {
console.log(`${level} ${target} - ${message}`);
}, level);
}
// wrap forward with code to vectorize and split out functions
const _forward = forward;
async function ngrokForward(config) {
if (config == undefined) config = 80;
if (Number.isInteger(config) || typeof config === "string" || config instanceof String) {
address = String(config);
if (Number.isInteger(config) && !address.includes(":")) {
address = `localhost:${address}`;
}
config = { addr: address };
}
if (typeof config["port"] === "string" || config["port"] instanceof String) {
const num = parseInt(config["port"], 10);
if (isNaN(num)) {
throw new Error(`port must be a number: '${config["port"]}'`);
}
config["port"] = num;
}
// Convert addr to string to allow for numeric port numbers
const addr = config["addr"];
if (Number.isInteger(addr)) config["addr"] = "localhost:" + String(config["addr"]);
// convert scalar values to arrays to meet what napi-rs expects
[
"allow_user_agent",
"auth",
"basic_auth",
"deny_user_agent",
"ip_restriction.allow_cidrs",
"ip_restriction.deny_cidrs",
"labels",
"oauth.allow_domains",
"oauth.allow_emails",
"oauth.scopes",
"oidc.scopes",
"oidc.allow_domains",
"oidc.allow_emails",
"request_header.add",
"request_header.remove",
"response_header.add",
"response_header.remove",
"schemes",
].forEach((key) => {
vectorize(config, key);
});
// convert dotted values to underscores for backwards compatibility
[
"ip_restriction.allow_cidrs",
"ip_restriction.deny_cidrs",
"oauth.allow_domains",
"oauth.allow_emails",
"oauth.scopes",
"oauth.provider",
"oidc.client_id",
"oidc.client_secret",
"oidc.scopes",
"oidc.issuer_url",
"oidc.allow_domains",
"oidc.allow_emails",
"request_header.add",
"request_header.remove",
"response_header.add",
"response_header.remove",
"verify_webhook.provider",
"verify_webhook.secret",
].forEach((key) => {
undot(config, key);
});
// break out the logging callback function to meet what napi-rs expects
var on_log_event;
if (config["onLogEvent"]) {
const onLogEvent = config.onLogEvent;
on_log_event = (level, target, message) => {
onLogEvent(`${level} ${target} - ${message}`);
};
config["onLogEvent"] = true;
}
// break out the status change callback functions to what napi-rs expects
var on_connection, on_disconnection;
if (config["onStatusChange"]) {
const onStatusChange = config.onStatusChange;
on_connection = (status, err) => {
onStatusChange(status);
};
on_disconnection = (addr, err) => {
onStatusChange("closed");
};
config["onStatusChange"] = true;
}
// call into rust
try {
return await _forward(config, on_log_event, on_connection, on_disconnection);
} catch (err) {
populateErrorCode(err);
throw err;
}
}
function undot(config, dotKey) {
const noDotKey = dotKey.replace(".", "_");
if (config[dotKey] == null) return; // no dotKey value, done
if (config[noDotKey] == null) {
// nothing at destination, just set and be done
config[noDotKey] = config[dotKey];
return;
}
if (config[dotKey] instanceof Array && config[noDotKey] instanceof Array) {
// merge arrays
for (const obj of config[dotKey]) {
config[noDotKey].push(obj);
}
}
// destination exists and is not an array, do nothing so noDotKey can take precedence
}
function vectorize(config, key) {
// backwards compatible keys are passed in, check the new style as well
const noDotKey = key.replace(".", "_");
if (key != noDotKey) vectorize(config, noDotKey);
if (config[key] == null) return; // no value, done
if (!(config[key] instanceof Array)) {
config[key] = [config[key]];
}
}
module.exports.connect = ngrokForward;
module.exports.forward = ngrokForward;
module.exports.consoleLog = consoleLog;
module.exports.listen = ngrokListen;
module.exports.listenable = listenable;