UNPKG

hfs

Version:
108 lines (107 loc) 6.36 kB
"use strict"; 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 })); }