UNPKG

dirigera

Version:

A TypeScript client for IKEA's DIRIGERA smart home hub

146 lines (145 loc) 5.66 kB
import os from 'node:os'; import { Agent } from 'node:https'; import got from 'got'; import pRetry from 'p-retry'; import { calculateCodeChallenge, CODE_CHALLENGE_METHOD, generateCodeVerifier, } from "./authCode.js"; import certificate from "./certificate.js"; import { discoverGatewayIP } from "./mdnsDiscovery.js"; import { closeWebSocket, initializeWebSocket } from "./ws.js"; import hub from "./api/hub.js"; import devices from "./api/devices.js"; import lights from "./api/lights.js"; import outlets from "./api/outlets.js"; import controllers from "./api/controllers.js"; import speakers from "./api/speakers.js"; import blinds from "./api/blinds.js"; import airPurifiers from "./api/airPurifiers.js"; import repeaters from "./api/repeaters.js"; import motionSensors from "./api/motionSensors.js"; import occupancySensors from "./api/occupancySensors.js"; import lightSensors from "./api/lightSensors.js"; import environmentSensors from "./api/environmentSensors.js"; import openCloseSensors from "./api/openCloseSensors.js"; import waterSensors from "./api/waterSensors.js"; import deviceSets from "./api/deviceSets.js"; import rooms from "./api/rooms.js"; import scenes from "./api/scenes.js"; import music from "./api/music.js"; import users from "./api/users.js"; export async function createDirigeraClient({ gatewayIP, accessToken, rejectUnauthorized = true, } = {}) { const ip = gatewayIP ?? (await discoverGatewayIP()); const gotInstance = got.extend({ prefixUrl: `https://${ip}:8443/v1`, headers: { ...(accessToken ? { authorization: `Bearer ${accessToken}` } : null), }, ...(rejectUnauthorized ? { agent: { https: new Agent({ ca: [certificate], checkServerIdentity: () => undefined, }), }, } : { https: { rejectUnauthorized: false } }), hooks: { beforeError: [ (error) => { if (error.code === 'ERR_NON_2XX_3XX_RESPONSE' && error?.response?.body) { error.name = 'DirigeraError'; error.message = `${error.response.body}`; } return error; }, ], }, }); return { async authenticate({ verbose = false } = {}) { const codeVerifier = generateCodeVerifier(); const codeChallenge = calculateCodeChallenge(codeVerifier); const { code } = await gotInstance .get(`oauth/authorize`, { searchParams: { audience: 'homesmart.local', response_type: 'code', code_challenge: codeChallenge, code_challenge_method: CODE_CHALLENGE_METHOD, }, }) .json(); if (verbose) { console.log('Press the Action Button on the bottom of your Dirigera Hub within 60 seconds.'); } return await pRetry(async () => { const { access_token } = await gotInstance .post(`oauth/token`, { form: { code, name: os.hostname(), grant_type: 'authorization_code', code_verifier: codeVerifier, }, }) .json(); if (!access_token) { throw new Error('Access token is missing.'); } if (verbose) { process.stdout.write('\n'); } return access_token; }, { retries: 59, minTimeout: 1000, factor: 1, onFailedAttempt: (context) => { if (verbose) { process.stdout.write(`\rTime left: ${String(context.retriesLeft).padStart(2, ' ')} seconds`); if (context.retriesLeft === 0) { process.stdout.write('\n'); } } }, }); }, async home() { return await gotInstance.get(`home`).json(); }, hub: hub(gotInstance), devices: devices(gotInstance), lights: lights(gotInstance), outlets: outlets(gotInstance), controllers: controllers(gotInstance), speakers: speakers(gotInstance), blinds: blinds(gotInstance), airPurifiers: airPurifiers(gotInstance), repeaters: repeaters(gotInstance), motionSensors: motionSensors(gotInstance), occupancySensors: occupancySensors(gotInstance), lightSensors: lightSensors(gotInstance), environmentSensors: environmentSensors(gotInstance), openCloseSensors: openCloseSensors(gotInstance), waterSensors: waterSensors(gotInstance), deviceSets: deviceSets(gotInstance), rooms: rooms(gotInstance), scenes: scenes(gotInstance), music: music(gotInstance), users: users(gotInstance), startListeningForUpdates(callback) { if (!accessToken) { throw new Error('Access token is missing.'); } initializeWebSocket({ ip, accessToken, callback, }); }, stopListeningForUpdates() { closeWebSocket(); }, }; }