UNPKG

homebridge-smartone

Version:

Homebridge plugin for SmartOne devices

204 lines (173 loc) 6.19 kB
const express = require("express"); const axios = require("axios"); const fs = require("fs"); const path = require("path"); const app = express(); const PORT = process.env.PORT || 3000; app.use(express.json()); app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`); next(); }); app.use((err, req, res, next) => { console.error('Error:', err.message); res.status(500).json({ error: 'Internal server error' }); }); const TOKEN_URL = "https://auth-us-east-1.smartone-solutions.com/connect/token"; const CLIENT_ID = "bdf1af15-da1f-4389-b3eb-111e2feb00b6"; const CLIENT_SECRET = ""; // add if required const REDIRECT_URI = "smartone://login"; const TOKENS_FILE = path.join(__dirname, "tokens.json"); function loadTokens() { if (fs.existsSync(TOKENS_FILE)) { return JSON.parse(fs.readFileSync(TOKENS_FILE, "utf-8")); } return { access_token: null, refresh_token: "", // initial refresh token expires_at: 0 }; } function saveTokens(tokens) { fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2)); } let tokens = loadTokens(); async function refreshAccessToken() { try { const params = new URLSearchParams(); params.append("client_id", CLIENT_ID); if (CLIENT_SECRET) params.append("client_secret", CLIENT_SECRET); params.append("refresh_token", tokens.refresh_token); params.append("grant_type", "refresh_token"); params.append("redirect_uri", REDIRECT_URI); params.append("scope", "offline_access openid profile email ApiGateway"); const response = await axios.post(TOKEN_URL, params.toString(), { headers: { "Content-Type": "application/x-www-form-urlencoded" } }); const data = response.data; tokens.access_token = data.access_token; tokens.refresh_token = data.refresh_token || tokens.refresh_token; tokens.expires_at = Math.floor(Date.now() / 1000) + data.expires_in; saveTokens(tokens); return tokens.access_token; } catch (err) { throw new Error(`Failed to refresh token: ${err.response?.data || err.message}`); } } async function getAccessToken() { const now = Math.floor(Date.now() / 1000); if (!tokens.access_token || now >= tokens.expires_at) { return await refreshAccessToken(); } return tokens.access_token; } app.get('/api/devices', async (req, res) => { try { const token = await getAccessToken(); const response = await axios.get("https://api-us-east-1.smartone-solutions.com/SmartDeviceService/api/devices/list", { headers: { Authorization: `Bearer ${token}` } }); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/user', async (req, res) => { try { const token = await getAccessToken(); const response = await axios.post( "https://api-us-east-1.smartone-solutions.com/UserManagementService/api/v2/User/GetBy/AccessToken", {}, { headers: { Authorization: `Bearer ${token}`, Host: "api-us-east-1.smartone-solutions.com", Accept: "application/json, text/plain, */*", "app-version": "mobile-app:1.10.4", "Content-Length": "0", "User-Agent": "ONE!/5358 CFNetwork/3860.100.1 Darwin/25.0.0", Connection: "keep-alive", }, } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/token/refresh', async (req, res) => { try { const token = await refreshAccessToken(); res.json({ access_token: token, expires_at: tokens.expires_at }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/devices/doorlock/set', async (req, res) => { try { const { state } = req.query; if (!state) { return res.status(400).json({ error: 'state is required' }); } const token = await getAccessToken(); // Get devices list to find the door lock const devicesResponse = await axios.get("https://api-us-east-1.smartone-solutions.com/SmartDeviceService/api/devices/list", { headers: { Authorization: `Bearer ${token}` } }); const doorLock = devicesResponse.data.devices.find(device => device.type === 'DoorLock'); if (!doorLock) { return res.status(404).json({ error: 'No door lock found' }); } const response = await axios.post( "https://api-us-east-1.smartone-solutions.com/SmartDeviceService/api/devices/doorLock/set", { state, deviceId: doorLock.id }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/devices/thermostat/set', async (req, res) => { try { const { coolingSetPoint } = req.query; if (coolingSetPoint === undefined) { return res.status(400).json({ error: 'coolingSetPoint are required' }); } const token = await getAccessToken(); // Get devices list to find the thermostat const devicesResponse = await axios.get("https://api-us-east-1.smartone-solutions.com/SmartDeviceService/api/devices/list", { headers: { Authorization: `Bearer ${token}` } }); const thermostat = devicesResponse.data.devices.find(device => device.type === 'Thermostat'); if (!thermostat) { return res.status(404).json({ error: 'No thermostat found' }); } const resourceId = "qLnLY1x6aYQg1If3YoucaV5F3I3MH7YrHLdE1RUB05CBcXVKH1"; const response = await axios.post( "https://api-us-east-1.smartone-solutions.com/SmartDeviceService/api/Devices/thermostat/set", { resourceId, coolingSetPoint, deviceId: thermostat.id }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } } ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });