@homebridge/ciao
Version:
ciao is a RFC 6763 compliant dns-sd library, advertising on multicast dns (RFC 6762) implemented in plain Typescript/JavaScript
457 lines • 17.2 kB
TypeScript
import { EventEmitter } from "events";
import { DNSResponseDefinition } from "./coder/DNSPacket";
import { AAAARecord } from "./coder/records/AAAARecord";
import { ARecord } from "./coder/records/ARecord";
import { NSECRecord } from "./coder/records/NSECRecord";
import { PTRRecord } from "./coder/records/PTRRecord";
import { SRVRecord } from "./coder/records/SRVRecord";
import { TXTRecord } from "./coder/records/TXTRecord";
import { ResourceRecord } from "./coder/ResourceRecord";
import { Protocol } from "./index";
import { InterfaceName, IPAddress, NetworkManager, NetworkUpdate } from "./NetworkManager";
import { Announcer } from "./responder/Announcer";
/**
* This enum defines some commonly used service types.
* This is also referred to as service name (as of RFC 6763).
* A service name must not be longer than 15 characters (RFC 6763 7.2).
*/
export declare const enum ServiceType {
AIRDROP = "airdrop",
AIRPLAY = "airplay",
AIRPORT = "airport",
COMPANION_LINK = "companion-link",
DACP = "dacp",// digital audio control protocol (iTunes)
HAP = "hap",// used by HomeKit accessories
HOMEKIT = "homekit",// used by home hubs
HTTP = "http",
HTTP_ALT = "http_alt",// http alternate
IPP = "ipp",// internet printing protocol
IPPS = "ipps",// internet printing protocol over https
RAOP = "raop",// remote audio output protocol
scanner = "scanner",// bonjour scanning
TOUCH_ABLE = "touch-able",// iPhone and iPod touch remote controllable
DNS_SD = "dns-sd",
PRINTER = "printer"
}
/**
* Service options supplied when creating a new ciao service.
*/
export interface ServiceOptions {
/**
* Instance name of the service
*/
name: string;
/**
* Type of the service.
*/
type: ServiceType | string;
/**
* Optional array of subtypes of the service.
* Refer to {@link ServiceType} for some known examples.
*/
subtypes?: (ServiceType | string)[];
/**
* Port of the service.
* If not supplied it must be set later via {@link CiaoService.updatePort} BEFORE advertising the service.
*/
port?: number;
/**
* The protocol the service uses. Default is TCP.
*/
protocol?: Protocol;
/**
* Defines a hostname under which the service can be reached.
* The specified hostname must not include the TLD.
* If undefined the service name will be used as default.
*/
hostname?: string;
/**
* If defined, a txt record will be published with the given service.
*/
txt?: ServiceTxt;
/**
* Adds ability to set custom domain. Will default to "local".
* The domain will also be automatically appended to the hostname.
*/
domain?: string;
/**
* If defined it restricts the service to be advertised on the specified
* ip addresses or interface names.
*
* If an interface name is specified, ANY address on that given interface will be advertised
* (if an IP address of the given interface is also given in the array, it will be overridden).
* If an IP address is specified, the service will only be advertised for the given addresses.
*
* Interface names and addresses can be mixed in the array.
* If an ip address is given, the ip address must be valid at the time of service creation.
*
* If the service is set to advertise on a given interface, though the MDNSServer is
* configured to ignore this interface, the service won't be advertised on the interface.
*/
restrictedAddresses?: (InterfaceName | IPAddress)[];
/**
* The service won't advertise ipv6 address records.
* This can be used to simulate binding on 0.0.0.0.
* May be combined with {@link restrictedAddresses}.
*/
disabledIpv6?: boolean;
}
/**
* A service txt consist of multiple key=value pairs,
* which get advertised on the network.
*/
export type ServiceTxt = Record<string, any>;
/**
* @private
*/
export declare const enum ServiceState {
UNANNOUNCED = "unannounced",
PROBING = "probing",
PROBED = "probed",// service was probed to be unique
ANNOUNCING = "announcing",// service is in the process of being announced
ANNOUNCED = "announced"
}
/**
* @private
*/
export interface ServiceRecords {
ptr: PTRRecord;
subtypePTRs?: PTRRecord[];
metaQueryPtr: PTRRecord;
srv: SRVRecord;
txt: TXTRecord;
serviceNSEC: NSECRecord;
a: Record<InterfaceName, ARecord>;
aaaa: Record<InterfaceName, AAAARecord>;
aaaaR: Record<InterfaceName, AAAARecord>;
aaaaULA: Record<InterfaceName, AAAARecord>;
reverseAddressPTRs: Record<IPAddress, PTRRecord>;
addressNSEC: NSECRecord;
}
/**
* Events thrown by a CiaoService
*/
export declare const enum ServiceEvent {
/**
* Event is called when the Prober identifies that the name for the service is already used
* and thus resolve the name conflict by adjusting the name (e.g. adding '(2)' to the name).
* This change must be persisted and thus a listener must hook up to this event
* in order for the name to be persisted.
*/
NAME_CHANGED = "name-change",
/**
* Event is called when the Prober identifies that the hostname for the service is already used
* and thus resolve the name conflict by adjusting the hostname (e.g. adding '(2)' to the hostname).
* The name change must be persisted. As the hostname is an optional parameter, it is derived
* from the service name if not supplied.
* If you supply a custom hostname (not automatically derived from the service name) you must
* hook up a listener to this event in order for the hostname to be persisted.
*/
HOSTNAME_CHANGED = "hostname-change"
}
/**
* Events thrown by a CiaoService, internal use only!
* @private
*/
export declare const enum InternalServiceEvent {
PUBLISH = "publish",
UNPUBLISH = "unpublish",
REPUBLISH = "republish",
RECORD_UPDATE = "records-update",
RECORD_UPDATE_ON_INTERFACE = "records-update-interface"
}
/**
* @private
*/
export type PublishCallback = (error?: Error) => void;
/**
* @private
*/
export type UnpublishCallback = (error?: Error) => void;
/**
* @private
*/
export type RecordsUpdateCallback = (error?: Error | null) => void;
export declare interface CiaoService {
on(event: "name-change", listener: (name: string) => void): this;
on(event: "hostname-change", listener: (hostname: string) => void): this;
/**
* @private
*/
on(event: InternalServiceEvent.PUBLISH, listener: (callback: PublishCallback) => void): this;
/**
* @private
*/
on(event: InternalServiceEvent.UNPUBLISH, listener: (callback: UnpublishCallback) => void): this;
/**
* @private
*/
on(event: InternalServiceEvent.REPUBLISH, listener: (callback: PublishCallback) => void): this;
/**
* @private
*/
on(event: InternalServiceEvent.RECORD_UPDATE, listener: (response: DNSResponseDefinition, callback?: (error?: Error | null) => void) => void): this;
/**
* @private
*/
on(event: InternalServiceEvent.RECORD_UPDATE_ON_INTERFACE, listener: (name: InterfaceName, records: ResourceRecord[], callback?: RecordsUpdateCallback) => void): this;
/**
* @private
*/
emit(event: ServiceEvent.NAME_CHANGED, name: string): boolean;
/**
* @private
*/
emit(event: ServiceEvent.HOSTNAME_CHANGED, hostname: string): boolean;
/**
* @private
*/
emit(event: InternalServiceEvent.PUBLISH, callback: PublishCallback): boolean;
/**
* @private
*/
emit(event: InternalServiceEvent.UNPUBLISH, callback: UnpublishCallback): boolean;
/**
* @private
*/
emit(event: InternalServiceEvent.REPUBLISH, callback?: PublishCallback): boolean;
/**
* @private
*/
emit(event: InternalServiceEvent.RECORD_UPDATE, response: DNSResponseDefinition, callback?: (error?: Error | null) => void): boolean;
/**
* @private
*/
emit(event: InternalServiceEvent.RECORD_UPDATE_ON_INTERFACE, name: InterfaceName, records: ResourceRecord[], callback?: RecordsUpdateCallback): boolean;
}
/**
* The CiaoService class represents a service which can be advertised on the network.
*
* A service is identified by its fully qualified domain name (FQDN), which consist of
* the service name, the service type, the protocol and the service domain (.local by default).
*
* The service defines a hostname and a port where the advertised service can be reached.
*
* Additionally, a TXT record can be published, which can contain information (in form of key-value pairs),
* which might be useful to a querier.
*
* A CiaoService class is always bound to a {@link Responder} and can be created using the
* {@link Responder.createService} method in the Responder class.
* Once the instance is created, {@link advertise} can be called to announce the service on the network.
*/
export declare class CiaoService extends EventEmitter {
private readonly networkManager;
private name;
private readonly type;
private readonly subTypes?;
private readonly protocol;
private readonly serviceDomain;
private fqdn;
private loweredFqdn;
private readonly typePTR;
private readonly loweredTypePTR;
private readonly subTypePTRs?;
private hostname;
private loweredHostname;
private port?;
private readonly restrictedAddresses?;
private readonly disableIpv6?;
private txt;
private txtTimer?;
/**
* this field is entirely controlled by the Responder class
* @private use by the Responder to set the current service state
*/
serviceState: ServiceState;
/**
* If service is in state {@link ServiceState.ANNOUNCING} the {@link Announcer} responsible for the
* service will be linked here. This is need to cancel announcing when for example the service
* should be terminated, and we still aren't fully announced yet.
* @private is controlled by the {@link Responder} instance
*/
currentAnnouncer?: Announcer;
private serviceRecords?;
private destroyed;
/**
* Constructs a new service. Please use {@link Responder.createService} to create new service.
* When calling the constructor a callee must listen to certain events in order to provide
* correct functionality.
* @private used by the Responder instance to create a new service
*/
constructor(networkManager: NetworkManager, options: ServiceOptions);
/**
* This method start the advertising process of the service:
* - The service name (and hostname) will be probed unique on all interfaces (as defined in RFC 6762 8.1).
* - Once probed unique the service will be announced (as defined in RFC 6762 8.3).
*
* The returned promise resolves once the last announcement packet was successfully sent on all network interfaces.
* The promise might be rejected caused by one of the following reasons:
* - A probe query could not be sent successfully
* - Prober could not find a unique service name while trying for a minute (timeout)
* - One of the announcement packets could not be sent successfully
*/
advertise(): Promise<void>;
/**
* This method will remove the advertisement for the service on all connected network interfaces.
* If the service is still in the Probing state, probing will simply be cancelled.
*
* @returns Promise will resolve once the last goodbye packet was sent out
*/
end(): Promise<void>;
/**
* This method must be called if you want to free the memory used by this service.
* The service instance is not usable anymore after this call.
*
* If the service is still announced, the service will first be removed
* from the network by calling {@link end}.
*
* @returns
*/
destroy(): Promise<void>;
/**
* @returns The fully qualified domain name of the service, used to identify the service.
*/
getFQDN(): string;
/**
* @returns The service type pointer.
*/
getTypePTR(): string;
/**
* @returns Array of subtype pointers (undefined if no subtypes are specified).
*/
getLowerCasedSubtypePTRs(): string[] | undefined;
/**
* @returns The current hostname of the service.
*/
getHostname(): string;
/**
* @returns The port the service is advertising for.
* {@code -1} is returned when the port is not yet set.
*/
getPort(): number;
/**
* @returns The current TXT of the service represented as Buffer array.
* @private There is not need for this to be public API
*/
getTXT(): Buffer[];
/**
* @private used for internal comparison {@link dnsLowerCase}
*/
getLowerCasedFQDN(): string;
/**
* @private used for internal comparison {@link dnsLowerCase}
*/
getLowerCasedTypePTR(): string;
/**
* @private used for internal comparison {@link dnsLowerCase}
*/
getLowerCasedHostname(): string;
/**
* Sets or updates the txt of the service.
*
* @param {ServiceTxt} txt - The updated txt record.
* @param {boolean} silent - If set to true no announcement is sent for the updated record.
*/
updateTxt(txt: ServiceTxt, silent?: boolean): void;
private queueTxtUpdate;
/**
* Sets or updates the port of the service.
* A new port number can only be set when the service is still UNANNOUNCED.
* Otherwise, an assertion error will be thrown.
*
* @param {number} port - The new port number.
*/
updatePort(port: number): void;
/**
* This method updates the name of the service.
* @param name - The new service name.
* @private Currently not public API and only used for bonjour conformance testing.
*/
updateName(name: string): Promise<void>;
private static txtBuffersFromRecord;
/**
* @param networkUpdate
* @private
*/
handleNetworkInterfaceUpdate(networkUpdate: NetworkUpdate): void;
/**
* This method is called by the Prober when encountering a conflict on the network.
* It advises the service to change its name, like incrementing a number appended to the name.
* So "My Service" will become "My Service (2)", and "My Service (2)" would become "My Service (3)"
* @private must only be called by the {@link Prober}
*/
incrementName(nameCheckOnly?: boolean): void;
/**
* @private called by the Prober once finished with probing to signal a (or more)
* name change(s) happened {@see incrementName}.
*/
informAboutNameUpdates(): void;
private formatFQDN;
/**
* @private called once the service data/state is updated and the records should be updated with the new data
*/
rebuildServiceRecords(): void;
/**
* Returns if the given service is advertising on the provided network interface.
*
* @param name - The desired interface name.
* @param skipAddressCheck - If true it is not checked if the service actually has
* an address record for the given interface.
* @private returns if the service should be advertised on the given service
*/
advertisesOnInterface(name: InterfaceName, skipAddressCheck?: boolean): boolean;
/**
* @private used to get a copy of the main PTR record
*/
ptrRecord(): PTRRecord;
/**
* @private used to get a copy of the array of subtype PTR records
*/
subtypePtrRecords(): PTRRecord[];
/**
* @private used to get a copy of the meta-query PTR record
*/
metaQueryPtrRecord(): PTRRecord;
/**
* @private used to get a copy of the SRV record
*/
srvRecord(): SRVRecord;
/**
* @private used to get a copy of the TXT record
*/
txtRecord(): TXTRecord;
/**
* @private used to get a copy of the A record
*/
aRecord(name: InterfaceName): ARecord | undefined;
/**
* @private used to get a copy of the AAAA record for the link-local ipv6 address
*/
aaaaRecord(name: InterfaceName): AAAARecord | undefined;
/**
* @private used to get a copy of the AAAA record for the routable ipv6 address
*/
aaaaRoutableRecord(name: InterfaceName): AAAARecord | undefined;
/**
* @private used to get a copy of the AAAA for the unique local ipv6 address
*/
aaaaUniqueLocalRecord(name: InterfaceName): AAAARecord | undefined;
/**
* @private used to get a copy of the A and AAAA records
*/
allAddressRecords(): (ARecord | AAAARecord)[];
/**
* @private used to get a copy of the address NSEC record
*/
addressNSECRecord(): NSECRecord;
/**
* @private user to get a copy of the service NSEC record
*/
serviceNSECRecord(shortenTTL?: boolean): NSECRecord;
/**
* @param address - The IP address to check.
* @private used to check if given address is exposed by this service
*/
hasAddress(address: IPAddress): boolean;
}
//# sourceMappingURL=CiaoService.d.ts.map