export class TapoError extends Error { constructor(public code: number, message: string) { super(message); this.name = 'TapoError'; } } export const throwErrorIfFound = (responseData: any) => { const errorCode = responseData['error_code']; if (errorCode) { console.debug('[Tapo] errorCode: ', errorCode, responseData); const errorMessage = getTapoErrorMessage(errorCode, responseData['msg']); if (errorCode !== 0) { throw new TapoError(errorCode, errorMessage); } } }; export const getTapoErrorMessage = (errorCode: number, originalMsg?: string): string => { const errorMessages: Record = { 0: 'Success', '-1010': 'Invalid public key length', '-1012': 'Invalid terminal UUID or key pair', '-1501': 'Invalid request or credentials', '9999': 'Device token expired or invalid', '-1003': 'JSON format error', '-1002': 'Incorrect request', '-1001': 'Invalid parameters', '-1004': 'JSON decode fail', '-1005': 'AES decrypt fail', '-1006': 'Request length error', '-1007': 'Cloud token expired', '-1008': 'Device token expired', '-20104': "Parameter doesn't exist - check login request format", '-20571': 'Invalid email or password', '-20580': 'Token expired, re-login required', '-20600': 'Account not found', '-20601': 'Incorrect email or password', '-20675': 'Cloud token expired or invalid', '1002': 'Incorrect request', '-40401': 'Invalid stok value', '-40210': 'Function not supported', '40210': 'Function not supported', '-64324': 'Privacy mode is ON, not able to execute', '-64302': 'Preset operation conflict', '-64321': 'Preset ID not found', }; return errorMessages[errorCode.toString()] || originalMsg || `Unknown error code: ${errorCode}`; }; export const throwTapoCareErrorIfFound = (responseData: any) => { const errorCode = responseData?.code; if (errorCode) { throw new TapoError(errorCode, responseData['message'] || `Tapo Care error: ${errorCode}`); } }; export const retryOperation = async (operation: () => Promise, maxRetries: number = 3, delay: number = 1000, backoffFactor: number = 2): Promise => { let lastError: Error; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; if (attempt === maxRetries) { throw lastError; } // Don't retry on certain error codes if (error instanceof TapoError) { const nonRetryableCodes = [-20571, -20580, -40401, -1501]; if (nonRetryableCodes.includes(error.code)) { throw error; } } const waitTime = delay * Math.pow(backoffFactor, attempt - 1); console.debug(`[Tapo] Retry attempt ${attempt}/${maxRetries} in ${waitTime}ms after error:`, (error as Error).message); await new Promise((resolve) => setTimeout(resolve, waitTime)); } } throw lastError!; };