diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 565717401..76f469381 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -75,7 +75,6 @@ import { SerializedService, Service, ServiceCharacteristicChange, - ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; @@ -244,6 +243,10 @@ export type AccessoryCharacteristicChange = ServiceCharacteristicChange & { service: Service; }; +export interface ServiceConfigurationChange { + service: Service; +} + const enum WriteRequestState { REGULAR_REQUEST, TIMED_WRITE_AUTHENTICATED, @@ -419,7 +422,7 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, (change: ServiceConfigurationChange) => { + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { if (!service.isPrimaryService && service === this.primaryService) { // service changed form primary to non primary service this.primaryService = undefined; diff --git a/src/lib/EventEmitter.ts b/src/lib/EventEmitter.ts deleted file mode 100644 index c1a550a38..000000000 --- a/src/lib/EventEmitter.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { EventEmitter as BaseEventEmitter } from "events"; -import { Callback } from '../types'; - -export type EventKey = string | symbol; -export type Event = T & EventKey; -export type EventMap = { [name: string]: Callback }; - -export class EventEmitter = Event> extends BaseEventEmitter { - addListener(event: K, listener: T[K]): this { - return super.addListener(event, listener); - }; - - on(event: K, listener: T[K]): this { - return super.on(event, listener); - } - - once(event: K, listener: T[K]): this { - return super.once(event, listener); - } - - removeListener(event: K, listener: T[K]): this { - return super.removeListener(event, listener); - } - - removeAllListeners(event?: K): this { - return super.removeAllListeners(event); - } - - setMaxListeners(n: number): this { - return super.setMaxListeners(n); - } - - getMaxListeners(): number { - return super.getMaxListeners(); - } - - listeners(event: K): T[K] [] { - return super.listeners(event) as T[K][]; - } - - emit(event: K, ...args: any[]): boolean { - return super.emit(event, ...args); - } - - listenerCount(type: string): number { - return super.listenerCount(type); - } -} diff --git a/src/lib/Service.ts b/src/lib/Service.ts index f91c58f69..05186c686 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from "events"; import { CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; import { Characteristic, @@ -5,12 +6,13 @@ import { CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; -import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from './gen'; import { IdentifierCache } from './model/IdentifierCache'; -import { clone } from './util/clone'; import { toShortForm } from './util/uuid'; +/** + * HAP spec allows a maximum of 100 characteristics per service! + */ const MAX_CHARACTERISTICS = 100; export interface SerializedService { @@ -27,28 +29,27 @@ export interface SerializedService { export type ServiceId = string; // string with the format: UUID + (subtype | "") -export const enum ServiceEventTypes { - CHARACTERISTIC_CHANGE = "characteristic-change", - SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", -} - -export type ServiceConfigurationChange = { - service: Service; -}; - export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic }; -type Events = { - [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: ServiceCharacteristicChange) => void; - [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; // TODO remove service: this -} - // noinspection JSUnusedGlobalSymbols /** * @deprecated Use ServiceEventTypes instead */ export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE; +export const enum ServiceEventTypes { + CHARACTERISTIC_CHANGE = "characteristic-change", + SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", +} + +export declare interface Service { + on(event: "characteristic-change", listener: (change: ServiceCharacteristicChange) => void): this; + on(event: "service-configurationChange", listener: () => void): this; + + emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean; + emit(event: "service-configurationChange"): boolean; +} + /** * Service represents a set of grouped values necessary to provide a logical function. For instance, a * "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the @@ -68,11 +69,8 @@ export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEven * You can also define custom Services by providing your own UUID for the type that you generate yourself. * Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to * work with these. - * - * @event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { } - * Emitted after a change in the value of one of our Characteristics has occurred. */ -export class Service extends EventEmitter { +export class Service extends EventEmitter { static AccessControl: typeof HomeKitTypes.Generated.AccessControl; static AccessoryInformation: typeof HomeKitTypes.Generated.AccessoryInformation; @@ -100,10 +98,6 @@ export class Service extends EventEmitter { static HumiditySensor: typeof HomeKitTypes.Generated.HumiditySensor; static InputSource: typeof HomeKitTypes.TV.InputSource; static IrrigationSystem: typeof HomeKitTypes.Generated.IrrigationSystem; - /** - * @deprecated Removed in iOS 11. Use ServiceLabel instead. - */ - static Label: typeof HomeKitTypes.Generated.ServiceLabel; static LeakSensor: typeof HomeKitTypes.Generated.LeakSensor; static LightSensor: typeof HomeKitTypes.Generated.LightSensor; static Lightbulb: typeof HomeKitTypes.Generated.Lightbulb; @@ -185,12 +179,11 @@ export class Service extends EventEmitter { return this.UUID + (this.subtype || ""); } - addCharacteristic = (characteristic: Characteristic | {new (...args: any[]): Characteristic}, ...constructorArgs: any[]) => { - // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance - // of Characteristic. Coerce if necessary. - if (typeof characteristic === 'function') { - characteristic = new characteristic(...constructorArgs) as Characteristic; - } + public addCharacteristic(input: Characteristic | {new (...args: any[]): Characteristic}, ...constructorArgs: any[]): Characteristic { + // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance of Characteristic. Coerce if necessary. + + let characteristic = typeof input === "function"? new input(...constructorArgs): input; + // check for UUID conflict for (let index in this.characteristics) { const existing = this.characteristics[index]; @@ -214,7 +207,7 @@ export class Service extends EventEmitter { this.characteristics.push(characteristic); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); return characteristic; } @@ -229,7 +222,7 @@ export class Service extends EventEmitter { */ setPrimaryService = (isPrimary: boolean = true) => { this.isPrimaryService = isPrimary; - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); }; /** @@ -239,7 +232,7 @@ export class Service extends EventEmitter { */ setHiddenService = (isHidden: boolean = true) => { this.isHiddenService = isHidden; - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } //Allows setting other services that link to this one. @@ -247,7 +240,7 @@ export class Service extends EventEmitter { //TODO: Add a check if the service is on the same accessory. if (!this.linkedServices.includes(newLinkedService)) this.linkedServices.push(newLinkedService); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } removeLinkedService = (oldLinkedService: Service) => { @@ -255,7 +248,7 @@ export class Service extends EventEmitter { const index = this.linkedServices.indexOf(oldLinkedService); if (index !== -1) this.linkedServices.splice(index, 1); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } removeCharacteristic = (characteristic: Characteristic) => { @@ -274,7 +267,7 @@ export class Service extends EventEmitter { this.characteristics.splice(Number.parseInt(targetCharacteristicIndex), 1); characteristic.removeAllListeners(); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } } diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index 2ddae009f..fb46f40ed 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; import createDebug from "debug"; +import { EventEmitter } from "events"; import { CameraStreamingOptions, Characteristic, @@ -17,7 +18,6 @@ import { StreamingRequest } from "../.."; import { SessionIdentifier } from "../../types"; -import { EventEmitter } from "../EventEmitter"; import { Doorbell, Microphone, Speaker } from "../gen/HomeKit"; import { Controller, ControllerServiceMap, ControllerType, DefaultControllerType } from "./Controller"; import Timeout = NodeJS.Timeout; @@ -90,9 +90,12 @@ export const enum CameraControllerEvents { SPEAKER_PROPERTIES_CHANGED = "speaker-change", } -export type CameraControllerEventMap = { - [CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; - [CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; +export declare interface CameraController { + on(event: "microphone-change", listener: (muted: boolean, volume: number) => void): this; + on(event: "speaker-change", listener: (muted: boolean, volume: number) => void): this; + + emit(event: "microphone-change", muted: boolean, volume: number): boolean; + emit(event: "speaker-change", muted: boolean, volume: number): boolean; } /** @@ -106,7 +109,7 @@ export type CameraControllerEventMap = { * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values * except the mute state. When you unmute the device microphone it will reset the mute state if it was set previously. */ -export class CameraController extends EventEmitter implements Controller { +export class CameraController extends EventEmitter implements Controller { private static readonly STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index cf2542f27..271140fd2 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1,5 +1,6 @@ import assert from 'assert'; import createDebug from 'debug'; +import { EventEmitter } from "events"; import { CharacteristicValue } from "../../types"; import { Accessory } from "../Accessory"; import { @@ -23,7 +24,6 @@ import { RequestHandler, Topics } from "../datastream"; -import { EventEmitter } from "../EventEmitter"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { AudioStreamManagement, Siri, TargetControl, TargetControlManagement } from "../gen/HomeKit-Remote"; import { Status } from "../HAPServer"; @@ -48,6 +48,7 @@ const enum SupportedButtonConfigurationTypes { } export const enum ButtonType { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, MENU = 0x01, PLAY_PAUSE = 0x02, @@ -71,6 +72,7 @@ const enum TargetControlList { } enum Operation { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, LIST = 0x01, ADD = 0x02, @@ -87,6 +89,7 @@ const enum TargetConfigurationTypes { } export const enum TargetCategory { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, APPLE_TV = 0x18 } @@ -148,6 +151,7 @@ const enum SelectedAudioInputStreamConfigurationTypes { // ---------- const enum SupportedAudioStreamConfigurationTypes { + // noinspection JSUnusedGlobalSymbols AUDIO_CODEC_CONFIGURATION = 0x01, COMFORT_NOISE_SUPPORT = 0x02, } @@ -158,6 +162,7 @@ const enum AudioCodecConfigurationTypes { } export const enum AudioCodecTypes { // only really by HAP supported codecs are AAC-ELD and OPUS + // noinspection JSUnusedGlobalSymbols PCMU = 0x00, PCMA = 0x01, AAC_ELD = 0x02, @@ -263,6 +268,13 @@ export interface SiriAudioStreamProducerConstructor { } +export const enum TargetUpdates { + NAME, + CATEGORY, + UPDATED_BUTTONS, + REMOVED_BUTTONS, +} + export const enum RemoteControllerEvents { ACTIVE_CHANGE = "active-change", ACTIVE_IDENTIFIER_CHANGE = "active-identifier-change", @@ -273,21 +285,22 @@ export const enum RemoteControllerEvents { TARGETS_RESET = "targets-reset", } -export const enum TargetUpdates { - NAME, - CATEGORY, - UPDATED_BUTTONS, - REMOVED_BUTTONS, -} +export declare interface RemoteController { + on(event: "active-change", listener: (active: boolean) => void): this; + on(event: "active-identifier-change", listener: (activeIdentifier: number) => void): this; + + on(event: "target-add", listener: (targetConfiguration: TargetConfiguration) => void): this; + on(event: "target-update", listener: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void): this; + on(event: "target-remove", listener: (targetIdentifier: number) => void): this; + on(event: "targets-reset", listener: () => void): this; -export type RemoteControllerEventMap = { - [RemoteControllerEvents.ACTIVE_CHANGE]: (active: boolean) => void; - [RemoteControllerEvents.ACTIVE_IDENTIFIER_CHANGE]: (activeIdentifier: number) => void; + emit(event: "active-change", active: boolean): boolean; + emit(event: "active-identifier-change", activeIdentifier: number): boolean; - [RemoteControllerEvents.TARGET_ADDED]: (targetConfiguration: TargetConfiguration) => void; - [RemoteControllerEvents.TARGET_UPDATED]: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void; - [RemoteControllerEvents.TARGET_REMOVED]: (targetIdentifier: number) => void; - [RemoteControllerEvents.TARGETS_RESET]: () => void; + emit(event: "target-add", targetConfiguration: TargetConfiguration): boolean; + emit(event: "target-update", targetConfiguration: TargetConfiguration, updates: TargetUpdates[]): boolean; + emit(event: "target-remove", targetIdentifier: number): boolean; + emit(event: "targets-reset"): boolean; } export interface RemoteControllerServiceMap extends ControllerServiceMap { @@ -334,8 +347,7 @@ export interface SerializedControllerState { * With this event every configuration made should be reset. This event is also called * when the accessory gets unpaired. */ -export class RemoteController extends EventEmitter - implements SerializableController, DataStreamProtocolHandler { +export class RemoteController extends EventEmitter implements SerializableController, DataStreamProtocolHandler { readonly controllerType = DefaultControllerType.REMOTE; stateChangeDelegate?: StateChangeDelegate; @@ -1249,14 +1261,16 @@ export const enum SiriAudioSessionEvents { CLOSE = "close", } -export type SiriAudioSessionEventMap = { - [SiriAudioSessionEvents.CLOSE]: () => void; +export declare interface SiriAudioSession { + on(event: "close", listener: () => void): this; + + emit(event: "close"): boolean; } /** * Represents an ongoing audio transmission */ -export class SiriAudioSession extends EventEmitter { +export class SiriAudioSession extends EventEmitter { readonly connection: DataStreamConnection; private readonly selectedAudioConfiguration: AudioCodecConfiguration; diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index c001b317f..4534154ed 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -6,15 +6,15 @@ import { CharacteristicGetCallback, CharacteristicSetCallback } from "../Characteristic"; -import { Event } from "../EventEmitter"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { Status } from "../HAPServer"; import { Service } from "../Service"; import { HAPConnection } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import { + DataStreamConnection, DataStreamServer, - DataStreamServerEventMap, + DataStreamServerEvents, GlobalEventHandler, GlobalRequestHandler } from "./DataStreamServer"; @@ -145,7 +145,8 @@ export class DataStreamManagement { * @param event - the event to register for * @param listener - the event handler */ - onServerEvent(event: Event, listener: DataStreamServerEventMap[Event]): this { + onServerEvent(event: DataStreamServerEvents, listener: (connection: DataStreamConnection) => void): this { + // @ts-expect-error this.dataStreamServer.on(event, listener); return this; } diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 2d77b5c3b..82aab7d0a 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -1,13 +1,12 @@ -import createDebug from 'debug'; import assert from 'assert'; +import crypto from 'crypto'; +import createDebug from 'debug'; +import { EventEmitter, EventEmitter as NodeEventEmitter } from "events"; +import net, { Socket } from 'net'; import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as hapCrypto from '../util/hapCrypto'; -import {DataStreamParser, DataStreamReader, DataStreamWriter, Int64} from './DataStreamParser'; -import crypto from 'crypto'; -import net, {Socket} from 'net'; -import {EventEmitter as NodeEventEmitter} from "events"; -import {EventEmitter} from "../EventEmitter"; +import { DataStreamParser, DataStreamReader, DataStreamWriter, Int64 } from './DataStreamParser'; import Timeout = NodeJS.Timeout; const debug = createDebug('HAP-NodeJS:DataStream:Server'); @@ -124,26 +123,29 @@ type DataStreamMessage = { } export const enum DataStreamServerEvents { + /** + * This event is emitted when a new client socket is received. At this point we have no idea to what + * hap session this connection will be matched. + */ CONNECTION_OPENED = "connection-opened", + /** + * This event is emitted when the socket of a connection gets closed. + */ CONNECTION_CLOSED = "connection-closed", } -export type DataStreamServerEventMap = { - [DataStreamServerEvents.CONNECTION_OPENED]: (connection: DataStreamConnection) => void; - [DataStreamServerEvents.CONNECTION_CLOSED]: (connection: DataStreamConnection) => void; +export declare interface DataStreamServer { + on(event: "connection-opened", listener: (connection: DataStreamConnection) => void): this; + on(event: "connection-closed", listener: (connection: DataStreamConnection) => void): this; + + emit(event: "connection-opened", connection: DataStreamConnection): boolean; + emit(event: "connection-closed", connection: DataStreamConnection): boolean; } /** * DataStreamServer which listens for incoming tcp connections and handles identification of new connections - * - * @event 'connection-opened': (connection: DataStreamConnection) => void - * This event is emitted when a new client socket is received. At this point we have no idea to what - * hap session this connection will be matched. - * - * @event 'connection-closed': (connection: DataStreamConnection) => void - * This event is emitted when the socket of a connection gets closed. */ -export class DataStreamServer extends EventEmitter { // TODO removeAllEvent handlers on closing +export class DataStreamServer extends EventEmitter { // TODO removeAllEvent handlers on closing static readonly version = "1.0"; @@ -391,18 +393,22 @@ export class DataStreamServer extends EventEmitter { / } +export type IdentificationCallback = (identifiedSession?: PreparedDataStreamSession) => void; + export const enum DataStreamConnectionEvents { IDENTIFICATION = "identification", HANDLE_MESSAGE_GLOBALLY = "handle-message-globally", CLOSED = "closed", } -export type IdentificationCallback = (identifiedSession?: PreparedDataStreamSession) => void; +export declare interface DataStreamConnection { + on(event: "identification", listener: (frame: HDSFrame, callback: IdentificationCallback) => void): this; + on(event: "handle-message-globally", listener: (message: DataStreamMessage) => void): this; + on(event: "closed", listener: () => void): this; -export type DataStreamConnectionEventMap = { - [DataStreamConnectionEvents.IDENTIFICATION]: (frame: HDSFrame, callback: IdentificationCallback) => void; - [DataStreamConnectionEvents.HANDLE_MESSAGE_GLOBALLY]: (message: DataStreamMessage) => void; - [DataStreamConnectionEvents.CLOSED]: () => void; + emit(event: "identification", frame: HDSFrame, callback: IdentificationCallback): boolean; + emit(event: "handle-message-globally", message: DataStreamMessage): boolean; + emit(event: "closed"): boolean; } /** @@ -421,7 +427,7 @@ export type DataStreamConnectionEventMap = { * @event 'closed': () => void * This event is emitted when the socket of the connection was closed. */ -export class DataStreamConnection extends EventEmitter { +export class DataStreamConnection extends EventEmitter { private static readonly MAX_PAYLOAD_LENGTH = 0b11111111111111111111; diff --git a/src/lib/tv/AccessControlManagement.ts b/src/lib/tv/AccessControlManagement.ts index 07ce8861c..f18402cb6 100644 --- a/src/lib/tv/AccessControlManagement.ts +++ b/src/lib/tv/AccessControlManagement.ts @@ -1,6 +1,6 @@ +import { EventEmitter } from "events"; import { AccessControl } from "../gen/HomeKit"; import { Service } from "../Service"; -import { EventEmitter } from "../EventEmitter"; import { Characteristic, CharacteristicEventTypes, @@ -44,12 +44,15 @@ export const enum AccessControlEvent { PASSWORD_SETTING_UPDATED = "update-password", } -export type AccessControlEventMap = { - [AccessControlEvent.ACCESS_LEVEL_UPDATED]: (accessLevel: AccessLevel) => void; - [AccessControlEvent.PASSWORD_SETTING_UPDATED]: (password: string | undefined, passwordRequired: boolean) => void; +export declare interface AccessControlManagement { + on(event: "update-control-level", listener: (accessLevel: AccessLevel) => void): this; + on(event: "update-password", listener: (password: string | undefined, passwordRequired: boolean) => void): this; + + emit(event: "update-control-level", accessLevel: AccessLevel): boolean; + emit(event: "update-password", password: string | undefined, passwordRequired: boolean): boolean; } -export class AccessControlManagement extends EventEmitter { +export class AccessControlManagement extends EventEmitter { private readonly accessControlService: AccessControl;