Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | import { Internal, type Errors, type IErrorFactory, type IErrorMap, type IPlatformApi, } from '@feasibleone/blong/types'; import {hostname} from 'os'; import multicastResolver from 'ut-bus/resolver.ts'; import discovery from 'ut-dns-discovery'; import type {IResolution} from './Resolution.ts'; const errorMap: IErrorMap = { 'mdns.notFound': "Multicast DNS: '{namespace}' service not found.", }; interface IConfig { domain: boolean; prefix: string; suffix: string; channel: string; tls: string; } export default class ResolutionDiscovery extends Internal implements IResolution { #announce: ReturnType<typeof discovery>; #services: Set<string> = new Set(); #errors: Errors<typeof errorMap>; #config: IConfig = { domain: true, prefix: '', suffix: '', tls: '', channel: '', }; public resolve: IResolution['resolve']; public constructor( config: IConfig, {error, platform}: {error: IErrorFactory; platform: IPlatformApi}, ) { super(); this.merge(this.#config, config); this.#announce = discovery(); this.#errors = error.register(errorMap); type HRTime = [number, number]; const cache: Record<string, [HRTime, {hostname: string; port: string}]> = {}; this.resolve = async (service, invalidate, namespace) => { try { const now = platform.timing.now(); const hostName = `${this._serviceId(service)}.dns-discovery.local`; if (invalidate) { delete cache[hostName]; } else { const cached = cache[hostName]; if (cached) { if (platform.timing.now(cached[0])[0] < 3) { cached[0] = now; return {...cached[1], cache: service, namespace}; } else { delete cache[hostName]; } } } const resolved = await multicastResolver(hostName, 'SRV', !!this.#config.tls); const result = { hostname: resolved.target === '0.0.0.0' ? 'localhost' : resolved.target, port: resolved.port, }; if (cache) cache[hostName] = [now, result]; return result; } catch (e) { const err = this.#errors['mdns.notFound']({params: {namespace}}); err.cause = e as Error | undefined; throw err; } }; } private _serviceId(service: string): string { const tld = this.#config.tls ? '.' + this.#config.channel : ''; // similar to top level domain const prefix = this.#config.prefix; const suffix = this.#config.suffix || '-service' + tld; const domain = this.#config.domain === true ? hostname() + tld : this.#config.domain; return `${prefix}${service}${suffix}-${domain}`; } public announce(service: string, port: number): void { this.#services.add(`${this._serviceId(service)}:${port}`); } public async start(): Promise<void> { await Promise.all( Array.from(this.#services.values()).map( serviceId => new Promise((resolve, reject) => { const [service, port] = serviceId.split(':'); this.#announce.announce(service, port, (error: Error | null | undefined) => error ? reject(error) : resolve(true), ); }), ), ); } public async stop(): Promise<void> { await Promise.all( Array.from(this.#services.values()).map( serviceId => new Promise((resolve, reject) => { const [service, port] = serviceId.split(':'); this.#announce.unannounce(service, port, (error: Error | null | undefined) => error ? reject(error) : this.#services.delete(service), ); }), ), ); } } |