@homebridge-plugins/homebridge-resideo 3.0.3-beta.6 → 3.0.3-beta.7

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.
@@ -48,6 +48,25 @@
48
48
  Support
49
49
  </button>
50
50
  </div>
51
+ <div id="pageFirstRun" class="mt-4" style="display: none;">
52
+ <div class="alert alert-info">
53
+ <h5>Getting Started</h5>
54
+ <p class="mb-2">Welcome to homebridge-resideo! Let's get your account configured.</p>
55
+ <p class="mb-0">Please enter your Resideo API credentials to continue.</p>
56
+ </div>
57
+ <div class="form-group">
58
+ <label for="firstRunKey">Consumer Key <span class="text-danger">*</span></label>
59
+ <input type="text" class="form-control" id="firstRunKey">
60
+ </div>
61
+ <div class="form-group">
62
+ <label for="firstRunSecret">Consumer Secret <span class="text-danger">*</span></label>
63
+ <input type="text" class="form-control" id="firstRunSecret">
64
+ </div>
65
+ <div id="loginError">&nbsp;</div>
66
+ <div class="form-group text-center">
67
+ <button type="button" class="btn btn-primary" id="firstRun">Setup Account &rarr;</button>
68
+ </div>
69
+ </div>
51
70
  <div id="pageAccount" class="mt-4" style="display: none;">
52
71
  <div class="alert alert-warning" style="display: none;" id="validateForm">
53
72
  Please enter both values
@@ -69,15 +88,40 @@
69
88
  <button type="button" class="btn btn-danger" id="unLinkButton">Unlink Account &rarr;</button>
70
89
  </div>
71
90
  </div>
91
+ <!-- Dynamic device configuration UI will be rendered here by webUI -->
92
+ <div id="pageFeatureOptions" class="mt-4" style="display: none;">
93
+ </div>
72
94
  <div id="pageDevices" class="mt-4" style="display: none;">
95
+ <div class="alert alert-info" id="deviceConfigInfo">
96
+ <h5>Device Configuration Made Easy</h5>
97
+ <p class="mb-2">This section shows devices available from your Resideo account and provides dynamic configuration options.</p>
98
+ <p class="mb-0">Select a device below to view its details and configure its settings using the feature options system.</p>
99
+ </div>
73
100
  <div id="deviceInfo">
74
101
  <h5>Cached Devices</h5>
75
102
  <p>These are devices already configured in Homebridge:</p>
76
103
  <form>
77
104
  <div class="form-group">
105
+ <label for="deviceSelect"><strong>Available Devices:</strong></label>
78
106
  <select class="form-control" id="deviceSelect"></select>
79
107
  </div>
80
108
  </form>
109
+ <div id="quickConfigSection" style="display: none;" class="mt-3">
110
+ <div class="alert alert-success">
111
+ <h6>Quick Configuration</h6>
112
+ <p class="mb-2">Copy the device ID below and paste it into the plugin settings:</p>
113
+ <div class="input-group">
114
+ <input type="text" class="form-control" id="quickDeviceID" readonly>
115
+ <div class="input-group-append">
116
+ <button class="btn btn-outline-secondary" type="button" id="copyDeviceID">Copy</button>
117
+ </div>
118
+ </div>
119
+ <small class="form-text text-muted mt-2">
120
+ Recommended device class: <span id="recommendedClass"></span><br>
121
+ Suggested device model: <span id="suggestedModel"></span>
122
+ </small>
123
+ </div>
124
+ </div>
81
125
  <table class="table w-100" id="deviceTable" style="display: none;">
82
126
  <thead>
83
127
  <tr class="table-active">
@@ -186,332 +230,188 @@
186
230
  </li>
187
231
  </ul>
188
232
  </div>
