{"version":3,"file":"Service.cjs","sources":["../../../src/registry/Service.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport os from 'os';\nimport { SrvAnswer, StringAnswer, TxtAnswer } from 'dns-packet';\n\nimport { encodeTXT, MDNSRecord, ServiceType } from '../utils';\nimport { Registry } from './Registry';\n\n// MARK: ServiceOptions\n/**\n * Options used to configure a service instance.\n */\nexport interface ServiceOptions {\n  /**\n   * The protocol used by the service, typically \"tcp\" or \"udp\".\n   * @default 'tcp'\n   */\n  protocol?: string;\n  /**\n   * The service type (e.g., \"http\", \"ipp\").\n   * Used to form the full service type domain.\n   */\n  type: string;\n  /**\n   * Optional list of subtype identifiers for selective discovery.\n   */\n  subtypes?: string[];\n  /**\n   * The instance name of the service.\n   * This will be sanitized by replacing dots with dashes.\n   * @example 'MyPrinter'\n   */\n  name: string;\n\n  /**\n   * The hostname of the machine offering the service.\n   * @default os.hostname()\n   */\n  host?: string;\n  /**\n   * The port number on which the service is listening.\n   */\n  port: number;\n\n  /**\n   * Optional TXT record key-value pairs to advertise service metadata.\n   */\n  txt?: Record<string, string | number | boolean | Buffer>;\n\n  /**\n   * The TTL (time to live) in seconds for the all records.\n   * @remarks The same TTL is set to all records (PTR, SRV, TXT, A, AAAA) for simplicity and compatibility.\n   * @default 4500\n   */\n  ttl?: number;\n\n  /**\n   * Whether to perform probing to detect name conflicts before publishing.\n   * @default true\n   */\n  probe?: boolean;\n  /**\n   * Whether to automatically resolve name conflicts by appending a number to the service name.\n   * @default true\n   */\n  probeAutoResolve?: boolean;\n\n  /**\n   * Whether to disable publishing IPv6 addresses for this service.\n   * @default false\n   */\n  disableIPv6?: boolean;\n}\n\ninterface ServiceEventMap {\n  up: [];\n  down: [];\n}\n\n// MARK: Service\n/**\n * Represents a service registered on the local network via mDNS/DNS-SD.\n *\n * This class manages the service's DNS resource records such as PTR, SRV, TXT, A, and AAAA,\n * constructed according to the DNS-Based Service Discovery specification.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6763 | RFC 6763 - DNS-Based Service Discovery}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6762 | RFC 6762 - Multicast DNS}\n */\nexport class Service extends EventEmitter<ServiceEventMap> implements Required<ServiceOptions> {\n  static TLD = '.local';\n\n  protocol: string;\n  type: string;\n  name: string;\n  subtypes: string[];\n\n  host: string;\n  port: number;\n\n  txt: Record<string, string | number | boolean | Buffer>;\n\n  ttl: number;\n\n  probe = true;\n  probeAutoResolve = true;\n  disableIPv6: boolean;\n\n  fqdn: string;\n\n  started = false;\n  published = false;\n  destroyed = false;\n\n  registry?: Registry;\n\n  constructor(options: ServiceOptions) {\n    super();\n\n    if (options.port <= 0 || options.port > 65535) {\n      throw new Error('Invalid port number');\n    }\n\n    this.protocol = options.protocol ?? 'tcp';\n    this.type = new ServiceType(options.type, this.protocol).toString();\n    this.subtypes = options.subtypes ?? [];\n    this.name = options.name.split('.').join('-');\n\n    this.host = options.host ?? os.hostname();\n    this.port = options.port;\n\n    this.txt = options.txt ?? {};\n\n    this.ttl = options.ttl ?? 4500;\n\n    this.probe = options.probe ?? true;\n    this.probeAutoResolve = options.probeAutoResolve ?? true;\n\n    this.disableIPv6 = options.disableIPv6 ?? false;\n\n    this.fqdn = `${this.name}.${this.type}${Service.TLD}`;\n  }\n\n  /**\n   * Starts the registered service, initiating its publication on the network.\n   *\n   * If the service has already been started, this method does nothing.\n   */\n  async start(): Promise<void> {\n    if (!this.started) {\n      this.started = true;\n      await this.registry?.onServiceStart(this);\n    }\n  }\n\n  /**\n   * Stops the registered service and removes it from the network.\n   */\n  async stop(): Promise<void> {\n    if (this.started) {\n      this.started = false;\n      await this.registry?.onServiceStop(this);\n    }\n  }\n\n  /**\n   * Generates all DNS resource records representing this service instance.\n   *\n   * This includes:\n   * - PTR record for the service type\n   * - SRV record with host and port\n   * - TXT record with metadata\n   * - PTR record advertising the service type in the _services._dns-sd._udp.local domain\n   * - PTR records for any declared subtypes\n   * - A and AAAA records for each network interface address (unless IPv6 is disabled)\n   *\n   * @returns An array of DNS resource records suitable for publishing via mDNS.\n   */\n  getRecords(): MDNSRecord[] {\n    const records: MDNSRecord[] = [\n      this.getRecordPTR(),\n      this.getRecordSRV(),\n      this.getRecordTXT(),\n      this.getRecordPTRServiceTypeEnumeration(),\n    ];\n\n    // Handle subtypes\n    for (const subtype of this.subtypes) {\n      records.push(this.getRecordPTRSubtype(subtype));\n    }\n\n    // Create record per interface address\n    const ifaces = Object.values(os.networkInterfaces());\n    for (const iface of ifaces) {\n      for (const addr of iface ?? []) {\n        if (addr.internal || addr.mac === '00:00:00:00:00:00') {\n          continue;\n        }\n        if (addr.family === 'IPv4') {\n          records.push(this.getRecordA(addr.address));\n        } else {\n          if (this.disableIPv6) continue;\n          records.push(this.getRecordAAAA(addr.address));\n        }\n      }\n    }\n\n    // Return all records\n    return records;\n  }\n\n  // MARK: private\n  /**\n   * Constructs the main PTR record pointing to this instance's FQDN.\n   *\n   * This advertises the presence of a service instance under the service type domain.\n   *\n   * @returns A PTR record for service instance discovery.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6763#section-4.1 | RFC 6763 §4.1 - Structured Service Instance Names}\n   */\n  private getRecordPTR(): StringAnswer {\n    return {\n      type: 'PTR',\n      name: `${this.type}${Service.TLD}`,\n      ttl: this.ttl,\n      data: this.fqdn,\n    };\n  }\n\n  /**\n   * Constructs a subtype PTR record to support selective instance discovery by subtype.\n   *\n   * This record maps from a subtype-specific domain to the instance FQDN.\n   *\n   * @param subtype - The subtype identifier (e.g., \"printer\").\n   * @returns A subtype-specific PTR record.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6763#section-7.1 | RFC 6763 §7.1 - Selective Instance Enumeration (Subtypes)}\n   */\n  private getRecordPTRSubtype(subtype: string): StringAnswer {\n    return {\n      type: 'PTR',\n      name: `_${subtype}._sub.${this.type}${Service.TLD}`,\n      ttl: this.ttl,\n      data: `${this.name}.${this.type}${Service.TLD}`,\n    };\n  }\n\n  /**\n   * Constructs a PTR record under the special _services._dns-sd._udp.local name.\n   *\n   * This enables enumeration of available service types on the local network.\n   *\n   * @returns A PTR record pointing to this service type.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6763#section-9 | RFC 6763 §9 - Service Type Enumeration}\n   */\n  private getRecordPTRServiceTypeEnumeration(): StringAnswer {\n    return {\n      type: 'PTR',\n      name: `_services._dns-sd._udp${Service.TLD}`,\n      ttl: this.ttl,\n      data: `${this.type}${Service.TLD}`,\n    };\n  }\n\n  /**\n   * Constructs an SRV record that contains the target host and port of this service.\n   *\n   * This enables clients to locate the host and communication endpoint.\n   *\n   * @returns An SRV record for this service instance.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6763#section-4.1 | RFC 6763 §4.1 - Structured Service Instance Names}\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc2782 | RFC 2782 - A DNS RR for specifying the location of services (DNS SRV)}\n   */\n  private getRecordSRV(): SrvAnswer {\n    return {\n      type: 'SRV',\n      name: this.fqdn,\n      ttl: this.ttl,\n      data: {\n        port: this.port,\n        target: this.host,\n      },\n    };\n  }\n\n  /**\n   * Constructs a TXT record carrying metadata key-value pairs about the service.\n   *\n   * Keys should be lowercase and limited in length. Empty value fields are valid.\n   *\n   * @returns A TXT record representing service metadata.\n   *\n   * The name of a TXT record is the same name as the SRV record\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6763#section-6 | RFC 6763 §6 - Data Syntax for DNS-SD TXT Records}\n   */\n  private getRecordTXT(): TxtAnswer {\n    return {\n      type: 'TXT',\n      name: this.fqdn,\n      ttl: this.ttl,\n      data: encodeTXT(this.txt),\n    };\n  }\n\n  /**\n   * Constructs an A record mapping the hostname to an IPv4 address.\n   *\n   * @param ip - The IPv4 address associated with the service host.\n   * @returns An A record for IPv4 address resolution.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6762#section-6 | RFC 6762 §6 - Responding}\n   */\n  private getRecordA(ip: string): StringAnswer {\n    return {\n      type: 'A',\n      name: this.host,\n      ttl: this.ttl,\n      data: ip,\n    };\n  }\n\n  /**\n   * Constructs an AAAA record mapping the hostname to an IPv6 address.\n   *\n   * @param ip - The IPv6 address associated with the service host.\n   * @returns An AAAA record for IPv6 address resolution.\n   * @see {@link https://datatracker.ietf.org/doc/html/rfc6762#section-6 | RFC 6762 §6 - Responding}\n   */\n  private getRecordAAAA(ip: string): StringAnswer {\n    return {\n      type: 'AAAA',\n      name: this.host,\n      ttl: this.ttl,\n      data: ip,\n    };\n  }\n}\n"],"names":["_Service","EventEmitter","options","ServiceType","os","records","subtype","ifaces","iface","addr","encodeTXT","ip","Service"],"mappings":"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"}