hfs
Version:
HTTP File Server
92 lines (91 loc) • 5.22 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const apiMiddleware_1 = require("./apiMiddleware");
const const_1 = require("./const");
const lodash_1 = __importDefault(require("lodash"));
const listen_1 = require("./listen");
const github_1 = require("./github");
const misc_1 = require("./misc");
const promises_1 = require("dns/promises");
const net_1 = require("net");
const nat_1 = require("./nat");
const acme_1 = require("./acme");
const selfCheck_1 = require("./selfCheck");
const apis = {
get_nat: nat_1.getNatInfo,
get_public_ips: nat_1.getPublicIps,
async check_domain({ domain }) {
(0, misc_1.apiAssertTypes)({ string: domain });
const resolver = new promises_1.Resolver();
const prjInfo = await (0, github_1.getProjectInfo)();
resolver.setServers(prjInfo.dnsServers);
const settled = await Promise.allSettled([
resolver.resolve(domain, 'A'),
resolver.resolve(domain, 'AAAA'),
(0, promises_1.lookup)(domain).then(x => [x.address]),
]);
if (settled[0].status === 'rejected' && settled[0].reason.code === 'ECONNREFUSED')
throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, "cannot resolve domain");
// merge all results
const domainIps = lodash_1.default.uniq((0, misc_1.onlyTruthy)(settled.map(x => x.status === 'fulfilled' && x.value)).flat());
if (!domainIps.length)
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "domain not working");
const publicIps = await (0, nat_1.getPublicIps)(); // do this before stopping the server
for (const v6 of [false, true]) {
const domainIpsThisVersion = domainIps.filter(x => (0, net_1.isIPv6)(x) === v6);
const ipsThisVersion = publicIps.filter(x => (0, net_1.isIPv6)(x) === v6);
if (domainIpsThisVersion.length && ipsThisVersion.length && !lodash_1.default.intersection(domainIpsThisVersion, ipsThisVersion).length)
throw new apiMiddleware_1.ApiError(const_1.HTTP_PRECONDITION_FAILED, `configure your domain to point to ${ipsThisVersion} (currently on ${domainIpsThisVersion[0]}) – a change can take hours to be effective`);
}
return {};
},
async map_port({ external, internal }) {
const { upnp, externalPort, internalPort } = await (0, nat_1.getNatInfo)();
if (!upnp)
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, "upnp failed");
if (!internalPort)
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "no internal port");
if (externalPort)
try {
await nat_1.upnpClient.removeMapping({ public: { host: '', port: externalPort } });
}
catch (e) {
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "removeMapping failed: " + String(e));
}
if (external) // must use the object form of 'public' to work around a bug of the library
await nat_1.upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 })
.catch(res => {
throw new apiMiddleware_1.ApiError(res.errorCode || res.statusCode, res.errorCode === 718 ? "Port not available" : res.errorDescription || "unknown error");
});
return {};
},
async self_check({ url }) {
if (url)
return await (0, selfCheck_1.selfCheck)(url)
|| new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
const [publicIps, nat] = await Promise.all([(0, nat_1.getPublicIps)(), (0, nat_1.getNatInfo)()]);
if (!publicIps.length)
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'cannot detect public ip');
if (!nat.internalPort)
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
const finalPort = nat.externalPort || nat.internalPort;
const proto = nat.proto || ((0, listen_1.getCertObject)() ? 'https' : 'http');
const defPort = proto === 'https' ? 443 : 80;
const results = (0, misc_1.onlyTruthy)(await (0, misc_1.promiseBestEffort)(publicIps.map(ip => (0, selfCheck_1.selfCheck)(`${proto}://${ip}${finalPort === defPort ? '' : ':' + finalPort}`))));
return results.length ? results : new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
},
async make_cert({ domain, email, altNames }) {
await (0, acme_1.makeCert)(domain, email, altNames).catch(e => {
throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message || String(e));
});
return {};
},
get_cert() {
return (0, listen_1.getCertObject)() || { none: true };
}
};
exports.default = apis;
;