sterfive-bonjour-service
Version:
A Bonjour/Zeroconf implementation in TypeScript
182 lines (158 loc) • 4.52 kB
text/typescript
/**
* Bonjour Service - Service Definition
*/
import { NetworkInterfaceInfo, networkInterfaces, hostname } from 'os'
import { EventEmitter } from 'events'
import * as mDNS from 'multicast-dns'
import { toString as ServiceToString } from './service-types'
import DnsTxt from './dns-txt'
const TLD: string = '.local'
export interface ServiceConfig extends Omit<mDNS.Options,"type"> {
name: string
type?: string
port: number
protocol?: 'tcp' | 'udp'
host?: string
fqdn?: string
subtypes?: Array<string>
txt?: Record<string, string>
probe?: boolean
}
export interface ServiceRecord {
name: string
type: 'PTR' | 'SRV' | 'TXT' | 'A' | 'AAAA'
ttl: number
data: { [key: string]: any } | string | any
}
export interface ServiceReferer {
address: string
family: 'IPv4' | 'IPv6'
port: number
size: number
}
/**
* Provide PTR record
* @param service
* @returns
*/
function RecordPTR(service: Service): ServiceRecord {
return {
name: `${service.type}${TLD}`,
type: 'PTR',
ttl: 28800,
data: service.fqdn
}
}
/**
* Provide SRV record
* @param service
* @returns
*/
function RecordSRV(service: Service): ServiceRecord {
return {
name: service.fqdn,
type: 'SRV',
ttl: 120,
data: {
port: service.port,
target: service.host
}
}
}
/**
* Provide TXT record
* @param service
* @returns
*/
function RecordTXT(service: Service): ServiceRecord {
const txtService = new DnsTxt()
return {
name: service.fqdn,
type: 'TXT',
ttl: 4500,
data: txtService.encode(service.txt)
}
}
/**
* Provide A record
* @param service
* @param ip
* @returns
*/
function RecordA(service: Service, ip: string): ServiceRecord {
return {
name: service.host,
type: 'A',
ttl: 120,
data: ip
}
}
/**
* Provide AAAA record
* @param service
* @param ip
* @returns
*/
function RecordAAAA(service: Service, ip: string): ServiceRecord {
return {
name: service.host,
type: 'AAAA',
ttl: 120,
data: ip
}
}
export class Service extends EventEmitter {
public name: string
public type: string
public protocol: 'tcp' | 'udp'
public port: number
public host: string
public fqdn: string
public txt?: Record<string, string>
public subtypes?: Array<string>
public addresses?: Array<string>
public referer?: ServiceReferer
public probe: boolean = true
public published: boolean = false
public activated: boolean = false
public destroyed: boolean = false
public _broadCastTimeout: NodeJS.Timer | undefined
public start?: any
public stop?: any
constructor(config: ServiceConfig) {
super()
if (!config.name) throw new Error('Required name not given')
if (!config.type) throw new Error('Required type not given')
if (!config.port) throw new Error('Required port not given')
this.name = config.name
this.protocol = config.protocol || 'tcp'
this.type = ServiceToString({ name: config.type, protocol: this.protocol })
this.port = config.port
this.host = config.host || hostname()
this.fqdn = `${this.name}.${this.type}${TLD}`
this.txt = config.txt
this.subtypes = config.subtypes
}
public records(): ServiceRecord[] {
const records: ServiceRecord[] = [RecordPTR(this), RecordSRV(this), RecordTXT(this)]
// Create record per interface address
const ifaces: Array<NetworkInterfaceInfo[]> = Object.values(networkInterfaces()) as unknown as NetworkInterfaceInfo[][]
for (let iface of ifaces) {
let addrs = iface
for (let addr of addrs) {
if (addr.internal || addr.mac === '00:00:00:00:00:00') continue
switch (addr.family) {
case 'IPv4':
records.push(RecordA(this, addr.address))
break
case 'IPv6':
records.push(RecordAAAA(this, addr.address))
break
}
}
}
// Return all records
return records
}
}
export default Service