From d1cf9da212d57f14c7886d5578ba7fdafcc207da Mon Sep 17 00:00:00 2001 From: Martin M Date: Tue, 23 Jul 2019 21:30:25 -0300 Subject: [PATCH] Release v1.2.2 (#7) * refactor websocket request * return response from websocket request * move request payloads to own file * refactor: removed duplicate device request * code linting * JSDoc methods * version bump --- lib/ewelink-helper.js | 54 +------------ lib/payloads/index.js | 9 +++ lib/payloads/loginPayload.js | 19 +++++ lib/payloads/wssLoginPayload.js | 24 ++++++ lib/payloads/wssUpdatePayload.js | 15 ++++ lib/websocket.js | 34 +++++++++ main.js | 125 ++++++++++++++++++++++--------- package-lock.json | 2 +- package.json | 2 +- 9 files changed, 193 insertions(+), 91 deletions(-) create mode 100644 lib/payloads/index.js create mode 100644 lib/payloads/loginPayload.js create mode 100644 lib/payloads/wssLoginPayload.js create mode 100644 lib/payloads/wssUpdatePayload.js create mode 100644 lib/websocket.js diff --git a/lib/ewelink-helper.js b/lib/ewelink-helper.js index d2c28a7..034ee2c 100644 --- a/lib/ewelink-helper.js +++ b/lib/ewelink-helper.js @@ -1,6 +1,5 @@ const crypto = require('crypto'); const random = require('random'); -const nonce = require('nonce')(); const makeFakeIMEI = () => { const num1 = random.int(1000, 9999); @@ -8,55 +7,6 @@ const makeFakeIMEI = () => { return `DF7425A0-${num1}-${num2}-9F5E-3BC9179E48FB`; }; -const loginPayload = ({ email, password }) => ({ - email, - password, - version: 6, - ts: `${Math.round(new Date().getTime() / 1000)}`, - nonce: `${nonce()}`, - appid: 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq', - imei: makeFakeIMEI(), - os: 'iOS', - model: 'iPhone10,6', - romVersion: '11.1.2', - appVersion: '3.5.3', -}); - -const wssLoginPayload = ({ at, apiKey }) => { - const timeStamp = new Date() / 1000; - const ts = Math.floor(timeStamp); - const sequence = Math.floor(timeStamp * 1000); - const payload = { - action: 'userOnline', - userAgent: 'app', - version: 6, - nonce: `${nonce()}`, - apkVesrion: '1.8', - os: 'ios', - at, - apikey: apiKey, - ts: `${ts}`, - model: 'iPhone10,6', - romVersion: '11.1.2', - sequence, - }; - return JSON.stringify(payload); -}; - -const wssUpdatePayload = ({ apiKey, deviceId, params }) => { - const timeStamp = new Date() / 1000; - const sequence = Math.floor(timeStamp * 1000); - const payload = { - action: 'update', - userAgent: 'app', - apikey: apiKey, - deviceid: `${deviceId}`, - params, - sequence, - }; - return JSON.stringify(payload); -}; - const makeAuthorizationSign = body => crypto .createHmac('sha256', '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM') @@ -153,8 +103,6 @@ const getDeviceChannelCount = deviceUUID => { module.exports = { makeAuthorizationSign, - loginPayload, - wssLoginPayload, - wssUpdatePayload, + makeFakeIMEI, getDeviceChannelCount, }; diff --git a/lib/payloads/index.js b/lib/payloads/index.js new file mode 100644 index 0000000..48baf7e --- /dev/null +++ b/lib/payloads/index.js @@ -0,0 +1,9 @@ +const loginPayload = require('./loginPayload'); +const wssLoginPayload = require('./wssLoginPayload'); +const wssUpdatePayload = require('./wssUpdatePayload'); + +module.exports = { + loginPayload, + wssLoginPayload, + wssUpdatePayload, +}; diff --git a/lib/payloads/loginPayload.js b/lib/payloads/loginPayload.js new file mode 100644 index 0000000..bccfc67 --- /dev/null +++ b/lib/payloads/loginPayload.js @@ -0,0 +1,19 @@ +const nonce = require('nonce')(); + +const { makeFakeIMEI } = require('../ewelink-helper'); + +const loginPayload = ({ email, password }) => ({ + email, + password, + version: 6, + ts: `${Math.round(new Date().getTime() / 1000)}`, + nonce: `${nonce()}`, + appid: 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq', + imei: makeFakeIMEI(), + os: 'iOS', + model: 'iPhone10,6', + romVersion: '11.1.2', + appVersion: '3.5.3', +}); + +module.exports = loginPayload; diff --git a/lib/payloads/wssLoginPayload.js b/lib/payloads/wssLoginPayload.js new file mode 100644 index 0000000..59f98a7 --- /dev/null +++ b/lib/payloads/wssLoginPayload.js @@ -0,0 +1,24 @@ +const nonce = require('nonce')(); + +const wssLoginPayload = ({ at, apiKey }) => { + const timeStamp = new Date() / 1000; + const ts = Math.floor(timeStamp); + const sequence = Math.floor(timeStamp * 1000); + const payload = { + action: 'userOnline', + userAgent: 'app', + version: 6, + nonce: `${nonce()}`, + apkVesrion: '1.8', + os: 'ios', + at, + apikey: apiKey, + ts: `${ts}`, + model: 'iPhone10,6', + romVersion: '11.1.2', + sequence, + }; + return JSON.stringify(payload); +}; + +module.exports = wssLoginPayload; diff --git a/lib/payloads/wssUpdatePayload.js b/lib/payloads/wssUpdatePayload.js new file mode 100644 index 0000000..d3a3849 --- /dev/null +++ b/lib/payloads/wssUpdatePayload.js @@ -0,0 +1,15 @@ +const wssUpdatePayload = ({ apiKey, deviceId, params }) => { + const timeStamp = new Date() / 1000; + const sequence = Math.floor(timeStamp * 1000); + const payload = { + action: 'update', + userAgent: 'app', + apikey: apiKey, + deviceid: `${deviceId}`, + params, + sequence, + }; + return JSON.stringify(payload); +}; + +module.exports = wssUpdatePayload; diff --git a/lib/websocket.js b/lib/websocket.js new file mode 100644 index 0000000..4ec9ba0 --- /dev/null +++ b/lib/websocket.js @@ -0,0 +1,34 @@ +const W3CWebSocket = require('websocket').w3cwebsocket; +const WebSocketAsPromised = require('websocket-as-promised'); +const delay = require('delay'); + +/** + * Open WebSocket connection and send provided payloads + * + * @param url + * @param payloads + * @param delayTime + * + * @returns {Array} + */ +const WebSocketRequest = async (url, payloads, ...{ delayTime = 1000 }) => { + const wsp = new WebSocketAsPromised(url, { + createWebSocket: wss => new W3CWebSocket(wss), + }); + + const responses = []; + wsp.onMessage.addListener(message => responses.push(message)); + + await wsp.open(); + + for (const payload of payloads) { + await wsp.send(payload); + await delay(delayTime); + } + + await wsp.close(); + + return responses; +}; + +module.exports = WebSocketRequest; diff --git a/main.js b/main.js index 62901ca..1398388 100644 --- a/main.js +++ b/main.js @@ -1,18 +1,15 @@ const rp = require('request-promise'); -const W3CWebSocket = require('websocket').w3cwebsocket; -const WebSocketAsPromised = require('websocket-as-promised'); -const delay = require('delay'); +const WebSocketRequest = require('./lib/websocket'); const { _get } = require('./lib/helpers'); const { makeAuthorizationSign, - loginPayload, - wssLoginPayload, - wssUpdatePayload, getDeviceChannelCount, } = require('./lib/ewelink-helper'); +const payloads = require('./lib/payloads'); + class eWeLink { constructor({ region = 'us', email, password, at, apiKey }) { if (!at && (!email && !password)) { @@ -26,14 +23,34 @@ class eWeLink { this.apiKey = apiKey; } + /** + * Generate eWeLink API URL + * + * @returns {string} + */ getApiUrl() { return `https://${this.region}-api.coolkit.cc:8080/api`; } + /** + * Generate eWeLink WebSocket URL + * + * @returns {string} + */ getApiWebSocket() { return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`; } + /** + * Generate http requests helpers + * + * @param method + * @param uri + * @param body + * @param qs + * + * @returns {Promise<{msg: string, error: *}>} + */ async makeRequest({ method = 'GET', uri, body = {}, qs = {} }) { const { at } = this; @@ -58,8 +75,13 @@ class eWeLink { return response; } + /** + * Helper to login into eWeLink API + * + * @returns {Promise<{msg: string, error: *}>} + */ async login() { - const body = loginPayload({ + const body = payloads.loginPayload({ email: this.email, password: this.password, }); @@ -93,28 +115,46 @@ class eWeLink { return response; } + /** + * Get specific device information + * + * @returns {Promise<{msg: string, error: *}>} + */ async getDevices() { - const response = await this.makeRequest({ + return this.makeRequest({ uri: '/user/device', qs: { lang: 'en', getTags: 1 }, }); - return response; } + /** + * Get information about all associated devices to account + * + * @param deviceId + * + * @returns {Promise<{msg: string, error: *}>} + */ async getDevice(deviceId) { - const response = await this.makeRequest({ + return this.makeRequest({ uri: `/user/device/${deviceId}`, qs: { lang: 'en', getTags: 1 }, }); - return response; } + /** + * Get current power state for a specific device + * + * @param deviceId + * @param channel + * + * @returns {Promise<{state: *, status: string}|{msg: string, error: *}>} + */ async getDevicePowerState(deviceId, channel = 1) { const device = await this.getDevice(deviceId); const error = _get(device, 'error', false); - const switchesAmount = getDeviceChannelCount(device.uiid); let state = _get(device, 'params.switch', false); const switches = _get(device, 'params.switches', false); + const switchesAmount = getDeviceChannelCount(device.uiid); if (error || switchesAmount < channel || (!state && !switches)) { if (error && parseInt(error) === 401) { @@ -130,12 +170,21 @@ class eWeLink { return { status: 'ok', state }; } + /** + * Change power state for a specific device + * + * @param deviceId + * @param state + * @param channel + * + * @returns {Promise<{state: *, status: string}|{msg: string, error: *}>} + */ async setDevicePowerState(deviceId, state, channel = 1) { const device = await this.getDevice(deviceId); const error = _get(device, 'error', false); - const switchesAmount = getDeviceChannelCount(device.uiid); const status = _get(device, 'params.switch', false); const switches = _get(device, 'params.switches', false); + const switchesAmount = getDeviceChannelCount(device.uiid); if (error || switchesAmount < channel || (!status && !switches)) { if (error && parseInt(error) === 401) { @@ -144,46 +193,50 @@ class eWeLink { return { error, msg: 'Device does not exist' }; } + let stateToSwitch = state; + + if (state === 'toggle') { + stateToSwitch = status === 'on' ? 'off' : 'on'; + } + const params = {}; if (switches) { params.switches = switches; - params.switches[channel - 1].switch = state; + params.switches[channel - 1].switch = stateToSwitch; } else { - params.switch = state; + params.switch = stateToSwitch; } - const payloadLogin = wssLoginPayload({ at: this.at, apiKey: this.apiKey }); - const payloadUpdate = wssUpdatePayload({ + const payloadLogin = payloads.wssLoginPayload({ + at: this.at, + apiKey: this.apiKey, + }); + + const payloadUpdate = payloads.wssUpdatePayload({ apiKey: this.apiKey, deviceId, params, }); - const wsp = new WebSocketAsPromised(this.getApiWebSocket(), { - createWebSocket: url => new W3CWebSocket(url), - }); - - await wsp.open(); - await wsp.send(payloadLogin); - await delay(1000); - await wsp.send(payloadUpdate); - await wsp.close(); + await WebSocketRequest(this.getApiWebSocket(), [ + payloadLogin, + payloadUpdate, + ]); return { status: 'ok', state }; } + /** + * Toggle power state for a specific device + * + * @param deviceId + * @param channel + * + * @returns {Promise<{state: *, status: string}|{msg: string, error: *}>} + */ async toggleDevice(deviceId, channel = 1) { - const powerState = await this.getDevicePowerState(deviceId, channel); - const state = _get(powerState, 'state', false); - - if (!state) { - return { error: powerState.error, msg: 'Device does not exist' }; - } - - const newState = state === 'on' ? 'off' : 'on'; - - return this.setDevicePowerState(deviceId, newState, channel); + return this.setDevicePowerState(deviceId, 'toggle', channel); } } diff --git a/package-lock.json b/package-lock.json index efbd224..8aaa964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ewelink-api", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2046fb6..8100137 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ewelink-api", - "version": "1.2.1", + "version": "1.2.2", "description": "eWeLink API for Node.js", "author": "Martín M.", "license": "MIT",