dirigera
Version:
A TypeScript client for IKEA's DIRIGERA smart home hub
146 lines (145 loc) • 5.66 kB
JavaScript
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();
},
};
}