UNPKG

millheat-api

Version:
257 lines (231 loc) 8.34 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('fetch-ponyfill'), require('spacetime')) : typeof define === 'function' && define.amd ? define(['fetch-ponyfill', 'spacetime'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.millheatApi = factory(global.fetchPonyfill, global.spacetime)); })(this, (function (fetchPonyfill, spacetime) { 'use strict'; const { fetch } = fetchPonyfill({}); const DEFAULT_HEADERS = { Accept: 'application/json', 'Content-Type': 'application/json', }; const authenticate = async (username, password, logger, endpoint) => { const url = endpoint + '/customer/auth/sign-in'; const method = 'POST'; const headers = { ...DEFAULT_HEADERS, }; const body = JSON.stringify({ login: username, password: password }); logger.debug(`request: { method: ${method}, url: ${url}, headers: ${JSON.stringify(headers)}, body: ${body} }`); const response = await fetch(url, { method, headers, body, }); const json = await response.json(); logger.debug(`response: ${JSON.stringify(json)}`); if (response.ok && !json.error) { return json; } else { throw new Error(`${json.error.type}`); } }; const refreshToken = async (refreshToken, logger, endpoint) => { const url = endpoint + '/customer/auth/refresh'; const method = 'POST'; const headers = { ...DEFAULT_HEADERS, Authorization: 'Bearer ' + refreshToken, }; logger.debug(`request: { method: ${method}, url: ${url}, headers: ${JSON.stringify(headers)} }`); const response = await fetch(url, { method, headers, }); const json = await response.json(); logger.debug(`response: ${JSON.stringify(json)}`); if (response.ok && !json.error) { return json; } else { throw new Error(`${json.error.type}`); } }; const command = async (accessToken, command, payload, logger, endpoint, method) => { const url = endpoint + command; const body = JSON.stringify(payload); const headers = { ...DEFAULT_HEADERS, Authorization: 'Bearer ' + accessToken, }; const options = { method: method, headers: headers, ...(method !== 'GET' && { body: body }), }; logger.debug(`request: { method: ${method}, url: ${url}, headers: ${JSON.stringify(headers)}, body: ${body} }`); const response = await fetch(url, options); let json; try { json = await response.json(); } catch (e) { json = {}; } logger.debug(`response: ${JSON.stringify(json)}`); if (response.ok && !json.error) { return json; } else { throw new Error(`${json.error.type}`); } }; const TOKEN_LIFETIME = 10; const SERVICE_ENDPOINT = 'https://api.millnorwaycloud.com/'; class Mill { constructor(username, password, opts = {}) { this.logger = opts.logger || console; this.serviceEndpoint = opts.serviceEndpoint || SERVICE_ENDPOINT; this.username = username; this.password = password; this.refreshToken = null; this.authenticating = false; this.devices = []; this._authenticate(); } async _authenticate() { if (!this.authenticating) { this.authenticating = true; try { const auth = this.refreshToken !== null ? await refreshToken(this.refreshToken, this.logger, this.serviceEndpoint) : await authenticate(this.username, this.password, this.logger, this.serviceEndpoint); this.accessToken = auth.idToken; this.refreshToken = auth.refreshToken; this.tokenExpire = spacetime.now().add(TOKEN_LIFETIME, 'minute'); this.authenticating = false; } catch (e) { this.accessToken = null; this.refreshToken = null; this.authenticating = false; throw e; } } else { while (this.authenticating) { await new Promise((resolve) => setTimeout(resolve, 50)); } if (!this.accessToken) { throw new Error('Authentication failed'); } } } async _command(commandName, payload, method) { while (this.authenticating) { await new Promise((resolve) => setTimeout(resolve, 50)); } try { if (!this.accessToken || spacetime.now().isAfter(this.tokenExpire)) { this.logger.debug('Refreshing token'); await this._authenticate(); } return await command(this.accessToken, commandName, payload, this.logger, this.serviceEndpoint, method); } catch (e) { const errorType = JSON.parse(JSON.stringify(e.message)); if (errorType === 'InvalidAuthTokenError') { this.logger.debug('Token expired, trying to refresh tokens'); try { await this._authenticate(); return await command(this.accessToken, commandName, payload, this.logger, this.serviceEndpoint, method); } catch (e) { this.logger.error("Couldn't perform command: " + e.message); throw e; } } else { this.logger.error("Couldn't perform command: " + e.message); throw e; } } } async _getLocalDevice(deviceId) { let device = this.devices.find((item) => item.deviceId === deviceId); if (!device) { device = await this.getDevice(deviceId); } return device; } async getHomes() { const command = 'houses'; return await this._command(command, null, 'GET'); } async getRooms(homeId) { const command = 'houses/' + homeId + '/rooms'; return await this._command(command, null, 'GET'); } async getIndependentDevices(homeId) { const command = 'houses/' + homeId + '/devices/independent?filterDevices=heatersAndSockets'; return await this._command(command, null, 'GET'); } async getHouseDevicesByType(homeId) { const command = 'houses/' + homeId + '/devices/grouped/type'; return await this._command(command, null, 'GET'); } async getDevice(deviceId) { const command = 'devices/' + deviceId + '/data'; const device = await this._command(command, null, 'GET'); if (!this.devices.find((item) => item.deviceId === device.deviceId)) { this.devices.push(device); } else { this.devices.map((item) => (item.deviceId === device.deviceId ? device : item)); } return device; } async setTemperature(deviceId, temperature) { const device = await this.getDevice(deviceId); const command = '/devices/' + deviceId + '/settings'; return await this._command( command, { deviceType: device.deviceType.parentType.name, enabled: device.isEnabled, settings: { temperature_normal: temperature, temperature_in_independent_mode: temperature, }, }, 'PATCH' ); } async setIndependentControl(deviceId, enable) { const device = await this._getLocalDevice(deviceId); const command = '/devices/' + deviceId + '/settings'; return await this._command( command, { deviceType: device.deviceType.parentType.name, enabled: true, settings: { operation_mode: enable ? device.roomId === null ? 'independent_device' : 'control_individually' : 'weekly_program', }, }, 'PATCH' ); } async setPower(deviceId, on, independentOrIndividualOperationMode) { const device = await this._getLocalDevice(deviceId); const command = '/devices/' + deviceId + '/settings'; return await this._command( command, { deviceType: device.deviceType.parentType.name, enabled: on ? true : false, settings: { operation_mode: on ? (independentOrIndividualOperationMode ? (device.roomId === null ? 'independent_device' : 'control_individually') : 'weekly_program') : 'off', }, }, 'PATCH' ); } } return Mill; }));