94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import type {
|
|
ITplinkClientCommand,
|
|
ITplinkCommandResult,
|
|
ITplinkConfig,
|
|
ITplinkEvent,
|
|
ITplinkSnapshot,
|
|
} from './tplink.types.js';
|
|
import { TplinkMapper } from './tplink.mapper.js';
|
|
|
|
type TTplinkEventHandler = (eventArg: ITplinkEvent) => void;
|
|
|
|
export class TplinkClient {
|
|
private readonly events: ITplinkEvent[] = [];
|
|
private readonly eventHandlers = new Set<TTplinkEventHandler>();
|
|
|
|
constructor(private readonly config: ITplinkConfig) {}
|
|
|
|
public async getSnapshot(): Promise<ITplinkSnapshot> {
|
|
return TplinkMapper.toSnapshot(this.config, undefined, this.events);
|
|
}
|
|
|
|
public onEvent(handlerArg: TTplinkEventHandler): () => void {
|
|
this.eventHandlers.add(handlerArg);
|
|
return () => this.eventHandlers.delete(handlerArg);
|
|
}
|
|
|
|
public async sendCommand(commandArg: ITplinkClientCommand): Promise<ITplinkCommandResult> {
|
|
this.emit({
|
|
type: 'command_mapped',
|
|
command: commandArg,
|
|
deviceId: commandArg.deviceId,
|
|
entityId: commandArg.entityId,
|
|
uniqueId: commandArg.uniqueId,
|
|
timestamp: Date.now(),
|
|
});
|
|
|
|
if (this.config.commandExecutor) {
|
|
const result = this.commandResult(await this.config.commandExecutor(commandArg), commandArg);
|
|
this.emit({
|
|
type: result.success ? 'command_executed' : 'command_failed',
|
|
command: commandArg,
|
|
data: result,
|
|
deviceId: commandArg.deviceId,
|
|
entityId: commandArg.entityId,
|
|
uniqueId: commandArg.uniqueId,
|
|
timestamp: Date.now(),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
const result: ITplinkCommandResult = {
|
|
success: false,
|
|
error: this.unsupportedLiveControlMessage(),
|
|
data: { command: commandArg },
|
|
};
|
|
this.emit({
|
|
type: 'command_failed',
|
|
command: commandArg,
|
|
data: result,
|
|
deviceId: commandArg.deviceId,
|
|
entityId: commandArg.entityId,
|
|
uniqueId: commandArg.uniqueId,
|
|
timestamp: Date.now(),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public async destroy(): Promise<void> {
|
|
this.eventHandlers.clear();
|
|
}
|
|
|
|
private emit(eventArg: ITplinkEvent): void {
|
|
this.events.push(eventArg);
|
|
for (const handler of this.eventHandlers) {
|
|
handler(eventArg);
|
|
}
|
|
}
|
|
|
|
private commandResult(resultArg: unknown, commandArg: ITplinkClientCommand): ITplinkCommandResult {
|
|
if (this.isCommandResult(resultArg)) {
|
|
return resultArg;
|
|
}
|
|
return { success: true, data: resultArg ?? { command: commandArg } };
|
|
}
|
|
|
|
private isCommandResult(valueArg: unknown): valueArg is ITplinkCommandResult {
|
|
return typeof valueArg === 'object' && valueArg !== null && 'success' in valueArg;
|
|
}
|
|
|
|
private unsupportedLiveControlMessage(): string {
|
|
return 'TP-Link Kasa/Tapo live local writes require full python-kasa-equivalent protocol selection and encrypted transports (legacy IOT XOR plus SMART AES/KLAP). This dependency-free TypeScript port is snapshot/manual unless commandExecutor is provided.';
|
|
}
|
|
}
|