homebridge-omlet 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,309 @@
1
+ const { HomebridgePluginUiServer, RequestError } = require('@homebridge/plugin-ui-utils');
2
+ const https = require('https');
3
+
4
+ class OmletPluginUiServer extends HomebridgePluginUiServer {
5
+ constructor() {
6
+ super();
7
+
8
+ this.onRequest('/login', this.handleLogin.bind(this));
9
+ this.onRequest('/discover', this.handleDiscover.bind(this));
10
+ this.onRequest('/validate', this.handleValidate.bind(this));
11
+
12
+ this.ready();
13
+ }
14
+
15
+ async handleValidate(payload) {
16
+ const { token, deviceId, debug } = payload;
17
+
18
+ if (!token) {
19
+ throw new RequestError('Bearer token is required', { status: 400 });
20
+ }
21
+
22
+ try {
23
+ const devices = await this.discoverOmletDevices(token, debug);
24
+
25
+ let deviceValid = false;
26
+ if (deviceId) {
27
+ deviceValid = devices.some(device => device.deviceId === deviceId);
28
+ }
29
+
30
+ return {
31
+ success: true,
32
+ tokenValid: true,
33
+ deviceValid: deviceValid,
34
+ devices: devices
35
+ };
36
+ } catch (error) {
37
+ // Token is invalid if we get 401/403
38
+ if (error.message.includes('HTTP 401') || error.message.includes('HTTP 403')) {
39
+ return {
40
+ success: false,
41
+ tokenValid: false,
42
+ deviceValid: false,
43
+ devices: []
44
+ };
45
+ }
46
+
47
+ throw new RequestError(`Validation failed: ${error.message}`, { status: 500 });
48
+ }
49
+ }
50
+
51
+ async handleLogin(payload) {
52
+ const { email, password, countryCode, debug } = payload;
53
+
54
+ if (!email || !password || !countryCode) {
55
+ throw new RequestError('Email, password, and country code are required', { status: 400 });
56
+ }
57
+
58
+ try {
59
+ const token = await this.performOmletLogin(email, password, countryCode, debug);
60
+
61
+ return {
62
+ success: true,
63
+ token: token
64
+ };
65
+ } catch (error) {
66
+ throw new RequestError(`Login failed: ${error.message}`, { status: 401 });
67
+ }
68
+ }
69
+
70
+ async handleDiscover(payload) {
71
+ const { token, debug } = payload;
72
+
73
+ if (!token) {
74
+ throw new RequestError('Bearer token is required', { status: 400 });
75
+ }
76
+
77
+ try {
78
+ const devices = await this.discoverOmletDevices(token, debug);
79
+
80
+ return {
81
+ success: true,
82
+ devices: devices
83
+ };
84
+ } catch (error) {
85
+ throw new RequestError(`Discovery failed: ${error.message}`, { status: 500 });
86
+ }
87
+ }
88
+
89
+ performOmletLogin(email, password, countryCode, debug = false) {
90
+ return new Promise((resolve, reject) => {
91
+ const postData = JSON.stringify({
92
+ emailAddress: email,
93
+ password: password,
94
+ cc: countryCode
95
+ });
96
+
97
+ const options = {
98
+ hostname: 'x107.omlet.co.uk',
99
+ port: 443,
100
+ path: '/api/v1/login',
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'Content-Length': Buffer.byteLength(postData)
105
+ }
106
+ };
107
+
108
+ // LOG REQUEST (only if debug enabled)
109
+ if (debug) {
110
+ console.log('=== OMLET LOGIN REQUEST ===');
111
+ console.log('URL:', `https://${options.hostname}${options.path}`);
112
+ console.log('Method:', options.method);
113
+ console.log('Headers:', JSON.stringify(options.headers, null, 2));
114
+ console.log('Body:', postData);
115
+ console.log('===========================');
116
+ }
117
+
118
+ const req = https.request(options, (res) => {
119
+ let data = '';
120
+
121
+ // LOG RESPONSE HEADERS (only if debug enabled)
122
+ if (debug) {
123
+ console.log('=== OMLET LOGIN RESPONSE ===');
124
+ console.log('Status Code:', res.statusCode);
125
+ console.log('Status Message:', res.statusMessage);
126
+ console.log('Headers:', JSON.stringify(res.headers, null, 2));
127
+ }
128
+
129
+ res.on('data', (chunk) => {
130
+ data += chunk;
131
+ });
132
+
133
+ res.on('end', () => {
134
+ // LOG RESPONSE BODY (only if debug enabled)
135
+ if (debug) {
136
+ console.log('Response Body:', data);
137
+ console.log('Body Length:', data.length, 'bytes');
138
+ console.log('============================');
139
+ }
140
+
141
+ if (res.statusCode === 200) {
142
+ try {
143
+ const response = JSON.parse(data);
144
+
145
+ if (debug) {
146
+ console.log('=== PARSED JSON ===');
147
+ console.log(JSON.stringify(response, null, 2));
148
+ console.log('===================');
149
+ }
150
+
151
+ const token = response.data?.api_key ||
152
+ response.data?.apiKey ||
153
+ response.data?.token ||
154
+ response.api_key ||
155
+ response.apiKey ||
156
+ response.token;
157
+
158
+ if (token) {
159
+ if (debug) {
160
+ console.log('✓ Token extracted:', token.substring(0, 20) + '...');
161
+ }
162
+ resolve(token);
163
+ } else {
164
+ if (debug) {
165
+ console.log('✗ NO TOKEN FOUND IN RESPONSE');
166
+ console.log('Response structure:', Object.keys(response));
167
+ if (response.data) {
168
+ console.log('Response.data structure:', Object.keys(response.data));
169
+ }
170
+ }
171
+ reject(new Error(`No API key found. Response keys: ${Object.keys(response).join(', ')}`));
172
+ }
173
+ } catch (e) {
174
+ if (debug) {
175
+ console.log('✗ JSON PARSE ERROR:', e.message);
176
+ }
177
+ reject(new Error(`JSON parse failed: ${e.message}. Raw data: ${data.substring(0, 200)}`));
178
+ }
179
+ } else {
180
+ if (debug) {
181
+ console.log('✗ NON-200 STATUS CODE');
182
+ }
183
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
184
+ }
185
+ });
186
+ });
187
+
188
+ req.on('error', (error) => {
189
+ if (debug) {
190
+ console.log('✗ REQUEST ERROR:', error.message);
191
+ }
192
+ reject(error);
193
+ });
194
+
195
+ req.write(postData);
196
+ req.end();
197
+ });
198
+ }
199
+
200
+ discoverOmletDevices(token, debug = false) {
201
+ return new Promise((resolve, reject) => {
202
+ const options = {
203
+ hostname: 'x107.omlet.co.uk',
204
+ port: 443,
205
+ path: '/api/v1/device',
206
+ method: 'GET',
207
+ headers: {
208
+ 'Authorization': `Bearer ${token}`
209
+ }
210
+ };
211
+
212
+ // LOG REQUEST (only if debug enabled)
213
+ if (debug) {
214
+ console.log('=== OMLET DISCOVERY REQUEST ===');
215
+ console.log('URL:', `https://${options.hostname}${options.path}`);
216
+ console.log('Method:', options.method);
217
+ console.log('Headers:', JSON.stringify({
218
+ ...options.headers,
219
+ 'Authorization': 'Bearer ' + token.substring(0, 20) + '...'
220
+ }, null, 2));
221
+ console.log('===============================');
222
+ }
223
+
224
+ const req = https.request(options, (res) => {
225
+ let data = '';
226
+
227
+ // LOG RESPONSE HEADERS (only if debug enabled)
228
+ if (debug) {
229
+ console.log('=== OMLET DISCOVERY RESPONSE ===');
230
+ console.log('Status Code:', res.statusCode);
231
+ console.log('Status Message:', res.statusMessage);
232
+ console.log('Headers:', JSON.stringify(res.headers, null, 2));
233
+ }
234
+
235
+ res.on('data', (chunk) => {
236
+ data += chunk;
237
+ });
238
+
239
+ res.on('end', () => {
240
+ // LOG RESPONSE BODY (only if debug enabled)
241
+ if (debug) {
242
+ console.log('Response Body:', data);
243
+ console.log('Body Length:', data.length, 'bytes');
244
+ console.log('================================');
245
+ }
246
+
247
+ if (res.statusCode === 200) {
248
+ try {
249
+ const response = JSON.parse(data);
250
+
251
+ if (debug) {
252
+ console.log('=== PARSED JSON ===');
253
+ console.log(JSON.stringify(response, null, 2));
254
+ console.log('===================');
255
+ }
256
+
257
+ // Check if response is directly an array OR has a data property with an array
258
+ const devicesArray = Array.isArray(response) ? response :
259
+ (response.data && Array.isArray(response.data)) ? response.data :
260
+ null;
261
+
262
+ if (devicesArray) {
263
+ const devices = devicesArray.map(device => ({
264
+ deviceId: device.deviceId,
265
+ name: device.name || 'Unknown Device',
266
+ type: device.deviceType || 'Unknown'
267
+ }));
268
+ if (debug) {
269
+ console.log('✓ Devices extracted:', devices.length);
270
+ }
271
+ resolve(devices);
272
+ } else {
273
+ if (debug) {
274
+ console.log('✗ NO DEVICES ARRAY FOUND');
275
+ console.log('Response is array?', Array.isArray(response));
276
+ console.log('Response structure:', typeof response === 'object' ? Object.keys(response) : typeof response);
277
+ }
278
+ reject(new Error(`No devices array found in response`));
279
+ }
280
+ } catch (e) {
281
+ if (debug) {
282
+ console.log('✗ JSON PARSE ERROR:', e.message);
283
+ }
284
+ reject(new Error(`JSON parse failed: ${e.message}. Raw data: ${data.substring(0, 200)}`));
285
+ }
286
+ } else {
287
+ if (debug) {
288
+ console.log('✗ NON-200 STATUS CODE');
289
+ }
290
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
291
+ }
292
+ });
293
+ });
294
+
295
+ req.on('error', (error) => {
296
+ if (debug) {
297
+ console.log('✗ REQUEST ERROR:', error.message);
298
+ }
299
+ reject(error);
300
+ });
301
+
302
+ req.end();
303
+ });
304
+ }
305
+ }
306
+
307
+ (() => {
308
+ return new OmletPluginUiServer();
309
+ })();