@homebridge-plugins/homebridge-resideo 3.0.3-beta.17 → 3.0.3-beta.19
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.
- package/.github/workflows/beta-release.yml +1 -4
- package/.github/workflows/release.yml +56 -16
- package/dist/homebridge-ui/public/index.html +65 -157
- package/dist/homebridge-ui/public/lib/config-module.mjs +94 -0
- package/dist/homebridge-ui/public/lib/oauth-module.mjs +139 -0
- package/dist/homebridge-ui/public/lib/ui-utils.mjs +127 -0
- package/dist/homebridge-ui/public/lib/webUi.mjs +4 -186
- package/dist/homebridge-ui/server.d.ts +12 -0
- package/dist/homebridge-ui/server.d.ts.map +1 -1
- package/dist/homebridge-ui/server.js +99 -150
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/platform.d.ts +33 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +290 -35
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +4 -0
- package/dist/settings.d.ts.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +2 -2
- package/.github/workflows/build.yml +0 -19
- package/.github/workflows/changerelease.yml +0 -11
- package/.github/workflows/labeler.yml +0 -9
- package/.github/workflows/release-drafter.yml +0 -14
|
@@ -18,7 +18,6 @@ jobs:
|
|
|
18
18
|
|
|
19
19
|
publish:
|
|
20
20
|
needs: lint
|
|
21
|
-
if: ${{ github.repository == 'homebridge-plugins/homebridge-resideo' }}
|
|
22
21
|
permissions:
|
|
23
22
|
id-token: write
|
|
24
23
|
uses: homebridge/.github/.github/workflows/npm-publish-esm.yml@latest
|
|
@@ -32,7 +31,6 @@ jobs:
|
|
|
32
31
|
|
|
33
32
|
pre-release:
|
|
34
33
|
needs: publish
|
|
35
|
-
if: ${{ github.repository == 'homebridge-plugins/homebridge-resideo' }}
|
|
36
34
|
uses: homebridge/.github/.github/workflows/pre-release.yml@latest
|
|
37
35
|
with:
|
|
38
36
|
npm_version: ${{ needs.publish.outputs.NPM_VERSION }}
|
|
@@ -44,7 +42,6 @@ jobs:
|
|
|
44
42
|
github-releases-to-discord:
|
|
45
43
|
name: Discord Webhooks
|
|
46
44
|
needs: [build_and_test,publish]
|
|
47
|
-
if: ${{ github.repository == 'homebridge-plugins/homebridge-resideo' }}
|
|
48
45
|
uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest
|
|
49
46
|
with:
|
|
50
47
|
title: "Resideo Beta Release"
|
|
@@ -52,4 +49,4 @@ jobs:
|
|
|
52
49
|
Version `v${{ needs.publish.outputs.NPM_VERSION }}`
|
|
53
50
|
url: "https://github.com/homebridge-plugins/homebridge-resideo/releases/tag/v${{ needs.publish.outputs.NPM_VERSION }}"
|
|
54
51
|
secrets:
|
|
55
|
-
DISCORD_WEBHOOK: ${{ secrets.
|
|
52
|
+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }}
|
|
@@ -1,35 +1,75 @@
|
|
|
1
|
-
name: Release
|
|
1
|
+
name: Unified Release
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
#- "alpha-*"
|
|
7
|
+
#- "beta-*"
|
|
8
|
+
- latest
|
|
9
|
+
workflow_dispatch:
|
|
6
10
|
|
|
7
11
|
jobs:
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
# 1️⃣ Determine release type, ESM status, and branch name
|
|
13
|
+
determine-release-type:
|
|
14
|
+
uses: homebridge/.github/.github/workflows/determine-release-type.yml@latest
|
|
10
15
|
with:
|
|
11
|
-
|
|
16
|
+
ref_name: ${{ github.ref_name }}
|
|
17
|
+
|
|
18
|
+
# 2️⃣ Update version and changelog using the scripts
|
|
19
|
+
update-version:
|
|
20
|
+
needs: determine-release-type
|
|
21
|
+
uses: homebridge/.github/.github/workflows/update-version.yml@latest
|
|
22
|
+
with:
|
|
23
|
+
release_type: ${{ needs.determine-release-type.outputs.release_type }}
|
|
24
|
+
is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }}
|
|
12
25
|
secrets:
|
|
13
|
-
|
|
26
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
# 3️⃣ Publish to NPM and create GitHub release
|
|
29
|
+
publish-release:
|
|
30
|
+
needs: [determine-release-type, update-version]
|
|
18
31
|
permissions:
|
|
19
32
|
id-token: write
|
|
20
|
-
|
|
33
|
+
contents: write
|
|
34
|
+
uses: homebridge/.github/.github/workflows/publish-release.yml@latest
|
|
35
|
+
with:
|
|
36
|
+
release_type: ${{ needs.determine-release-type.outputs.release_type }}
|
|
37
|
+
version: ${{ needs.update-version.outputs.version }}
|
|
38
|
+
is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }}
|
|
39
|
+
secrets:
|
|
40
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
41
|
+
|
|
42
|
+
# 4️⃣ Promote branch if this is a prerelease (alpha/beta)
|
|
43
|
+
promote-branch:
|
|
44
|
+
needs: [determine-release-type, publish-release]
|
|
45
|
+
if: ${{ needs.determine-release-type.outputs.release_type != 'latest' && needs.determine-release-type.outputs.release_type != 'skip' }}
|
|
46
|
+
uses: homebridge/.github/.github/workflows/promote-branch.yml@latest
|
|
47
|
+
with:
|
|
48
|
+
branch_name: ${{ needs.determine-release-type.outputs.branch_name }}
|
|
49
|
+
release_type: ${{ needs.determine-release-type.outputs.release_type }}
|
|
50
|
+
is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }}
|
|
21
51
|
secrets:
|
|
22
|
-
|
|
52
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
53
|
+
|
|
54
|
+
# 5️⃣ Notify if any previous job fails
|
|
55
|
+
workflow-failure:
|
|
56
|
+
if: ${{ failure() }}
|
|
57
|
+
needs: [determine-release-type, update-version, publish-release, promote-branch]
|
|
58
|
+
uses: homebridge/.github/.github/workflows/report-failure.yml@latest
|
|
59
|
+
with:
|
|
60
|
+
workflow_name: ${{ github.workflow }}
|
|
61
|
+
job_name: ${{ github.job }}
|
|
62
|
+
run_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
23
63
|
|
|
64
|
+
# 6️⃣ Post to Discord
|
|
24
65
|
github-releases-to-discord:
|
|
25
66
|
name: Discord Webhooks
|
|
26
|
-
needs: [
|
|
27
|
-
if: ${{ github.repository == 'homebridge-plugins/homebridge-resideo' }}
|
|
67
|
+
needs: [determine-release-type, update-version, publish-release]
|
|
28
68
|
uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest
|
|
29
69
|
with:
|
|
30
70
|
title: "Resideo Release"
|
|
31
71
|
description: |
|
|
32
|
-
Version `v${{ needs.
|
|
33
|
-
url: "https://github.com/homebridge-plugins/homebridge-resideo/releases/tag/v${{ needs.
|
|
72
|
+
Version `v${{ needs.update-version.outputs.version }}`
|
|
73
|
+
url: "https://github.com/homebridge-plugins/homebridge-resideo/releases/tag/v${{ needs.update-version.outputs.version }}"
|
|
34
74
|
secrets:
|
|
35
75
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }}
|
|
@@ -131,6 +131,13 @@
|
|
|
131
131
|
</div>
|
|
132
132
|
<script type="module">
|
|
133
133
|
import { webUi } from "./lib/webUi.mjs";
|
|
134
|
+
import { OAuthModule } from "./lib/oauth-module.mjs";
|
|
135
|
+
import { ConfigModule } from "./lib/config-module.mjs";
|
|
136
|
+
import { UIUtils } from "./lib/ui-utils.mjs";
|
|
137
|
+
|
|
138
|
+
// Initialize modules
|
|
139
|
+
const oauthModule = new OAuthModule();
|
|
140
|
+
const configModule = new ConfigModule();
|
|
134
141
|
|
|
135
142
|
// Initialize the webUI with homebridge-plugin-utils
|
|
136
143
|
const ui = new webUi({
|
|
@@ -152,130 +159,62 @@
|
|
|
152
159
|
}
|
|
153
160
|
},
|
|
154
161
|
firstRun: {
|
|
155
|
-
isRequired: () =>
|
|
156
|
-
// Check if we have complete credentials to skip first-run setup
|
|
157
|
-
const currentConfig = homebridge.getPluginConfig();
|
|
158
|
-
if (!Array.isArray(currentConfig) || !currentConfig[0]) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const config = currentConfig[0];
|
|
163
|
-
const credentials = config?.credentials;
|
|
164
|
-
|
|
165
|
-
// All four credential fields are required to skip first-run setup
|
|
166
|
-
const requiredCredentials = ['consumerKey', 'consumerSecret', 'accessToken', 'refreshToken'];
|
|
167
|
-
const hasAllCredentials = requiredCredentials.every(field =>
|
|
168
|
-
credentials?.[field] && credentials[field].trim() !== ''
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Return true if first-run is required (missing credentials), false if can skip
|
|
172
|
-
return !hasAllCredentials;
|
|
173
|
-
},
|
|
162
|
+
isRequired: () => configModule.isFirstRunRequired(),
|
|
174
163
|
onStart: () => {
|
|
175
|
-
//
|
|
164
|
+
// Clean first run initialization
|
|
176
165
|
return true;
|
|
177
166
|
},
|
|
178
167
|
onSubmit: async () => {
|
|
179
|
-
// Handle first run setup with
|
|
180
|
-
const key =
|
|
181
|
-
const secret =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
errorDiv.innerHTML = ' ';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Validate input fields
|
|
190
|
-
if (!key || !secret) {
|
|
191
|
-
if (errorDiv) {
|
|
192
|
-
errorDiv.innerHTML = '<div class="alert alert-danger"><strong>Error:</strong> Please enter both Consumer Key and Consumer Secret.</div>';
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
168
|
+
// Handle first run setup with modular approach
|
|
169
|
+
const key = UIUtils.getFieldValue('firstRunKey');
|
|
170
|
+
const secret = UIUtils.getFieldValue('firstRunSecret');
|
|
171
|
+
|
|
172
|
+
UIUtils.clearMessage('loginError');
|
|
173
|
+
UIUtils.storeButtonText('firstRun');
|
|
174
|
+
UIUtils.setButtonLoading('firstRun', true);
|
|
196
175
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
176
|
+
// Validate credentials
|
|
177
|
+
const validation = oauthModule.validateCredentials(key, secret);
|
|
178
|
+
if (!validation.valid) {
|
|
179
|
+
UIUtils.showError('loginError', validation.message);
|
|
180
|
+
UIUtils.setButtonLoading('firstRun', false);
|
|
202
181
|
return false;
|
|
203
182
|
}
|
|
204
183
|
|
|
205
184
|
try {
|
|
206
|
-
// Create
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
name: 'Resideo',
|
|
210
|
-
credentials: {
|
|
211
|
-
consumerKey: key,
|
|
212
|
-
consumerSecret: secret
|
|
213
|
-
}
|
|
214
|
-
}];
|
|
215
|
-
|
|
216
|
-
await homebridge.updatePluginConfig(currentConfig);
|
|
185
|
+
// Create initial config
|
|
186
|
+
const initialConfig = configModule.createInitialConfig(key, secret);
|
|
187
|
+
const updateResult = await configModule.updateConfig(initialConfig);
|
|
217
188
|
|
|
218
|
-
|
|
219
|
-
|
|
189
|
+
if (!updateResult.success) {
|
|
190
|
+
throw new Error(updateResult.error || 'Failed to update configuration');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Start OAuth flow
|
|
194
|
+
const popup = await oauthModule.startOAuthFlow(key, secret);
|
|
220
195
|
|
|
221
|
-
//
|
|
222
|
-
const
|
|
223
|
-
const w = 450;
|
|
224
|
-
const h = 700;
|
|
225
|
-
const y = window.top.outerHeight / 2 + window.top.screenY - (h / 2);
|
|
226
|
-
const x = window.top.outerWidth / 2 + window.top.screenX - (w / 2);
|
|
227
|
-
const urlToOpen = 'http://' + hostname + ':8585/start?' +
|
|
228
|
-
'key=' + encodeURIComponent(key) +
|
|
229
|
-
'&secret=' + encodeURIComponent(secret) +
|
|
230
|
-
'&host=' + encodeURIComponent(hostname);
|
|
196
|
+
// Verify OAuth completion
|
|
197
|
+
const result = await oauthModule.verifyOAuthCompletion(popup);
|
|
231
198
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
'
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const updatedConfig = homebridge.getPluginConfig();
|
|
247
|
-
const credentials = Array.isArray(updatedConfig) && updatedConfig[0]?.credentials;
|
|
248
|
-
const hasCompleteAuth = credentials?.accessToken &&
|
|
249
|
-
credentials?.refreshToken &&
|
|
250
|
-
credentials?.consumerKey &&
|
|
251
|
-
credentials?.consumerSecret;
|
|
252
|
-
|
|
253
|
-
if (hasCompleteAuth) {
|
|
254
|
-
if (errorDiv) {
|
|
255
|
-
errorDiv.innerHTML = '<div class="alert alert-success"><strong>Success!</strong> Your account has been linked successfully.</div>';
|
|
256
|
-
}
|
|
257
|
-
resolve(true);
|
|
258
|
-
} else {
|
|
259
|
-
if (errorDiv) {
|
|
260
|
-
errorDiv.innerHTML = '<div class="alert alert-warning"><strong>Authentication incomplete.</strong> Please try the setup process again or check your Resideo developer credentials.</div>';
|
|
261
|
-
}
|
|
262
|
-
resolve(false);
|
|
263
|
-
}
|
|
264
|
-
}, 500);
|
|
265
|
-
}
|
|
266
|
-
}, 1000);
|
|
267
|
-
});
|
|
268
|
-
} catch (error) {
|
|
269
|
-
console.error('Resideo setup error:', error);
|
|
270
|
-
if (errorDiv) {
|
|
271
|
-
if (error.message.includes('port')) {
|
|
272
|
-
errorDiv.innerHTML = '<div class="alert alert-danger"><strong>Error:</strong> OAuth server port conflict. Please wait a moment and try again.</div>';
|
|
273
|
-
} else if (error.message.includes('network') || error.message.includes('fetch')) {
|
|
274
|
-
errorDiv.innerHTML = '<div class="alert alert-danger"><strong>Error:</strong> Network error. Please check your connection and try again.</div>';
|
|
275
|
-
} else {
|
|
276
|
-
errorDiv.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> Setup failed. ${error.message || 'Please try again.'}</div>`;
|
|
277
|
-
}
|
|
199
|
+
if (result.success) {
|
|
200
|
+
UIUtils.showSuccess('loginError', 'Your account has been linked successfully.');
|
|
201
|
+
UIUtils.setButtonLoading('firstRun', false);
|
|
202
|
+
|
|
203
|
+
// Force UI refresh after short delay
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
return true;
|
|
206
|
+
}, 100);
|
|
207
|
+
|
|
208
|
+
return true;
|
|
209
|
+
} else {
|
|
210
|
+
UIUtils.showWarning('loginError', result.message || 'Authentication incomplete. Please try again.');
|
|
211
|
+
UIUtils.setButtonLoading('firstRun', false);
|
|
212
|
+
return false;
|
|
278
213
|
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('First run setup error:', error);
|
|
216
|
+
UIUtils.handleError(error, 'loginError');
|
|
217
|
+
UIUtils.setButtonLoading('firstRun', false);
|
|
279
218
|
return false;
|
|
280
219
|
}
|
|
281
220
|
}
|
|
@@ -283,60 +222,29 @@
|
|
|
283
222
|
name: 'homebridge-resideo'
|
|
284
223
|
});
|
|
285
224
|
|
|
286
|
-
//
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
homebridge.addEventListener('creds-received', async event => {
|
|
291
|
-
try {
|
|
292
|
-
if (event.data.access && event.data.refresh) {
|
|
293
|
-
// Create clean config structure for first version
|
|
294
|
-
const currentConfig = [{
|
|
295
|
-
platform: 'Resideo',
|
|
296
|
-
name: 'Resideo',
|
|
297
|
-
credentials: {
|
|
298
|
-
consumerKey: event.data.key,
|
|
299
|
-
consumerSecret: event.data.secret,
|
|
300
|
-
accessToken: event.data.access,
|
|
301
|
-
refreshToken: event.data.refresh
|
|
302
|
-
}
|
|
303
|
-
}];
|
|
304
|
-
|
|
305
|
-
await homebridge.updatePluginConfig(currentConfig);
|
|
306
|
-
await homebridge.savePluginConfig();
|
|
307
|
-
homebridge.toast.success("Successfully Linked Resideo Account", "homebridge-resideo");
|
|
308
|
-
}
|
|
309
|
-
} catch (err) {
|
|
310
|
-
console.log(err);
|
|
311
|
-
homebridge.toast.error('Setup failed. Please try again.', 'Error');
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
window.copyMyText = () => {
|
|
316
|
-
const textToCopy = document.getElementById("copyMe");
|
|
317
|
-
if (textToCopy) {
|
|
318
|
-
textToCopy.select();
|
|
319
|
-
document.execCommand("copy");
|
|
320
|
-
}
|
|
321
|
-
};
|
|
225
|
+
// Setup OAuth credential handler and other legacy functionality
|
|
226
|
+
const setupLegacyIntegration = () => {
|
|
227
|
+
// Setup OAuth credential reception
|
|
228
|
+
oauthModule.setupCredentialHandler();
|
|
322
229
|
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
230
|
+
// Setup copy to clipboard functionality
|
|
231
|
+
UIUtils.setupCopyToClipboard();
|
|
232
|
+
|
|
233
|
+
// Initialize callback URL display
|
|
234
|
+
const hostname = window.location.hostname;
|
|
235
|
+
const callbackUrl = `http://${hostname}:8585/auth`;
|
|
236
|
+
UIUtils.setFieldValue('copyMe', callbackUrl);
|
|
330
237
|
};
|
|
331
238
|
|
|
332
|
-
//
|
|
239
|
+
// Initialize the application
|
|
333
240
|
(async () => {
|
|
334
241
|
try {
|
|
335
242
|
await homebridge.request('Start Resideo Login Server');
|
|
336
|
-
|
|
243
|
+
setupLegacyIntegration();
|
|
337
244
|
ui.show();
|
|
338
245
|
} catch (err) {
|
|
339
|
-
|
|
246
|
+
console.error('Initialization error:', err);
|
|
247
|
+
homebridge.toast.error(err.message || 'Failed to initialize plugin UI', 'Error');
|
|
340
248
|
}
|
|
341
249
|
})();
|
|
342
250
|
</script>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2025, homebridge-plugins/homebridge-resideo developers. All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* config-module.mjs: Configuration management module for Resideo plugin.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration management module for Resideo plugin.
|
|
8
|
+
*/
|
|
9
|
+
export class ConfigModule {
|
|
10
|
+
constructor() {
|
|
11
|
+
// Initialize configuration management
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if first-run setup is required.
|
|
16
|
+
*/
|
|
17
|
+
isFirstRunRequired() {
|
|
18
|
+
const currentConfig = homebridge.getPluginConfig();
|
|
19
|
+
if (!Array.isArray(currentConfig) || !currentConfig[0]) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const config = currentConfig[0];
|
|
24
|
+
const credentials = config?.credentials;
|
|
25
|
+
|
|
26
|
+
if (!credentials) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check for minimum credentials needed to skip first-run
|
|
31
|
+
const hasMinimumCredentials = credentials.consumerKey &&
|
|
32
|
+
credentials.consumerKey.trim() !== '' &&
|
|
33
|
+
credentials.consumerSecret &&
|
|
34
|
+
credentials.consumerSecret.trim() !== '' &&
|
|
35
|
+
credentials.refreshToken &&
|
|
36
|
+
credentials.refreshToken.trim() !== '';
|
|
37
|
+
|
|
38
|
+
return !hasMinimumCredentials;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a clean initial configuration.
|
|
43
|
+
*/
|
|
44
|
+
createInitialConfig(key, secret) {
|
|
45
|
+
return [{
|
|
46
|
+
platform: 'Resideo',
|
|
47
|
+
name: 'Resideo',
|
|
48
|
+
credentials: {
|
|
49
|
+
consumerKey: key,
|
|
50
|
+
consumerSecret: secret
|
|
51
|
+
}
|
|
52
|
+
}];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Updates plugin configuration.
|
|
57
|
+
*/
|
|
58
|
+
async updateConfig(config) {
|
|
59
|
+
try {
|
|
60
|
+
await homebridge.updatePluginConfig(config);
|
|
61
|
+
return { success: true };
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Config update error:', error);
|
|
64
|
+
return { success: false, error: error.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets current plugin configuration.
|
|
70
|
+
*/
|
|
71
|
+
getCurrentConfig() {
|
|
72
|
+
const config = homebridge.getPluginConfig();
|
|
73
|
+
return Array.isArray(config) ? config[0] : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validates configuration structure.
|
|
78
|
+
*/
|
|
79
|
+
validateConfig(config) {
|
|
80
|
+
if (!config) {
|
|
81
|
+
return { valid: false, message: 'Configuration is missing' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (config.platform !== 'Resideo') {
|
|
85
|
+
return { valid: false, message: 'Invalid platform configuration' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!config.credentials) {
|
|
89
|
+
return { valid: false, message: 'Credentials are missing' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { valid: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2025, homebridge-plugins/homebridge-resideo developers. All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* oauth-module.mjs: OAuth authentication module for Resideo plugin.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OAuth authentication module for Resideo plugin setup.
|
|
8
|
+
*/
|
|
9
|
+
export class OAuthModule {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.hostname = window.location.hostname;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validates OAuth credentials format.
|
|
16
|
+
*/
|
|
17
|
+
validateCredentials(key, secret) {
|
|
18
|
+
if (!key || !secret) {
|
|
19
|
+
return { valid: false, message: 'Please enter both Consumer Key and Consumer Secret.' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (key.length < 10 || secret.length < 10) {
|
|
23
|
+
return {
|
|
24
|
+
valid: false,
|
|
25
|
+
message: 'Consumer Key and Secret appear to be too short. Please verify your credentials.'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { valid: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sets up OAuth flow with popup window.
|
|
34
|
+
*/
|
|
35
|
+
async startOAuthFlow(key, secret) {
|
|
36
|
+
try {
|
|
37
|
+
await homebridge.request('Start Resideo Login Server');
|
|
38
|
+
|
|
39
|
+
const w = 450;
|
|
40
|
+
const h = 700;
|
|
41
|
+
const y = window.top.outerHeight / 2 + window.top.screenY - (h / 2);
|
|
42
|
+
const x = window.top.outerWidth / 2 + window.top.screenX - (w / 2);
|
|
43
|
+
|
|
44
|
+
const urlToOpen = `http://${this.hostname}:8585/start?` +
|
|
45
|
+
`key=${encodeURIComponent(key)}&` +
|
|
46
|
+
`secret=${encodeURIComponent(secret)}&` +
|
|
47
|
+
`host=${encodeURIComponent(this.hostname)}`;
|
|
48
|
+
|
|
49
|
+
const popup = window.open(
|
|
50
|
+
urlToOpen,
|
|
51
|
+
'resideo-auth',
|
|
52
|
+
`toolbar=no, location=no, directories=no, status=no, menubar=no scrollbars=no, resizable=no, copyhistory=no, ` +
|
|
53
|
+
`width=${w}, height=${h}, top=${y}, left=${x}`
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return popup;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('OAuth flow error:', error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Verifies OAuth completion with multiple attempts.
|
|
65
|
+
*/
|
|
66
|
+
async verifyOAuthCompletion(popup, maxAttempts = 10) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const checkClosed = setInterval(() => {
|
|
69
|
+
if (popup.closed) {
|
|
70
|
+
clearInterval(checkClosed);
|
|
71
|
+
|
|
72
|
+
let verificationAttempts = 0;
|
|
73
|
+
|
|
74
|
+
const verifyCredentials = () => {
|
|
75
|
+
verificationAttempts++;
|
|
76
|
+
|
|
77
|
+
const updatedConfig = homebridge.getPluginConfig();
|
|
78
|
+
const credentials = Array.isArray(updatedConfig) && updatedConfig[0]?.credentials;
|
|
79
|
+
|
|
80
|
+
// Check for minimum required credentials
|
|
81
|
+
const hasMinimumAuth = credentials?.consumerKey &&
|
|
82
|
+
credentials?.consumerSecret &&
|
|
83
|
+
credentials?.refreshToken;
|
|
84
|
+
|
|
85
|
+
const hasCompleteAuth = hasMinimumAuth && credentials?.accessToken;
|
|
86
|
+
|
|
87
|
+
if (hasCompleteAuth) {
|
|
88
|
+
resolve({ success: true, complete: true });
|
|
89
|
+
} else if (hasMinimumAuth && verificationAttempts >= maxAttempts) {
|
|
90
|
+
// Minimum credentials are acceptable
|
|
91
|
+
resolve({ success: true, complete: false, message: 'Minimum credentials received' });
|
|
92
|
+
} else if (verificationAttempts >= maxAttempts) {
|
|
93
|
+
resolve({
|
|
94
|
+
success: false,
|
|
95
|
+
message: 'Authentication incomplete. Please try the setup process again.'
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
setTimeout(verifyCredentials, 1000);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
setTimeout(verifyCredentials, 500);
|
|
103
|
+
}
|
|
104
|
+
}, 1000);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Sets up OAuth credential reception handler.
|
|
110
|
+
*/
|
|
111
|
+
setupCredentialHandler() {
|
|
112
|
+
homebridge.addEventListener('creds-received', async (event) => {
|
|
113
|
+
try {
|
|
114
|
+
if (event.data.access && event.data.refresh) {
|
|
115
|
+
const currentConfig = [{
|
|
116
|
+
platform: 'Resideo',
|
|
117
|
+
name: 'Resideo',
|
|
118
|
+
credentials: {
|
|
119
|
+
consumerKey: event.data.key,
|
|
120
|
+
consumerSecret: event.data.secret,
|
|
121
|
+
accessToken: event.data.access,
|
|
122
|
+
refreshToken: event.data.refresh
|
|
123
|
+
}
|
|
124
|
+
}];
|
|
125
|
+
|
|
126
|
+
await homebridge.updatePluginConfig(currentConfig);
|
|
127
|
+
await homebridge.savePluginConfig();
|
|
128
|
+
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
homebridge.toast.success("Successfully Linked Resideo Account", "homebridge-resideo");
|
|
131
|
+
}, 200);
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error('Credential handling error:', err);
|
|
135
|
+
homebridge.toast.error('Setup failed. Please try again.', 'Error');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|