UNPKG

tedee-api-client

Version:
440 lines (371 loc) 16.5 kB
import axios from 'axios' import qs from 'qs' import { TokenResponse } from './models/token-response' import { Lock } from './models/lock' import { LocksResponse } from './models/locks-response' import { LockSync } from './models/lock-sync' import { LocksSyncResponse } from './models/locks-sync-response' import { LockSyncResponse } from './models/lock-sync-response' import { OperationResponse } from './models/operation-response' import { OperationStatus } from './models/operation-status' import { DeviceActivity, DeviceActivityResponse } from './models/device-activity-response' import { createLogger, Logger, transports } from 'winston' import { Configuration } from '../configuration/configuration' const TOKEN_URL = 'https://tedee.b2clogin.com/tedee.onmicrosoft.com/oauth2/v2.0/token?p=B2C_1_SignIn_Ropc' const API_BASE_URL = 'https://api.tedee.com/api/v1.18' /** * Represents a client that communicates with the Tedee HTTP API. */ export class TedeeApiClient { /** * Initializes a new TedeeApiClient instance. * @param configuration The client configuration. */ constructor(private configuration: Configuration, logger?: Logger) { this.logger = logger || createLogger({level: 'warn', transports: [new transports.Console()]}) } /** * Contains the expiration date time for the access token. */ private expirationDateTime: Date|null = null /** * Contains the currently active access token. */ private accessToken: string|null = null private logger: Logger /** * Gets the access token either from cache or from the token endpoint. * @param retryCount The number of retries before reporting failure. */ private async getAccessTokenAsync(retryCount?: number): Promise<string> { this.logger.debug(`Getting access token...`) // Checks if the current access token is expired if (this.expirationDateTime && this.expirationDateTime.getTime() < new Date().getTime() - (120 * 1000)) { this.expirationDateTime = null this.accessToken = null } // Checks if a cached access token exists if (this.accessToken) { this.logger.debug(`Access token cached.`) return this.accessToken } // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumTokenRetry } // Sends the HTTP request to get a new access token try { const response = await axios.post<TokenResponse>(TOKEN_URL, qs.stringify({ grant_type: 'password', username: this.configuration.emailAddress, password: this.configuration.password, scope: 'openid 02106b82-0524-4fd3-ac57-af774f340979', client_id: '02106b82-0524-4fd3-ac57-af774f340979', response_type: 'token id_token' }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) // Stores the access token this.accessToken = response.data.access_token this.expirationDateTime = new Date(new Date().getTime() + (response.data.expires_in * 1000)) // Returns the access token this.logger.debug(`Access token received from server.`) return this.accessToken } catch (e) { this.logger.warn(`Error while retrieving access token: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.tokenRetryInterval)) return await this.getAccessTokenAsync(retryCount) } else { throw e } } } /** * Gets all locks from the API. * @param retryCount The number of retries before reporting failure. */ public async getLocksAsync(retryCount?: number): Promise<Array<Lock>> { this.logger.debug(`Getting locks from API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to get the locks try { const response = await axios.get<LocksResponse>(`${API_BASE_URL}/my/lock`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.debug(`Locks received from API.`) return response.data.result } catch (e) { this.logger.warn(`Error while getting locks from API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) return await this.getLocksAsync(retryCount) } else { throw e } } } public async getLockByNameAsync(name: string, retryCount?: number): Promise<Lock|undefined> { return (await this.getLocksAsync(retryCount)).find(l => l.name === name) } /** * Syncs the recent changes of all locks from the API. * @param retryCount The number of retries before reporting failure. */ public async syncLocksAsync(retryCount?: number): Promise<Array<LockSync>> { this.logger.debug(`Syncing locks from API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to sync the locks try { const response = await axios.get<LocksSyncResponse>(`${API_BASE_URL}/my/lock/sync`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.debug(`Locks synced from API.`) return response.data.result } catch (e) { this.logger.warn(`Error while syncing locks from API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) return await this.syncLocksAsync(retryCount) } else { throw e } } } /** * Syncs the recent changes of a single lock from the API. * @param id The ID of the lock. * @param retryCount The number of retries before reporting failure. */ public async syncLockAsync(id: number, retryCount?: number): Promise<LockSync> { this.logger.debug(`Syncing lock with ID ${id} from API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to sync the locks try { const response = await axios.get<LockSyncResponse>(`${API_BASE_URL}/my/lock/${id}/sync`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.debug(`Lock with ID ${id} synced from API.`) return response.data.result } catch (e) { this.logger.warn(`Error while syncing lock with ID ${id} from API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) return await this.syncLockAsync(id, retryCount) } else { throw e } } } /** * Closes the lock with the specified ID. * @param lock The lock which should be closed. * @param retryCount The number of retries before reporting failure. */ public async closeAsync(lock: Lock, retryCount?: number): Promise<void> { this.logger.debug(`[${lock.name}] Closing via API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to set the box status try { let response = await axios.post<OperationResponse>(`${API_BASE_URL}/my/lock/close`, { deviceId: lock.id }, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) // Waits for the operation to complete while (response.data.result.status !== OperationStatus.Completed) { await new Promise<void>(r => setTimeout(() => r(), 1000)) response = await axios.get<OperationResponse>(`${API_BASE_URL}/my/device/operation/${response.data.result.operationId}`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.info(`[${lock.name}] Waiting for close operation to be completed.`) } this.logger.info(`[${lock.name}] Closed via API.`) } catch (e) { this.logger.warn(`[${lock.name}] Error while closing via API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) await this.closeAsync(lock, retryCount) } else { throw e } } } /** * Opens the lock with the specified ID. * @param id The ID of the lock. * @param retryCount The number of retries before reporting failure. */ public async openAsync(lock: Lock, retryCount?: number): Promise<void> { this.logger.debug(`[${lock.name}] Opening via API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to set the box status try { let response = await axios.post<OperationResponse>(`${API_BASE_URL}/my/lock/open`, { deviceId: lock.id }, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) // Waits for the operation to complete while (response.data.result.status !== OperationStatus.Completed) { await new Promise<void>(r => setTimeout(() => r(), 1000)) response = await axios.get<OperationResponse>(`${API_BASE_URL}/my/device/operation/${response.data.result.operationId}`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.info(`[${lock.name}] Waiting for open operation to be completed.`) } this.logger.info(`[${lock.name}] Opened via API.`) } catch (e) { this.logger.warn(`[${lock.name}] Error while opening via API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) await this.openAsync(lock, retryCount) } else { throw e } } } /** * Pulls the spring on the lock with the specified ID. * @param id The ID of the lock. * @param retryCount The number of retries before reporting failure. */ public async pullSpringAsync(lock: Lock, retryCount?: number): Promise<void> { this.logger.debug(`[${lock.name}] Pulling spring via API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to set the box status try { let response = await axios.post<OperationResponse>(`${API_BASE_URL}/my/lock/pull-spring`, { deviceId: lock.id }, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) // Waits for the operation to complete while (response.data.result.status !== OperationStatus.Completed) { await new Promise<void>(r => setTimeout(() => r(), 1000)) response = await axios.get<OperationResponse>(`${API_BASE_URL}/my/device/operation/${response.data.result.operationId}`, { headers: { Authorization: `Bearer ${accessToken}` } }) this.logger.debug(JSON.stringify(response.data)) this.logger.info(`[${lock.name}] Waiting for pull spring operation to be completed.`) } this.logger.info(`[${lock.name}] Pulled spring via API.`) } catch (e) { this.logger.warn(`[${lock.name}] Error while pulling spring via API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) await this.pullSpringAsync(lock, retryCount) } else { throw e } } } /** * * @param id The ID of the lock. * @param retryCount The number of retries before reporting failure. */ public async getDeviceActivityAsync(lock: Lock, count: number, retryCount?: number): Promise<Array<DeviceActivity>> { this.logger.debug(`[${lock.name}] Fetching device activity via API...`) // Set the default retry count if (!retryCount) { retryCount = this.configuration.maximumApiRetry } // Gets the access token const accessToken = await this.getAccessTokenAsync() // Sends the HTTP request to set the box status try { let response = await axios.get<DeviceActivityResponse>(`${API_BASE_URL}/my/deviceactivity?deviceId=${lock.id}&elements=${count}`, { headers: { Authorization: `Bearer ${accessToken}` } }) return response.data.result } catch (e) { this.logger.warn(`[${lock.name}] Error while fetching device activity via API: ${e}`) // Decreased the retry count and tries again retryCount-- if (retryCount > 0) { await new Promise(resolve => setTimeout(resolve, this.configuration.apiRetryInterval)) return await this.getDeviceActivityAsync(lock, retryCount) } else { throw e } } } /** * * @param id The ID of the lock. * @param retryCount The number of retries before reporting failure. */ public async getLatestDeviceActivityAsync(lock: Lock, retryCount?: number): Promise<DeviceActivity | null> { const activities = await this.getDeviceActivityAsync(lock, 1, retryCount) return activities[0] || null } }