hap-nodejs
Version:
HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.
251 lines • 11.1 kB
TypeScript
import { EventEmitter } from "events";
import { SrpServer } from "fast-srp-hap";
import { IncomingMessage, ServerResponse } from "http";
import { AddressInfo, Socket } from "net";
import { CharacteristicValue, Nullable, SessionIdentifier } from "../../types";
/**
* @group HAP Accessory Server
*/
export type HAPUsername = string;
/**
* @group HAP Accessory Server
*/
export type EventName = string;
/**
* Simple struct to hold vars needed to support HAP encryption.
*
* @group Cryptography
*/
export declare class HAPEncryption {
readonly clientPublicKey: Buffer;
readonly secretKey: Buffer;
readonly publicKey: Buffer;
readonly sharedSecret: Buffer;
readonly hkdfPairEncryptionKey: Buffer;
accessoryToControllerCount: number;
controllerToAccessoryCount: number;
accessoryToControllerKey: Buffer;
controllerToAccessoryKey: Buffer;
incompleteFrame?: Buffer;
constructor(clientPublicKey: Buffer, secretKey: Buffer, publicKey: Buffer, sharedSecret: Buffer, hkdfPairEncryptionKey: Buffer);
}
/**
* @group HAP Accessory Server
*/
export declare const enum EventedHTTPServerEvent {
LISTENING = "listening",
CONNECTION_OPENED = "connection-opened",
REQUEST = "request",
CONNECTION_CLOSED = "connection-closed"
}
/**
* @group HAP Accessory Server
*/
export declare interface EventedHTTPServer {
on(event: "listening", listener: (port: number, address: string) => void): this;
on(event: "connection-opened", listener: (connection: HAPConnection) => void): this;
on(event: "request", listener: (connection: HAPConnection, request: IncomingMessage, response: ServerResponse) => void): this;
on(event: "connection-closed", listener: (connection: HAPConnection) => void): this;
emit(event: "listening", port: number, address: string): boolean;
emit(event: "connection-opened", connection: HAPConnection): boolean;
emit(event: "request", connection: HAPConnection, request: IncomingMessage, response: ServerResponse): boolean;
emit(event: "connection-closed", connection: HAPConnection): boolean;
}
/**
* EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events.
*
* Implementation
* --------------
* In order to implement the "custom HTTP" server required by the HAP protocol (see HAPServer.js) without completely
* reinventing the wheel, we create both a generic TCP socket server and a standard Node HTTP server.
* The TCP socket server acts as a proxy, allowing users of this class to transform data (for encryption) as necessary
* and passing through bytes directly to the HTTP server for processing. This way we get Node to do all
* the "heavy lifting" of HTTP like parsing headers and formatting responses.
*
* Events are sent by simply waiting for current HTTP traffic to subside and then sending a custom response packet
* directly down the wire via the socket.
*
* Each connection to the main TCP server gets its own internal HTTP server, so we can track ongoing requests/responses
* for safe event insertion.
*
* @group HAP Accessory Server
*/
export declare class EventedHTTPServer extends EventEmitter {
private static readonly CONNECTION_TIMEOUT_LIMIT;
private static readonly MAX_CONNECTION_IDLE_TIME;
private readonly tcpServer;
/**
* Set of all currently connected HAP connections.
*/
private readonly connections;
/**
* Session dictionary indexed by username/identifier. The username uniquely identifies every person added to the home.
* So there can be multiple sessions open for a single username (multiple devices connected to the same Apple ID).
*/
private readonly connectionsByUsername;
private connectionIdleTimeout?;
private connectionLoggingInterval?;
constructor();
private scheduleNextConnectionIdleTimeout;
address(): AddressInfo;
listen(targetPort: number, hostname?: string): void;
stop(): void;
destroy(): void;
/**
* Send an event notification for given characteristic and changed value to all connected clients.
* If `originator` is specified, the given {@link HAPConnection} will be excluded from the broadcast.
*
* @param aid - The accessory id of the updated characteristic.
* @param iid - The instance id of the updated characteristic.
* @param value - The newly set value of the characteristic.
* @param originator - If specified, the connection will not get an event message.
* @param immediateDelivery - The HAP spec requires some characteristics to be delivery immediately.
* Namely, for the {@link Characteristic.ButtonEvent} and the {@link Characteristic.ProgrammableSwitchEvent} characteristics.
*/
broadcastEvent(aid: number, iid: number, value: Nullable<CharacteristicValue>, originator?: HAPConnection, immediateDelivery?: boolean): void;
private onConnection;
private handleConnectionAuthenticated;
private handleConnectionClose;
/**
* This method is to be called when a given {@link HAPConnection} performs a request that should result in the disconnection
* of all other {@link HAPConnection} with the same {@link HAPUsername}.
*
* The initiator MUST be in the middle of a http request were the response was not served yet.
* Otherwise, the initiator connection might reside in a state where it isn't disconnected and can't make any further requests.
*
* @param initiator - The connection that requested to disconnect all connections of the same username.
* @param username - The username for which all connections shall be closed.
*/
static destroyExistingConnectionsAfterUnpair(initiator: HAPConnection, username: HAPUsername): void;
}
/**
* @private
* @group HAP Accessory Server
*/
export declare const enum HAPConnectionState {
CONNECTING = 0,// initial state, setup is going on
FULLY_SET_UP = 1,// internal http server is running and connection is established
AUTHENTICATED = 2,// encryption is set up
TO_BE_TEARED_DOWN = 3,// when in this state, connection should be closed down after response was sent out
CLOSING = 4,// close was called
CLOSED = 5
}
/**
* @group HAP Accessory Server
*/
export declare const enum HAPConnectionEvent {
REQUEST = "request",
AUTHENTICATED = "authenticated",
CLOSED = "closed"
}
/**
* @group HAP Accessory Server
*/
export declare interface HAPConnection {
on(event: "request", listener: (request: IncomingMessage, response: ServerResponse) => void): this;
on(event: "authenticated", listener: (username: HAPUsername) => void): this;
on(event: "closed", listener: () => void): this;
emit(event: "request", request: IncomingMessage, response: ServerResponse): boolean;
emit(event: "authenticated", username: HAPUsername): boolean;
emit(event: "closed"): boolean;
}
/**
* Manages a single iOS-initiated HTTP connection during its lifetime.
* @group HAP Accessory Server
*/
export declare class HAPConnection extends EventEmitter {
/**
* @private file-private API
*/
readonly server: EventedHTTPServer;
readonly sessionID: SessionIdentifier;
private state;
readonly localAddress: string;
readonly remoteAddress: string;
readonly remotePort: number;
readonly networkInterface: string;
private readonly tcpSocket;
private readonly internalHttpServer;
private httpSocket?;
private internalHttpServerPort?;
private internalHttpServerAddress?;
lastSocketOperation: number;
private pendingClientSocketData?;
private handlingRequest;
username?: HAPUsername;
encryption?: HAPEncryption;
srpServer?: SrpServer;
_pairSetupState?: number;
_pairVerifyState?: number;
private registeredEvents;
private eventsTimer?;
private readonly queuedEvents;
/**
* If true, the above {@link queuedEvents} contains events which are set to be delivered immediately!
*/
private eventsQueuedForImmediateDelivery;
timedWritePid?: number;
timedWriteTimeout?: NodeJS.Timeout;
constructor(server: EventedHTTPServer, clientSocket: Socket);
private debugListenerRegistration;
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
off(event: string | symbol, listener: (...args: any[]) => void): this;
/**
* This method is called once the connection has gone through pair-verify.
* As any HomeKit controller will initiate a pair-verify after the pair-setup procedure, this method gets
* not called on the initial pair-setup.
*
* Once this method has been called, the connection is authenticated and encryption is turned on.
*/
connectionAuthenticated(username: HAPUsername): void;
isAuthenticated(): boolean;
close(): void;
closeConnectionAsOfUnpair(initiator: HAPConnection): void;
sendEvent(aid: number, iid: number, value: Nullable<CharacteristicValue>, immediateDelivery?: boolean): void;
private handleEventsTimeout;
private writeQueuedEventNotifications;
/**
* This will create an EVENT/1.0 notification header with the provided event notification.
* If currently an HTTP request is in progress the assembled packet will be
* added to the pending events list.
*
* @param notification - The event which should be sent out
*/
private writeEventNotification;
enableEventNotifications(aid: number, iid: number): void;
disableEventNotifications(aid: number, iid: number): void;
hasEventNotifications(aid: number, iid: number): boolean;
getRegisteredEvents(): Set<EventName>;
clearRegisteredEvents(): void;
private encrypt;
private decrypt;
private onHttpServerListening;
/**
* This event handler is called when we receive data from a HomeKit controller on our tcp socket.
* We store the data if the internal http server is not read yet, or forward it to the http server.
*/
private onTCPSocketData;
/**
* This event handler is called when the internal http server receives a request.
* Meaning we received data from the HomeKit controller in {@link onTCPSocketData}, which then send the
* data unencrypted to the internal http server. And now it landed here, fully parsed as a http request.
*/
private handleHttpServerRequest;
/**
* This event handler is called by the socket which is connected to our internal http server.
* It is called with the response returned from the http server.
* In this method we have to encrypt and forward the message back to the HomeKit controller.
*/
private handleHttpServerResponse;
private handleTCPSocketWriteFulfilled;
private onTCPSocketError;
private onTCPSocketClose;
private onHttpServerError;
private onHttpServerClose;
private onHttpSocketError;
private onHttpSocketClose;
getLocalAddress(ipVersion: "ipv4" | "ipv6"): string;
private static getLocalNetworkInterface;
}
//# sourceMappingURL=eventedhttp.d.ts.map