UNPKG

homebridge-pura

Version:

Homebridge plugin for Pura smart fragrance diffusers

200 lines 6.8 kB
/** * Pura API Client * Based on pypura library: https://github.com/natekspencer/pypura */ import { CognitoUserPool, CognitoUser, AuthenticationDetails, } from 'amazon-cognito-identity-js'; import fetch from 'node-fetch'; // Constants from pypura const USER_POOL_ID = 'us-east-1_LaB718hYv'; // Base64 decoded from pypura const CLIENT_ID = '4ie4kbat0jb5iljfbaalsiqf9j'; // Base64 decoded from pypura const BASE_URL = 'https://trypura.io/mobile/api/'; export class PuraApi { userPool; cognitoUser = null; session = null; log; constructor(log) { this.log = log; this.userPool = new CognitoUserPool({ UserPoolId: USER_POOL_ID, ClientId: CLIENT_ID, }); } /** * Authenticate with Pura API */ async authenticate(username, password) { return new Promise((resolve, reject) => { const authenticationDetails = new AuthenticationDetails({ Username: username, Password: password, }); this.cognitoUser = new CognitoUser({ Username: username, Pool: this.userPool, }); this.cognitoUser.authenticateUser(authenticationDetails, { onSuccess: (session) => { this.session = session; this.log.debug('Pura authentication successful'); resolve({ accessToken: session.getAccessToken().getJwtToken(), idToken: session.getIdToken().getJwtToken(), refreshToken: session.getRefreshToken().getToken(), }); }, onFailure: (err) => { this.log.error('Pura authentication failed:', err.message); reject(new Error(`Pura authentication failed: ${err.message}`)); }, }); }); } /** * Refresh authentication tokens */ async refreshToken() { if (!this.cognitoUser || !this.session) { throw new Error('Not authenticated'); } return new Promise((resolve, reject) => { const refreshToken = this.session.getRefreshToken(); this.cognitoUser.refreshSession(refreshToken, (err, session) => { if (err) { this.log.error('Token refresh failed:', err.message); reject(new Error(`Token refresh failed: ${err.message}`)); return; } this.session = session; this.log.debug('Pura token refresh successful'); resolve({ accessToken: session.getAccessToken().getJwtToken(), idToken: session.getIdToken().getJwtToken(), refreshToken: session.getRefreshToken().getToken(), }); }); }); } /** * Get authorization header for API requests */ getAuthHeader() { if (!this.session) { throw new Error('Not authenticated'); } return `Bearer ${this.session.getIdToken().getJwtToken()}`; } /** * Make authenticated API request */ async makeRequest(method, endpoint, data) { const url = new URL(endpoint, BASE_URL).toString(); const options = { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json', Authorization: this.getAuthHeader(), }, // timeout: 10000, // Not supported in fetch }; if (data && method.toLowerCase() !== 'get') { options.body = JSON.stringify(data); } this.log.debug(`Making ${method} request to ${url}`); try { const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); this.log.error(`API request failed: ${response.status} - ${errorText}`); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const result = await response.json(); this.log.debug('API response:', result); return result; } catch (error) { this.log.error('API request error:', error); throw error; } } /** * Get all devices */ async getDevices() { try { const response = await this.makeRequest('GET', 'v2/users/devices'); return response.devices || []; } catch (error) { this.log.error('Failed to get devices:', error); throw error; } } /** * Set device intensity */ async setIntensity(deviceId, bay, intensity) { try { const response = await this.makeRequest('POST', `devices/${deviceId}/intensity`, { bay, controller: 'mobile', intensity, }); return response.success === true; } catch (error) { this.log.error(`Failed to set intensity for device ${deviceId}:`, error); return false; } } /** * Set always on mode */ async setAlwaysOn(deviceId, bay) { try { const response = await this.makeRequest('POST', `devices/${deviceId}/always-on`, { bay, }); return response.success === true; } catch (error) { this.log.error(`Failed to set always on for device ${deviceId}:`, error); return false; } } /** * Stop all diffusion */ async stopAll(deviceId) { try { const response = await this.makeRequest('POST', `devices/${deviceId}/stop-all`); return response.success === true; } catch (error) { this.log.error(`Failed to stop all for device ${deviceId}:`, error); return false; } } /** * Set timer */ async setTimer(deviceId, bay, intensity, durationMinutes) { try { const start = Math.floor(Date.now() / 1000); const end = start + (durationMinutes * 60); const response = await this.makeRequest('POST', `devices/${deviceId}/timer`, { bay, intensity, start, end, validateOverride: true, }); return response.success === true; } catch (error) { this.log.error(`Failed to set timer for device ${deviceId}:`, error); return false; } } } //# sourceMappingURL=puraApi.js.map