Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tuyapi/link

Package Overview
Dependencies
Maintainers
2
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tuyapi/link - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

210

index.js

@@ -1,2 +0,2 @@

const Cloud = require('@tuyapi/cloud');
const Cloud = require('@tuyapi/openapi');
const debug = require('debug')('@tuyapi/link:wizard');

@@ -6,115 +6,135 @@ const TuyaLink = require('./lib/link.js');

/**
* A wrapper that combines `@tuyapi/cloud` and
* `(@tuyapi/link).manual` (included in this package)
* to make registration Just Work™️. Exported as
* `(@tuyapi/link).wizard`.
* @class
* @param {Object} options construction options
* @param {String} options.apiKey API key
* @param {String} options.apiSecret API secret
* @param {String} options.email user email
* @param {String} options.password user password
* @param {String} [options.region='AZ'] region (AZ=Americas, AY=Asia, EU=Europe)
* @param {String} [options.timezone='-05:00'] timezone of device
* @example
* // Note: user account does not need to already exist
* const register = new TuyaLink.wizard({key: 'your-api-key',
* secret: 'your-api-secret',
* email: 'example@example.com',
* password: 'example-password'});
*/
function TuyaLinkWizard(options) {
// Set to empty object if undefined
options = options ? options : {};
* A wrapper that combines `@tuyapi/openapi` and
* `(@tuyapi/link).manual` (included in this package)
* to make registration Just Work™️. Exported as
* `(@tuyapi/link).wizard`.
* @class
* @param {Object} options construction options
* @param {String} options.apiKey API key
* @param {String} options.apiSecret API secret
* @param {String} options.schema app schema to register the device under
* @param {String} options.email user email
* @param {String} options.password user password
* @param {String} [options.region='AZ'] region (AZ=Americas, AY=Asia, EU=Europe)
* @param {String} [options.timezone='-05:00'] timezone of device
* @example
* // Note: user account does not need to already exist
* const register = new TuyaLink.wizard({key: 'your-api-key',
* secret: 'your-api-secret',
* email: 'example@example.com',
* password: 'example-password'});
*/
class TuyaLinkWizard {
constructor({email, password, region = 'AZ', timezone = '-05:00', apiKey, apiSecret, schema} = {}) {
if (!email || !password) {
throw new Error('Both email and password must be provided');
}
if (!options.email || !options.password) {
throw new Error('Both email and password must be provided');
this.email = email;
this.password = password;
this.region = region;
this.timezone = timezone;
// Don't need to check key and secret for correct format as
// tuyapi/openapi already does
this.api = new Cloud({key: apiKey, secret: apiSecret, schema});
// Construct instance of TuyaLink
this.device = new TuyaLink();
}
this.email = options.email;
this.password = options.password;
/**
* Logins to Tuya cloud using credentials provided to constructor
* @example
* register.init()
* @returns {Promise<String>} A Promise that contains the session ID
*/
async init() {
// Register/login user
await this.api.getToken();
// Set defaults
this.region = options.region ? options.region : 'AZ';
this.timezone = options.timezone ? options.timezone : '-05:00';
this.uid = await this.api.putUser({countryCode: '1', username: this.email, password: this.password, usernameType: 2});
}
// Don't need to check key and secret for correct format as
// tuyapi/cloud already does
this.api = new Cloud({key: options.apiKey,
secret: options.apiSecret,
region: this.region});
/**
* Links device to WiFi and cloud
* @param {Object} options
* options
* @param {Number} [options.timeout=60]
* how long we should wait for devices to
* connect before throwing an error, in seconds
* @param {String} options.ssid
* the SSID to send to the device
* @param {String} options.wifiPassword
* password for the SSID
* @param {Number} [options.devices=1]
* if linking more than 1 device at a time,
* set to number of devices being linked
* @example
* register.linkDevice({ssid: 'example-ssid',
wifiPassword: 'example-password'}).then(device => {
* console.log(device);
* });
* @returns {Promise<Object>} A Promise that contains data on device(s)
*/
async linkDevice({timeout = 60, ssid, wifiPassword = '', devices = 1} = {}) {
if (!ssid) {
throw new Error('SSID must be provided');
}
// Construct instance of TuyaLink
this.device = new TuyaLink();
}
try {
const token = await this.api.getDeviceToken({uid: this.uid, timezone: this.timezone});
/**
* Logins to Tuya cloud using credentials provided to constructor
* @example
* register.init()
* @returns {Promise<String>} A Promise that contains the session ID
*/
TuyaLinkWizard.prototype.init = function () {
// Register/login user
return this.api.register({email: this.email, password: this.password});
};
debug('Token: ', token);
/**
* Links device to WiFi and cloud
* @param {Object} options
* options
* @param {String} options.ssid
* the SSID to send to the device
* @param {String} options.wifiPassword
* password for the SSID
* @param {Number} [options.devices=1]
* if linking more than 1 device at a time,
* set to number of devices being linked
* @example
* register.linkDevice({ssid: 'example-ssid',
wifiPassword: 'example-password'}).then(device => {
* console.log(device);
* });
* @returns {Promise<Object>} A Promise that contains data on device(s)
*/
TuyaLinkWizard.prototype.linkDevice = async function (options) {
if (!options.ssid || !options.wifiPassword) {
throw new Error('Both SSID and WiFI password must be provided');
}
this.device.registerSmartLink({region: this.region,
token: token.token,
secret: token.secret,
ssid,
wifiPassword});
// Default for options.devices
options.devices = options.devices ? options.devices : 1;
// While UDP packets are being sent, start polling for device
debug('Polling cloud for details on token...');
try {
const token = await this.api.request({action: 'tuya.m.device.token.create',
data: {timeZone: this.timezone}});
let waitingForDevices = true;
let lastAPIResponse = {};
debug('Token: ', token);
const timeoutAt = new Date().getTime() + (timeout * 1000);
this.device.registerSmartLink({region: this.region,
token: token.token,
secret: token.secret,
ssid: options.ssid,
wifiPassword: options.wifiPassword});
while (waitingForDevices) {
// eslint-disable-next-line no-await-in-loop
lastAPIResponse = await this.api.getDevicesByToken(token.token);
// While UDP packets are being sent, start polling for device
debug('Polling cloud for details on token...');
debug(`${lastAPIResponse.successDevices.length} devices returned by API.`);
const devices = await this.api.waitForToken({token: token.token,
devices: options.devices});
debug('Found device(s)!', devices);
if (lastAPIResponse.successDevices.length >= devices) {
waitingForDevices = false;
}
// Stop broadcasting setup data
this.device.abortBroadcastingData();
// Check for timeout
const now = new Date().getTime();
// Remove binding on socket
this.device.cleanup();
if (now > timeoutAt) {
throw new Error('Timed out waiting for devices to connect.');
}
}
return devices;
} catch (err) {
this.device.cleanup();
throw err;
const returnedDevices = lastAPIResponse.successDevices;
debug('Found device(s)!', returnedDevices);
// Stop broadcasting setup data
this.device.abortBroadcastingData();
// Remove binding on socket
this.device.cleanup();
return returnedDevices;
} catch (error) {
this.device.cleanup();
throw error;
}
}
};
}
module.exports = {wizard: TuyaLinkWizard, manual: TuyaLink};

@@ -6,177 +6,160 @@ const dgram = require('dgram');

/**
* A lower level option for linking
* devices. Use only if you're not generating
* a token through `@tuyapi/cloud`. Exported
* as `(@tuyapi/link).manual`.
* Currently has no options, but some may
* be added in the future.
* @class
* @param {Object} options construction options
* @example
* const register = new TuyaLink.manual({});
*/
function TuyaLink(options) {
this.abortBroadcasting = false;
* A lower level option for linking
* devices. Use only if you're not generating
* a token through `@tuyapi/cloud`. Exported
* as `(@tuyapi/link).manual`.
* @class
* @example
* const register = new TuyaLink.manual();
*/
class TuyaLink {
constructor() {
this.abortBroadcasting = false;
}
return options;
}
/**
* Thin wrapper for this.sendSmartLinkStart()
* and this.sendSmartLinkData(). Unless you
* have a special use case, prefer this method
* over calling this.sendSmartLinkStart() and
* this.sendSmartLinkData() directly.
* @param {Object} options
* options
* @param {String} options.region
* region (see smartLinkEncode() for options)
* @param {String} options.token
* generated token to send
* @param {String} options.secret
* generated secret to send
* @param {String} options.ssid
* SSID to connect to
* @param {String} options.wifiPassword
* password of WiFi
* @example
* device.registerSmartLink({region: 'AZ',
* token: '00000000',
* secret: '0101',
* ssid: 'Example SSID',
* wifiPassword: 'example-password'}).then(() => {
* console.log('Done!');
* });
* @returns {Promise<Undefined>} A Promise that resolves when all data has been transmitted
*/
TuyaLink.prototype.registerSmartLink = function (options) {
/**
* Thin wrapper for this.sendSmartLinkStart()
* and this.sendSmartLinkData(). Unless you
* have a special use case, prefer this method
* over calling this.sendSmartLinkStart() and
* this.sendSmartLinkData() directly.
* @param {Object} options
* options
* @param {String} options.region
* region (see smartLinkEncode() for options)
* @param {String} options.token
* generated token to send
* @param {String} options.secret
* generated secret to send
* @param {String} options.ssid
* SSID to connect to
* @param {String} options.wifiPassword
* password of WiFi
* @example
* device.registerSmartLink({region: 'AZ',
* token: '00000000',
* secret: '0101',
* ssid: 'Example SSID',
* wifiPassword: 'example-password'}).then(() => {
* console.log('Done!');
* });
* @returns {Promise<Undefined>} A Promise that resolves when all data has been transmitted
*/
async registerSmartLink(options) {
// Check arguments
if (options.region.length !== 2) {
throw new Error('Invalid region');
}
if (options.token.length !== 8) {
throw new Error('Invalid token');
}
if (options.secret.length !== 4) {
throw new Error('Invalid secret');
}
if (options.ssid.length > 32) {
throw new Error('Invalid SSID');
}
if (options.wifiPassword.length > 64) {
throw new Error('Invalid WiFi password');
}
if (options.region.length !== 2 || !['AZ', 'AY', 'EU'].includes(options.region)) {
throw new Error('Invalid region');
}
debug('Sending SmartLink initialization packets');
const that = this;
return new Promise(async (resolve, reject) => {
try {
await this.sendSmartLinkStart();
debug('Sending SmartLink data packets');
await this.sendSmartLinkData(that.smartLinkEncode(options));
debug('Finished sending packets.');
resolve();
} catch (err) {
reject(err);
if (options.token.length !== 8) {
throw new Error('Invalid token');
}
});
};
/**
* Transmits start pattern of packets
* (1, 3, 6, 10) 144 times with
* a delay between transmits.
* @returns {Promise<Undefined>} A Promise that resolves when data has been transmitted
*/
TuyaLink.prototype.sendSmartLinkStart = function () {
const that = this;
return new Promise((async (resolve, reject) => {
try {
/* eslint-disable no-await-in-loop */
for (let x = 0; x < 144; x++) {
await that._broadcastUDP(1);
await that._broadcastUDP(3);
await that._broadcastUDP(6);
await that._broadcastUDP(10);
await delay((x % 8) + 33);
}
/* eslint-enable no-await-in-loop */
if (options.secret.length !== 4) {
throw new Error('Invalid secret');
}
resolve();
} catch (err) {
reject(err);
if (options.ssid.length > 32) {
throw new Error('Invalid SSID');
}
}));
};
/**
* Transmits provided data
* as UDP packet lengths 30
* times with a delay between
* transmits.
* @param {Array} data of packet lengths to send
* @returns {Promise<Undefined>} A Promise that resolves when data has been transmitted
*/
TuyaLink.prototype.sendSmartLinkData = function (data) {
const that = this;
if (options.wifiPassword.length > 64) {
throw new Error('Invalid WiFi password');
}
return new Promise(async (resolve, reject) => {
try {
let delayMs = 0;
debug('Sending SmartLink initialization packets');
/* eslint-disable no-await-in-loop */
for (let x = 0; x < 30 && !this.abortBroadcasting; x++) {
if (delayMs > 26) {
delayMs = 6;
}
await this.sendSmartLinkStart();
debug('Sending SmartLink data packets');
await this.sendSmartLinkData(this.smartLinkEncode(options));
debug('Finished sending packets.');
}
await that._asyncForEach(data, async b => {
await that._broadcastUDP(b);
await delay(delayMs);
}); // 17, 40, 53, 79
/**
* Transmits start pattern of packets
* (1, 3, 6, 10) 32 times with
* a delay between transmits.
* @returns {Promise<Undefined>} A Promise that resolves when data has been transmitted
*/
async sendSmartLinkStart() {
const gap = 2;
await delay(200);
delayMs += 3;
}
/* eslint-enable no-await-in-loop */
/* eslint-disable no-await-in-loop */ // 143/2
for (let x = 0; x < 32; x++) {
await this._broadcastUDP(1);
await delay(gap);
await this._broadcastUDP(3);
await delay(gap);
await this._broadcastUDP(6);
await delay(gap);
await this._broadcastUDP(10);
await delay(gap);
await this._broadcastUDP(1);
await delay(gap);
await this._broadcastUDP(3);
await delay(gap);
await this._broadcastUDP(6);
await delay(gap);
await this._broadcastUDP(10);
await delay(40);// 70+x%8)
}
/* eslint-enable no-await-in-loop */
}
this.abortBroadcasting = false;
/**
* Transmits provided data
* as UDP packet lengths 30
* times with a delay between
* transmits.
* @param {Array} data of packet lengths to send
* @returns {Promise<Undefined>} A Promise that resolves when data has been transmitted
*/
async sendSmartLinkData(data) {
const gap = 2;
resolve();
} catch (err) {
reject(err);
/* eslint-disable no-await-in-loop */
for (let x = 0; x < 10 && !this.abortBroadcasting; x++) {
await delay(160);
await this._asyncForEach(data, async b => {
await this._broadcastUDP(b);
await delay(gap);
}); // 17, 40, 53, 79
}
});
};
/* eslint-enable no-await-in-loop */
/**
* Aborts broadcasting UDP packets.
*/
TuyaLink.prototype.abortBroadcastingData = function () {
debug('Aborting broadcast of data...');
this.abortBroadcasting = true;
};
this.abortBroadcasting = false;
}
/**
* Encodes data as UDP packet
* lengths.
* @param {Object} options options
* @param {String} options.region
* two-letter region (AZ=Americas, AY=Asia, EU=Europe)
* @param {String} options.token token
* @param {String} options.secret secret
* @param {String} options.ssid SSID
* @param {String} options.wifiPassword
* password of WiFi
* @returns {Array} array of packet lengths
*/
TuyaLink.prototype.smartLinkEncode = function (options) {
// Convert strings to Buffers
const wifiPasswordBytes = Buffer.from(options.wifiPassword);
const regionTokenSecretBytes = Buffer.from(options.region +
/**
* Aborts broadcasting UDP packets.
*/
abortBroadcastingData() {
debug('Aborting broadcast of data...');
this.abortBroadcasting = true;
}
/**
* Encodes data as UDP packet
* lengths.
* @param {Object} options options
* @param {String} options.region
* two-letter region (AZ=Americas, AY=Asia, EU=Europe)
* @param {String} options.token token
* @param {String} options.secret secret
* @param {String} options.ssid SSID
* @param {String} options.wifiPassword
* password of WiFi
* @returns {Array} array of packet lengths
*/
smartLinkEncode(options) {
// Convert strings to Buffers
const wifiPasswordBytes = Buffer.from(options.wifiPassword);
const regionTokenSecretBytes = Buffer.from(options.region +
options.token + options.secret);
const ssidBytes = Buffer.from(options.ssid);
const ssidBytes = Buffer.from(options.ssid);
// Calculate size of byte array
const rawByteArray = Buffer.alloc(1 +
// Calculate size of byte array
// (must add 1 byte for lengths)
const rawByteArray = Buffer.alloc(1 +
wifiPasswordBytes.length +

@@ -187,202 +170,204 @@ 1 +

let rawByteArrayIndex = 0;
let rawByteArrayIndex = 0;
// Write WiFi password length
rawByteArray.writeInt8(this._getLength(options.wifiPassword), rawByteArrayIndex);
rawByteArrayIndex++;
// Write WiFi password length
rawByteArray.writeInt8(this._getLength(options.wifiPassword), rawByteArrayIndex);
rawByteArrayIndex++;
// Write WiFi password
wifiPasswordBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += wifiPasswordBytes.length;
// Write WiFi password
wifiPasswordBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += wifiPasswordBytes.length;
// Write region token secret length
rawByteArray.writeInt8(this._getLength(regionTokenSecretBytes), rawByteArrayIndex);
rawByteArrayIndex++;
// Write region token secret length
rawByteArray.writeInt8(this._getLength(regionTokenSecretBytes), rawByteArrayIndex);
rawByteArrayIndex++;
// Write region token secret bytes
regionTokenSecretBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += regionTokenSecretBytes.length;
// Write region token secret bytes
regionTokenSecretBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += regionTokenSecretBytes.length;
// Write WiFi SSID bytes
ssidBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += ssidBytes.length;
// Write WiFi SSID bytes
ssidBytes.copy(rawByteArray, rawByteArrayIndex);
rawByteArrayIndex += ssidBytes.length;
if (rawByteArray.length !== rawByteArrayIndex) {
throw new Error('Byte buffer filled improperly');
}
if (rawByteArray.length !== rawByteArrayIndex) {
throw new Error('Byte buffer filled improperly');
}
// Now, encode above data into packet lengths
const rawDataLengthRoundedUp = this._rounder(rawByteArray.length, 4);
// Now, encode above data into packet lengths
const rawDataLengthRoundedUp = this._rounder(rawByteArray.length, 4);
const encodedData = [];
const encodedData = [];
// First 4 bytes of header
const stringLength = (wifiPasswordBytes.length +
// First 4 bytes of header
const stringLength = (wifiPasswordBytes.length +
regionTokenSecretBytes.length + ssidBytes.length + 2) % 256;
const stringLengthCRC = this._tuyaCRC8([stringLength]);
const stringLengthCRC = this._tuyaCRC8([stringLength]);
// Length encoded into the first two bytes based at 16 and then 32
encodedData[0] = (stringLength / 16) | 16;
encodedData[1] = (stringLength % 16) | 32;
// Length CRC encoded into the next two bytes based at 46 and 64
encodedData[2] = (stringLengthCRC / 16) | 48;
encodedData[3] = (stringLengthCRC % 16) | 64;
// Length encoded into the first two bytes based at 16 and then 32
encodedData[0] = (stringLength / 16) | 16;
encodedData[1] = (stringLength % 16) | 32;
// Length CRC encoded into the next two bytes based at 48 and 64
encodedData[2] = (stringLengthCRC / 16) | 48;
encodedData[3] = (stringLengthCRC % 16) | 64;
// Rest of data
let encodedDataIndex = 4;
let sequenceCounter = 0;
// Rest of data
let encodedDataIndex = 4;
let sequenceCounter = 0;
for (let x = 0; x < rawDataLengthRoundedUp; x += 4) {
// Build CRC buffer, using data from rawByteArray or 0 values if too long
const crcData = [];
crcData[0] = sequenceCounter++;
crcData[1] = x + 0 < rawByteArray.length ? rawByteArray[x + 0] : 0;
crcData[2] = x + 1 < rawByteArray.length ? rawByteArray[x + 1] : 0;
crcData[3] = x + 2 < rawByteArray.length ? rawByteArray[x + 2] : 0;
crcData[4] = x + 3 < rawByteArray.length ? rawByteArray[x + 3] : 0;
for (let x = 0; x < rawDataLengthRoundedUp; x += 4) {
// Build CRC buffer, using data from rawByteArray or 0 values if too long
const crcData = [];
crcData[0] = sequenceCounter++;
crcData[1] = x + 0 < rawByteArray.length ? rawByteArray[x + 0] : 0;
crcData[2] = x + 1 < rawByteArray.length ? rawByteArray[x + 1] : 0;
crcData[3] = x + 2 < rawByteArray.length ? rawByteArray[x + 2] : 0;
crcData[4] = x + 3 < rawByteArray.length ? rawByteArray[x + 3] : 0;
// Calculate the CRC
const crc = this._tuyaCRC8(crcData);
// Calculate the CRC
const crc = this._tuyaCRC8(crcData);
// Move data to encodedData array
// CRC
encodedData[encodedDataIndex++] = (crc % 128) | 128;
// Sequence number
encodedData[encodedDataIndex++] = (crcData[0] % 128) | 128;
// Data
encodedData[encodedDataIndex++] = (crcData[1] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[2] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[3] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[4] % 256) | 256;
// Move data to encodedData array
// CRC
encodedData[encodedDataIndex++] = (crc % 128) | 128;
// Sequence number
encodedData[encodedDataIndex++] = (crcData[0] % 128) | 128;
// Data
encodedData[encodedDataIndex++] = (crcData[1] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[2] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[3] % 256) | 256;
encodedData[encodedDataIndex++] = (crcData[4] % 256) | 256;
}
return encodedData;
}
return encodedData;
};
/**
* Un-references UDP instance
* so that a script can cleanly
* exit.
*/
cleanup() {
if (this.udpClient) {
this.udpClient.unref();
}
}
/**
* Un-references UDP instance
* so that a script can cleanly
* exit.
*/
TuyaLink.prototype.cleanup = function () {
if (this.udpClient) {
this.udpClient.unref();
/**
* Returns the length in bytes
* of a string.
* @param {String} str input string
* @returns {Number} length in bytes
* @private
*/
_getLength(str) {
return Buffer.byteLength(str, 'utf8');
}
};
/**
* Returns the length in bytes
* of a string.
* @param {String} str input string
* @returns {Number} length in bytes
* @private
*/
TuyaLink.prototype._getLength = function (str) {
return Buffer.byteLength(str, 'utf8');
};
/**
* Rounds input `x` to the next
* highest multiple of `g`.
* @param {Number} x input number
* @param {Number} g rounding factor
* @returns {Number} rounded result
* @private
*/
_rounder(x, g) {
return Math.ceil(x / g) * g;
}
/**
* Rounds input `x` to the next
* highest multiple of `g`.
* @param {Number} x input number
* @param {Number} g rounding factor
* @returns {Number} rounded result
* @private
*/
TuyaLink.prototype._rounder = function (x, g) {
return Math.ceil(x / g) * g;
};
/**
* Calculates a modified CRC8
* of a given arary of data.
* @param {Array} p input data
* @returns {Number} CRC result
* @private
*/
_tuyaCRC8(p) {
let crc = 0;
let i = 0;
const len = p.length;
/**
* Calculates a modified CRC8
* of a given arary of data.
* @param {Array} p input data
* @returns {Number} CRC result
* @private
*/
TuyaLink.prototype._tuyaCRC8 = function (p) {
let crc = 0;
let i = 0;
const len = p.length;
while (i < len) {
crc = this._calcrc1Byte(crc ^ p[i]);
i++;
}
while (i < len) {
crc = this._calcrc1Byte(crc ^ p[i]);
i++;
return crc;
}
return crc;
};
/**
* Calculates a modified
* CRC8 of one byte.
* @param {Number} abyte one byte as an integer
* @returns {Number} resulting CRC8 byte
* @private
*/
_calcrc1Byte(abyte) {
const crc1Byte = Buffer.alloc(1);
crc1Byte[0] = 0;
/**
* Calculates a modified
* CRC8 of one byte.
* @param {Number} abyte one byte as an integer
* @returns {Number} resulting CRC8 byte
* @private
*/
TuyaLink.prototype._calcrc1Byte = function (abyte) {
const crc1Byte = Buffer.alloc(1);
crc1Byte[0] = 0;
for (let i = 0; i < 8; i++) {
if (((crc1Byte[0] ^ abyte) & 0x01) > 0) {
crc1Byte[0] ^= 0x18;
crc1Byte[0] >>= 1;
crc1Byte[0] |= 0x80;
} else {
crc1Byte[0] >>= 1;
}
for (let i = 0; i < 8; i++) {
if (((crc1Byte[0] ^ abyte) & 0x01) > 0) {
crc1Byte[0] ^= 0x18;
crc1Byte[0] >>= 1;
crc1Byte[0] |= 0x80;
} else {
crc1Byte[0] >>= 1;
abyte >>= 1;
}
abyte >>= 1;
return crc1Byte[0];
}
return crc1Byte[0];
};
/**
* Broadcasts input number as the
* length of a UDP packet.
* @param {Number} len length of packet to broadcast
* @returns {Promise<Undefined>}
* A Promise that resolves when input has been broadcasted
* @private
*/
_broadcastUDP(len) {
// Create and bind UDP socket
if (!this.udpClient) {
this.udpClient = dgram.createSocket({type: 'udp4', recvBufferSize: 0, sendBufferSize: 0});
this.udpClient.on('listening', function () {
this.setBroadcast(true);
});
this.udpClient.bind(63145);
}
/**
* Broadcasts input number as the
* length of a UDP packet.
* @param {Number} len length of packet to broadcast
* @returns {Promise<Undefined>}
* A Promise that resolves when input has been broadcasted
* @private
*/
TuyaLink.prototype._broadcastUDP = function (len) {
// Create and bind UDP socket
if (!this.udpClient) {
this.udpClient = dgram.createSocket('udp4');
this.udpClient.on('listening', function () {
this.setBroadcast(true);
// 0-filled buffer
const message = Buffer.alloc(len);
return new Promise((resolve, reject) => {
this.udpClient.send(message, 0, message.length, 30011, '255.255.255.255', err => {
if (err) {
reject(err);
}
resolve();
});
});
this.udpClient.bind(63145);
}
// 0-filled buffer
const message = Buffer.alloc(len);
return new Promise((resolve, reject) => {
this.udpClient.send(message, 0, message.length, 30011, '255.255.255.255', err => {
if (err) {
reject(err);
}
resolve();
});
});
};
/**
* A helper that provides an easy
* way to iterate over an array with
* an asynchronous function.
* @param {Array} array input array to iterate over
* @param {function(item, index, array)} callback
* function to call for iterations
* @private
*/
TuyaLink.prototype._asyncForEach = async function (array, callback) {
for (let index = 0; index < array.length; index++) {
/**
* A helper that provides an easy
* way to iterate over an array with
* an asynchronous function.
* @param {Array} array input array to iterate over
* @param {function(item, index, array)} callback
* function to call for iterations
* @private
*/
async _asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
// eslint-disable-next-line no-await-in-loop
await callback(array[index], index, array);
await callback(array[index], index, array);
}
}
};
}
module.exports = TuyaLink;
{
"name": "@tuyapi/link",
"version": "0.2.1",
"version": "0.3.0",
"description": "📡 Effortlessly connect devices to WiFi and the cloud",
"main": "index.js",
"files": ["index.js", "lib"],
"scripts": {

@@ -19,8 +20,2 @@ "test": "xo",

"rules": {
"max-len": [
"error",
{
"code": 90
}
],
"indent": [

@@ -43,3 +38,6 @@ "error",

"tuya",
"udp"
"udp",
"link",
"smartlink",
"wifi"
],

@@ -53,9 +51,9 @@ "author": "“Max <codetheweb@icloud.com> (https://maxisom.me)",

"dependencies": {
"@tuyapi/cloud": "^0.2.2",
"debug": "^3.1.0",
"delay": "^2.0.0"
"@tuyapi/openapi": "^0.1.2",
"debug": "^4.1.1",
"delay": "^4.3.0"
},
"devDependencies": {
"xo": "^0.21.1"
"xo": "^0.25.3"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc