"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GetServiceInterface = exports.filledAddress = exports.addressHostToUrl = exports.getHostname = void 0; exports.filterNonLocal = filterNonLocal; exports.getOwnServiceInterface = getOwnServiceInterface; exports.getServiceInterface = getServiceInterface; const Host_1 = require("../interfaces/Host"); const ip_1 = require("./ip"); const deepEqual_1 = require("./deepEqual"); const once_1 = require("./once"); const Watchable_1 = require("./Watchable"); const getHostnameRegex = /^(\w+:\/\/)?([^\/\:]+)(:\d{1,3})?(\/)?/; const getHostname = (url) => { const founds = url.match(getHostnameRegex)?.[2]; if (!founds) return null; const parts = founds.split('@'); const last = parts[parts.length - 1]; return last; }; exports.getHostname = getHostname; const nonLocalFilter = { exclude: { kind: ['localhost', 'link-local', 'bridge'], }, }; const publicFilter = { visibility: 'public', }; const either = (...args) => (a) => args.some((x) => x(a)); const negate = (fn) => (a) => !fn(a); const unique = (values) => Array.from(new Set(values)); const addressHostToUrl = ({ scheme, sslScheme, username, suffix }, hostname) => { const effectiveScheme = hostname.ssl ? sslScheme : scheme; let host; if (hostname.metadata.kind === 'ipv6') { host = ip_1.IPV6_LINK_LOCAL.contains(hostname.hostname) ? `[${hostname.hostname}%${hostname.metadata.scopeId}]` : `[${hostname.hostname}]`; } else { host = hostname.hostname; } let portStr = ''; if (hostname.port !== null) { const excludePort = effectiveScheme && effectiveScheme in Host_1.knownProtocols && hostname.port === Host_1.knownProtocols[effectiveScheme] .defaultPort; if (!excludePort) portStr = `:${hostname.port}`; } return `${effectiveScheme ? `${effectiveScheme}://` : ''}${username ? `${username}@` : ''}${host}${portStr}${suffix}`; }; exports.addressHostToUrl = addressHostToUrl; function filterRec(hostnames, filter, invert) { if (filter.predicate) { const pred = filter.predicate; hostnames = hostnames.filter((h) => invert !== pred(h)); } if (filter.visibility === 'public') hostnames = hostnames.filter((h) => invert !== h.public); if (filter.visibility === 'private') hostnames = hostnames.filter((h) => invert !== !h.public); if (filter.kind) { const kind = new Set(Array.isArray(filter.kind) ? filter.kind : [filter.kind]); if (kind.has('ip')) { kind.add('ipv4'); kind.add('ipv6'); } hostnames = hostnames.filter((h) => invert !== ((kind.has('mdns') && h.metadata.kind === 'mdns') || (kind.has('domain') && (h.metadata.kind === 'private-domain' || h.metadata.kind === 'public-domain')) || (kind.has('ipv4') && h.metadata.kind === 'ipv4') || (kind.has('ipv6') && h.metadata.kind === 'ipv6') || (kind.has('localhost') && ['localhost', '127.0.0.1', '::1'].includes(h.hostname)) || (kind.has('link-local') && h.metadata.kind === 'ipv6' && ip_1.IPV6_LINK_LOCAL.contains(ip_1.IpAddress.parse(h.hostname))) || (kind.has('bridge') && h.metadata.kind === 'ipv4' && h.metadata.gateway === 'lxcbr0') || (kind.has('plugin') && h.metadata.kind === 'plugin'))); } if (filter.pluginId) { const id = filter.pluginId; hostnames = hostnames.filter((h) => invert !== (h.metadata.kind === 'plugin' && h.metadata.packageId === id)); } if (filter.exclude) return filterRec(hostnames, filter.exclude, !invert); return hostnames; } function isPublicIp(h) { return h.public && (h.metadata.kind === 'ipv4' || h.metadata.kind === 'ipv6'); } function enabledAddresses(addr) { return addr.available.filter((h) => { if (isPublicIp(h)) { // Public IPs: disabled by default, explicitly enabled via SocketAddr string if (h.port === null) return true; const sa = h.metadata.kind === 'ipv6' ? `[${h.hostname}]:${h.port}` : `${h.hostname}:${h.port}`; return addr.enabled.includes(sa); } else { // Everything else: enabled by default, explicitly disabled via [hostname, port] tuple return !addr.disabled.some(([hostname, port]) => hostname === h.hostname && port === (h.port ?? 0)); } }); } /** * Filters out localhost and IPv6 link-local hostnames from a list. * Equivalent to the `nonLocal` filter on `Filled` addresses. */ function filterNonLocal(hostnames) { return filterRec(hostnames, nonLocalFilter, false); } const filledAddress = (host, addressInfo) => { const toUrl = exports.addressHostToUrl.bind(null, addressInfo); const binding = host.bindings[addressInfo.internalPort]; const hostnames = binding ? enabledAddresses(binding.addresses) : []; function filledAddressFromHostnames(hostnames) { const getNonLocal = (0, once_1.once)(() => filledAddressFromHostnames(filterRec(hostnames, nonLocalFilter, false))); const getPublic = (0, once_1.once)(() => filledAddressFromHostnames(filterRec(hostnames, publicFilter, false))); return { ...addressInfo, hostnames, toUrl, format: (format) => { let res = hostnames; if (format === 'hostname-info') return res; const urls = hostnames.map(toUrl); if (format === 'url') res = urls.map((u) => new URL(u)); else res = urls; return res; }, filter: (filter) => { return filledAddressFromHostnames(filterRec(hostnames, filter, false)); }, matchesAny: (filters) => { const seen = new Set(); const union = []; for (const f of filters) { for (const h of filterRec(hostnames, f, false)) { if (!seen.has(h)) { seen.add(h); union.push(h); } } } return filledAddressFromHostnames(union); }, get nonLocal() { return getNonLocal(); }, get public() { return getPublic(); }, }; } return filledAddressFromHostnames(hostnames); }; exports.filledAddress = filledAddress; const makeInterfaceFilled = async ({ effects, id, packageId, callback, }) => { const serviceInterfaceValue = await effects.getServiceInterface({ serviceInterfaceId: id, packageId, callback, }); if (!serviceInterfaceValue) { return null; } const hostId = serviceInterfaceValue.addressInfo.hostId; const host = await effects.getHostInfo({ packageId, hostId, callback, }); const interfaceFilled = { ...serviceInterfaceValue, host, addressInfo: host ? (0, exports.filledAddress)(host, serviceInterfaceValue.addressInfo) : null, }; return interfaceFilled; }; class GetServiceInterface extends Watchable_1.Watchable { constructor(effects, opts, options) { super(effects, options); this.opts = opts; this.label = 'GetServiceInterface'; } fetch(callback) { return makeInterfaceFilled({ effects: this.effects, id: this.opts.id, packageId: this.opts.packageId, callback, }); } } exports.GetServiceInterface = GetServiceInterface; function getOwnServiceInterface(effects, id, map, eq) { return new GetServiceInterface(effects, { id }, { map: map ?? ((a) => a), eq: eq ?? ((a, b) => (0, deepEqual_1.deepEqual)(a, b)), }); } function getServiceInterface(effects, opts, map, eq) { return new GetServiceInterface(effects, opts, { map: map ?? ((a) => a), eq: eq ?? ((a, b) => (0, deepEqual_1.deepEqual)(a, b)), }); } //# sourceMappingURL=getServiceInterface.js.map