diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dda6d7e..95eb8dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x] + node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4 diff --git a/index.js b/index.js index 2c12c9b..5618d73 100644 --- a/index.js +++ b/index.js @@ -131,7 +131,7 @@ class TuyaDevice extends EventEmitter { * @example * // get all available data from device * tuya.get({schema: true}).then(data => console.log(data)) - * @returns {Promise} + * @returns {Promise} * returns boolean if single property is requested, otherwise returns object of results */ async get(options = {}) { @@ -194,7 +194,7 @@ class TuyaDevice extends EventEmitter { } // Return first property by default - return data.dps['1']; + return data.dps ? data.dps['1'] : undefined; } /** @@ -525,8 +525,10 @@ class TuyaDevice extends EventEmitter { // Send ping this.client.write(buffer); if (this.globalOptions.issueRefreshOnPing) { - this.refresh(); - this.get(); + this.refresh().then(() => this.get()).catch(error => { + debug('Error refreshing/getting on ping: ' + error); + this.emit('error', error); + }); } } @@ -1016,12 +1018,25 @@ class TuyaDevice extends EventEmitter { dataRes = parser.parse(message)[0]; } catch (error) { debug(error); - reject(error); + + const devParser = new MessageParser({key: this.device.key, version: this.device.version}); + try { + dataRes = devParser.parse(message)[0]; + } catch (devError) { + debug(devError); + reject(error); + return; + } } debug('UDP data:'); debug(dataRes); + if (typeof dataRes.payload === 'string') { + debug('Received string payload. Ignoring.'); + return; + } + const thisID = dataRes.payload.gwId; const thisIP = dataRes.payload.ip; @@ -1048,7 +1063,7 @@ class TuyaDevice extends EventEmitter { this.device.id = dataRes.payload.gwId; this.device.gwID = dataRes.payload.gwId; - // Change product key if neccessary + // Change product key if necessary this.device.productKey = dataRes.payload.productKey; // Change protocol version if necessary diff --git a/lib/cipher.js b/lib/cipher.js index ddac1d0..7a4ebbc 100644 --- a/lib/cipher.js +++ b/lib/cipher.js @@ -112,17 +112,19 @@ class TuyaCipher { } /** - * Decrypts data. - * @param {String|Buffer} data to decrypt - * @returns {Object|String} - * returns object if data is JSON, else returns string - */ - decrypt(data) { - if (this.version === '3.4') { + * Decrypts data. + * @param {String|Buffer} data to decrypt + * @param {String} [version] protocol version + * @returns {Object|String} + * returns object if data is JSON, else returns string + */ + decrypt(data, version) { + version = version || this.version; + if (version === '3.4') { return this._decrypt34(data); } - if (this.version === '3.5') { + if (version === '3.5') { return this._decrypt35(data); } diff --git a/lib/message-parser.js b/lib/message-parser.js index 371df00..37a74cf 100644 --- a/lib/message-parser.js +++ b/lib/message-parser.js @@ -138,6 +138,7 @@ class MessageParser { let sequenceN; let commandByte; let payloadSize; + let overwriteVersion; if (suffix === 0x0000AA55) { // Get sequence number @@ -154,6 +155,9 @@ class MessageParser { throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`); } } else if (suffix === 0x00009966) { + // When this suffix comes in we should have 3.5 version + overwriteVersion = '3.5'; + // Get sequence number sequenceN = buffer.readUInt32BE(6); @@ -167,6 +171,8 @@ class MessageParser { if (buffer.length - 8 < payloadSize) { throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`); } + } else { + throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`); // Should never happen } const packageFromDiscovery = ( @@ -183,7 +189,7 @@ class MessageParser { // Get the payload // Adjust for messages lacking a return code let payload; - if (this.version === '3.5') { + if (overwriteVersion === '3.5' || this.version === '3.5') { payload = buffer.slice(HEADER_SIZE_3_5, HEADER_SIZE_3_5 + payloadSize); sequenceN = buffer.slice(6, 10).readUInt32BE(); commandByte = buffer.slice(10, 14).readUInt32BE(); @@ -222,17 +228,18 @@ class MessageParser { } } - return {payload, leftover, commandByte, sequenceN}; + return {payload, leftover, commandByte, sequenceN, version: overwriteVersion || this.version}; } /** * Attempts to decode a given payload into * an object or string. * @param {Buffer} data to decode + * @param {String} version of protocol * @returns {Object|String} * object if payload is JSON, otherwise string */ - getPayload(data) { + getPayload(data, version) { if (data.length === 0) { return false; } @@ -243,13 +250,13 @@ class MessageParser { throw new Error('Missing key or version in constructor.'); } - data = this.cipher.decrypt(data); + data = this.cipher.decrypt(data, version); } catch (_) { data = data.toString('utf8'); } // Incoming 3.5 data isn't 0 because of iv and tag so check size after - if (this.version === '3.5') { + if (version === '3.5') { if (data.length === 0) { return false; } @@ -279,7 +286,7 @@ class MessageParser { parseRecursive(buffer, packets) { const result = this.parsePacket(buffer); - result.payload = this.getPayload(result.payload); + result.payload = this.getPayload(result.payload, result.version); packets.push(result); diff --git a/package-lock.json b/package-lock.json index 2f8de0c..91a3199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "7.7.0", "license": "MIT", "dependencies": { - "debug": "^4.3.7", + "debug": "^4.4.0", "p-queue": "6.6.2", "p-retry": "4.6.2", "p-timeout": "3.2.0" @@ -20,7 +20,7 @@ "clone": "2.1.2", "coveralls": "3.1.1", "delay": "4.4.1", - "documentation": "^14.0.0", + "documentation": "^14.0.3", "nyc": "15.1.0", "xo": "0.25.4" } diff --git a/package.json b/package.json index e8f7312..81195a4 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "homepage": "https://github.com/codetheweb/tuyapi#readme", "dependencies": { - "debug": "^4.3.7", + "debug": "^4.4.0", "p-queue": "6.6.2", "p-retry": "4.6.2", "p-timeout": "3.2.0" @@ -49,7 +49,7 @@ "clone": "2.1.2", "coveralls": "3.1.1", "delay": "4.4.1", - "documentation": "^14.0.0", + "documentation": "^14.0.3", "nyc": "15.1.0", "xo": "0.25.4" },