From fd336b20668b926c982e0cee5e83665b65f643ac Mon Sep 17 00:00:00 2001 From: Patrick Seal Date: Tue, 14 Nov 2023 18:02:30 -0800 Subject: [PATCH] fix: exported type fixes --- examples/events.ts | 71 ++++++++++++++++++---------------- examples/multi-plug.ts | 42 ++++++++++---------- src/bulb/index.ts | 4 +- src/client.ts | 87 +++++++++++++++++------------------------- src/device/index.ts | 29 ++++++++------ src/index.ts | 87 +++++++++++++++++++++++++++++++++++------- src/logger.ts | 2 +- src/plug/index.ts | 51 ++++++++++++------------- src/shared/emeter.ts | 1 + src/shared/schedule.ts | 16 ++++++-- 10 files changed, 224 insertions(+), 166 deletions(-) diff --git a/examples/events.ts b/examples/events.ts index ba012b8..c86eafe 100755 --- a/examples/events.ts +++ b/examples/events.ts @@ -1,6 +1,8 @@ import util from 'util'; import { + Bulb, Client, + Plug, type Device, type LightState, type RealtimeNormalized, @@ -44,40 +46,45 @@ client.on('device-new', (device: Device) => { logEvent('emeter-realtime-update', device, emeterRealtime); }); - // Plug Events - device.on('power-on', () => { - logEvent('power-on', device); - }); - device.on('power-off', () => { - logEvent('power-off', device); - }); - device.on('power-update', (powerOn: boolean) => { - logEvent('power-update', device, powerOn); - }); - device.on('in-use', () => { - logEvent('in-use', device); - }); - device.on('not-in-use', () => { - logEvent('not-in-use', device); - }); - device.on('in-use-update', (inUse: boolean) => { - logEvent('in-use-update', device, inUse); - }); + if (device instanceof Plug) { + // Plug Events + device.on('power-on', () => { + logEvent('power-on', device); + }); + device.on('power-off', () => { + logEvent('power-off', device); + }); + device.on('power-update', (powerOn: boolean) => { + logEvent('power-update', device, powerOn); + }); + device.on('in-use', () => { + logEvent('in-use', device); + }); + device.on('not-in-use', () => { + logEvent('not-in-use', device); + }); + device.on('in-use-update', (inUse: boolean) => { + logEvent('in-use-update', device, inUse); + }); + } - // Bulb Events - device.on('lightstate-on', (lightstate: LightState) => { - logEvent('lightstate-on', device, lightstate); - }); - device.on('lightstate-off', (lightstate: LightState) => { - logEvent('lightstate-off', device, lightstate); - }); - device.on('lightstate-change', (lightstate: LightState) => { - logEvent('lightstate-change', device, lightstate); - }); - device.on('lightstate-update', (lightstate: LightState) => { - logEvent('lightstate-update', device, lightstate); - }); + if (device instanceof Bulb) { + // Bulb Events + device.on('lightstate-on', (lightstate: LightState) => { + logEvent('lightstate-on', device, lightstate); + }); + device.on('lightstate-off', (lightstate: LightState) => { + logEvent('lightstate-off', device, lightstate); + }); + device.on('lightstate-change', (lightstate: LightState) => { + logEvent('lightstate-change', device, lightstate); + }); + device.on('lightstate-update', (lightstate: LightState) => { + logEvent('lightstate-update', device, lightstate); + }); + } }); + client.on('device-online', (device: Device) => { logEvent('device-online', device); }); diff --git a/examples/multi-plug.ts b/examples/multi-plug.ts index 4f8b820..876b6db 100644 --- a/examples/multi-plug.ts +++ b/examples/multi-plug.ts @@ -1,5 +1,5 @@ import util from 'util'; -import { Client, Device } from '..'; // 'tplink-smarthome-api' +import { Client, Device, Plug } from '..'; // 'tplink-smarthome-api' const client = new Client({ defaultSendOptions: { timeout: 20000, transport: 'tcp' }, @@ -24,25 +24,27 @@ const monitorEvents = function monitorEvents(device: Device) { logEvent('emeter-realtime-update', device, emeterRealtime); }); - // Plug Events - device.on('power-on', () => { - logEvent('power-on', device); - }); - device.on('power-off', () => { - logEvent('power-off', device); - }); - device.on('power-update', (powerOn) => { - logEvent('power-update', device, powerOn); - }); - device.on('in-use', () => { - logEvent('in-use', device); - }); - device.on('not-in-use', () => { - logEvent('not-in-use', device); - }); - device.on('in-use-update', (inUse) => { - logEvent('in-use-update', device, inUse); - }); + if (device instanceof Plug) { + // Plug Events + device.on('power-on', () => { + logEvent('power-on', device); + }); + device.on('power-off', () => { + logEvent('power-off', device); + }); + device.on('power-update', (powerOn) => { + logEvent('power-update', device, powerOn); + }); + device.on('in-use', () => { + logEvent('in-use', device); + }); + device.on('not-in-use', () => { + logEvent('not-in-use', device); + }); + device.on('in-use-update', (inUse) => { + logEvent('in-use-update', device, inUse); + }); + } // Poll device every 5 seconds setTimeout(function pollDevice() { diff --git a/src/bulb/index.ts b/src/bulb/index.ts index 7c71681..422039e 100644 --- a/src/bulb/index.ts +++ b/src/bulb/index.ts @@ -95,7 +95,7 @@ export interface BulbConstructorOptions extends DeviceConstructorOptions { sysInfo: BulbSysinfo; } -interface BulbEvents { +export interface BulbEvents { 'emeter-realtime-update': (value: RealtimeNormalized) => void; /** * Bulb was turned on (`lightstate.on_off`). @@ -147,7 +147,6 @@ interface BulbEvents { 'lightstate-sysinfo-update': (value: BulbSysinfoLightState) => void; } -// TODO: Fix this // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging declare interface Bulb { on(event: U, listener: BulbEvents[U]): this; @@ -171,7 +170,6 @@ declare interface Bulb { * @fires Bulb#lightstate-sysinfo-change * @fires Bulb#lightstate-sysinfo-update */ -// TODO: Fix this // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class Bulb extends Device { emitEventsEnabled = true; diff --git a/src/client.ts b/src/client.ts index a251793..a66c528 100644 --- a/src/client.ts +++ b/src/client.ts @@ -19,8 +19,8 @@ const discoveryMsgBuf = encrypt('{"system":{"get_sysinfo":{}}}'); export type AnyDevice = Bulb | Plug; -type DeviceDiscovery = { status: string; seenOnDiscovery: number }; -type AnyDeviceDiscovery = (Bulb | Plug) & Partial; +export type DeviceDiscovery = { status: string; seenOnDiscovery: number }; +export type AnyDeviceDiscovery = (Bulb | Plug) & Partial; type SysinfoResponse = { system: { get_sysinfo: Sysinfo } }; type EmeterResponse = PlugEmeterResponse | BulbEmeterResponse; @@ -38,11 +38,11 @@ type AnyDeviceOptions = | ConstructorParameters[0] | ConstructorParameters[0]; -type AnyDeviceOptionsCon = +export type AnyDeviceOptionsConstructable = | MarkOptional[0], 'client' | 'sysInfo'> | MarkOptional[0], 'client' | 'sysInfo'>; -type DeviceOptionsDiscovery = +export type DeviceOptionsDiscovery = | MarkOptional< ConstructorParameters[0], 'client' | 'sysInfo' | 'host' @@ -159,59 +159,47 @@ export type SendOptions = { sharedSocketTimeout?: number; }; -/* eslint-disable @typescript-eslint/unified-signatures -- for jsdoc we don't want to combine signatures */ -export interface ClientEventEmitter { +export interface ClientEvents { /** * First response from device. */ - on( - event: 'device-new', - listener: (device: Device | Bulb | Plug) => void, - ): this; + 'device-new': (device: Bulb | Plug) => void; /** * Follow up response from device. */ - on( - event: 'device-online', - listener: (device: Device | Bulb | Plug) => void, - ): this; + 'device-online': (device: Bulb | Plug) => void; /** * No response from device. */ - on( - event: 'device-offline', - listener: (device: Device | Bulb | Plug) => void, - ): this; + 'device-offline': (device: Bulb | Plug) => void; /** * First response from Bulb. */ - on(event: 'bulb-new', listener: (device: Bulb) => void): this; + 'bulb-new': (device: Bulb) => void; /** * Follow up response from Bulb. */ - on(event: 'bulb-online', listener: (device: Bulb) => void): this; + 'bulb-online': (device: Bulb) => void; /** * No response from Bulb. */ - on(event: 'bulb-offline', listener: (device: Bulb) => void): this; + 'bulb-offline': (device: Bulb) => void; /** * First response from Plug. */ - on(event: 'plug-new', listener: (device: Plug) => void): this; + 'plug-new': (device: Plug) => void; /** * Follow up response from Plug. */ - on(event: 'plug-online', listener: (device: Plug) => void): this; + 'plug-online': (device: Plug) => void; /** * No response from Plug. */ - on(event: 'plug-offline', listener: (device: Plug) => void): this; + 'plug-offline': (device: Plug) => void; /** * Invalid/Unknown response from device. */ - on( - event: 'discovery-invalid', - listener: ({ + 'discovery-invalid': ({ rinfo, response, decryptedResponse, @@ -219,33 +207,23 @@ export interface ClientEventEmitter { rinfo: RemoteInfo; response: Buffer; decryptedResponse: Buffer; - }) => void, - ): this; + }) => void; + /** * Error during discovery. */ - on(event: 'error', listener: (error: Error) => void): this; - - emit(event: 'device-new', device: Device | Bulb | Plug): boolean; - emit(event: 'device-online', device: Device | Bulb | Plug): boolean; - emit(event: 'device-offline', device: Device | Bulb | Plug): boolean; - emit(event: 'bulb-new', device: Bulb): boolean; - emit(event: 'bulb-online', device: Bulb): boolean; - emit(event: 'bulb-offline', device: Bulb): boolean; - emit(event: 'plug-new', device: Plug): boolean; - emit(event: 'plug-online', device: Plug): boolean; - emit(event: 'plug-offline', device: Plug): boolean; - emit( - event: 'discovery-invalid', - { - rinfo, - response, - decryptedResponse, - }: { rinfo: RemoteInfo; response: Buffer; decryptedResponse: Buffer }, + error: (error: Error) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +declare interface Client { + on(event: U, listener: ClientEvents[U]): this; + + emit( + event: U, + ...args: Parameters ): boolean; - emit(event: 'error', error: Error): boolean; } -/* eslint-enable @typescript-eslint/unified-signatures */ /** * Client that sends commands to specified devices or discover devices on the local subnet. @@ -253,7 +231,8 @@ export interface ClientEventEmitter { * - Events are emitted after {@link #startDiscovery} is called. * @noInheritDoc */ -export default class Client extends EventEmitter implements ClientEventEmitter { +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +class Client extends EventEmitter { defaultSendOptions: Required = { timeout: 10000, transport: 'tcp', @@ -451,7 +430,7 @@ export default class Client extends EventEmitter implements ClientEventEmitter { * @throws {@link ResponseError} */ async getDevice( - deviceOptions: AnyDeviceOptionsCon, + deviceOptions: AnyDeviceOptionsConstructable, sendOptions?: SendOptions, ): Promise { this.log.debug('client.getDevice(%j)', { deviceOptions, sendOptions }); @@ -482,7 +461,7 @@ export default class Client extends EventEmitter implements ClientEventEmitter { */ getDeviceFromSysInfo( sysInfo: Sysinfo, - deviceOptions: AnyDeviceOptionsCon, + deviceOptions: AnyDeviceOptionsConstructable, ): AnyDevice { if (isPlugSysinfo(sysInfo)) { return this.getPlug({ ...deviceOptions, sysInfo }); @@ -751,7 +730,7 @@ export default class Client extends EventEmitter implements ClientEventEmitter { device.seenOnDiscovery = this.discoveryPacketSequence; this.emit('online', device); } else { - const opts: AnyDeviceOptionsCon = { + const opts: AnyDeviceOptionsConstructable = { ...options, client: this, host, @@ -846,3 +825,5 @@ export default class Client extends EventEmitter implements ClientEventEmitter { } } } + +export default Client; diff --git a/src/device/index.ts b/src/device/index.ts index 5a0452e..0c95aa2 100644 --- a/src/device/index.ts +++ b/src/device/index.ts @@ -7,8 +7,8 @@ import type { default as Client, SendOptions } from '../client'; // eslint-disab import type { Logger } from '../logger'; import TcpConnection from '../network/tcp-connection'; import UdpConnection from '../network/udp-connection'; -import type { PlugEventEmitter, PlugSysinfo } from '../plug'; -import type { Realtime, RealtimeNormalized } from '../shared/emeter'; +import type { PlugSysinfo } from '../plug'; +import type { RealtimeNormalized } from '../shared/emeter'; import { extractResponse, isObjectLike, @@ -98,16 +98,21 @@ function isSysinfo(candidate: unknown): candidate is Sysinfo { return isPlugSysinfo(candidate) || isBulbSysinfo(candidate); } -export interface DeviceEventEmitter { +export interface DeviceEvents { /** * Energy Monitoring Details were updated from device. Fired regardless if status was changed. */ - on( - event: 'emeter-realtime-update', - listener: (value: Realtime) => void, - ): this; + 'emeter-realtime-update': (value: RealtimeNormalized) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +declare interface Device { + on(event: U, listener: DeviceEvents[U]): this; - emit(event: 'emeter-realtime-update', value: RealtimeNormalized): boolean; + emit( + event: U, + ...args: Parameters + ): boolean; } /** @@ -117,10 +122,8 @@ export interface DeviceEventEmitter { * @fires Device#emeter-realtime-update * @noInheritDoc */ -export default abstract class Device - extends EventEmitter - implements DeviceEventEmitter, PlugEventEmitter -{ +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +abstract class Device extends EventEmitter { readonly client: Client; host: string; @@ -586,3 +589,5 @@ export default abstract class Device abstract getInfo(sendOptions?: SendOptions): Promise>; } + +export default Device; diff --git a/src/index.ts b/src/index.ts index a0686c8..d0736d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,30 +1,89 @@ -export { default as Bulb, BulbConstructorOptions, BulbSysinfo } from './bulb'; -export { LightState, LightStateInput } from './bulb/lighting'; -export { BulbScheduleRule, BulbScheduleRuleInput } from './bulb/schedule'; +export { + default as Bulb, + type BulbConstructorOptions, + type BulbEvents, + type BulbSysinfo, + type BulbSysinfoLightState, +} from './bulb'; +export type { + LightState, + LightStateInput, + default as Lighting, +} from './bulb/lighting'; +export type { + default as BulbSchedule, + BulbScheduleRule, + BulbScheduleRuleInput, +} from './bulb/schedule'; export { default as Client, - ClientConstructorOptions, - DiscoveryOptions, + type AnyDevice, + type AnyDeviceDiscovery, + type AnyDeviceOptionsConstructable, + type ClientConstructorOptions, + type ClientEvents, + type DeviceDiscovery, + type DeviceOptionsDiscovery, + type DiscoveryDevice, + type DiscoveryOptions, + type SendOptions, } from './client'; export { default as Device, - ApiModuleNamespace, - DeviceConstructorOptions, - Sysinfo, + type ApiModuleNamespace, + type CommonSysinfo, + type DeviceConstructorOptions, + type DeviceEvents, + type Sysinfo, } from './device'; - -export { default as Plug, PlugConstructorOptions, PlugSysinfo } from './plug'; -export { AwayRule, AwayRuleInput } from './plug/away'; -export { DimmerActionInput, DimmerTransitionInput } from './plug/dimmer'; -export { PlugScheduleRule, PlugScheduleRuleInput } from './plug/schedule'; +export type { default as Netif } from './device/netif'; export { + default as Plug, + type PlugChild, + type PlugConstructorOptions, + type PlugEvents, + type PlugSysinfo, + type SysinfoChildren, +} from './plug'; +export type { default as Away, AwayRule, AwayRuleInput } from './plug/away'; +export type { + default as Dimmer, + DimmerActionInput, + DimmerTransitionInput, +} from './plug/dimmer'; +export type { + default as PlugSchedule, + PlugScheduleRule, + PlugScheduleRuleInput, +} from './plug/schedule'; +export type { default as Timer, TimerRuleInput } from './plug/timer'; + +export type { default as Cloud, CloudInfo } from './shared/cloud'; +export type { + default as Emeter, Realtime, RealtimeNormalized, RealtimeV1, RealtimeV2, } from './shared/emeter'; +export type { + HasRuleListWithRuleIds, + ScheduleDateStart, + ScheduleNextAction, + ScheduleNextActionResponse, + ScheduleRule, + ScheduleRuleInputTime, + ScheduleRuleResponse, + ScheduleRuleWithId, + ScheduleRules, + ScheduleRulesResponse, + WDay, +} from './shared/schedule'; +export type { default as Time } from './shared/time'; + +export type { LogLevelMethodNames, Logger } from './logger'; -export { ResponseError } from './utils'; +export { ResponseError, type HasErrCode } from './utils'; diff --git a/src/logger.ts b/src/logger.ts index cca8e3b..90cd758 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -2,7 +2,7 @@ import loglevel from 'loglevel'; import { isDefinedAndNotNull } from './utils'; -type LogLevelMethodNames = 'debug' | 'info' | 'warn' | 'error'; +export type LogLevelMethodNames = 'debug' | 'info' | 'warn' | 'error'; export type Logger = Record; diff --git a/src/plug/index.ts b/src/plug/index.ts index 696c03e..f28cef2 100644 --- a/src/plug/index.ts +++ b/src/plug/index.ts @@ -22,9 +22,9 @@ import Dimmer from './dimmer'; import Schedule from './schedule'; import Timer from './timer'; -type PlugChild = { id: string; alias: string; state: number }; +export type PlugChild = { id: string; alias: string; state: number }; -type SysinfoChildren = { +export type SysinfoChildren = { children?: [{ id: string; alias: string; state: number }]; }; @@ -67,56 +67,50 @@ export interface PlugConstructorOptions extends DeviceConstructorOptions { childId?: string; } -/* eslint-disable @typescript-eslint/unified-signatures -- for jsdoc we don't want to combine signatures */ -export interface PlugEventEmitter { +export interface PlugEvents { /** * Plug's Energy Monitoring Details were updated from device. Fired regardless if status was changed. * @event Plug#emeter-realtime-update */ - on( - event: 'emeter-realtime-update', - listener: (value: RealtimeNormalized) => void, - ): this; + 'emeter-realtime-update': (value: RealtimeNormalized) => void; /** * Plug's relay was turned on. */ - on(event: 'power-on', listener: () => void): this; + 'power-on': () => void; /** * Plug's relay was turned off. */ - on(event: 'power-off', listener: () => void): this; + 'power-off': () => void; /** * Plug's relay state was updated from device. Fired regardless if status was changed. */ - on(event: 'power-update', listener: (value: boolean) => void): this; + 'power-update': (value: boolean) => void; /** * Plug's relay was turned on _or_ power draw exceeded `inUseThreshold` */ - on(event: 'in-use', listener: () => void): this; + 'in-use': () => void; /** * Plug's relay was turned off _or_ power draw fell below `inUseThreshold` */ - on(event: 'not-in-use', listener: () => void): this; + 'not-in-use': () => void; /** * Plug's in-use state was updated from device. Fired regardless if status was changed. */ - on(event: 'in-use-update', listener: (value: boolean) => void): this; + 'in-use-update': (value: boolean) => void; - on(event: 'brightness-change', listener: (value: boolean) => void): this; - on(event: 'brightness-update', listener: (value: boolean) => void): this; + 'brightness-change': (value: number) => void; + 'brightness-update': (value: number) => void; +} - emit(event: 'emeter-realtime-update', value: RealtimeNormalized): boolean; +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +declare interface Plug { + on(event: U, listener: PlugEvents[U]): this; - emit(event: 'power-on'): boolean; - emit(event: 'power-off'): boolean; - emit(event: 'power-update', value: boolean): boolean; - emit(event: 'in-use'): boolean; - emit(event: 'not-in-use'): boolean; - emit(event: 'in-use-update', value: boolean): boolean; - emit(event: 'brightness-change', value: boolean): boolean; - emit(event: 'brightness-update', value: boolean): boolean; + emit( + event: U, + ...args: Parameters + ): boolean; } -/* eslint-enable @typescript-eslint/unified-signatures */ /** * Plug Device. @@ -138,7 +132,8 @@ export interface PlugEventEmitter { * @fires Plug#in-use-update * @fires Plug#emeter-realtime-update */ -export default class Plug extends Device implements PlugEventEmitter { +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +class Plug extends Device { protected override _sysInfo: PlugSysinfo; #children: Map = new Map(); @@ -719,3 +714,5 @@ export default class Plug extends Device implements PlugEventEmitter { } } } + +export default Plug; diff --git a/src/shared/emeter.ts b/src/shared/emeter.ts index bc77d3d..47ccafc 100644 --- a/src/shared/emeter.ts +++ b/src/shared/emeter.ts @@ -70,6 +70,7 @@ export default class Emeter { normalize('voltage', 'voltage_mv', 1000); this.#realtime = normRealtime; + // @ts-expect-error typescript limitation this.device.emit('emeter-realtime-update', this.#realtime); } diff --git a/src/shared/schedule.ts b/src/shared/schedule.ts index 101a082..8edb148 100644 --- a/src/shared/schedule.ts +++ b/src/shared/schedule.ts @@ -9,7 +9,7 @@ import { type HasErrCode, } from '../utils'; -type ScheduleDateStart = { +export type ScheduleDateStart = { smin: number; stime_opt: number; }; @@ -19,7 +19,15 @@ type ScheduleDateEnd = { etime_opt: number; }; -type WDay = [boolean, boolean, boolean, boolean, boolean, boolean, boolean]; +export type WDay = [ + boolean, + boolean, + boolean, + boolean, + boolean, + boolean, + boolean, +]; export type ScheduleRule = { name?: string; @@ -35,8 +43,8 @@ export type ScheduleRule = { export type ScheduleRuleWithId = ScheduleRule & { id: string }; -type ScheduleRules = { rule_list: ScheduleRuleWithId[] }; -type ScheduleNextAction = Record; +export type ScheduleRules = { rule_list: ScheduleRuleWithId[] }; +export type ScheduleNextAction = Record; export type ScheduleRuleResponse = ScheduleRule & HasErrCode; export type ScheduleRulesResponse = ScheduleRules & HasErrCode;