node-red-contrib-tplink-tapo-connect-api
Version:
This unofficial node-RED node allows connection to TP-Link Tapo devices. This project has been enhanced with AI support to enable new features. Starting with v0.50, we have added support for the KLAP protocol. To prioritize the operation of this node, we
207 lines • 7.75 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnifiedTapoProtocol = void 0;
const auth_1 = require("./auth");
const klap_auth_1 = require("./klap-auth");
/**
* Unified protocol manager similar to Rust tapo's TapoProtocol
* This addresses the core issue of distributed session management
*/
class UnifiedTapoProtocol {
constructor(ip, credentials) {
this.activeProtocol = null;
this.lastRequestTime = 0;
this.minRequestInterval = 100; // Minimum 100ms between requests
this.auth = new auth_1.TapoAuth(ip, credentials);
this.klapAuth = new klap_auth_1.KlapAuth(ip, credentials);
}
/**
* Initialize connection with automatic protocol detection
*/
async connect() {
let klapError = null;
let passthroughError = null;
// Try KLAP first (preferred protocol)
try {
console.log('Attempting KLAP connection...');
await this.klapAuth.authenticate();
this.activeProtocol = 'klap';
const version = this.klapAuth.getSessionVersion();
console.log(`KLAP ${version === null || version === void 0 ? void 0 : version.toUpperCase()} connection successful`);
return;
}
catch (error) {
klapError = error;
console.log('KLAP failed, trying Passthrough...');
}
// Fallback to Secure Passthrough
try {
await this.auth.authenticate();
this.activeProtocol = 'passthrough';
console.log('Secure Passthrough connection successful');
return;
}
catch (error) {
passthroughError = error;
}
throw new Error(`All protocols failed. KLAP: ${klapError === null || klapError === void 0 ? void 0 : klapError.message}; Passthrough: ${passthroughError === null || passthroughError === void 0 ? void 0 : passthroughError.message}`);
}
/**
* Execute request with automatic session management (like Rust implementation)
*/
async executeRequest(request) {
if (!this.activeProtocol) {
throw new Error('Not connected. Call connect() first.');
}
// Add small delay to prevent KLAP-1012 errors on rapid requests
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequestTime;
if (timeSinceLastRequest < this.minRequestInterval) {
const delayNeeded = this.minRequestInterval - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, delayNeeded));
}
this.lastRequestTime = Date.now();
// Attempt with current protocol
try {
const result = await this.sendWithCurrentProtocol(request);
return result;
}
catch (error) {
// Handle session errors by trying to recover
if (this.isSessionError(error)) {
console.log('Session error detected, attempting recovery...');
try {
// Try to refresh current protocol session
await this.refreshCurrentSession();
const result = await this.sendWithCurrentProtocol(request);
return result;
}
catch (refreshError) {
console.log('Session refresh failed, trying protocol switch...');
// Try switching to the other protocol
const switchResult = await this.trySwitchProtocol(request);
if (switchResult !== null) {
return switchResult;
}
}
}
// All recovery attempts failed
throw error;
}
}
/**
* Send request using current active protocol
*/
async sendWithCurrentProtocol(request) {
if (this.activeProtocol === 'klap') {
return await this.klapAuth.secureRequest(request);
}
else if (this.activeProtocol === 'passthrough') {
return await this.auth.secureRequest(request);
}
throw new Error('No active protocol');
}
/**
* Refresh session for current protocol
*/
async refreshCurrentSession() {
if (this.activeProtocol === 'klap') {
await this.klapAuth.authenticate();
}
else if (this.activeProtocol === 'passthrough') {
await this.auth.authenticate();
}
}
/**
* Try switching to alternative protocol and execute request
*/
async trySwitchProtocol(request) {
const alternativeProtocol = this.activeProtocol === 'klap' ? 'passthrough' : 'klap';
try {
console.log(`Switching to ${alternativeProtocol} protocol...`);
if (alternativeProtocol === 'klap') {
this.klapAuth.clearSession();
await this.klapAuth.authenticate();
const result = await this.klapAuth.secureRequest(request);
this.activeProtocol = 'klap';
const version = this.klapAuth.getSessionVersion();
console.log(`Switched to KLAP ${version === null || version === void 0 ? void 0 : version.toUpperCase()} successfully`);
return result;
}
else {
this.auth.clearSession();
await this.auth.authenticate();
const result = await this.auth.secureRequest(request);
this.activeProtocol = 'passthrough';
return result;
}
}
catch (error) {
console.log(`Protocol switch to ${alternativeProtocol} failed:`, error);
return null;
}
}
/**
* Check if error indicates session issues
*/
isSessionError(error) {
const errorMessage = error.message.toLowerCase();
return errorMessage.includes('klap 1002') ||
errorMessage.includes('session expired') ||
errorMessage.includes('session needs to be re-established') ||
errorMessage.includes('klap -1001') ||
errorMessage.includes('tapo api error: 1003');
}
/**
* Check if currently connected
*/
isConnected() {
if (!this.activeProtocol)
return false;
if (this.activeProtocol === 'klap') {
return this.klapAuth.isAuthenticated();
}
else {
return this.auth.isAuthenticated();
}
}
/**
* Clear all sessions
*/
async disconnect() {
try {
if (this.activeProtocol === 'klap') {
await this.klapAuth.clearSession();
}
else if (this.activeProtocol === 'passthrough') {
this.auth.clearSession();
}
}
catch (error) {
console.warn('Warning during disconnect:', error);
}
this.activeProtocol = null;
}
/**
* Get current active protocol
*/
getActiveProtocol() {
if (this.activeProtocol === 'klap') {
const version = this.klapAuth.getSessionVersion();
return version ? `klap-${version}` : 'klap';
}
return this.activeProtocol;
}
/**
* Get detailed protocol information
*/
getProtocolInfo() {
if (this.activeProtocol === 'klap') {
const version = this.klapAuth.getSessionVersion();
return version ? { protocol: 'klap', version } : { protocol: 'klap' };
}
return { protocol: this.activeProtocol };
}
}
exports.UnifiedTapoProtocol = UnifiedTapoProtocol;
//# sourceMappingURL=unified-protocol.js.map