From 0a202c218db390872c61a89e5961a059d500aad0 Mon Sep 17 00:00:00 2001 From: atrovato <1839717+atrovato@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:05:49 +0100 Subject: [PATCH] ewelink: adapting server --- .../ewelink/api/ewelink.controller.js | 96 +- server/services/ewelink/index.js | 8 +- .../lib/config/ewelink.createClient.js | 18 + .../lib/config/ewelink.loadConfiguration.js | 53 + .../lib/config/ewelink.saveConfiguration.js | 42 + .../lib/config/ewelink.updateStatus.js | 20 + .../ewelink/lib/{device => config}/status.js | 8 +- server/services/ewelink/lib/device/connect.js | 56 - .../services/ewelink/lib/device/discover.js | 51 +- server/services/ewelink/lib/device/index.js | 76 - server/services/ewelink/lib/device/poll.js | 19 +- .../services/ewelink/lib/device/setValue.js | 35 +- server/services/ewelink/lib/features/index.js | 12 +- .../lib/handlers/ewelink.handleRequest.js | 25 + .../lib/handlers/ewelink.handleResponse.js | 38 + server/services/ewelink/lib/index.js | 58 + .../ewelink/lib/user/ewelink.buildLoginUrl.js | 25 + .../ewelink/lib/user/ewelink.deleteToken.js | 33 + .../ewelink/lib/user/ewelink.exchangeToken.js | 43 + .../services/ewelink/lib/utils/constants.js | 19 +- server/services/ewelink/package-lock.json | 1280 ++++++----------- server/services/ewelink/package.json | 3 +- .../controllers/ewelink.controller.test.js | 20 - server/test/services/ewelink/index.test.js | 26 +- server/test/services/ewelink/lib/constants.js | 19 + .../ewelink/lib/device/connect.test.js | 101 -- .../ewelink/lib/device/discover.test.js | 113 +- .../services/ewelink/lib/device/poll.test.js | 96 +- .../ewelink/lib/device/setValue.test.js | 61 +- .../lib/device/throwErrorIfNeeded.test.js | 95 -- .../ewelink/lib/ewelink-api.mock.test.js | 94 ++ .../ewelink/lib/features/features.test.js | 32 +- .../{mocks => lib/payloads}/Gladys-2ch.json | 0 .../{mocks => lib/payloads}/Gladys-Basic.json | 0 .../payloads}/Gladys-offline.json | 0 .../{mocks => lib/payloads}/Gladys-pow.json | 0 .../{mocks => lib/payloads}/Gladys-th.json | 0 .../payloads}/Gladys-unhandled.json | 0 .../{mocks => lib/payloads}/eweLink-2ch.json | 10 +- .../payloads}/eweLink-basic.json | 6 +- .../payloads}/eweLink-offline.json | 6 +- .../{mocks => lib/payloads}/eweLink-pow.json | 7 +- .../{mocks => lib/payloads}/eweLink-th.json | 9 +- .../payloads}/eweLink-unhandled.json | 6 +- .../services/ewelink/mocks/consts.test.js | 136 -- .../mocks/ewelink-api-empty.mock.test.js | 100 -- .../ewelink/mocks/ewelink-api.mock.test.js | 158 -- 47 files changed, 1265 insertions(+), 1848 deletions(-) create mode 100644 server/services/ewelink/lib/config/ewelink.createClient.js create mode 100644 server/services/ewelink/lib/config/ewelink.loadConfiguration.js create mode 100644 server/services/ewelink/lib/config/ewelink.saveConfiguration.js create mode 100644 server/services/ewelink/lib/config/ewelink.updateStatus.js rename server/services/ewelink/lib/{device => config}/status.js (55%) delete mode 100644 server/services/ewelink/lib/device/connect.js delete mode 100644 server/services/ewelink/lib/device/index.js create mode 100644 server/services/ewelink/lib/handlers/ewelink.handleRequest.js create mode 100644 server/services/ewelink/lib/handlers/ewelink.handleResponse.js create mode 100644 server/services/ewelink/lib/index.js create mode 100644 server/services/ewelink/lib/user/ewelink.buildLoginUrl.js create mode 100644 server/services/ewelink/lib/user/ewelink.deleteToken.js create mode 100644 server/services/ewelink/lib/user/ewelink.exchangeToken.js create mode 100644 server/test/services/ewelink/lib/constants.js delete mode 100644 server/test/services/ewelink/lib/device/connect.test.js delete mode 100644 server/test/services/ewelink/lib/device/throwErrorIfNeeded.test.js create mode 100644 server/test/services/ewelink/lib/ewelink-api.mock.test.js rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-2ch.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-Basic.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-offline.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-pow.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-th.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/Gladys-unhandled.json (100%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-2ch.json (62%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-basic.json (78%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-offline.json (74%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-pow.json (78%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-th.json (67%) rename server/test/services/ewelink/{mocks => lib/payloads}/eweLink-unhandled.json (71%) delete mode 100644 server/test/services/ewelink/mocks/consts.test.js delete mode 100644 server/test/services/ewelink/mocks/ewelink-api-empty.mock.test.js delete mode 100644 server/test/services/ewelink/mocks/ewelink-api.mock.test.js diff --git a/server/services/ewelink/api/ewelink.controller.js b/server/services/ewelink/api/ewelink.controller.js index c81fbeef50..7fa765b946 100644 --- a/server/services/ewelink/api/ewelink.controller.js +++ b/server/services/ewelink/api/ewelink.controller.js @@ -2,17 +2,79 @@ const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); module.exports = function EwelinkController(eweLinkHandler) { /** - * @api {post} /api/v1/service/ewelink/connect Connect to eWeLink cloud account. - * @apiName save + * @api {get} /api/v1/service/ewelink/config Get eWeLink application configuration. + * @apiName getConfig * @apiGroup Ewelink */ - async function connect(req, res) { - await eweLinkHandler.connect(); + function getConfig(req, res) { + const { applicationId, applicationSecret, applicationRegion } = eweLinkHandler.configuration; res.json({ - success: true, + application_id: applicationId, + application_secret: applicationSecret, + application_region: applicationRegion, }); } + /** + * @api {post} /api/v1/service/ewelink/config Save eWeLink application configuration. + * @apiName saveConfig + * @apiGroup Ewelink + */ + async function saveConfig(req, res) { + const { + application_id: applicationId, + application_secret: applicationSecret, + application_region: applicationRegion, + } = req.body; + + await eweLinkHandler.saveConfiguration({ + applicationId, + applicationSecret, + applicationRegion, + }); + + getConfig(req, res); + } + + /** + * @api {get} /api/v1/service/ewelink/loginUrl Gets the eWelink login URL. + * @apiName loginUrl + * @apiGroup Ewelink + */ + async function loginUrl(req, res) { + const { redirect_url: redirectUrl } = req.query; + const ewelinkLoginUrl = await eweLinkHandler.buildLoginUrl({ + redirectUrl, + }); + res.json({ loginUrl: ewelinkLoginUrl }); + } + + /** + * @api {post} /api/v1/service/ewelink/token Generates a user token. + * @apiName exchangeToken + * @apiGroup Ewelink + */ + async function exchangeToken(req, res) { + const { redirect_url: redirectUrl, code, region, state } = req.body; + await eweLinkHandler.exchangeToken({ + redirectUrl, + code, + region, + state, + }); + res.json({ success: true }); + } + + /** + * @api {delete} /api/v1/service/ewelink/token Delete stored tokens and logout user. + * @apiName deleteToken + * @apiGroup Ewelink + */ + async function deleteToken(req, res) { + await eweLinkHandler.deleteToken(); + res.json({ success: true }); + } + /** * @api {get} /api/v1/service/ewelink/status Get eWeLink connection status. * @apiName status @@ -34,10 +96,30 @@ module.exports = function EwelinkController(eweLinkHandler) { } return { - 'post /api/v1/service/ewelink/connect': { + 'get /api/v1/service/ewelink/config': { + authenticated: true, + admin: true, + controller: asyncMiddleware(getConfig), + }, + 'post /api/v1/service/ewelink/config': { + authenticated: true, + admin: true, + controller: asyncMiddleware(saveConfig), + }, + 'get /api/v1/service/ewelink/loginUrl': { + authenticated: true, + admin: true, + controller: asyncMiddleware(loginUrl), + }, + 'post /api/v1/service/ewelink/token': { + authenticated: true, + admin: true, + controller: asyncMiddleware(exchangeToken), + }, + 'delete /api/v1/service/ewelink/token': { authenticated: true, admin: true, - controller: asyncMiddleware(connect), + controller: asyncMiddleware(deleteToken), }, 'get /api/v1/service/ewelink/status': { authenticated: true, diff --git a/server/services/ewelink/index.js b/server/services/ewelink/index.js index 299ca1a1a2..b872cae0e5 100644 --- a/server/services/ewelink/index.js +++ b/server/services/ewelink/index.js @@ -1,11 +1,11 @@ const logger = require('../../utils/logger'); -const EweLinkHandler = require('./lib/device'); +const EweLinkHandler = require('./lib'); const EwelinkController = require('./api/ewelink.controller'); module.exports = function EwelinkService(gladys, serviceId) { // require the eWeLink module - const eWeLinkApi = require('ewelink-api'); - const eWeLinkHandler = new EweLinkHandler(gladys, eWeLinkApi, serviceId); + const { default: eweLinkApi } = require('ewelink-api-next'); + const eWeLinkHandler = new EweLinkHandler(gladys, eweLinkApi, serviceId); /** * @public @@ -15,7 +15,7 @@ module.exports = function EwelinkService(gladys, serviceId) { */ async function start() { logger.info('Starting eWeLink service'); - await eWeLinkHandler.connect(); + await eWeLinkHandler.loadConfiguration(); } /** diff --git a/server/services/ewelink/lib/config/ewelink.createClient.js b/server/services/ewelink/lib/config/ewelink.createClient.js new file mode 100644 index 0000000000..50a78b206b --- /dev/null +++ b/server/services/ewelink/lib/config/ewelink.createClient.js @@ -0,0 +1,18 @@ +/** + * @description Create eWeLink client. + * @example + * this.createClient(); + */ +async function createClient() { + const { applicationId, applicationSecret, applicationRegion } = this.configuration; + + this.ewelinkClient = new this.eweLinkApi.WebAPI({ + appId: applicationId, + appSecret: applicationSecret, + region: applicationRegion, + }); +} + +module.exports = { + createClient, +}; diff --git a/server/services/ewelink/lib/config/ewelink.loadConfiguration.js b/server/services/ewelink/lib/config/ewelink.loadConfiguration.js new file mode 100644 index 0000000000..a9fd04e804 --- /dev/null +++ b/server/services/ewelink/lib/config/ewelink.loadConfiguration.js @@ -0,0 +1,53 @@ +const { ServiceNotConfiguredError } = require('../../../../utils/coreErrors'); +const logger = require('../../../../utils/logger'); +const { CONFIGURATION_KEYS } = require('../utils/constants'); + +/** + * @description Load eWeLink configuration. + * @example + * await this.loadConfiguration(); + */ +async function loadConfiguration() { + logger.info('eWeLink: loading stored configuration...'); + this.updateStatus({ configured: false }); + + try { + const applicationId = await this.gladys.variable.getValue(CONFIGURATION_KEYS.APPLICATION_ID, this.serviceId); + const applicationSecret = await this.gladys.variable.getValue( + CONFIGURATION_KEYS.APPLICATION_SECRET, + this.serviceId, + ); + const applicationRegion = await this.gladys.variable.getValue( + CONFIGURATION_KEYS.APPLICATION_REGION, + this.serviceId, + ); + + if (!applicationId || !applicationSecret || !applicationRegion) { + throw new ServiceNotConfiguredError('eWeLink configuration is not setup'); + } + + this.configuration = { applicationId, applicationSecret, applicationRegion }; + + this.createClient(); + + // Load tokens from databate + const tokens = await this.gladys.variable.getValue(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId); + if (tokens) { + const tokenObject = JSON.parse(tokens); + this.ewelinkClient.at = tokenObject.accessToken; + this.ewelinkClient.rt = tokenObject.refreshToken; + } else { + throw new ServiceNotConfiguredError('eWeLink user is not connected'); + } + + this.updateStatus({ configured: true, connected: !!tokens }); + logger.info('eWeLink: stored configuration well loaded...'); + } catch (e) { + this.updateStatus({ configured: false, connected: false }); + throw e; + } +} + +module.exports = { + loadConfiguration, +}; diff --git a/server/services/ewelink/lib/config/ewelink.saveConfiguration.js b/server/services/ewelink/lib/config/ewelink.saveConfiguration.js new file mode 100644 index 0000000000..12bcef6f84 --- /dev/null +++ b/server/services/ewelink/lib/config/ewelink.saveConfiguration.js @@ -0,0 +1,42 @@ +const { BadParameters } = require('../../../../utils/coreErrors'); +const logger = require('../../../../utils/logger'); +const { CONFIGURATION_KEYS } = require('../utils/constants'); + +/** + * @description Save eWeLink application configuration. + * @param {object} configuration - EWeLink application configuration. + * @param {string} [configuration.applicationId] - Application ID. + * @param {string} [configuration.applicationSecret] - Application secret. + * @param {string} [configuration.applicationRegion] - Application region. + * @example + * await this.saveConfiguration(configuration); + */ +async function saveConfiguration({ applicationId = '', applicationSecret = '', applicationRegion = '' }) { + logger.info('eWeLink: saving new configuration...'); + this.updateStatus({ configured: false }); + + if (applicationId === '' || applicationSecret === '' || applicationRegion === '') { + throw new BadParameters('eWeLink: all application ID/Secret/Region are required.'); + } + + try { + await this.gladys.variable.setValue(CONFIGURATION_KEYS.APPLICATION_ID, applicationId, this.serviceId); + await this.gladys.variable.setValue(CONFIGURATION_KEYS.APPLICATION_SECRET, applicationSecret, this.serviceId); + await this.gladys.variable.setValue(CONFIGURATION_KEYS.APPLICATION_REGION, applicationRegion, this.serviceId); + await this.gladys.variable.destroy(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId); + + this.configuration = { applicationId, applicationSecret, applicationRegion }; + + this.createClient(); + + this.updateStatus({ configured: true, connected: false }); + logger.info('eWeLink: new configuration well saved...'); + } catch (e) { + this.updateStatus({ configured: false }); + throw e; + } +} + +module.exports = { + saveConfiguration, +}; diff --git a/server/services/ewelink/lib/config/ewelink.updateStatus.js b/server/services/ewelink/lib/config/ewelink.updateStatus.js new file mode 100644 index 0000000000..1297015eec --- /dev/null +++ b/server/services/ewelink/lib/config/ewelink.updateStatus.js @@ -0,0 +1,20 @@ +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description Update the service status and emit WebSocket event. + * @param {object} newStatus - New service status. + * @example + * this.updateStatus({ configured: true }); + */ +function updateStatus(newStatus) { + this.status = { ...this.status, ...newStatus }; + + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.EWELINK.STATUS, + payload: this.status, + }); +} + +module.exports = { + updateStatus, +}; diff --git a/server/services/ewelink/lib/device/status.js b/server/services/ewelink/lib/config/status.js similarity index 55% rename from server/services/ewelink/lib/device/status.js rename to server/services/ewelink/lib/config/status.js index 286085afcf..586d6d9705 100644 --- a/server/services/ewelink/lib/device/status.js +++ b/server/services/ewelink/lib/config/status.js @@ -2,14 +2,10 @@ * @description Get eWeLink status. * @returns {object} Current eWeLink network status. * @example - * status(); + * this.status(); */ function status() { - const eweLinkStatus = { - configured: this.configured, - connected: this.connected, - }; - return eweLinkStatus; + return this.status; } module.exports = { diff --git a/server/services/ewelink/lib/device/connect.js b/server/services/ewelink/lib/device/connect.js deleted file mode 100644 index e0bf91419d..0000000000 --- a/server/services/ewelink/lib/device/connect.js +++ /dev/null @@ -1,56 +0,0 @@ -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); -const { ServiceNotConfiguredError } = require('../../../../utils/coreErrors'); -const { EWELINK_EMAIL_KEY, EWELINK_PASSWORD_KEY, EWELINK_REGION_KEY, EWELINK_REGIONS } = require('../utils/constants'); - -/** - * @description Connect to eWeLink cloud account and get access token and api key. - * @example - * connect(); - */ -async function connect() { - this.configured = false; - this.connected = false; - - const email = await this.gladys.variable.getValue(EWELINK_EMAIL_KEY, this.serviceId); - const password = await this.gladys.variable.getValue(EWELINK_PASSWORD_KEY, this.serviceId); - let region = await this.gladys.variable.getValue(EWELINK_REGION_KEY, this.serviceId); - - if (!email || !password) { - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: 'Service is not configured', - }); - throw new ServiceNotConfiguredError('eWeLink: Error, service is not configured'); - } - - if (!Object.values(EWELINK_REGIONS).includes(region)) { - const connection = new this.EweLinkApi({ email, password }); - const response = await connection.getRegion(); - // belt, suspenders ;) - if (response.error && [401, 406].indexOf(response.error) !== -1) { - response.msg = 'Service is not configured'; - } - await this.throwErrorIfNeeded(response, true, true); - - ({ region } = response); - await this.gladys.variable.setValue(EWELINK_REGION_KEY, region, this.serviceId); - } - - this.configured = true; - - const connection = new this.EweLinkApi({ email, password, region }); - const auth = await connection.getCredentials(); - await this.throwErrorIfNeeded(auth, true, true); - - this.connected = true; - this.accessToken = auth.at; - this.apiKey = auth.user.apikey; - - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); -} - -module.exports = { - connect, -}; diff --git a/server/services/ewelink/lib/device/discover.js b/server/services/ewelink/lib/device/discover.js index 3ab8bf19bd..53a30247f7 100644 --- a/server/services/ewelink/lib/device/discover.js +++ b/server/services/ewelink/lib/device/discover.js @@ -1,7 +1,5 @@ -const Promise = require('bluebird'); const logger = require('../../../../utils/logger'); const features = require('../features'); -const { EWELINK_REGION_KEY } = require('../utils/constants'); const { getExternalId } = require('../utils/externalId'); /** @@ -11,43 +9,24 @@ const { getExternalId } = require('../utils/externalId'); * discover(); */ async function discover() { - if (!this.connected) { - await this.connect(); - } + const { thingList = [] } = await this.handleRequest(async () => this.ewelinkClient.device.getAllThingsAllPages()); + logger.info(`eWeLink: ${thingList.length} device(s) found while retrieving from the cloud !`); - const region = await this.gladys.variable.getValue(EWELINK_REGION_KEY, this.serviceId); - const connection = new this.EweLinkApi({ at: this.accessToken, region }); - const discoveredDevices = await connection.getDevices(); - logger.debug(`eWeLink: Get devices: ${JSON.stringify(discoveredDevices)}`); - await this.throwErrorIfNeeded(discoveredDevices, true); + const discoveredDevices = []; - const unknownDevices = []; + thingList.forEach(({ itemData }) => { + const deviceInGladys = this.gladys.stateManager.get('deviceByExternalId', getExternalId(itemData)); + // ...if it is already in Gladys, ignore it... + if (deviceInGladys) { + logger.debug(`eWeLink: device "${itemData.deviceid}" is already in Gladys !`); + } else { + logger.debug(`eWeLink: new device "${itemData.deviceid}" (${itemData.productModel}) discovered`); + const discoveredDevice = features.getDevice(this.serviceId, itemData); + discoveredDevices.push(discoveredDevice); + } + }); - // If devices are found... - logger.info(`eWeLink: ${discoveredDevices.length} device(s) found while retrieving from the cloud !`); - if (discoveredDevices.length) { - // ...check, for each of them, ... - await Promise.map( - discoveredDevices, - async (discoveredDevice) => { - // ...if it is already in Gladys... - const deviceInGladys = this.gladys.stateManager.get('deviceByExternalId', getExternalId(discoveredDevice)); - if (deviceInGladys) { - logger.debug(`eWeLink: Device "${discoveredDevice.deviceid}" is already in Gladys !`); - } else { - const channels = await connection.getDeviceChannelCount(discoveredDevice.deviceid); - logger.debug(`eWeLink: Get device channel count "${discoveredDevice.deviceid}": ${JSON.stringify(channels)}`); - - logger.debug( - `eWeLink: Device "${discoveredDevice.deviceid}" found, uiid: ${discoveredDevice.uiid}, model: "${discoveredDevice.productModel}, switches: ${channels.switchesAmount}`, - ); - unknownDevices.push(features.getDevice(this.serviceId, discoveredDevice, channels.switchesAmount)); - } - }, - { concurrency: 1 }, - ); - } - return unknownDevices; + return discoveredDevices; } module.exports = { diff --git a/server/services/ewelink/lib/device/index.js b/server/services/ewelink/lib/device/index.js deleted file mode 100644 index 56b2523f0f..0000000000 --- a/server/services/ewelink/lib/device/index.js +++ /dev/null @@ -1,76 +0,0 @@ -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); -const { Error403, Error500 } = require('../../../../utils/httpErrors'); -const { EWELINK_EMAIL_KEY, EWELINK_PASSWORD_KEY, EWELINK_REGION_KEY } = require('../utils/constants'); -const { connect } = require('./connect'); -const { discover } = require('./discover'); -const { poll } = require('./poll'); -const { setValue } = require('./setValue'); -const { status } = require('./status'); - -/** - * @description Add ability to control an eWeLink device. - * @param {object} gladys - Gladys instance. - * @param {object} eweLinkApi - EweLink Client. - * @param {string} serviceId - UUID of the service in DB. - * @example - * const EweLinkHandler = new EweLinkHandler(gladys, client, serviceId); - */ -const EweLinkHandler = function EweLinkHandler(gladys, eweLinkApi, serviceId) { - this.gladys = gladys; - this.EweLinkApi = eweLinkApi; - this.serviceId = serviceId; - - this.configured = false; - this.connected = false; - this.accessToken = ''; - this.apiKey = ''; -}; - -/** - * @description Throw error if EweLinkApi call response has error. - * @param {object} response - EweLinkApi call response. - * @param {boolean} emit - True to emit message. - * @param {boolean} config - True to reset config. - * @example - * const EweLinkHandler = new EweLinkHandler(gladys, client, serviceId); - */ -async function throwErrorIfNeeded(response, emit = false, config = false) { - if (response.error) { - if (response.error === 406) { - this.connected = false; - this.accessToken = ''; - this.apiKey = ''; - if (emit) { - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: response.msg, - }); - } - if (config) { - await Promise.all([ - this.gladys.variable.setValue(EWELINK_EMAIL_KEY, '', this.serviceId), - this.gladys.variable.setValue(EWELINK_PASSWORD_KEY, '', this.serviceId), - this.gladys.variable.setValue(EWELINK_REGION_KEY, '', this.serviceId), - ]); - this.configured = false; - } - throw new Error403(`eWeLink: ${response.msg}`); - } - if (emit) { - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: response.msg, - }); - } - throw new Error500(`eWeLink: ${response.msg}`); - } -} - -EweLinkHandler.prototype.connect = connect; -EweLinkHandler.prototype.discover = discover; -EweLinkHandler.prototype.poll = poll; -EweLinkHandler.prototype.setValue = setValue; -EweLinkHandler.prototype.status = status; -EweLinkHandler.prototype.throwErrorIfNeeded = throwErrorIfNeeded; - -module.exports = EweLinkHandler; diff --git a/server/services/ewelink/lib/device/poll.js b/server/services/ewelink/lib/device/poll.js index 9aede1e221..cfcfe7ba63 100644 --- a/server/services/ewelink/lib/device/poll.js +++ b/server/services/ewelink/lib/device/poll.js @@ -1,4 +1,3 @@ -const Promise = require('bluebird'); const { EVENTS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants'); const { NotFoundError } = require('../../../../utils/coreErrors'); const { getDeviceParam, setDeviceParam } = require('../../../../utils/device'); @@ -7,7 +6,7 @@ const { readOnlineValue } = require('../features'); const { pollBinary } = require('../features/binary'); const { pollHumidity } = require('../features/humidity'); const { pollTemperature } = require('../features/temperature'); -const { DEVICE_FIRMWARE, EWELINK_REGION_KEY, DEVICE_ONLINE } = require('../utils/constants'); +const { DEVICE_FIRMWARE, DEVICE_ONLINE } = require('../utils/constants'); const { parseExternalId } = require('../utils/externalId'); /** @@ -19,16 +18,12 @@ const { parseExternalId } = require('../utils/externalId'); * poll(device); */ async function poll(device) { - if (!this.connected) { - await this.connect(); - } - - const region = await this.gladys.variable.getValue(EWELINK_REGION_KEY, this.serviceId); const { deviceId } = parseExternalId(device.external_id); - const connection = new this.EweLinkApi({ at: this.accessToken, region }); - const eWeLinkDevice = await connection.getDevice(deviceId); - logger.debug(`eWeLink: eWeLinkDevice: ${JSON.stringify(eWeLinkDevice)}`); - await this.throwErrorIfNeeded(eWeLinkDevice); + const { thingList } = await this.handleRequest(async () => + this.ewelinkClient.device.getThings({ thingList: [{ id: deviceId }] }), + ); + const [{ itemData: eWeLinkDevice }] = thingList; + logger.debug('eWeLink: load device: %j', eWeLinkDevice); const onlineParam = getDeviceParam(device, DEVICE_ONLINE); const currentOnline = readOnlineValue(eWeLinkDevice.online); @@ -42,7 +37,7 @@ async function poll(device) { throw new NotFoundError('eWeLink: Error, device is not currently online'); } - await Promise.mapSeries(device.features || [], (feature) => { + (device.features || []).forEach((feature) => { let state; switch (feature.category) { case DEVICE_FEATURE_CATEGORIES.SWITCH: // Binary diff --git a/server/services/ewelink/lib/device/setValue.js b/server/services/ewelink/lib/device/setValue.js index d823ee2efd..4fc30436ae 100644 --- a/server/services/ewelink/lib/device/setValue.js +++ b/server/services/ewelink/lib/device/setValue.js @@ -1,8 +1,6 @@ const { DEVICE_FEATURE_TYPES } = require('../../../../utils/constants'); -const { NotFoundError } = require('../../../../utils/coreErrors'); const logger = require('../../../../utils/logger'); const { writeBinaryValue } = require('../features/binary'); -const { EWELINK_REGION_KEY } = require('../utils/constants'); const { parseExternalId } = require('../utils/externalId'); /** @@ -14,27 +12,26 @@ const { parseExternalId } = require('../utils/externalId'); * setValue(device, deviceFeature); */ async function setValue(device, deviceFeature, value) { - if (!this.connected) { - await this.connect(); - } - - const region = await this.gladys.variable.getValue(EWELINK_REGION_KEY, this.serviceId); - const connection = new this.EweLinkApi({ at: this.accessToken, apiKey: this.apiKey, region }); - const { deviceId, channel } = parseExternalId(deviceFeature.external_id); - const eweLinkDevice = await connection.getDevice(deviceId); - await this.throwErrorIfNeeded(eweLinkDevice); + switch (deviceFeature.type) { + case DEVICE_FEATURE_TYPES.SWITCH.BINARY: { + const params = {}; + // Count number of binary features to determine if "switch" or "switches" param need to be changed + const nbBinaryFeatures = device.features.reduce( + (acc, currentFeature) => (currentFeature.type === DEVICE_FEATURE_TYPES.SWITCH.BINARY ? acc + 1 : acc), + 0, + ); - if (!eweLinkDevice.online) { - throw new NotFoundError('eWeLink: Error, device is not currently online'); - } + const binaryValue = writeBinaryValue(value); + if (nbBinaryFeatures > 1) { + params.switches = [{ switch: binaryValue, outlet: channel }]; + } else { + params.switch = binaryValue; + } - let response; - switch (deviceFeature.type) { - case DEVICE_FEATURE_TYPES.SWITCH.BINARY: - response = await connection.setDevicePowerState(deviceId, writeBinaryValue(value), channel); - await this.throwErrorIfNeeded(response); + await this.handleRequest(async () => this.ewelinkClient.device.setThingStatus(1, deviceId, params)); break; + } default: logger.warn(`eWeLink: Warning, feature type "${deviceFeature.type}" not handled yet!`); break; diff --git a/server/services/ewelink/lib/features/index.js b/server/services/ewelink/lib/features/index.js index a89d654a3b..60d5676945 100644 --- a/server/services/ewelink/lib/features/index.js +++ b/server/services/ewelink/lib/features/index.js @@ -52,14 +52,14 @@ function readOnlineValue(online) { * @description Create an eWeLink device for Gladys. * @param {string} serviceId - The UUID of the service. * @param {object} device - The eWeLink device. - * @param {number} channel - The channel of the device to control. * @returns {object} Return Gladys device. * @example * getDevice(serviceId, device, channel); */ -function getDevice(serviceId, device, channel = 0) { +function getDevice(serviceId, device) { const name = getDeviceName(device); const externalId = getExternalId(device); + const { params = {} } = device; const createdDevice = { name, @@ -77,7 +77,7 @@ function getDevice(serviceId, device, channel = 0) { }, { name: DEVICE_FIRMWARE, - value: (device.params && device.params.fwVersion) || '?.?.?', + value: params.fwVersion || '?.?.?', }, { name: DEVICE_ONLINE, @@ -86,9 +86,11 @@ function getDevice(serviceId, device, channel = 0) { ], }; - if (device.online) { + const deviceUiid = (device.extra || {}).uiid; + if (device.online && deviceUiid) { + const channel = params.switch ? 1 : (params.switches || []).length; Object.keys(AVAILABLE_FEATURE_MODELS).forEach((type) => { - if (AVAILABLE_FEATURE_MODELS[type].uiid.includes(device.uiid)) { + if (AVAILABLE_FEATURE_MODELS[type].uiid.includes(deviceUiid)) { let ch = 1; do { const featureExternalId = (type === 'binary' ? [externalId, type, ch] : [externalId, type]).join(':'); diff --git a/server/services/ewelink/lib/handlers/ewelink.handleRequest.js b/server/services/ewelink/lib/handlers/ewelink.handleRequest.js new file mode 100644 index 0000000000..5a0c1eb867 --- /dev/null +++ b/server/services/ewelink/lib/handlers/ewelink.handleRequest.js @@ -0,0 +1,25 @@ +const { NB_MAX_RETRY_EXPIRED } = require('../utils/constants'); + +/** + * @description Provides a single way to manage WS requests with retries and refresh token. + * @param {Function} request - Client request. + * @param {number} nbRetry - Number of retry. + * @returns {Promise} The WS call response. + * @example + * const data = await this.handleRequest(() => client.getDevices()); + */ +async function handleRequest(request, nbRetry = 0) { + const response = await request(); + + // 402 - Access token expired, so refresh it and retry. + // see https://coolkit-technologies.github.io/eWeLink-API/#/en/APICenterV2?id=error-codes + if (response.error === 402 && nbRetry < NB_MAX_RETRY_EXPIRED) { + await this.ewelinkClient.user.refreshToken(); + // Retry request + return this.handleRequest(request, nbRetry + 1); + } + + return this.handleResponse(response); +} + +module.exports = { handleRequest }; diff --git a/server/services/ewelink/lib/handlers/ewelink.handleResponse.js b/server/services/ewelink/lib/handlers/ewelink.handleResponse.js new file mode 100644 index 0000000000..3ab0aa77b5 --- /dev/null +++ b/server/services/ewelink/lib/handlers/ewelink.handleResponse.js @@ -0,0 +1,38 @@ +const { BadParameters, NotFoundError, ServiceNotConfiguredError } = require('../../../../utils/coreErrors'); +const logger = require('../../../../utils/logger'); +const { CONFIGURATION_KEYS } = require('../utils/constants'); + +/** + * @description Provides a single way to manage WS responses. + * @param {object} response - WebService response. + * @returns {Promise} The WS call response. + * @example + * const data = this.handleResponse(res, (data) => console.log); + */ +async function handleResponse(response) { + const { error, msg, data } = response; + logger.debug(`eWeLink response: %j`, response); + + if (error) { + // see https://coolkit-technologies.github.io/eWeLink-API/#/en/APICenterV2?id=error-codes + logger.error(`eWeLink: error with API - ${msg}`); + switch (error) { + case 401: + case 402: + await this.gladys.variable.destroy(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId); + this.updateStatus({ connected: false }); + throw new ServiceNotConfiguredError(msg); + case 400: + throw new BadParameters(msg); + case 405: + case 4002: + throw new NotFoundError(msg); + default: + throw new Error(msg); + } + } + + return data; +} + +module.exports = { handleResponse }; diff --git a/server/services/ewelink/lib/index.js b/server/services/ewelink/lib/index.js new file mode 100644 index 0000000000..75ea1aba1b --- /dev/null +++ b/server/services/ewelink/lib/index.js @@ -0,0 +1,58 @@ +const { discover } = require('./device/discover'); +const { poll } = require('./device/poll'); +const { setValue } = require('./device/setValue'); + +const { updateStatus } = require('./config/ewelink.updateStatus'); + +const { status } = require('./config/status'); +const { saveConfiguration } = require('./config/ewelink.saveConfiguration'); +const { loadConfiguration } = require('./config/ewelink.loadConfiguration'); +const { createClient } = require('./config/ewelink.createClient'); + +const { buildLoginUrl } = require('./user/ewelink.buildLoginUrl'); +const { exchangeToken } = require('./user/ewelink.exchangeToken'); +const { deleteToken } = require('./user/ewelink.deleteToken'); + +const { handleRequest } = require('./handlers/ewelink.handleRequest'); +const { handleResponse } = require('./handlers/ewelink.handleResponse'); + +/** + * @description Add ability to control an eWeLink device. + * @param {object} gladys - Gladys instance. + * @param {object} eweLinkApi - Next eweLink Client. + * @param {string} serviceId - UUID of the service in DB. + * @example + * const EweLinkHandler = new EweLinkHandler(gladys, client, serviceId); + */ +const EweLinkHandler = function EweLinkHandler(gladys, eweLinkApi, serviceId) { + this.gladys = gladys; + this.eweLinkApi = eweLinkApi; + this.serviceId = serviceId; + + this.ewelinkClient = null; + this.configuration = {}; + this.status = { + configured: false, + connected: false, + }; +}; + +EweLinkHandler.prototype.updateStatus = updateStatus; + +EweLinkHandler.prototype.saveConfiguration = saveConfiguration; +EweLinkHandler.prototype.loadConfiguration = loadConfiguration; +EweLinkHandler.prototype.createClient = createClient; + +EweLinkHandler.prototype.buildLoginUrl = buildLoginUrl; +EweLinkHandler.prototype.exchangeToken = exchangeToken; +EweLinkHandler.prototype.deleteToken = deleteToken; + +EweLinkHandler.prototype.handleRequest = handleRequest; +EweLinkHandler.prototype.handleResponse = handleResponse; + +EweLinkHandler.prototype.discover = discover; +EweLinkHandler.prototype.poll = poll; +EweLinkHandler.prototype.setValue = setValue; +EweLinkHandler.prototype.status = status; + +module.exports = EweLinkHandler; diff --git a/server/services/ewelink/lib/user/ewelink.buildLoginUrl.js b/server/services/ewelink/lib/user/ewelink.buildLoginUrl.js new file mode 100644 index 0000000000..93cbf803c1 --- /dev/null +++ b/server/services/ewelink/lib/user/ewelink.buildLoginUrl.js @@ -0,0 +1,25 @@ +const logger = require('../../../../utils/logger'); +const { generate } = require('../../../../utils/password'); + +/** + * @description Generates eWeLink login URL. + * @param {object} params - EWeLink login configuration. + * @param {string} [params.redirectUrl] - Login redirect URL. + * @returns {string} Login URL. + * @example + * const loginURL = this.buildLoginUrl({ redirect_url: 'http://gladys' }); + */ +function buildLoginUrl({ redirectUrl }) { + logger.info('eWeLink: create new login URL'); + const state = generate(10, { number: true, lowercase: true, uppercase: true }); + this.loginState = state; + return this.ewelinkClient.oauth.createLoginUrl({ + redirectUrl, + grantType: 'authorization_code', + state, + }); +} + +module.exports = { + buildLoginUrl, +}; diff --git a/server/services/ewelink/lib/user/ewelink.deleteToken.js b/server/services/ewelink/lib/user/ewelink.deleteToken.js new file mode 100644 index 0000000000..747c712ba5 --- /dev/null +++ b/server/services/ewelink/lib/user/ewelink.deleteToken.js @@ -0,0 +1,33 @@ +const logger = require('../../../../utils/logger'); +const { CONFIGURATION_KEYS } = require('../utils/constants'); + +/** + * @description Delete tokens and logout user. + * @example + * await this.deleteToken(); + */ +async function deleteToken() { + logger.info('eWeLink: disconnecting user...'); + // see https://coolkit-technologies.github.io/eWeLink-API/#/en/OAuth2.0?id=unbind-third-party-accounts + const logoutCall = async () => + this.ewelinkClient.request.delete('/v2/user/oauth/token', { + headers: { + 'X-CK-Appid': this.ewelinkClient.appId || '', + Authorization: `Bearer ${this.ewelinkClient.at}`, + }, + }); + + await this.handleRequest(logoutCall); + + // Clear tokens + await this.gladys.variable.destroy(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId); + this.ewelinkClient.at = null; + this.ewelinkClient.rt = null; + + this.updateStatus({ connected: false }); + logger.info('eWeLink: user well disconnected'); +} + +module.exports = { + deleteToken, +}; diff --git a/server/services/ewelink/lib/user/ewelink.exchangeToken.js b/server/services/ewelink/lib/user/ewelink.exchangeToken.js new file mode 100644 index 0000000000..ae03a0ddaf --- /dev/null +++ b/server/services/ewelink/lib/user/ewelink.exchangeToken.js @@ -0,0 +1,43 @@ +const { BadParameters } = require('../../../../utils/coreErrors'); +const logger = require('../../../../utils/logger'); + +const { CONFIGURATION_KEYS } = require('../utils/constants'); + +/** + * @description Generates a token for the connected user. + * @param {object} params - EWeLink login configuration. + * @param {string} [params.redirectUrl] - Login redirect URL. + * @param {string} [params.code] - OAuth authorization code. + * @param {string} [params.region] - User region. + * @param {string} [params.state] - Login state. + * @example + * await this.exchangeToken({ redirectUrl, code, region, state }); + */ +async function exchangeToken({ redirectUrl, code, region, state }) { + logger.info('eWeLink: exchanging user authorization code...'); + + if (state !== this.loginState) { + throw new BadParameters('eWeLink login state is invalid.'); + } + + const tokenResponse = await this.ewelinkClient.oauth.getToken({ + region, + redirectUrl, + code, + }); + + const data = await this.handleResponse(tokenResponse); + + this.ewelinkClient.at = data.accessToken; + this.ewelinkClient.rt = data.refreshToken; + + // Store tokens into databate + await this.gladys.variable.setValue(CONFIGURATION_KEYS.USER_TOKENS, JSON.stringify(data), this.serviceId); + + this.updateStatus({ connected: true }); + logger.info('eWeLink: user well connected...'); +} + +module.exports = { + exchangeToken, +}; diff --git a/server/services/ewelink/lib/utils/constants.js b/server/services/ewelink/lib/utils/constants.js index aa754d5a51..54b1421469 100644 --- a/server/services/ewelink/lib/utils/constants.js +++ b/server/services/ewelink/lib/utils/constants.js @@ -1,9 +1,8 @@ -const EWELINK_EMAIL_KEY = 'EWELINK_EMAIL'; -const EWELINK_PASSWORD_KEY = 'EWELINK_PASSWORD'; -const EWELINK_REGION_KEY = 'EWELINK_REGION'; -const EWELINK_REGIONS = { - EU: 'eu', - US: 'us', +const CONFIGURATION_KEYS = { + APPLICATION_ID: 'APPLICATION_ID', + APPLICATION_SECRET: 'APPLICATION_SECRET', + APPLICATION_REGION: 'APPLICATION_REGION', + USER_TOKENS: 'USER_TOKENS', }; const DEVICE_SERVICE_ID = 'ewelink'; @@ -13,14 +12,14 @@ const DEVICE_IP_ADDRESS = 'IP_ADDRESS'; const DEVICE_FIRMWARE = 'FIRMWARE'; const DEVICE_ONLINE = 'ONLINE'; +const NB_MAX_RETRY_EXPIRED = 1; + module.exports = { - EWELINK_EMAIL_KEY, - EWELINK_PASSWORD_KEY, - EWELINK_REGION_KEY, - EWELINK_REGIONS, + CONFIGURATION_KEYS, DEVICE_SERVICE_ID, DEVICE_EXTERNAL_ID_BASE, DEVICE_IP_ADDRESS, DEVICE_FIRMWARE, DEVICE_ONLINE, + NB_MAX_RETRY_EXPIRED, }; diff --git a/server/services/ewelink/package-lock.json b/server/services/ewelink/package-lock.json index 965b82cfc7..c0904dba02 100644 --- a/server/services/ewelink/package-lock.json +++ b/server/services/ewelink/package-lock.json @@ -18,530 +18,352 @@ "win32" ], "dependencies": { - "bluebird": "^3.7.2", - "ewelink-api": "^3.1.1" + "ewelink-api-next": "^1.0.3" } }, - "node_modules/arpping": { - "version": "0.3.1", - "resolved": "git+ssh://git@github.com/skydiver/arpping.git#ae65410343bdcbddb64b37ac9f674c65af1fe92c", - "integrity": "sha512-Sa474Qr/j4z0nDgJ5xfVwKnKpe8fOXW9CRafTP1gm7oYj+3drUDe5CUJpSYw6IC301GMJ9gDtlal9tNQZ2XYLw==", - "license": "MIT", + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "dependencies": { - "child_process": "^1.0.2", - "os": "^0.1.1" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "node_modules/bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "node_modules/bufferutil": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.2.tgz", "integrity": "sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.2.0" } }, - "node_modules/call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "delayed-stream": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.8" } }, - "node_modules/child_process": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=" - }, - "node_modules/chnl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/chnl/-/chnl-1.2.0.tgz", - "integrity": "sha512-g5gJb59edwCliFbX2j7G6sBfY4sX9YLy211yctONI2GRaiX0f2zIbKWmBm+sPqFNEpM7Ljzm7IJX/xrjiEbPrw==" - }, - "node_modules/core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, "node_modules/crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dependencies": { - "object-keys": "^1.0.12" - }, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "engines": { - "node": ">= 0.4" + "node": ">=4.0" } }, - "node_modules/delay": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.4.0.tgz", - "integrity": "sha512-txgOrJu3OdtOfTiEOT2e76dJVfG/1dz2NZ4F0Pyt4UGZJryssMRp5vdM5wQoLwSOBNdrJv3F9PAhp/heqd7vrA==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" }, - "node_modules/es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.4.0" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "@leichtgewicht/ip-codec": "^2.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "node": ">=6" } }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "node_modules/ewelink-api-next": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ewelink-api-next/-/ewelink-api-next-1.0.3.tgz", + "integrity": "sha512-TeBbo+CU81sk5Zeo69pwVuct3kg6OLrl0VtsMvYfGxDeFQnU5tQRrfuQ/IuWYSdQUTUbCIpHUiIaDDaExK/nCA==", "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "axios": "1.2.1", + "bonjour-service": "1.0.14", + "crypto-js": "^4.1.1", + "dayjs": "1.11.7", + "log4js": "6.7.1", + "node-localstorage": "^2.2.1", + "ws": "8.11.0" } }, - "node_modules/es6-symbol": { + "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/ewelink-api": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ewelink-api/-/ewelink-api-3.1.1.tgz", - "integrity": "sha512-MJyVrEoAKFVbSJhlYeBqb6B5EFVIkMZG4L95CeF4YmjUO5/wr2mtfZkZfBwBwrJKUHqAI4qKJiTZeSXEQlzwgA==", + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { - "arpping": "github:skydiver/arpping", - "crypto-js": "^4.0.0", - "delay": "^4.4.0", - "node-fetch": "^2.6.1", - "random": "^2.2.0", - "websocket": "^1.0.32", - "websocket-as-promised": "^1.0.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dependencies": { - "type": "^2.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" } }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } }, - "node_modules/get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/log4js": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz", + "integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==", "dependencies": { - "function-bind": "^1.1.1" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" }, "engines": { - "node": ">= 0.4.0" + "node": ">=8.0" } }, - "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "engines": { - "node": ">= 0.4" + "node_modules/log4js/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/log4js/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "has-symbols": "^1.0.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dependencies": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" + "bin": { + "multicast-dns": "cli.js" } }, "node_modules/node-gyp-build": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "optional": true, + "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, - "node_modules/object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "node_modules/node-localstorage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", + "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" + "write-file-atomic": "^1.1.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, - "node_modules/ow": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.4.0.tgz", - "integrity": "sha512-kJNzxUgVd6EF5LoGs+s2/etJPwjfRDLXPTCfEgV8At77sRrV+PSFA8lcoW2HF15Qd455mIR2Stee/2MzDiFBDA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ow-lite": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ow-lite/-/ow-lite-0.0.2.tgz", - "integrity": "sha1-359QDmdAtlkKHpqWVzDUmo61l9E=", - "engines": { - "node": ">=6" + "node": ">=0.12" } }, - "node_modules/promise-controller": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/promise-controller/-/promise-controller-1.0.0.tgz", - "integrity": "sha512-goA0zA9L91tuQbUmiMinSYqlyUtEgg4fxJcjYnLYOQnrktb4o4UqciXDNXiRUPiDBPACmsr1k8jDW4r7UDq9Qw==", + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/promise.prototype.finally": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz", - "integrity": "sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA==", + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.0", - "function-bind": "^1.1.1" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.0" } }, - "node_modules/random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/random/-/random-2.2.0.tgz", - "integrity": "sha512-4HBR4Xye4jJ41QBi6RfIaO1yKQpxVUZafQtdE6NvvjzirNlwWgsk3tkGLTbQtWUarF4ofZsUVEmWqB1TDQlkwA==", + "node_modules/streamroller/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "babel-runtime": "^6.26.0", - "ow": "^0.4.0", - "ow-lite": "^0.0.2", - "seedrandom": "^3.0.5" + "ms": "2.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", - "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/string.prototype.trimend/node_modules/es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/streamroller/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", - "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, - "node_modules/string.prototype.trimstart/node_modules/es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" + "node": ">= 4.0.0" } }, "node_modules/utf-8-validate": { @@ -549,489 +371,337 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz", "integrity": "sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.2.0" } }, - "node_modules/websocket": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.32.tgz", - "integrity": "sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==", + "node_modules/write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" } }, - "node_modules/websocket-as-promised": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/websocket-as-promised/-/websocket-as-promised-1.1.0.tgz", - "integrity": "sha512-agq8bPsPFKBWinKQkoXwY7LoBYe+2fQ7Gnuxx964+BTIiyAdL130FnB60bXuVQdUCdaS17R/MyRaaO4WIqtl4Q==", - "dependencies": { - "chnl": "^1.2.0", - "promise-controller": "^1.0.0", - "promise.prototype.finally": "^3.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "engines": { - "node": ">=0.10.32" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } } }, "dependencies": { - "arpping": { - "version": "git+ssh://git@github.com/skydiver/arpping.git#ae65410343bdcbddb64b37ac9f674c65af1fe92c", - "integrity": "sha512-Sa474Qr/j4z0nDgJ5xfVwKnKpe8fOXW9CRafTP1gm7oYj+3drUDe5CUJpSYw6IC301GMJ9gDtlal9tNQZ2XYLw==", - "from": "arpping@github:skydiver/arpping", + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "requires": { - "child_process": "^1.0.2", - "os": "^0.1.1" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "bufferutil": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.2.tgz", "integrity": "sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==", + "optional": true, + "peer": true, "requires": { "node-gyp-build": "^4.2.0" } }, - "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "delayed-stream": "~1.0.0" } }, - "child_process": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=" + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" }, - "chnl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/chnl/-/chnl-1.2.0.tgz", - "integrity": "sha512-g5gJb59edwCliFbX2j7G6sBfY4sX9YLy211yctONI2GRaiX0f2zIbKWmBm+sPqFNEpM7Ljzm7IJX/xrjiEbPrw==" + "date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==" }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" }, - "crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "requires": { - "ms": "2.0.0" + "@leichtgewicht/ip-codec": "^2.0.1" } }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "ewelink-api-next": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ewelink-api-next/-/ewelink-api-next-1.0.3.tgz", + "integrity": "sha512-TeBbo+CU81sk5Zeo69pwVuct3kg6OLrl0VtsMvYfGxDeFQnU5tQRrfuQ/IuWYSdQUTUbCIpHUiIaDDaExK/nCA==", "requires": { - "object-keys": "^1.0.12" + "axios": "1.2.1", + "bonjour-service": "1.0.14", + "crypto-js": "^4.1.1", + "dayjs": "1.11.7", + "log4js": "6.7.1", + "node-localstorage": "^2.2.1", + "ws": "8.11.0" } }, - "delay": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.4.0.tgz", - "integrity": "sha512-txgOrJu3OdtOfTiEOT2e76dJVfG/1dz2NZ4F0Pyt4UGZJryssMRp5vdM5wQoLwSOBNdrJv3F9PAhp/heqd7vrA==" + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "ewelink-api": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ewelink-api/-/ewelink-api-3.1.1.tgz", - "integrity": "sha512-MJyVrEoAKFVbSJhlYeBqb6B5EFVIkMZG4L95CeF4YmjUO5/wr2mtfZkZfBwBwrJKUHqAI4qKJiTZeSXEQlzwgA==", + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { - "arpping": "github:skydiver/arpping", - "crypto-js": "^4.0.0", - "delay": "^4.4.0", - "node-fetch": "^2.6.1", - "random": "^2.2.0", - "websocket": "^1.0.32", - "websocket-as-promised": "^1.0.1" + "graceful-fs": "^4.1.6" } }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "log4js": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz", + "integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==", "requires": { - "type": "^2.0.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" }, "dependencies": { - "type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "has-symbols": "^1.0.1" + "mime-db": "1.52.0" } }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "requires": { - "has-symbols": "^1.0.1" + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, "node-gyp-build": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, - "ow": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.4.0.tgz", - "integrity": "sha512-kJNzxUgVd6EF5LoGs+s2/etJPwjfRDLXPTCfEgV8At77sRrV+PSFA8lcoW2HF15Qd455mIR2Stee/2MzDiFBDA==" - }, - "ow-lite": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ow-lite/-/ow-lite-0.0.2.tgz", - "integrity": "sha1-359QDmdAtlkKHpqWVzDUmo61l9E=" - }, - "promise-controller": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/promise-controller/-/promise-controller-1.0.0.tgz", - "integrity": "sha512-goA0zA9L91tuQbUmiMinSYqlyUtEgg4fxJcjYnLYOQnrktb4o4UqciXDNXiRUPiDBPACmsr1k8jDW4r7UDq9Qw==" - }, - "promise.prototype.finally": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz", - "integrity": "sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.0", - "function-bind": "^1.1.1" - } + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "optional": true, + "peer": true }, - "random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/random/-/random-2.2.0.tgz", - "integrity": "sha512-4HBR4Xye4jJ41QBi6RfIaO1yKQpxVUZafQtdE6NvvjzirNlwWgsk3tkGLTbQtWUarF4ofZsUVEmWqB1TDQlkwA==", + "node-localstorage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", + "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", "requires": { - "babel-runtime": "^6.26.0", - "ow": "^0.4.0", - "ow-lite": "^0.0.2", - "seedrandom": "^3.0.5" + "write-file-atomic": "^1.1.4" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" }, - "string.prototype.trimend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", - "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==" }, - "string.prototype.trimstart": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", - "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" }, "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "utf-8-validate": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz", "integrity": "sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==", + "optional": true, + "peer": true, "requires": { "node-gyp-build": "^4.2.0" } }, - "websocket": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.32.tgz", - "integrity": "sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==", - "requires": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - } - }, - "websocket-as-promised": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/websocket-as-promised/-/websocket-as-promised-1.1.0.tgz", - "integrity": "sha512-agq8bPsPFKBWinKQkoXwY7LoBYe+2fQ7Gnuxx964+BTIiyAdL130FnB60bXuVQdUCdaS17R/MyRaaO4WIqtl4Q==", + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", "requires": { - "chnl": "^1.2.0", - "promise-controller": "^1.0.0", - "promise.prototype.finally": "^3.1.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" } }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} } } } diff --git a/server/services/ewelink/package.json b/server/services/ewelink/package.json index a9b5695817..213f611d71 100644 --- a/server/services/ewelink/package.json +++ b/server/services/ewelink/package.json @@ -13,7 +13,6 @@ "arm64" ], "dependencies": { - "bluebird": "^3.7.2", - "ewelink-api": "^3.1.1" + "ewelink-api-next": "^1.0.3" } } diff --git a/server/test/services/ewelink/controllers/ewelink.controller.test.js b/server/test/services/ewelink/controllers/ewelink.controller.test.js index d756f92c31..9b7de0328a 100644 --- a/server/test/services/ewelink/controllers/ewelink.controller.test.js +++ b/server/test/services/ewelink/controllers/ewelink.controller.test.js @@ -11,26 +11,6 @@ const ewelinkHandler = { discover: fake.returns(devices), }; -describe('EweLinkController POST /api/v1/service/ewelink/connect', () => { - let controller; - - beforeEach(() => { - controller = EweLinkController(ewelinkHandler); - sinon.reset(); - }); - - it('should connect', async () => { - const req = {}; - const res = { - json: fake.returns(null), - }; - - await controller['post /api/v1/service/ewelink/connect'].controller(req, res); - assert.calledOnce(ewelinkHandler.connect); - assert.calledOnce(res.json); - }); -}); - describe('EweLinkController GET /api/v1/service/ewelink/status', () => { let controller; diff --git a/server/test/services/ewelink/index.test.js b/server/test/services/ewelink/index.test.js index cb5cb9b04b..6ed267c500 100644 --- a/server/test/services/ewelink/index.test.js +++ b/server/test/services/ewelink/index.test.js @@ -1,19 +1,27 @@ const { expect } = require('chai'); +const sinon = require('sinon'); const proxyquire = require('proxyquire').noCallThru(); -const { event, variableOk } = require('./mocks/consts.test'); -const EwelinkApi = require('./mocks/ewelink-api.mock.test'); +const EwelinkApi = require('./lib/ewelink-api.mock.test'); +const { SERVICE_ID } = require('./lib/constants'); + +const { fake, assert } = sinon; + +const EweLinkHandlerMock = sinon.stub(); +EweLinkHandlerMock.prototype.loadConfiguration = fake.returns(null); const EweLinkService = proxyquire('../../../services/ewelink/index', { - 'ewelink-api': EwelinkApi, + './lib': EweLinkHandlerMock, + 'ewelink-api-next': EwelinkApi, }); -const gladys = { - event, - variable: variableOk, -}; +const gladys = {}; describe('EweLinkService', () => { - const eweLinkService = EweLinkService(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279'); + const eweLinkService = EweLinkService(gladys, SERVICE_ID); + + afterEach(() => { + sinon.reset(); + }); it('should have controllers', () => { expect(eweLinkService) @@ -22,8 +30,10 @@ describe('EweLinkService', () => { }); it('should start service', async () => { await eweLinkService.start(); + assert.calledOnceWithExactly(eweLinkService.device.loadConfiguration); }); it('should stop service', async () => { await eweLinkService.stop(); + assert.notCalled(eweLinkService.device.loadConfiguration); }); }); diff --git a/server/test/services/ewelink/lib/constants.js b/server/test/services/ewelink/lib/constants.js new file mode 100644 index 0000000000..60762c2ee6 --- /dev/null +++ b/server/test/services/ewelink/lib/constants.js @@ -0,0 +1,19 @@ +const SERVICE_ID = 'a810b8db-6d04-4697-bed3-c4b72c996279'; + +const EWELINK_APP_ID = 'ewelink-app-id'; +const EWELINK_APP_SECRET = 'ewelink-app-secret'; +const EWELINK_APP_REGION = 'ewelink-app-region'; + +const EWELINK_VALID_ACCESS_TOKEN = 'ewelink-valid-access-token'; +const EWELINK_DENIED_ACCESS_TOKEN = 'ewelink-invalid-access-token'; +const EWELINK_INVALID_ACCESS_TOKEN = 'ewelink-not-configured-access-token'; + +module.exports = { + SERVICE_ID, + EWELINK_APP_ID, + EWELINK_APP_SECRET, + EWELINK_APP_REGION, + EWELINK_VALID_ACCESS_TOKEN, + EWELINK_INVALID_ACCESS_TOKEN, + EWELINK_DENIED_ACCESS_TOKEN, +}; diff --git a/server/test/services/ewelink/lib/device/connect.test.js b/server/test/services/ewelink/lib/device/connect.test.js deleted file mode 100644 index 040c2d1e9b..0000000000 --- a/server/test/services/ewelink/lib/device/connect.test.js +++ /dev/null @@ -1,101 +0,0 @@ -const { expect } = require('chai'); -const proxyquire = require('proxyquire').noCallThru(); -const sinon = require('sinon'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../../utils/constants'); -const { - event, - serviceId, - variableNok, - variableNotConfigured, - variableOk, - variableOkFalseRegion, - variableOkNoRegion, -} = require('../../mocks/consts.test'); -const EweLinkApiMock = require('../../mocks/ewelink-api.mock.test'); - -const { assert } = sinon; - -const EwelinkService = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EweLinkApiMock, -}); - -describe('EweLinkHandler connect', () => { - beforeEach(() => { - sinon.reset(); - }); - - it('should connect and receive success', async () => { - const gladys = { event, variable: variableOk }; - const eweLinkService = EwelinkService(gladys, serviceId); - await eweLinkService.device.connect(); - - assert.notCalled(gladys.variable.setValue); - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - - expect(eweLinkService.device.configured).to.equal(true); - expect(eweLinkService.device.connected).to.equal(true); - expect(eweLinkService.device.accessToken).to.equal('validAccessToken'); - expect(eweLinkService.device.apiKey).to.equal('validApiKey'); - }); - it('should return not configured error', async () => { - const gladys = { event, variable: variableNotConfigured }; - const eweLinkService = EwelinkService(gladys, serviceId); - try { - await eweLinkService.device.connect(); - assert.fail(); - } catch (error) { - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: 'Service is not configured', - }); - expect(error.message).to.equal('eWeLink: Error, service is not configured'); - } - }); - it('should get region and connect', async () => { - const gladys = { event, variable: variableOkNoRegion }; - const eweLinkService = EwelinkService(gladys, serviceId); - await eweLinkService.device.connect(); - - assert.calledWith(gladys.variable.setValue, 'EWELINK_REGION', 'eu', serviceId); - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - - expect(eweLinkService.device.configured).to.equal(true); - expect(eweLinkService.device.connected).to.equal(true); - expect(eweLinkService.device.accessToken).to.equal('validAccessToken'); - expect(eweLinkService.device.apiKey).to.equal('validApiKey'); - }); - it('should get right region and connect', async () => { - const gladys = { event, variable: variableOkFalseRegion }; - const eweLinkService = EwelinkService(gladys, serviceId); - await eweLinkService.device.connect(); - - assert.calledWith(gladys.variable.setValue, 'EWELINK_REGION', 'eu', serviceId); - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - - expect(eweLinkService.device.configured).to.equal(true); - expect(eweLinkService.device.connected).to.equal(true); - expect(eweLinkService.device.accessToken).to.equal('validAccessToken'); - expect(eweLinkService.device.apiKey).to.equal('validApiKey'); - }); - it('should throw an error and emit a message when authentication fail', async () => { - const gladys = { event, variable: variableNok }; - const eweLinkService = EwelinkService(gladys, serviceId); - try { - await eweLinkService.device.connect(); - assert.fail(); - } catch (error) { - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: 'Authentication error', - }); - expect(error.status).to.equal(403); - expect(error.message).to.equal('eWeLink: Authentication error'); - } - }); -}); diff --git a/server/test/services/ewelink/lib/device/discover.test.js b/server/test/services/ewelink/lib/device/discover.test.js index e12099b65e..eab1815091 100644 --- a/server/test/services/ewelink/lib/device/discover.test.js +++ b/server/test/services/ewelink/lib/device/discover.test.js @@ -1,100 +1,79 @@ const { expect } = require('chai'); -const proxyquire = require('proxyquire').noCallThru(); const sinon = require('sinon'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../../utils/constants'); -const { - event, - serviceId, - stateManagerWith0Devices, - stateManagerWith2Devices, - variableNotConfigured, - variableOk, -} = require('../../mocks/consts.test'); -const Gladys2ChDevice = require('../../mocks/Gladys-2ch.json'); -const GladysOfflineDevice = require('../../mocks/Gladys-offline.json'); -const GladysPowDevice = require('../../mocks/Gladys-pow.json'); -const GladysThDevice = require('../../mocks/Gladys-th.json'); -const GladysUnhandledDevice = require('../../mocks/Gladys-unhandled.json'); -const EweLinkApiMock = require('../../mocks/ewelink-api.mock.test'); -const EweLinkApiEmptyMock = require('../../mocks/ewelink-api-empty.mock.test'); + +const { SERVICE_ID, EWELINK_INVALID_ACCESS_TOKEN, EWELINK_DENIED_ACCESS_TOKEN } = require('../constants'); +const Gladys2ChDevice = require('../payloads/Gladys-2ch.json'); +const GladysOfflineDevice = require('../payloads/Gladys-offline.json'); +const GladysPowDevice = require('../payloads/Gladys-pow.json'); +const GladysThDevice = require('../payloads/Gladys-th.json'); +const GladysUnhandledDevice = require('../payloads/Gladys-unhandled.json'); const { assert } = sinon; -const EwelinkService = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EweLinkApiMock, -}); -const EwelinkServiceEmpty = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EweLinkApiEmptyMock, -}); +const EwelinkHandler = require('../../../../../services/ewelink/lib'); +const EweLinkApiMock = require('../ewelink-api.mock.test'); +const { ServiceNotConfiguredError } = require('../../../../../utils/coreErrors'); -const gladysWith0Devices = { - variable: variableOk, - event, - stateManager: stateManagerWith0Devices, -}; -const gladysWith2Devices = { - variable: variableOk, - event, - stateManager: stateManagerWith2Devices, +const gladys = { + stateManager: { + get: (key, externalId) => { + if (externalId === 'ewelink:10004531ae') { + return Gladys2ChDevice; + } + if (externalId === 'ewelink:10004533ae') { + return GladysPowDevice; + } + return undefined; + }, + }, + event: { + emit: () => {}, + }, + variable: { + destroy: async () => {}, + }, }; describe('EweLinkHandler discover', () => { + let eWeLinkHandler; + beforeEach(() => { - sinon.reset(); + eWeLinkHandler = new EwelinkHandler(gladys, EweLinkApiMock, SERVICE_ID); + eWeLinkHandler.ewelinkClient = new EweLinkApiMock.WebAPI(); }); - it('should found 5 devices, 5 of wich are new unknown devices', async () => { - const eweLinkService = EwelinkService(gladysWith0Devices, serviceId); - const newDevices = await eweLinkService.device.discover(); - expect(newDevices.length).to.equal(5); - expect(newDevices).to.have.deep.members([ - Gladys2ChDevice, - GladysUnhandledDevice, - GladysThDevice, - GladysOfflineDevice, - GladysPowDevice, - ]); + afterEach(() => { + sinon.reset(); }); - it('should found 5 devices, 2 of wich are already in Gladys and 3 are a new unknown device', async () => { - const eweLinkService = EwelinkService(gladysWith2Devices, serviceId); - const newDevices = await eweLinkService.device.discover(); + + it('should found 3 devices, 2 of wich are already in Gladys and 3 are a new unknown device', async () => { + const newDevices = await eWeLinkHandler.discover(); expect(newDevices.length).to.equal(3); expect(newDevices).to.have.deep.members([GladysOfflineDevice, GladysThDevice, GladysUnhandledDevice]); }); it('should found 0 devices', async () => { - const eweLinkService = EwelinkServiceEmpty(gladysWith0Devices, serviceId); - const newDevices = await eweLinkService.device.discover(); + // Force eWeLink API to give empty response + sinon.stub(eWeLinkHandler.ewelinkClient.device, 'getAllThingsAllPages').resolves({ error: 0, data: {} }); + const newDevices = await eWeLinkHandler.discover(); expect(newDevices).to.have.deep.members([]); }); it('should return not configured error', async () => { - const gladys = { event, variable: variableNotConfigured }; - const eweLinkService = EwelinkService(gladys, serviceId); - eweLinkService.device.connected = false; + eWeLinkHandler.ewelinkClient.at = EWELINK_INVALID_ACCESS_TOKEN; try { - await eweLinkService.device.discover(); + await eWeLinkHandler.discover(); assert.fail(); } catch (error) { - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: 'Service is not configured', - }); + expect(error).instanceOf(ServiceNotConfiguredError); expect(error.message).to.equal('eWeLink: Error, service is not configured'); } }); it('should throw an error and emit a message when AccessToken is no more valid', async () => { - const gladys = { event, variable: variableOk }; - const eweLinkService = EwelinkService(gladys, serviceId); - eweLinkService.device.connected = true; - eweLinkService.device.accessToken = 'NoMoreValidAccessToken'; + eWeLinkHandler.ewelinkClient.at = EWELINK_DENIED_ACCESS_TOKEN; try { - await eweLinkService.device.discover(); + await eWeLinkHandler.discover(); assert.fail(); } catch (error) { - assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.ERROR, - payload: 'Authentication error', - }); - expect(error.status).to.equal(403); + expect(error).instanceOf(Error); expect(error.message).to.equal('eWeLink: Authentication error'); } }); diff --git a/server/test/services/ewelink/lib/device/poll.test.js b/server/test/services/ewelink/lib/device/poll.test.js index 16612d7ec6..c17b17f97a 100644 --- a/server/test/services/ewelink/lib/device/poll.test.js +++ b/server/test/services/ewelink/lib/device/poll.test.js @@ -1,93 +1,82 @@ const { expect } = require('chai'); -const proxyquire = require('proxyquire').noCallThru(); const sinon = require('sinon'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../../utils/constants'); -const { deviceManagerFull, event, serviceId, stateManagerFull, variableOk } = require('../../mocks/consts.test'); -const Gladys2ChDevice = require('../../mocks/Gladys-2ch.json'); -const GladysBasicDevice = require('../../mocks/Gladys-Basic.json'); -const GladysOfflineDevice = require('../../mocks/Gladys-offline.json'); -const GladysPowDevice = require('../../mocks/Gladys-pow.json'); -const GladysThDevice = require('../../mocks/Gladys-th.json'); -const EwelinkApiMock = require('../../mocks/ewelink-api.mock.test'); -const { assert } = sinon; +const { EVENTS } = require('../../../../../utils/constants'); +const EwelinkHandler = require('../../../../../services/ewelink/lib'); -const EwelinkService = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EwelinkApiMock, -}); +const { SERVICE_ID, EWELINK_DENIED_ACCESS_TOKEN } = require('../constants'); +const Gladys2ChDevice = require('../payloads/Gladys-2ch.json'); +const GladysBasicDevice = require('../payloads/Gladys-Basic.json'); +const GladysOfflineDevice = require('../payloads/Gladys-offline.json'); +const GladysPowDevice = require('../payloads/Gladys-pow.json'); +const GladysThDevice = require('../payloads/Gladys-th.json'); +const EweLinkApiMock = require('../ewelink-api.mock.test'); -const gladys = { - variable: variableOk, - event, - device: deviceManagerFull, - stateManager: stateManagerFull, -}; +const { assert, fake } = sinon; -describe('EweLinkHandler poll', () => { - const eweLinkService = EwelinkService(gladys, serviceId); +describe('eWeLinkHandler poll', () => { + let eWeLinkHandler; + let gladys; beforeEach(() => { + gladys = { + event: { + emit: fake.resolves(null), + }, + }; + + eWeLinkHandler = new EwelinkHandler(gladys, EweLinkApiMock, SERVICE_ID); + eWeLinkHandler.ewelinkClient = new EweLinkApiMock.WebAPI(); + }); + + afterEach(() => { sinon.reset(); - eweLinkService.device.connected = false; - eweLinkService.device.accessToken = ''; }); it('should poll device and emit 2 states for a "2CH" model', async () => { - await eweLinkService.device.poll(Gladys2ChDevice); - assert.callCount(gladys.event.emit, 3); - assert.calledWith(gladys.event.emit.getCall(0), EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - assert.calledWith(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, { + await eWeLinkHandler.poll(Gladys2ChDevice); + assert.callCount(gladys.event.emit, 2); + assert.calledWithExactly(gladys.event.emit.getCall(0), EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004531ae:binary:1', state: 1, }); - assert.calledWith(gladys.event.emit.getCall(2), EVENTS.DEVICE.NEW_STATE, { + assert.calledWithExactly(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004531ae:binary:2', state: 0, }); }); it('should poll device and emit 1 state for a "POW" device', async () => { - await eweLinkService.device.poll(GladysPowDevice); - assert.callCount(gladys.event.emit, 2); - assert.calledWith(gladys.event.emit.getCall(0), EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - assert.calledWith(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, { + await eWeLinkHandler.poll(GladysPowDevice); + assert.calledOnceWithExactly(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004533ae:binary:1', state: 1, }); }); it('should poll device and emit 3 states for a "TH" model', async () => { - await eweLinkService.device.poll(GladysThDevice); - assert.callCount(gladys.event.emit, 4); - assert.calledWith(gladys.event.emit.getCall(0), EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); - assert.calledWith(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, { + await eWeLinkHandler.poll(GladysThDevice); + assert.callCount(gladys.event.emit, 3); + assert.calledWithExactly(gladys.event.emit.getCall(0), EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004534ae:binary:1', state: 1, }); - assert.calledWith(gladys.event.emit.getCall(2), EVENTS.DEVICE.NEW_STATE, { + assert.calledWithExactly(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004534ae:humidity', state: 42, }); - assert.calledWith(gladys.event.emit.getCall(3), EVENTS.DEVICE.NEW_STATE, { + assert.calledWithExactly(gladys.event.emit.getCall(2), EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'ewelink:10004534ae:temperature', state: 20, }); }); it('should poll device and update 2 params for a "Basic" model', async () => { + // this check that some values are set on the device, and will be changed expect(GladysBasicDevice.params).to.deep.equal([ { name: 'IP_ADDRESS', value: '192.168.0.6' }, { name: 'FIRMWARE', value: '3.1.2' }, { name: 'ONLINE', value: '0' }, ]); - await eweLinkService.device.poll(GladysBasicDevice); - assert.callCount(gladys.event.emit, 1); - assert.calledWith(gladys.event.emit.getCall(0), EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.EWELINK.CONNECTED, - }); + await eWeLinkHandler.poll(GladysBasicDevice); + assert.notCalled(gladys.event.emit); expect(GladysBasicDevice.params).to.deep.equal([ { name: 'IP_ADDRESS', value: '192.168.0.6' }, { name: 'FIRMWARE', value: '3.3.0' }, @@ -96,20 +85,19 @@ describe('EweLinkHandler poll', () => { }); it('should throw an error when device is offline', async () => { try { - await eweLinkService.device.poll(GladysOfflineDevice); + await eWeLinkHandler.poll(GladysOfflineDevice); assert.fail(); } catch (error) { expect(error.message).to.equal('eWeLink: Error, device is not currently online'); } }); it('should throw an error when AccessToken is no more valid', async () => { - eweLinkService.device.connected = true; - eweLinkService.device.accessToken = 'NoMoreValidAccessToken'; + eWeLinkHandler.ewelinkClient.at = EWELINK_DENIED_ACCESS_TOKEN; try { - await eweLinkService.device.poll(Gladys2ChDevice); + await eWeLinkHandler.poll(Gladys2ChDevice); assert.fail(); } catch (error) { - expect(error.status).to.equal(403); + expect(error).instanceOf(Error); expect(error.message).to.equal('eWeLink: Authentication error'); } }); diff --git a/server/test/services/ewelink/lib/device/setValue.test.js b/server/test/services/ewelink/lib/device/setValue.test.js index c3e8e82e2a..0e4d160373 100644 --- a/server/test/services/ewelink/lib/device/setValue.test.js +++ b/server/test/services/ewelink/lib/device/setValue.test.js @@ -1,52 +1,49 @@ const { expect } = require('chai'); -const proxyquire = require('proxyquire').noCallThru(); const sinon = require('sinon'); -const { event, serviceId, variableOk } = require('../../mocks/consts.test'); -const Gladys2ChDevice = require('../../mocks/Gladys-2ch.json'); -const GladysOfflineDevice = require('../../mocks/Gladys-offline.json'); -const GladysPowDevice = require('../../mocks/Gladys-pow.json'); -const EweLinkApiMock = require('../../mocks/ewelink-api.mock.test'); -const { assert } = sinon; +const { NotFoundError } = require('../../../../../utils/coreErrors'); +const EwelinkHandler = require('../../../../../services/ewelink/lib'); -const EwelinkService = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EweLinkApiMock, -}); +const { SERVICE_ID, EWELINK_DENIED_ACCESS_TOKEN } = require('../constants'); +const Gladys2ChDevice = require('../payloads/Gladys-2ch.json'); +const GladysOfflineDevice = require('../payloads/Gladys-offline.json'); +const GladysPowDevice = require('../payloads/Gladys-pow.json'); +const EweLinkApiMock = require('../ewelink-api.mock.test'); -const gladys = { - event, - variable: variableOk, -}; +const { assert } = sinon; -describe('EweLinkHandler setValue', () => { - const eweLinkService = EwelinkService(gladys, serviceId); - const functionToTest = sinon.spy(EweLinkApiMock.prototype, 'setDevicePowerState'); +describe('eWeLinkHandler setValue', () => { + let eWeLinkHandler; + const functionToTest = sinon.spy(EweLinkApiMock.Device.prototype, 'setThingStatus'); beforeEach(() => { + const gladys = {}; + eWeLinkHandler = new EwelinkHandler(gladys, EweLinkApiMock, SERVICE_ID); + eWeLinkHandler.ewelinkClient = new EweLinkApiMock.WebAPI(); + }); + + afterEach(() => { sinon.reset(); - eweLinkService.device.connected = false; - eweLinkService.device.accessToken = ''; - eweLinkService.device.apiKey = ''; }); it('should set the binary value of the channel 1 of the "2CH" device to 1', async () => { - await eweLinkService.device.setValue( + await eWeLinkHandler.setValue( GladysPowDevice, { external_id: 'ewelink:10004533ae:power:1', category: 'switch', type: 'binary' }, 1, ); - assert.calledWith(functionToTest, '10004533ae', 'on', 1); + assert.calledOnceWithExactly(functionToTest, 1, '10004533ae', { switch: 'on' }); }); it('should set the binary value of the channel 2 of the "2CH" device to 0', async () => { - await eweLinkService.device.setValue( + await eWeLinkHandler.setValue( Gladys2ChDevice, { external_id: 'ewelink:10004531ae:power:2', category: 'switch', type: 'binary' }, 0, ); - assert.calledWith(functionToTest, '10004531ae', 'off', 2); + assert.calledOnceWithExactly(functionToTest, 1, '10004531ae', { switches: [{ outlet: 2, switch: 'off' }] }); }); it('should do nothing because of the feature type is not handled yet', async () => { - await eweLinkService.device.setValue( + await eWeLinkHandler.setValue( GladysPowDevice, { external_id: 'ewelink:10004533ae:power:1', category: 'switch', type: 'not_handled' }, 1, @@ -55,30 +52,30 @@ describe('EweLinkHandler setValue', () => { }); it('should throw an error when device is offline', async () => { try { - await eweLinkService.device.setValue( + await eWeLinkHandler.setValue( GladysOfflineDevice, { external_id: 'ewelink:10004532ae:power:1', category: 'switch', type: 'binary' }, 1, ); assert.fail(); } catch (error) { - assert.notCalled(functionToTest); + assert.calledOnceWithExactly(functionToTest, 1, '10004532ae', { switch: 'on' }); + expect(error).instanceOf(NotFoundError); expect(error.message).to.equal('eWeLink: Error, device is not currently online'); } }); it('should throw an error when AccessToken is no more valid', async () => { - eweLinkService.device.connected = true; - eweLinkService.device.accessToken = 'NoMoreValidAccessToken'; + eWeLinkHandler.ewelinkClient.at = EWELINK_DENIED_ACCESS_TOKEN; try { - await eweLinkService.device.setValue( + await eWeLinkHandler.setValue( Gladys2ChDevice, { external_id: 'ewelink:10004531ae:power:2', category: 'switch', type: 'binary' }, 1, ); assert.fail(); } catch (error) { - assert.notCalled(functionToTest); - expect(error.status).to.equal(403); + assert.calledOnceWithExactly(functionToTest, 1, '10004531ae', { switches: [{ outlet: 2, switch: 'on' }] }); + expect(error).instanceOf(Error); expect(error.message).to.equal('eWeLink: Authentication error'); } }); diff --git a/server/test/services/ewelink/lib/device/throwErrorIfNeeded.test.js b/server/test/services/ewelink/lib/device/throwErrorIfNeeded.test.js deleted file mode 100644 index a7699d5410..0000000000 --- a/server/test/services/ewelink/lib/device/throwErrorIfNeeded.test.js +++ /dev/null @@ -1,95 +0,0 @@ -const { expect } = require('chai'); -const sinon = require('sinon'); -const proxyquire = require('proxyquire').noCallThru(); -const { serviceId, event } = require('../../mocks/consts.test'); -const EweLinkApi = require('../../mocks/ewelink-api.mock.test'); - -const { assert } = sinon; - -const EwelinkService = proxyquire('../../../../../services/ewelink/index', { - 'ewelink-api': EweLinkApi, -}); - -describe('EweLinkHandler throwErrorIfNeeded', () => { - const gladys = { event }; - const eweLinkService = EwelinkService(gladys, serviceId); - - beforeEach(() => { - sinon.reset(); - eweLinkService.device.connected = true; - eweLinkService.device.accessToken = 'validAccessToken'; - eweLinkService.device.apiKey = 'validApiKey'; - }); - - it('should throws a error and not emit a message', async () => { - try { - const response = { error: 406, msg: 'Authentication error' }; - await eweLinkService.device.throwErrorIfNeeded(response); - assert.fail(); - } catch (error) { - expect(error.status).to.equal(403); - expect(error.message).to.equal('eWeLink: Authentication error'); - } - }); - it('should throws an error and emit a message', async () => { - try { - const response = { error: 406, msg: 'Authentication error' }; - await eweLinkService.device.throwErrorIfNeeded(response, true); - assert.fail(); - } catch (error) { - assert.calledOnceWithExactly(gladys.event.emit, 'websocket.send-all', { - type: 'ewelink.error', - payload: 'Authentication error', - }); - expect(error.status).to.equal(403); - expect(error.message).to.equal('eWeLink: Authentication error'); - } - }); - it('should reset authentication values when authentication fail', async () => { - eweLinkService.device.accessToken = 'NoMoreValidAccessToken'; - try { - const response = { error: 406, msg: 'Authentication error' }; - await eweLinkService.device.throwErrorIfNeeded(response); - assert.fail(); - } catch (error) { - expect(eweLinkService.device.connected).to.equal(false); - expect(eweLinkService.device.accessToken).to.equal(''); - expect(eweLinkService.device.apiKey).to.equal(''); - } - }); - it('should throws a error and not emit a message', async () => { - try { - const response = { error: 404, msg: 'Device does not exist' }; - await eweLinkService.device.throwErrorIfNeeded(response); - assert.fail(); - } catch (error) { - expect(error.status).to.equal(500); - expect(error.error).to.equal('eWeLink: Device does not exist'); - } - }); - it('should throws an error and emit a message', async () => { - try { - const response = { error: 404, msg: 'Device does not exist' }; - await eweLinkService.device.throwErrorIfNeeded(response, true); - assert.fail(); - } catch (error) { - assert.calledOnceWithExactly(gladys.event.emit, 'websocket.send-all', { - type: 'ewelink.error', - payload: 'Device does not exist', - }); - expect(error.status).to.equal(500); - expect(error.error).to.equal('eWeLink: Device does not exist'); - } - }); - it('should not reset authentication values', async () => { - try { - const response = { error: 500, msg: 'Device does not exist' }; - await eweLinkService.device.throwErrorIfNeeded(response); - assert.fail(); - } catch (error) { - expect(eweLinkService.device.connected).to.equal(true); - expect(eweLinkService.device.accessToken).to.equal('validAccessToken'); - expect(eweLinkService.device.apiKey).to.equal('validApiKey'); - } - }); -}); diff --git a/server/test/services/ewelink/lib/ewelink-api.mock.test.js b/server/test/services/ewelink/lib/ewelink-api.mock.test.js new file mode 100644 index 0000000000..1995a61335 --- /dev/null +++ b/server/test/services/ewelink/lib/ewelink-api.mock.test.js @@ -0,0 +1,94 @@ +const Promise = require('bluebird'); +const EweLink2ChDevice = require('./payloads/eweLink-2ch.json'); +const EweLinkBasicDevice = require('./payloads/eweLink-basic.json'); +const EweLinkOfflineDevice = require('./payloads/eweLink-offline.json'); +const EweLinkPowDevice = require('./payloads/eweLink-pow.json'); +const EweLinkThDevice = require('./payloads/eweLink-th.json'); +const EweLinkUnhandledDevice = require('./payloads/eweLink-unhandled.json'); +const { + EWELINK_APP_ID, + EWELINK_APP_SECRET, + EWELINK_APP_REGION, + EWELINK_VALID_ACCESS_TOKEN, + EWELINK_INVALID_ACCESS_TOKEN, +} = require('./constants'); + +const fakeDevices = [EweLink2ChDevice, EweLinkOfflineDevice, EweLinkPowDevice, EweLinkThDevice, EweLinkUnhandledDevice]; + +const buildResponse = async (data, accessToken) => { + const response = { + status: 200, + error: 0, + data, + }; + + if (accessToken === EWELINK_INVALID_ACCESS_TOKEN) { + response.error = 401; + response.msg = 'eWeLink: Error, service is not configured'; + } else if (accessToken !== EWELINK_VALID_ACCESS_TOKEN) { + response.error = 406; + response.msg = 'eWeLink: Authentication error'; + } + + return Promise.resolve(response); +}; + +class Device { + constructor(root) { + this.root = root; + } + + async getAllThingsAllPages() { + const data = { + thingList: fakeDevices.map((device) => { + return { itemData: device }; + }), + }; + + return buildResponse(data, this.root.at); + } + + async getThings({ thingList }) { + const [firstItem] = thingList; + const { id: deviceId } = firstItem; + + const device = [...fakeDevices, EweLinkBasicDevice].find((fakeDevice) => fakeDevice.deviceid === deviceId); + const response = await buildResponse({ thingList: [{ itemData: device }] }, this.root.at); + + if (!response.error) { + if (!device) { + response.error = 405; + response.msg = 'Device does not exist'; + // } else if (deviceId === '10004536ae') { + // response.data.thingList[0].itemData.online = true; + } else if (!device.online) { + response.error = 4002; + response.msg = 'eWeLink: Error, device is not currently online'; + } + } + + return response; + } + + async setThingStatus(type, id, params) { + const deviceResponse = await this.getThings({ thingList: [{ id }] }); + return { ...deviceResponse, data: {} }; + } +} + +class WebAPI { + constructor(options = { appId: EWELINK_APP_ID, appSecret: EWELINK_APP_SECRET, region: EWELINK_APP_REGION }) { + this.appId = options.appId; + this.appSecret = options.appSecret; + this.region = options.region; + + // default with right access token + this.at = EWELINK_VALID_ACCESS_TOKEN; + + this.device = new Device(this); + } +} + +const items = { WebAPI, Device }; + +module.exports = { default: items, ...items }; diff --git a/server/test/services/ewelink/lib/features/features.test.js b/server/test/services/ewelink/lib/features/features.test.js index 6feb135935..0741b03ab6 100644 --- a/server/test/services/ewelink/lib/features/features.test.js +++ b/server/test/services/ewelink/lib/features/features.test.js @@ -1,16 +1,18 @@ const { expect } = require('chai'); + +const { SERVICE_ID } = require('../constants'); const features = require('../../../../../services/ewelink/lib/features'); const { parseExternalId } = require('../../../../../services/ewelink/lib/utils/externalId'); -const GladysOfflineDevice = require('../../mocks/Gladys-offline.json'); -const GladysPowDevice = require('../../mocks/Gladys-pow.json'); -const GladysThDevice = require('../../mocks/Gladys-th.json'); -const Gladys2ChDevice = require('../../mocks/Gladys-2ch.json'); -const GladysUnhandledDevice = require('../../mocks/Gladys-unhandled.json'); -const eweLinkOfflineDevice = require('../../mocks/eweLink-offline.json'); -const eweLinkPowDevice = require('../../mocks/eweLink-pow.json'); -const eweLinkThDevice = require('../../mocks/eweLink-th.json'); -const eweLink2ChDevice = require('../../mocks/eweLink-2ch.json'); -const eweLinkUnhandledDevice = require('../../mocks/eweLink-unhandled.json'); +const GladysOfflineDevice = require('../payloads/Gladys-offline.json'); +const GladysPowDevice = require('../payloads/Gladys-pow.json'); +const GladysThDevice = require('../payloads/Gladys-th.json'); +const Gladys2ChDevice = require('../payloads/Gladys-2ch.json'); +const GladysUnhandledDevice = require('../payloads/Gladys-unhandled.json'); +const eweLinkOfflineDevice = require('../payloads/eweLink-offline.json'); +const eweLinkPowDevice = require('../payloads/eweLink-pow.json'); +const eweLinkThDevice = require('../payloads/eweLink-th.json'); +const eweLink2ChDevice = require('../payloads/eweLink-2ch.json'); +const eweLinkUnhandledDevice = require('../payloads/eweLink-unhandled.json'); describe('eWeLink features parseExternalId', () => { it('should return prefix, deviceId, channel and type', () => { @@ -35,23 +37,23 @@ describe('eWeLink features readOnlineValue', () => { describe('eWeLink features getDevice', () => { it('should return device without features if offline', () => { - const device = features.getDevice('a810b8db-6d04-4697-bed3-c4b72c996279', eweLinkOfflineDevice, 0); + const device = features.getDevice(SERVICE_ID, eweLinkOfflineDevice); expect(device).to.deep.equal(GladysOfflineDevice); }); it('should return device with binary feature for a "POW" model', () => { - const device = features.getDevice('a810b8db-6d04-4697-bed3-c4b72c996279', eweLinkPowDevice, 1); + const device = features.getDevice(SERVICE_ID, eweLinkPowDevice); expect(device).to.deep.equal(GladysPowDevice); }); it('should return a device with 2 binary features for a "2CH" model', () => { - const device = features.getDevice('a810b8db-6d04-4697-bed3-c4b72c996279', eweLink2ChDevice, 2); + const device = features.getDevice(SERVICE_ID, eweLink2ChDevice); expect(device).to.deep.equal(Gladys2ChDevice); }); it('should return device with binary, humidity and temperature features for a "TH" model', () => { - const device = features.getDevice('a810b8db-6d04-4697-bed3-c4b72c996279', eweLinkThDevice, 1); + const device = features.getDevice(SERVICE_ID, eweLinkThDevice); expect(device).to.deep.equal(GladysThDevice); }); it('should return device without features for an unhandled model', () => { - const device = features.getDevice('a810b8db-6d04-4697-bed3-c4b72c996279', eweLinkUnhandledDevice, 0); + const device = features.getDevice(SERVICE_ID, eweLinkUnhandledDevice); expect(device).to.deep.equal(GladysUnhandledDevice); }); }); diff --git a/server/test/services/ewelink/mocks/Gladys-2ch.json b/server/test/services/ewelink/lib/payloads/Gladys-2ch.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-2ch.json rename to server/test/services/ewelink/lib/payloads/Gladys-2ch.json diff --git a/server/test/services/ewelink/mocks/Gladys-Basic.json b/server/test/services/ewelink/lib/payloads/Gladys-Basic.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-Basic.json rename to server/test/services/ewelink/lib/payloads/Gladys-Basic.json diff --git a/server/test/services/ewelink/mocks/Gladys-offline.json b/server/test/services/ewelink/lib/payloads/Gladys-offline.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-offline.json rename to server/test/services/ewelink/lib/payloads/Gladys-offline.json diff --git a/server/test/services/ewelink/mocks/Gladys-pow.json b/server/test/services/ewelink/lib/payloads/Gladys-pow.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-pow.json rename to server/test/services/ewelink/lib/payloads/Gladys-pow.json diff --git a/server/test/services/ewelink/mocks/Gladys-th.json b/server/test/services/ewelink/lib/payloads/Gladys-th.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-th.json rename to server/test/services/ewelink/lib/payloads/Gladys-th.json diff --git a/server/test/services/ewelink/mocks/Gladys-unhandled.json b/server/test/services/ewelink/lib/payloads/Gladys-unhandled.json similarity index 100% rename from server/test/services/ewelink/mocks/Gladys-unhandled.json rename to server/test/services/ewelink/lib/payloads/Gladys-unhandled.json diff --git a/server/test/services/ewelink/mocks/eweLink-2ch.json b/server/test/services/ewelink/lib/payloads/eweLink-2ch.json similarity index 62% rename from server/test/services/ewelink/mocks/eweLink-2ch.json rename to server/test/services/ewelink/lib/payloads/eweLink-2ch.json index a674065359..7cd0a38a04 100644 --- a/server/test/services/ewelink/mocks/eweLink-2ch.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-2ch.json @@ -4,12 +4,14 @@ "deviceid": "10004531ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 7, - "model": "PSA-BHA-GL" - } + "uiid": 7, + "model": "PSA-BHA-GL" }, "params": { + "switches": [ + { "switch": "on", "outlet": 0 }, + { "switch": "off", "outlet": 1 } + ], "fwVersion": "3.3.0" }, "ip": "192.168.0.1", diff --git a/server/test/services/ewelink/mocks/eweLink-basic.json b/server/test/services/ewelink/lib/payloads/eweLink-basic.json similarity index 78% rename from server/test/services/ewelink/mocks/eweLink-basic.json rename to server/test/services/ewelink/lib/payloads/eweLink-basic.json index ae1ad8ff39..35467bc54c 100644 --- a/server/test/services/ewelink/mocks/eweLink-basic.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-basic.json @@ -4,10 +4,8 @@ "deviceid": "10004536ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 6, - "model": "PSA-BHA-GL" - } + "uiid": 6, + "model": "PSA-BHA-GL" }, "params": { "fwVersion": "3.3.0" diff --git a/server/test/services/ewelink/mocks/eweLink-offline.json b/server/test/services/ewelink/lib/payloads/eweLink-offline.json similarity index 74% rename from server/test/services/ewelink/mocks/eweLink-offline.json rename to server/test/services/ewelink/lib/payloads/eweLink-offline.json index 88b0319b91..b7efbb7eb4 100644 --- a/server/test/services/ewelink/mocks/eweLink-offline.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-offline.json @@ -5,10 +5,8 @@ "deviceid": "10004532ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 1, - "model": "PSF-BD1-GL" - } + "uiid": 1, + "model": "PSF-BD1-GL" }, "brandName": "SONOFF", "productModel": "MINI", diff --git a/server/test/services/ewelink/mocks/eweLink-pow.json b/server/test/services/ewelink/lib/payloads/eweLink-pow.json similarity index 78% rename from server/test/services/ewelink/mocks/eweLink-pow.json rename to server/test/services/ewelink/lib/payloads/eweLink-pow.json index f6f3a53b63..7a695aca7c 100644 --- a/server/test/services/ewelink/mocks/eweLink-pow.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-pow.json @@ -4,12 +4,11 @@ "deviceid": "10004533ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 5, - "model": "ITA-GZ1-GL" - } + "uiid": 5, + "model": "ITA-GZ1-GL" }, "params": { + "switch": "on", "fwVersion": "3.3.0" }, "ip": "192.168.0.3", diff --git a/server/test/services/ewelink/mocks/eweLink-th.json b/server/test/services/ewelink/lib/payloads/eweLink-th.json similarity index 67% rename from server/test/services/ewelink/mocks/eweLink-th.json rename to server/test/services/ewelink/lib/payloads/eweLink-th.json index dd7f16c814..a3a42bfc91 100644 --- a/server/test/services/ewelink/mocks/eweLink-th.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-th.json @@ -4,12 +4,13 @@ "deviceid": "10004534ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 15, - "model": "ITA-GZ1-GL" - } + "uiid": 15, + "model": "ITA-GZ1-GL" }, "params": { + "switch": "on", + "currentHumidity": 42, + "currentTemperature": 20, "fwVersion": "3.1.2" }, "ip": "192.168.0.4", diff --git a/server/test/services/ewelink/mocks/eweLink-unhandled.json b/server/test/services/ewelink/lib/payloads/eweLink-unhandled.json similarity index 71% rename from server/test/services/ewelink/mocks/eweLink-unhandled.json rename to server/test/services/ewelink/lib/payloads/eweLink-unhandled.json index 4409120554..da7e643fb8 100644 --- a/server/test/services/ewelink/mocks/eweLink-unhandled.json +++ b/server/test/services/ewelink/lib/payloads/eweLink-unhandled.json @@ -4,10 +4,8 @@ "deviceid": "10004535ae", "apikey": "validApikey", "extra": { - "extra": { - "uiid": 10000, - "model": "ITU-GB1-LG" - } + "uiid": 10000, + "model": "ITU-GB1-LG" }, "brandName": "SONOFF", "productModel": "UNKNOWN", diff --git a/server/test/services/ewelink/mocks/consts.test.js b/server/test/services/ewelink/mocks/consts.test.js deleted file mode 100644 index 3fd2e2cc5f..0000000000 --- a/server/test/services/ewelink/mocks/consts.test.js +++ /dev/null @@ -1,136 +0,0 @@ -const Promise = require('bluebird'); -const { fake } = require('sinon'); -const GladysPowDevice = require('./Gladys-pow.json'); -const GladysOfflineDevice = require('./Gladys-offline.json'); -const Gladys2ChDevice = require('./Gladys-2ch.json'); -const GladysUnhandledDevice = require('./Gladys-unhandled.json'); -const GladysThDevice = require('./Gladys-th.json'); - -const serviceId = 'a810b8db-6d04-4697-bed3-c4b72c996279'; - -const event = { emit: fake.resolves(null) }; - -const variableNotConfigured = { - getValue: (valueId, notUsed) => { - return Promise.resolve(undefined); - }, - setValue: fake.returns(null), -}; - -const variableOk = { - getValue: (valueId, notUsed) => { - if (valueId === 'EWELINK_EMAIL') { - return Promise.resolve('email@valid.ok'); - } - if (valueId === 'EWELINK_PASSWORD') { - return Promise.resolve('S0m3Th1ngTru3'); - } - if (valueId === 'EWELINK_REGION') { - return Promise.resolve('eu'); - } - return Promise.resolve(undefined); - }, - setValue: fake.returns(null), -}; - -const variableOkNoRegion = { - getValue: (valueId, notUsed) => { - if (valueId === 'EWELINK_EMAIL') { - return Promise.resolve('email@valid.ok'); - } - if (valueId === 'EWELINK_PASSWORD') { - return Promise.resolve('S0m3Th1ngTru3'); - } - return Promise.resolve(undefined); - }, - setValue: fake.returns(null), -}; - -const variableOkFalseRegion = { - getValue: (valueId, notUsed) => { - if (valueId === 'EWELINK_EMAIL') { - return Promise.resolve('email@valid.ok'); - } - if (valueId === 'EWELINK_PASSWORD') { - return Promise.resolve('S0m3Th1ngTru3'); - } - if (valueId === 'EWELINK_REGION') { - return Promise.resolve('uk'); - } - return Promise.resolve(undefined); - }, - setValue: fake.returns(null), -}; - -const variableNok = { - getValue: (valueId, notUsed) => { - if (valueId === 'EWELINK_EMAIL') { - return Promise.resolve('email@unvalid.ko'); - } - if (valueId === 'EWELINK_PASSWORD') { - return Promise.resolve('S0m3Th1ngF4ls3'); - } - if (valueId === 'EWELINK_REGION') { - return Promise.resolve('eu'); - } - return Promise.resolve(undefined); - }, - setValue: fake.returns(null), -}; - -const deviceManagerFull = { - get: fake.resolves([Gladys2ChDevice, GladysOfflineDevice, GladysPowDevice, GladysThDevice, GladysUnhandledDevice]), -}; - -const stateManagerWith0Devices = { - get: (key, externalId) => { - return undefined; - }, -}; - -const stateManagerWith2Devices = { - get: (key, externalId) => { - if (externalId === 'ewelink:10004531ae') { - return Gladys2ChDevice; - } - if (externalId === 'ewelink:10004533ae') { - return GladysPowDevice; - } - return undefined; - }, -}; - -const stateManagerFull = { - get: (key, externalId) => { - if (externalId === 'ewelink:10004531ae') { - return Gladys2ChDevice; - } - if (externalId === 'ewelink:10004532ae') { - return GladysOfflineDevice; - } - if (externalId === 'ewelink:10004533ae') { - return GladysPowDevice; - } - if (externalId === 'ewelink:10004534ae') { - return GladysThDevice; - } - if (externalId === 'ewelink:10004535ae') { - return GladysUnhandledDevice; - } - return undefined; - }, -}; - -module.exports = { - serviceId, - event, - variableNotConfigured, - variableOk, - variableOkNoRegion, - variableOkFalseRegion, - variableNok, - deviceManagerFull, - stateManagerWith0Devices, - stateManagerWith2Devices, - stateManagerFull, -}; diff --git a/server/test/services/ewelink/mocks/ewelink-api-empty.mock.test.js b/server/test/services/ewelink/mocks/ewelink-api-empty.mock.test.js deleted file mode 100644 index e6e88a6f2a..0000000000 --- a/server/test/services/ewelink/mocks/ewelink-api-empty.mock.test.js +++ /dev/null @@ -1,100 +0,0 @@ -const Promise = require('bluebird'); - -class EwelinkApi { - constructor({ region = 'us', email, password, at, apiKey, devicesCache, arpTable }) { - this.region = region; - this.email = email; - this.password = password; - this.at = at || undefined; - this.apiKey = apiKey || undefined; - this.devicesCache = devicesCache; - this.arpTable = arpTable; - } - - getCredentials() { - if (this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') { - return Promise.resolve({ - at: 'validAccessToken', - user: { apikey: 'validApiKey' }, - region: 'eu', - }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getRegion() { - if (this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') { - return Promise.resolve({ - email: this.email, - region: 'eu', - }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getDevices() { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve([]); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getDevice(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getDeviceChannelCount(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - setDevicePowerState(deviceId, state, channel = 0) { - if (this.email === 'email@valid.ok' || (this.at === 'validAccessToken' && this.apiKey === 'validApiKey')) { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getDevicePowerState(deviceId, channel = 1) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - async getDevicePowerUsage(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - async getDeviceCurrentTH(deviceId, type = '') { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } - - getDeviceCurrentTemperature(deviceId) { - return this.getDeviceCurrentTH(deviceId, 'temp'); - } - - getDeviceCurrentHumidity(deviceId) { - return this.getDeviceCurrentTH(deviceId, 'humd'); - } - - getFirmwareVersion(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 401, msg: 'Authentication error' }); - } -} - -module.exports = EwelinkApi; diff --git a/server/test/services/ewelink/mocks/ewelink-api.mock.test.js b/server/test/services/ewelink/mocks/ewelink-api.mock.test.js deleted file mode 100644 index 028fdcdc59..0000000000 --- a/server/test/services/ewelink/mocks/ewelink-api.mock.test.js +++ /dev/null @@ -1,158 +0,0 @@ -const Promise = require('bluebird'); -const EweLink2ChDevice = require('./eweLink-2ch.json'); -const EweLinkBasicDevice = require('./eweLink-basic.json'); -const EweLinkOfflineDevice = require('./eweLink-offline.json'); -const EweLinkPowDevice = require('./eweLink-pow.json'); -const EweLinkThDevice = require('./eweLink-th.json'); -const EweLinkUnhandledDevice = require('./eweLink-unhandled.json'); - -const fakeDevices = [EweLink2ChDevice, EweLinkOfflineDevice, EweLinkPowDevice, EweLinkThDevice, EweLinkUnhandledDevice]; - -class EwelinkApi { - constructor({ region = 'us', email, password, at, apiKey, devicesCache, arpTable }) { - this.region = region; - this.email = email; - this.password = password; - this.at = at || undefined; - this.apiKey = apiKey || undefined; - this.devicesCache = devicesCache; - this.arpTable = arpTable; - } - - getCredentials() { - if (this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') { - return Promise.resolve({ - at: 'validAccessToken', - user: { apikey: 'validApiKey' }, - region: 'eu', - }); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } - - getRegion() { - if (this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') { - return Promise.resolve({ - email: this.email, - region: 'eu', - }); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } - - getDevices() { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - return Promise.resolve(fakeDevices); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } - - getDevice(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - const device = [...fakeDevices, EweLinkBasicDevice].find((fakeDevice) => fakeDevice.deviceid === deviceId); - if (device) { - if (deviceId === '10004531ae') { - return Promise.resolve({ ...device, params: { switches: [{ switch: 'on' }, { switch: 'off' }] } }); - } - if (deviceId === '10004533ae') { - return Promise.resolve({ ...device, params: { switch: 'on' } }); - } - if (deviceId === '10004534ae') { - return Promise.resolve({ ...device, params: { switch: 'on', currentHumidity: 42, currentTemperature: 20 } }); - } - if (deviceId === '10004536ae') { - return Promise.resolve(device); - } - return Promise.resolve(device); - } - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } - - getDeviceChannelCount(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - if (deviceId === '10004533ae' || deviceId === '10004534ae') { - return Promise.resolve({ status: 'ok', switchesAmount: 1 }); - } - if (deviceId === '10004531ae') { - return Promise.resolve({ status: 'ok', switchesAmount: 2 }); - } - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } - - async setDevicePowerState(deviceId, state, channel = 0) { - const device = await this.getDevice(deviceId); - if (device && !device.error && device.online) { - return Promise.resolve({ status: 'ok', state }); - } - return Promise.resolve(device); - } - - async getDevicePowerState(deviceId, channel = 1) { - const device = await this.getDevice(deviceId); - if (device && !device.error && device.online) { - if (deviceId === '10004531ae' && channel === 2) { - return Promise.resolve({ status: 'ok', state: 'off' }); - } - return Promise.resolve({ status: 'ok', state: 'on' }); - } - return Promise.resolve(device); - } - - async getDevicePowerUsage(deviceId) { - const device = await this.getDevice(deviceId); - if (device && !device.error && device.online) { - return Promise.resolve({ - status: 'ok', - monthly: 22.3, - daily: [ - { day: 5, usage: 5.94 }, - { day: 4, usage: 3.64 }, - { day: 3, usage: 2.39 }, - { day: 2, usage: 3.1 }, - { day: 1, usage: 7.23 }, - ], - }); - } - return Promise.resolve(device); - } - - async getDeviceCurrentTH(deviceId, type = '') { - const device = await this.getDevice(deviceId); - if (device && !device.error && device.online) { - const data = { status: 'ok', temperature: 20, humidity: 42 }; - if (type === 'temp') { - delete data.humidity; - } - if (type === 'humd') { - delete data.temperature; - } - return Promise.resolve(data); - } - return Promise.resolve(device); - } - - getDeviceCurrentTemperature(deviceId) { - return this.getDeviceCurrentTH(deviceId, 'temp'); - } - - getDeviceCurrentHumidity(deviceId) { - return this.getDeviceCurrentTH(deviceId, 'humd'); - } - - getFirmwareVersion(deviceId) { - if ((this.email === 'email@valid.ok' && this.password === 'S0m3Th1ngTru3') || this.at === 'validAccessToken') { - const device = fakeDevices.find((fakeDevice) => fakeDevice.deviceid === deviceId); - if (device && device.params && device.params.fwVersion) { - return Promise.resolve({ status: 'ok', fwVersion: device.params.fwVersion }); - } - return Promise.resolve({ error: false, msg: 'Device does not exist' }); - } - return Promise.resolve({ error: 406, msg: 'Authentication error' }); - } -} - -module.exports = EwelinkApi;