189
- <script>
190
- ; (async () => {
191
- try {
192
- const currentConfig = await homebridge.getPluginConfig()
193
- const hostname = window.location.hostname
194
- try {
195
- await homebridge.request('Start Resideo Login Server')
196
- } catch (err) {
197
- const msg = err.message
198
- homebridge.toast.error(msg, 'HTTP Server Not Started')
233
+ <script type="module">
234
+ import { webUi } from "./lib/webUi.mjs";
235
+
236
+ // Initialize the webUI with homebridge-plugin-utils
237
+ const ui = new webUi({
238
+ featureOptions: {
239
+ getFeatureOptions: () => homebridge.request('/getFeatureOptions'),
240
+ getDevices: () => homebridge.request('/getDevices'),
241
+ infoPanel: (device) => {
242
+ return `
243
+ <div class="mt-3">
244
+ <h6>Device Information</h6>
245
+ <p><strong>Name:</strong> ${device.name}</p>
246
+ <p><strong>Device ID:</strong> ${device.deviceID}</p>
247
+ <p><strong>Type:</strong> ${device.deviceType || 'Unknown'}</p>
248
+ <p><strong>Location:</strong> ${device.locationName || 'Unknown'}</p>
249
+ <p><strong>Model:</strong> ${device.model}</p>
250
+ <p><strong>Firmware:</strong> ${device.firmwareRevision}</p>
251
+ </div>
252
+ `;
199
253
  }
200
- homebridge.addEventListener('creds-received', async event => {
201
- try {
202
- if (event.data.access && event.data.refresh) {
203
- this.popup.close()
204
- currentConfig[0].credentials = {
205
- consumerKey: event.data.key,
206
- consumerSecret: event.data.secret,
207
- accessToken: event.data.access,
208
- refreshToken: event.data.refresh
209
- }
210
- await homebridge.updatePluginConfig(currentConfig)
211
- await homebridge.savePluginConfig()
212
- homebridge.toast.success("Successfully Linked Resideo Account", "homebridge-resideo")
213
- document.getElementById('backToIntro').style.display = 'none'
214
- document.getElementById('linkButton').style.display = 'none'
215
- document.getElementById('unLinkButton').style.display = 'none'
216
- document.getElementById('linkSuccess').style.display = 'block'
217
- } else {
218
- throw new Error('no access/token received from server')
219
- }
220
- } catch (err) {
221
- console.log(err)
222
- homebridge.toast.error('Try again, or see the console message for info.', 'Error')
223
- }
224
- })
225
- copyMyText = () => {
226
- var textToCopy = document.getElementById("copyMe");
227
- textToCopy.select();
228
- document.execCommand("copy");
229
- }
230
- linkAccount = async () => {
231
- document.getElementById('validateForm').style.display = 'none'
232
- if (
233
- !document.getElementById('inputConsumerKey').value ||
234
- !document.getElementById('inputConsumerSecret').value
235
- ) {
236
- document.getElementById('validateForm').style.display = 'block'
237
- return
238
- }
239
- const w = 450;
240
- const h = 700;
241
- const y = window.top.outerHeight / 2 + window.top.screenY - (h / 2);
242
- const x = window.top.outerWidth / 2 + window.top.screenX - (w / 2);
243
- const urlToOpen = 'http://' + hostname + ':8585/start?' +
244
- 'key=' + document.getElementById('inputConsumerKey').value +
245
- '&secret=' + document.getElementById('inputConsumerSecret').value +
246
- '&host=' + encodeURI(hostname);
247
- this.popup = window.open(
248
- urlToOpen, 'honeywell-auth',
249
- 'toolbar=no, location=no, directories=no, status=no, menubar=no scrollbars=no, resizable=no, copyhistory=no, ' +
250
- 'width=' + w + ', height=' + h + ', top=' + y + ', left=' + x,
251
- )
252
- }
253
- unLinkAccount = async () => {
254
- try {
255
- document.getElementById('validateForm').style.display = 'none'
256
- if (
257
- !document.getElementById('inputConsumerKey').value ||
258
- !document.getElementById('inputConsumerSecret').value
259
- ) {
260
- document.getElementById('validateForm').style.display = 'block'
261
- return
262
- }
263
- delete currentConfig[0].credentials
264
- await homebridge.updatePluginConfig(currentConfig)
265
- await homebridge.savePluginConfig()
266
- document.getElementById('inputConsumerKey').value = ''
267
- document.getElementById('inputConsumerSecret').value = ''
268
- document.getElementById('unLinkButton').style.display = 'none'
269
- } catch (err) {
270
- console.error(err)
271
- homebridge.toast.error('Try again, or see the console message for info.', 'Error')
272
- }
273
- }
274
- showIntro = () => {
275
- const introLink = document.getElementById('introLink')
276
- document.getElementById('copyMe').value = 'http://' + hostname + ':8585/auth'
277
- introLink.addEventListener('click', () => {
278
- homebridge.showSpinner()
279
- document.getElementById('pageIntro').style.display = 'none'
280
- document.getElementById('menuWrapper').style.display = 'inline-flex'
281
- showAccount()
282
- homebridge.hideSpinner()
283
- })
284
- document.getElementById('pageAccount').style.display = 'none'
285
- document.getElementById('menuWrapper').style.display = 'none'
286
- document.getElementById('pageIntro').style.display = 'block'
287
- }
288
- showDevices = async () => {
289
- homebridge.showSpinner()
290
- homebridge.hideSchemaForm()
291
- document.getElementById('menuHome').classList.remove('btn-elegant')
292
- document.getElementById('menuHome').classList.add('btn-primary')
293
- document.getElementById('menuDevices').classList.add('btn-elegant')
294
- document.getElementById('menuDevices').classList.remove('btn-primary')
295
- document.getElementById('menuAccount').classList.remove('btn-elegant')
296
- document.getElementById('menuAccount').classList.add('btn-primary')
297
- document.getElementById('menuSettings').classList.remove('btn-elegant')
298
- document.getElementById('menuSettings').classList.add('btn-primary')
299
- document.getElementById('pageSupport').style.display = 'none'
300
- document.getElementById('pageAccount').style.display = 'none'
301
- document.getElementById('pageDevices').style.display = 'block'
302
- const cachedAccessories =
303
- typeof homebridge.getCachedAccessories === 'function'
304
- ? await homebridge.getCachedAccessories()
305
- : await homebridge.request('/getCachedAccessories')
306
- if (cachedAccessories.length > 0) {
307
- cachedAccessories.sort((a, b) => {
308
- return a.displayName.toLowerCase() > b.displayName.toLowerCase()
309
- ? 1
310
- : b.displayName.toLowerCase() > a.displayName.toLowerCase()
311
- ? -1
312
- : 0
313
- })
314
- }
315
- const deviceSelect = document.getElementById('deviceSelect')
316
- deviceSelect.innerHTML = ''
317
- cachedAccessories.forEach(a => {
318
- const option = document.createElement('option')
319
- option.text = a.displayName
320
- option.value = a.UUID
321
- deviceSelect.add(option)
322
- })
323
- showDeviceInfo = async UUID => {
324
- homebridge.showSpinner()
325
- const thisAcc = cachedAccessories.find(x => x.UUID === UUID)
326
- const context = thisAcc.context
327
- document.getElementById('displayName').innerHTML = thisAcc.displayName
328
- document.getElementById('deviceID').innerHTML = context.deviceID
329
- document.getElementById('model').innerHTML = context.model
330
- document.getElementById('firmwareRevision').innerHTML = context.firmwareRevision || 'N/A'
331
- document.getElementById('deviceTable').style.display = 'inline-table'
332
- homebridge.hideSpinner()
333
- }
334
- deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
335
- if (cachedAccessories.length > 0) {
336
- showDeviceInfo(cachedAccessories[0].UUID)
254
+ },
255
+ firstRun: {
256
+ isRequired: () => {
257
+ // Check if we need to run first-time setup
258
+ const currentConfig = homebridge.getPluginConfig();
259
+ return !currentConfig[0]?.credentials?.consumerKey || !currentConfig[0]?.credentials?.consumerSecret;
260
+ },
261
+ onStart: () => {
262
+ // Initialize first run form
263
+ const currentConfig = homebridge.getPluginConfig();
264
+ if (Array.isArray(currentConfig) && currentConfig.length > 0 && currentConfig[0].credentials) {
265
+ document.getElementById('firstRunKey').value = currentConfig[0].credentials.consumerKey || '';
266
+ document.getElementById('firstRunSecret').value = currentConfig[0].credentials.consumerSecret || '';
337
267
  } else {
338
- const option = document.createElement('option')
339
- option.text = 'No Devices'
340
- deviceSelect.add(option)
341
- deviceSelect.disabled = true
268
+ document.getElementById('firstRunKey').value = '';
269
+ document.getElementById('firstRunSecret').value = '';
342
270
  }
343
- homebridge.hideSpinner()
344
-
345
- // Load available devices when the page loads
346
- loadAvailableDevices()
347
-
348
- // Set up refresh button
349
- document.getElementById('refreshDevicesButton').addEventListener('click', () => {
350
- loadAvailableDevices()
351
- })
352
- }
353
-
354
- loadAvailableDevices = async () => {
355
- const messageDiv = document.getElementById('availableDevicesMessage')
356
- const tableDiv = document.getElementById('availableDevicesTable')
357
- const tableBody = document.getElementById('availableDevicesTableBody')
358
-
271
+ return true;
272
+ },
273
+ onSubmit: async () => {
274
+ // Handle first run setup
275
+ const key = document.getElementById('firstRunKey').value;
276
+ const secret = document.getElementById('firstRunSecret').value;
277
+ const errorDiv = document.getElementById('loginError');
278
+
279
+ if (!key || !secret) {
280
+ errorDiv.innerHTML = '<div class="alert alert-danger">Please enter both Consumer Key and Consumer Secret.</div>';
281
+ return false;
282
+ }
283
+
359
284
  try {
360
- homebridge.showSpinner()
361
- messageDiv.style.display = 'none'
362
- tableDiv.style.display = 'none'
363
-
364
- const response = await homebridge.request('/getAvailableDevices')
365
-
366
- if (response.status === 'error') {
367
- messageDiv.className = 'alert alert-danger'
368
- messageDiv.innerHTML = response.message || 'Failed to load devices'
369
- messageDiv.style.display = 'block'
370
- return
285
+ // Test the credentials by attempting to link the account
286
+ const currentConfig = homebridge.getPluginConfig();
287
+ if (!currentConfig[0]) {
288
+ currentConfig.push({ name: 'Resideo' });
371
289
  }
372
290
 
373
- if (!response.data || response.data.length === 0) {
374
- messageDiv.className = 'alert alert-warning'
375
- messageDiv.innerHTML = 'No devices found in your Resideo account'
376
- messageDiv.style.display = 'block'
377
- return
378
- }
291
+ currentConfig[0].credentials = {
292
+ consumerKey: key,
293
+ consumerSecret: secret
294
+ };
295
+
296
+ await homebridge.updatePluginConfig(currentConfig);
379
297
 
380
- // Clear existing table data
381
- tableBody.innerHTML = ''
298
+ // Now trigger the OAuth flow
299
+ await homebridge.request('Start Resideo Login Server');
382
300
 
383
- // Populate table with devices
384
- response.data.forEach(device => {
385
- const row = document.createElement('tr')
386
- row.innerHTML = `
387
- <td><strong>${device.deviceName}</strong></td>
388
- <td><code>${device.deviceID}</code></td>
389
- <td>${device.deviceClass}</td>
390
- <td>${device.deviceModel || 'N/A'}</td>
391
- <td>${device.locationName}</td>
392
- <td>
393
- <span class="badge badge-${device.isOnline ? 'success' : 'secondary'}">
394
- ${device.isOnline ? 'Online' : 'Offline'}
395
- </span>
396
- </td>
397
- `
398
- tableBody.appendChild(row)
399
- })
400
-
401
- tableDiv.style.display = 'table'
402
- messageDiv.className = 'alert alert-success'
403
- messageDiv.innerHTML = `Found ${response.data.length} device(s) in your Resideo account. Copy the Device ID to add devices to your configuration.`
404
- messageDiv.style.display = 'block'
301
+ // Open OAuth popup
302
+ const hostname = window.location.hostname;
303
+ const w = 450;
304
+ const h = 700;
305
+ const y = window.top.outerHeight / 2 + window.top.screenY - (h / 2);
306
+ const x = window.top.outerWidth / 2 + window.top.screenX - (w / 2);
307
+ const urlToOpen = 'http://' + hostname + ':8585/start?' +
308
+ 'key=' + encodeURIComponent(key) +
309
+ '&secret=' + encodeURIComponent(secret) +
310
+ '&host=' + encodeURIComponent(hostname);
405
311
 
312
+ const popup = window.open(
313
+ urlToOpen, 'resideo-auth',
314
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no scrollbars=no, resizable=no, copyhistory=no, ' +
315
+ 'width=' + w + ', height=' + h + ', top=' + y + ', left=' + x,
316
+ );
317
+
318
+ return new Promise((resolve) => {
319
+ const checkClosed = setInterval(() => {
320
+ if (popup.closed) {
321
+ clearInterval(checkClosed);
322
+ // Check if credentials were updated
323
+ const updatedConfig = homebridge.getPluginConfig();
324
+ if (updatedConfig[0]?.credentials?.accessToken) {
325
+ resolve(true);
326
+ } else {
327
+ errorDiv.innerHTML = '<div class="alert alert-danger">Authentication failed. Please try again.</div>';
328
+ resolve(false);
329
+ }
330
+ }
331
+ }, 1000);
332
+ });
406
333
  } catch (error) {
407
- messageDiv.className = 'alert alert-danger'
408
- messageDiv.innerHTML = `Error loading devices: ${error.message}`
409
- messageDiv.style.display = 'block'
410
- } finally {
411
- homebridge.hideSpinner()
334
+ errorDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
335
+ return false;
412
336
  }
413
337
  }
414
- showSupport = () => {
415
- homebridge.showSpinner()
416
- homebridge.hideSchemaForm()
417
- document.getElementById('menuHome').classList.add('btn-elegant')
418
- document.getElementById('menuHome').classList.remove('btn-primary')
419
- document.getElementById('menuDevices').classList.remove('btn-elegant')
420
- document.getElementById('menuDevices').classList.add('btn-primary')
421
- document.getElementById('menuSettings').classList.remove('btn-elegant')
422
- document.getElementById('menuSettings').classList.add('btn-primary')
423
- document.getElementById('menuAccount').classList.remove('btn-elegant')
424
- document.getElementById('menuAccount').classList.add('btn-primary')
425
- document.getElementById('pageSupport').style.display = 'block'
426
- document.getElementById('pageDevices').style.display = 'none'
427
- document.getElementById('pageAccount').style.display = 'none'
428
- homebridge.hideSpinner()
429
- }
430
- showSettings = () => {
431
- homebridge.showSpinner()
432
- document.getElementById('menuHome').classList.remove('btn-elegant')
433
- document.getElementById('menuHome').classList.add('btn-primary')
434
- document.getElementById('menuDevices').classList.remove('btn-elegant')
435
- document.getElementById('menuDevices').classList.add('btn-primary')
436
- document.getElementById('menuSettings').classList.add('btn-elegant')
437
- document.getElementById('menuSettings').classList.remove('btn-primary')
438
- document.getElementById('menuAccount').classList.remove('btn-elegant')
439
- document.getElementById('menuAccount').classList.add('btn-primary')
440
- document.getElementById('pageSupport').style.display = 'none'
441
- document.getElementById('pageDevices').style.display = 'none'
442
- document.getElementById('pageAccount').style.display = 'none'
443
- homebridge.showSchemaForm()
444
- homebridge.hideSpinner()
445
- }
446
- showAccount = () => {
447
- homebridge.showSpinner()
448
- const backToIntro = document.getElementById('backToIntro')
449
- backToIntro.addEventListener('click', () => {
450
- showIntro()
451
- })
452
- backToIntro.style.display = 'none'
453
- const linkButton = document.getElementById('linkButton')
454
- linkButton.addEventListener('click', () => {
455
- linkAccount()
456
- })
457
- const unLinkButton = document.getElementById('unLinkButton')
458
- unLinkButton.style.display = 'none'
459
- unLinkButton.addEventListener('click', () => {
460
- unLinkAccount()
461
- })
462
- document.getElementById('linkSuccess').style.display = 'none'
463
- homebridge.hideSchemaForm()
464
- document.getElementById('menuHome').classList.remove('btn-elegant')
465
- document.getElementById('menuHome').classList.add('btn-primary')
466
- document.getElementById('menuDevices').classList.remove('btn-elegant')
467
- document.getElementById('menuDevices').classList.add('btn-primary')
468
- document.getElementById('menuAccount').classList.add('btn-elegant')
469
- document.getElementById('menuAccount').classList.remove('btn-primary')
470
- document.getElementById('menuSettings').classList.remove('btn-elegant')
471
- document.getElementById('menuSettings').classList.add('btn-primary')
472
- document.getElementById('pageSupport').style.display = 'none'
473
- document.getElementById('pageDevices').style.display = 'none'
474
- document.getElementById('pageAccount').style.display = 'block'
475
- document.getElementById('linkButton').style.display = 'inline-block'
476
- let isRelink = false
477
- if (currentConfig[0] && currentConfig[0].credentials) {
478
- const key = currentConfig[0].credentials.consumerKey || ''
479
- const secret = currentConfig[0].credentials.consumerSecret || ''
480
- document.getElementById('inputConsumerKey').value = key
481
- document.getElementById('inputConsumerSecret').value = secret
482
- if (key && secret) {
483
- isRelink = true
484
- document.getElementById('unLinkButton').style.display = 'inline-block'
485
- } else {
486
- document.getElementById('unLinkButton').style.display = 'none'
487
- }
488
- }
489
- document.getElementById('linkButton').innerHTML = isRelink
490
- ? 'Relink Account &rarr;'
491
- : 'Link Account &rarr;'
492
- if (!isRelink) {
493
- document.getElementById('backToIntro').style.display = 'inline-block'
494
- } else {
495
- document.getElementById('backToIntro').style.display = 'none'
338
+ },
339
+ name: 'homebridge-resideo'
340
+ });
341
+
342
+ // Legacy functionality for existing UI elements
343
+ const setupLegacyHandlers = () => {
344
+ const currentConfig = homebridge.getPluginConfig();
345
+ const hostname = window.location.hostname;
346
+
347
+ homebridge.addEventListener('creds-received', async event => {
348
+ try {
349
+ if (event.data.access && event.data.refresh) {
350
+ currentConfig[0].credentials = {
351
+ consumerKey: event.data.key,
352
+ consumerSecret: event.data.secret,
353
+ accessToken: event.data.access,
354
+ refreshToken: event.data.refresh
355
+ };
356
+ await homebridge.updatePluginConfig(currentConfig);
357
+ await homebridge.savePluginConfig();
358
+ homebridge.toast.success("Successfully Linked Resideo Account", "homebridge-resideo");
496
359
  }
497
- homebridge.hideSpinner()
360
+ } catch (err) {
361
+ console.log(err);
362
+ homebridge.toast.error('Try again, or see the console message for info.', 'Error');
498
363
  }
499
- menuHome.addEventListener('click', () => showSupport())
500
- menuAccount.addEventListener('click', () => showAccount())
501
- menuDevices.addEventListener('click', () => showDevices())
502
- menuSettings.addEventListener('click', () => showSettings())
503
- if (currentConfig.length) {
504
- document.getElementById('menuWrapper').style.display = 'inline-flex'
505
- showAccount()
506
- } else {
507
- currentConfig.push({ name: 'Resideo' })
508
- await homebridge.updatePluginConfig(currentConfig)
509
- showIntro()
364
+ });
365
+
366
+ window.copyMyText = () => {
367
+ const textToCopy = document.getElementById("copyMe");
368
+ textToCopy.select();
369
+ document.execCommand("copy");
370
+ };
371
+
372
+ window.linkAccount = async () => {
373
+ document.getElementById('validateForm').style.display = 'none';
374
+ if (!document.getElementById('inputConsumerKey').value || !document.getElementById('inputConsumerSecret').value) {
375
+ document.getElementById('validateForm').style.display = 'block';
376
+ return;
510
377
  }
378
+ const w = 450;
379
+ const h = 700;
380
+ const y = window.top.outerHeight / 2 + window.top.screenY - (h / 2);
381
+ const x = window.top.outerWidth / 2 + window.top.screenX - (w / 2);
382
+ const urlToOpen = 'http://' + hostname + ':8585/start?' +
383
+ 'key=' + document.getElementById('inputConsumerKey').value +
384
+ '&secret=' + document.getElementById('inputConsumerSecret').value +
385
+ '&host=' + encodeURI(hostname);
386
+ window.popup = window.open(
387
+ urlToOpen, 'honeywell-auth',
388
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no scrollbars=no, resizable=no, copyhistory=no, ' +
389
+ 'width=' + w + ', height=' + h + ', top=' + y + ', left=' + x,
390
+ );
391
+ };
392
+
393
+ // Initialize legacy components if needed for backwards compatibility
394
+ if (!ui.featureOptions.isRequired()) {
395
+ setupLegacyMenus();
396
+ }
397
+ };
398
+
399
+ const setupLegacyMenus = () => {
400
+ // Legacy menu functionality for account, devices, support
401
+ document.getElementById('menuAccount')?.addEventListener('click', () => showAccount());
402
+ document.getElementById('menuDevices')?.addEventListener('click', () => showDevices());
403
+ document.getElementById('menuHome')?.addEventListener('click', () => showSupport());
404
+ document.getElementById('menuSettings')?.addEventListener('click', () => showSettings());
405
+ };
406
+
407
+ // Show the webUI
408
+ (async () => {
409
+ try {
410
+ await homebridge.request('Start Resideo Login Server');
411
+ setupLegacyHandlers();
412
+ ui.show();
511
413
  } catch (err) {
512
- homebridge.toast.error(err.message, 'Error')
513
- } finally {
514
- homebridge.hideSpinner()
414
+ homebridge.toast.error(err.message, 'Error');
515
415
  }
516
- })()
416
+ })();
517
417
  </script>