UNPKG

homebridge-fujitsu-airstage

Version:

A Homebridge plugin to control devices that use the Fujitsu Airstage API.

369 lines (304 loc) 10.1 kB
'use strict'; const crypto = require('node:crypto'); const https = require('node:https'); const querystring = require('node:querystring'); const constants = require('./constants'); class Client { constructor( region, country, language, osVersion = null, userAgent = null, accessToken = null, accessTokenExpiry = null, refreshToken = null ) { if (typeof accessTokenExpiry === 'string') { accessTokenExpiry = new Date(accessTokenExpiry); } this.region = region; this.country = country; this.language = language; this.osVersion = osVersion || constants.DEFAULT_OS_VERSION; this.userAgent = userAgent || constants.DEFAULT_USER_AGENT; this.accessToken = accessToken; this.accessTokenExpiry = accessTokenExpiry; this.refreshToken = refreshToken; } // POST /apiv1/users/sign_in postUsersSignIn(email, password, callback) { const requestBody = { 'user': { 'country': this.country, 'deviceToken': this._generateDeviceToken(), 'email': email, 'language': this.language, 'osVersion': this.osVersion, 'password': password, 'ssid': this._generateSsid() } }; this._makePostRequest( constants.PATH_USERS_SIGN_IN, requestBody, (function(result) { this._storeAccessToken(result); callback(result); }).bind(this) ); } // POST /apiv1/users/me/refresh_token postUsersMeRefreshToken(refreshToken, callback) { const requestBody = { 'user': { 'refreshToken': refreshToken } }; this._makePostRequest( constants.PATH_USERS_ME_REFRESH_TOKEN, requestBody, (function(result) { this._storeAccessToken(result); callback(result); }).bind(this) ); } // GET /apiv1/users/me getUsersMe(callback) { this._makeGetRequest( constants.PATH_USERS_ME, null, callback ); } // PUT /apiv1/users/me putUsersMe(parameterName, parameterValue, callback) { let requestBody = { 'user': {} }; requestBody['user'][parameterName] = parameterValue; this._makePutRequest( constants.PATH_USERS_ME, requestBody, callback ); } // GET /apiv1/devices/all getDevicesAll(limit, callback) { const params = { 'limit': limit || constants.DEFAULT_DEVICES_ALL_LIMIT }; this._makeGetRequest( constants.PATH_DEVICES_ALL, params, callback ); } // GET /apiv1/devices/all/authorize_request/type getDevicesAllAuthorizeRequestType(callback) { this._makeGetRequest( constants.PATH_DEVICES_ALL_AUTHORIZE_REQUEST_TYPE, null, callback ); } // GET /apiv1/devices/[deviceId] getDevice(deviceId, callback) { const path = constants.PATH_DEVICES_ONE .replace('%1', deviceId); this._makeGetRequest( path, null, callback ); } // POST /apiv1/devices/[deviceId]/set_parameters_request postDevicesSetParametersRequest(deviceId, deviceSubId, parameterName, parameterValue, callback) { const path = constants.PATH_DEVICES_SET_PARAMETERS_REQUEST .replace('%1', deviceId); const requestBody = { 'deviceSubId': deviceSubId, 'parameters': [ { 'desiredValue': parameterValue, 'name': parameterName } ] }; this._makePostRequest( path, requestBody, callback ); } // GET /apiv1/devices/[deviceId]/requests getDevicesRequests(deviceId, callback) { const path = constants.PATH_DEVICES_REQUESTS_ALL .replace('%1', deviceId); this._makeGetRequest( path, null, callback ); } // GET /apiv1/devices/[deviceId]/requests/[requestId] getDevicesRequest(deviceId, requestId, callback) { const path = constants.PATH_DEVICES_REQUESTS_ONE .replace('%1', deviceId) .replace('%2', requestId); this._makeGetRequest( path, null, callback ); } // GET /apiv1/groups getGroups(callback) { this._makeGetRequest( constants.PATH_GROUPS, null, callback ); } _makePostRequest(path, requestBody, callback) { this._makeRequestWithRequestBody( constants.REQUEST_METHOD_POST, path, requestBody, callback ); } _makePutRequest(path, requestBody, callback) { this._makeRequestWithRequestBody( constants.REQUEST_METHOD_PUT, path, requestBody, callback ); } _makeGetRequest(path, params, callback) { let queryStringParams = ''; let request = null; let requestOptions = null; let requestHeaders = structuredClone(constants.REQUEST_HEADERS_GET); if (params !== null) { queryStringParams = querystring.stringify(params); } if (queryStringParams !== '') { path = path + '?' + queryStringParams; } requestHeaders[constants.REQUEST_HEADER_USER_AGENT] = this.userAgent; if (this.accessToken) { requestHeaders[constants.REQUEST_HEADER_AUTHORIZATION] = 'Bearer ' + this.accessToken; } else { const result = structuredClone(constants.REQUEST_RESULT); result.error = 'Access token not set'; return callback(result); } requestOptions = { 'hostname': null, 'path': path, 'method': constants.REQUEST_METHOD_GET, 'headers': requestHeaders }; this._makeHttpsRequest(requestOptions, null, callback); } _makeRequestWithRequestBody(method, path, requestBody, callback) { const requestBodyJson = JSON.stringify(requestBody); let request = null; let requestOptions = null; let requestHeaders = structuredClone(constants.REQUEST_HEADERS_POST_PUT); requestHeaders[constants.REQUEST_HEADER_USER_AGENT] = this.userAgent; requestHeaders[constants.REQUEST_HEADER_CONTENT_LENGTH] = requestBodyJson.length; if (this.accessToken) { requestHeaders[constants.REQUEST_HEADER_AUTHORIZATION] = 'Bearer ' + this.accessToken; } else if (path !== constants.PATH_USERS_SIGN_IN) { const result = structuredClone(constants.REQUEST_RESULT); result.error = 'Access token not set'; return callback(result); } requestOptions = { 'hostname': null, 'path': path, 'method': method, 'headers': requestHeaders, 'timeout': 30000 // 30 seconds }; this._makeHttpsRequest(requestOptions, requestBodyJson, callback); } _makeHttpsRequest(requestOptions, requestBodyJson, callback) { const hostname = this._getHostname(); let result = structuredClone(constants.REQUEST_RESULT); if (hostname === null) { result.error = 'Could not determine hostname for region: ' + this.region; return callback(result); } requestOptions.hostname = hostname; const request = https.request(requestOptions, (response) => { result.statusCode = response.statusCode; response.on('data', (chunk) => { result.response += chunk; }); response.on('end', () => { if (result.response) { result.response = JSON.parse(result.response); } callback(result); }); }).on('error', (error) => { result.error = error; callback(result); }).on('timeout', () => { result.error = 'Request timeout'; callback(result); request.destroy(); }); if (requestBodyJson) { request.write(requestBodyJson); } request.end(); } _storeAccessToken(result) { let accessToken = null; let accessTokenExpiry = null; let refreshToken = null; if (result.response) { if (result.response.accessToken) { accessToken = result.response.accessToken; } if (result.response.expiresIn) { accessTokenExpiry = new Date(); accessTokenExpiry.setSeconds( accessTokenExpiry.getSeconds() + result.response.expiresIn ); } if (result.response.refreshToken) { refreshToken = result.response.refreshToken; } } this.accessToken = accessToken; this.accessTokenExpiry = accessTokenExpiry; this.refreshToken = refreshToken; } _generateDeviceToken() { return ( crypto.randomBytes(11).toString('hex') + ':' + crypto.randomBytes(70).toString('hex') ); } _generateSsid() { return crypto.randomUUID(); } _getHostname() { let hostname = null; if (this.region === constants.REGION_US) { hostname = constants.HOSTNAME_US; } else if (this.region === constants.REGION_EU) { hostname = constants.HOSTNAME_EU; } return hostname; } } module.exports = Client;