hfs
Version:
HTTP File Server
108 lines (107 loc) • 6.36 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNatInfo = exports.getPublicIps = exports.upnpClient = exports.defaultBaseUrl = void 0;
const valtio_1 = require("valtio");
const nat_upnp_rejetto_1 = require("nat-upnp-rejetto");
const debounceAsync_1 = require("./debounceAsync");
const cross_1 = require("./cross");
const github_1 = require("./github");
const lodash_1 = __importDefault(require("lodash"));
const util_http_1 = require("./util-http");
const promises_1 = require("dns/promises");
const net_1 = require("net");
const listen_1 = require("./listen");
const child_process_1 = require("child_process");
const const_1 = require("./const");
exports.defaultBaseUrl = (0, valtio_1.proxy)({
proto: 'http',
publicIps: [],
externalIp: '',
localIp: '',
port: 0,
async get() {
const defPort = this.proto === 'https' ? 443 : 80;
const status = await (0, listen_1.getServerStatus)();
const port = this.port || (this.proto === 'https' ? status.https.port : status.http.port);
return `${this.proto}://${(0, cross_1.ipForUrl)(this.publicIps[0] || this.externalIp || this.localIp)}${!port || port === defPort ? '' : ':' + port}`;
}
});
exports.upnpClient = new nat_upnp_rejetto_1.Client({ timeout: 4000 });
const originalMethod = exports.upnpClient.getGateway;
// other client methods call getGateway too, so this will ensure they reuse this same result
exports.upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(exports.upnpClient), { retain: cross_1.HOUR, retainFailure: 30000 });
exports.upnpClient.getGateway().then(res => {
console.log("upnp found", res.gateway.description);
}, e => console.debug('upnp failed:', e.message || String(e)));
// poll external ip
(0, cross_1.repeat)(10 * cross_1.MINUTE, () => exports.upnpClient.getPublicIp().then(v => {
if (v === exports.defaultBaseUrl.externalIp)
return;
exports.getPublicIps.clearRetain();
return exports.defaultBaseUrl.externalIp = v;
}));
exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
const res = await (0, github_1.getProjectInfo)();
const groupedByVersion = Object.values(lodash_1.default.groupBy(res.publicIpServices, x => { var _a; return (_a = x.v) !== null && _a !== void 0 ? _a : 4; }));
const ips = await (0, cross_1.promiseBestEffort)(groupedByVersion.map(singleVersion => Promise.any(singleVersion.map(async (svc) => {
if (typeof svc === 'string')
svc = { type: 'http', url: svc };
console.debug("trying ip service", svc.url || svc.name);
if (svc.type === 'http')
return (0, util_http_1.httpString)(svc.url, { timeout: 5000 });
if (svc.type !== 'dns')
throw "unsupported";
const resolver = new promises_1.Resolver({ timeout: 2000 });
resolver.setServers(svc.ips);
return resolver.resolve(svc.name, svc.dnsRecord);
}).map(async (ret) => {
const validIps = (0, cross_1.wantArray)(await ret).map(x => x.trim()).filter(net_1.isIP);
if (!validIps.length)
throw "no good";
return validIps;
}))));
return exports.defaultBaseUrl.publicIps = lodash_1.default.uniq(ips.flat());
}, { retain: 10 * cross_1.MINUTE });
exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
var _a, _b, _c, _d;
const gatewayIpPromise = findGateway().catch(() => undefined);
const res = await (0, cross_1.haveTimeout)(10000, exports.upnpClient.getGateway()).catch(() => null);
const status = await (0, listen_1.getServerStatus)();
const mappings = res && await (0, cross_1.haveTimeout)(5000, exports.upnpClient.getMappings()).catch(() => null);
console.debug("mappings found", (mappings === null || mappings === void 0 ? void 0 : mappings.map(x => x.description).join(', ')) || "none");
const localIps = await (0, listen_1.getIps)(false);
const gatewayIp = await gatewayIpPromise;
const localIp = (res === null || res === void 0 ? void 0 : res.address) || (gatewayIp ? lodash_1.default.maxBy(localIps, x => (0, cross_1.inCommon)(x, gatewayIp)) : localIps[0]);
const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port || undefined;
const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
const externalPort = mapped === null || mapped === void 0 ? void 0 : mapped.public.port;
if (localIp)
exports.defaultBaseUrl.localIp = localIp;
exports.defaultBaseUrl.port = externalPort || internalPort || 0;
return {
upnp: Boolean(res),
localIp,
gatewayIp,
externalIp: exports.defaultBaseUrl.externalIp,
mapped,
mapped80: lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === 80 && x.public.port === 80),
internalPort,
externalPort,
proto: ((_c = status === null || status === void 0 ? void 0 : status.https) === null || _c === void 0 ? void 0 : _c.listening) ? 'https' : ((_d = status === null || status === void 0 ? void 0 : status.http) === null || _d === void 0 ? void 0 : _d.listening) ? 'http' : '',
};
}, { reuseRunning: true });
(0, exports.getNatInfo)();
function findGateway() {
return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
var _a, _b;
if (err)
return reject(err);
if (!const_1.IS_WINDOWS)
return resolve((_a = out.match(const_1.IS_MAC ? /default +([\d.]+)/ : /^0\.0\.0\.0 +([\d.]+)/)) === null || _a === void 0 ? void 0 : _a[1]);
const sortedByMetric = lodash_1.default.sortBy([...out.matchAll(/(?:0\.0\.0\.0 +){2}([\d.]+)\s+[\d.]+\s+(\d+)/g)], x => Number(x[2]));
resolve((_b = sortedByMetric[0]) === null || _b === void 0 ? void 0 : _b[1]); // take ip with lowest metric
}));
}
;