homebridge-tuya-without-developer-account 1.0.0 → 1.0.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.
- package/CHANGELOG.md +18 -0
- package/README.md +20 -0
- package/dist/cloud/api/TuyaHACloudAPI.js +61 -18
- package/dist/platform.js +85 -26
- package/homebridge-ui/public/homebridge-tuya.png +0 -0
- package/homebridge-ui/public/index.html +9 -1
- package/homebridge-ui/server.js +12 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
- Fixed startup abort when Homebridge UI saves an empty or incomplete `deviceOverrides` row. Invalid override rows without `id` are now skipped with a warning instead of stopping QR cloud startup.
|
|
6
|
+
- Duplicate device override IDs are now ignored safely, keeping the first valid entry.
|
|
7
|
+
- Invalid or duplicate schema override entries are now skipped with warnings instead of blocking Homebridge startup.
|
|
8
|
+
|
|
9
|
+
## 1.0.1
|
|
10
|
+
|
|
11
|
+
- Fixes repeated `code=-9999999, msg=sign invalid` errors caused by incomplete token expiry handling and non-persistent token refreshes.
|
|
12
|
+
- Saves refreshed Tuya QR access/refresh tokens immediately to the Homebridge storage auth file.
|
|
13
|
+
- Retries a signed Tuya request once after forcing a token refresh when Tuya returns `sign invalid`.
|
|
14
|
+
- Accepts both snake_case and camelCase token fields returned by Tuya QR login/refresh responses.
|
|
15
|
+
- Adds the plugin icon to the custom Homebridge settings UI and README.
|
|
16
|
+
|
|
17
|
+
## 1.0.0
|
|
18
|
+
|
|
19
|
+
- Initial QR-only release.
|
|
20
|
+
|
|
3
21
|
## 1.0.0
|
|
4
22
|
|
|
5
23
|
- Renamed plugin to **Tuya without developer account for Homebridge**.
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./homebridge-ui/public/homebridge-tuya.png" width="96" alt="Tuya without developer account for Homebridge" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# Tuya without developer account for Homebridge
|
|
2
6
|
|
|
3
7
|
A Homebridge platform plugin for Tuya and Smart Life devices that uses **Home Assistant-style Tuya QR Cloud Authentication**.
|
|
@@ -188,6 +192,11 @@ Use `
|
|
|
188
192
|
|
|
189
193
|
## Troubleshooting
|
|
190
194
|
|
|
195
|
+
### Plugin starts from cache only and logs `Each device override must include an "id"`
|
|
196
|
+
|
|
197
|
+
Version 1.0.2 and newer no longer abort startup for empty override rows created by the Homebridge UI. Invalid override entries are skipped with a warning. If you still see old warnings, remove empty rows from the Device Overrides section in the plugin settings and restart Homebridge.
|
|
198
|
+
|
|
199
|
+
|
|
191
200
|
### The QR code does not appear
|
|
192
201
|
|
|
193
202
|
Make sure you opened the settings for this plugin, not another Tuya plugin. The plugin name should be:
|
|
@@ -231,3 +240,14 @@ This project is based on the Homebridge Tuya plugin codebase and adapts the Tuya
|
|
|
231
240
|
## License
|
|
232
241
|
|
|
233
242
|
MIT
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## Token refresh and sign invalid errors
|
|
246
|
+
|
|
247
|
+
Version 1.0.1 and later persist refreshed Tuya QR tokens back to the Homebridge storage auth file. This prevents repeated startup failures such as:
|
|
248
|
+
|
|
249
|
+
```text
|
|
250
|
+
[Tuya QR] Fetching home list failed. code=-9999999, msg=sign invalid
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
If this still happens after upgrading, open the plugin settings, clear the saved authentication, generate a new QR code, scan it with the Tuya Smart or Smart Life app, save the configuration, and restart Homebridge. Also confirm the Homebridge host clock is synchronized, because Tuya signed requests depend on the current time.
|
|
@@ -13,12 +13,13 @@ exports.TUYA_HA_CLIENT_ID = 'HA_3y9q4ak7g4ephrvke';
|
|
|
13
13
|
exports.TUYA_HA_SCHEMA = 'haauthorize';
|
|
14
14
|
exports.TUYA_HA_QR_ENDPOINT = 'https://apigw.iotbing.com';
|
|
15
15
|
class TuyaHACloudAPI {
|
|
16
|
-
constructor(userCode, terminalId, endpoint, tokenInfo, log = console, debug = false) {
|
|
16
|
+
constructor(userCode, terminalId, endpoint, tokenInfo, log = console, debug = false, tokenUpdateListener) {
|
|
17
17
|
this.userCode = userCode;
|
|
18
18
|
this.terminalId = terminalId;
|
|
19
19
|
this.endpoint = endpoint;
|
|
20
20
|
this.log = log;
|
|
21
21
|
this.debug = debug;
|
|
22
|
+
this.tokenUpdateListener = tokenUpdateListener;
|
|
22
23
|
this.tokenInfo = {
|
|
23
24
|
access_token: '',
|
|
24
25
|
refresh_token: '',
|
|
@@ -31,23 +32,49 @@ class TuyaHACloudAPI {
|
|
|
31
32
|
this.setTokenInfo(tokenInfo);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
setTokenUpdateListener(listener) {
|
|
36
|
+
this.tokenUpdateListener = listener;
|
|
37
|
+
}
|
|
38
|
+
normaliseTokenInfo(tokenInfo = {}) {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const expireTimeRaw = tokenInfo.expire_time ?? tokenInfo.expireTime ?? tokenInfo.expire;
|
|
41
|
+
const expireTime = Number.isFinite(Number(expireTimeRaw)) && Number(expireTimeRaw) > 0
|
|
42
|
+
? Number(expireTimeRaw)
|
|
43
|
+
: 7200;
|
|
44
|
+
return {
|
|
45
|
+
access_token: tokenInfo.access_token || tokenInfo.accessToken || this.tokenInfo.access_token || '',
|
|
46
|
+
refresh_token: tokenInfo.refresh_token || tokenInfo.refreshToken || this.tokenInfo.refresh_token || '',
|
|
47
|
+
uid: tokenInfo.uid || this.tokenInfo.uid || '',
|
|
48
|
+
expire: Number(tokenInfo.expireAt || 0) > now
|
|
49
|
+
? Number(tokenInfo.expireAt)
|
|
50
|
+
: (Number(tokenInfo.t || 0) > 0 ? Number(tokenInfo.t) : now) + expireTime * 1000,
|
|
40
51
|
};
|
|
41
52
|
}
|
|
53
|
+
setTokenInfo(tokenInfo) {
|
|
54
|
+
this.tokenInfo = this.normaliseTokenInfo(tokenInfo);
|
|
55
|
+
}
|
|
42
56
|
exportTokenInfo() {
|
|
43
57
|
return {
|
|
44
58
|
t: Date.now(),
|
|
45
59
|
uid: this.tokenInfo.uid,
|
|
46
60
|
expire_time: Math.max(0, Math.floor((this.tokenInfo.expire - Date.now()) / 1000)),
|
|
61
|
+
expireAt: this.tokenInfo.expire,
|
|
47
62
|
access_token: this.tokenInfo.access_token,
|
|
48
63
|
refresh_token: this.tokenInfo.refresh_token,
|
|
49
64
|
};
|
|
50
65
|
}
|
|
66
|
+
async notifyTokenUpdate() {
|
|
67
|
+
if (typeof this.tokenUpdateListener !== 'function') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await this.tokenUpdateListener(this.exportTokenInfo());
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
75
|
+
this.log.warn('Could not persist refreshed Tuya token: %s', msg);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
51
78
|
isLogin() {
|
|
52
79
|
return this.tokenInfo.access_token.length > 0;
|
|
53
80
|
}
|
|
@@ -93,43 +120,52 @@ class TuyaHACloudAPI {
|
|
|
93
120
|
this.log.debug('HA raw response: %s', JSON.stringify(res));
|
|
94
121
|
return res;
|
|
95
122
|
}
|
|
96
|
-
async refreshAccessTokenIfNeed() {
|
|
123
|
+
async refreshAccessTokenIfNeed(force = false) {
|
|
97
124
|
if (!this.isLogin()) {
|
|
98
|
-
return;
|
|
125
|
+
return false;
|
|
99
126
|
}
|
|
100
|
-
if (!this.isTokenExpired()) {
|
|
101
|
-
return;
|
|
127
|
+
if (!force && !this.isTokenExpired()) {
|
|
128
|
+
return false;
|
|
102
129
|
}
|
|
103
130
|
if (this.refreshTokenInProgress) {
|
|
104
|
-
return;
|
|
131
|
+
return false;
|
|
105
132
|
}
|
|
106
133
|
this.refreshTokenInProgress = true;
|
|
107
134
|
try {
|
|
108
135
|
this.log.info('Refreshing Tuya Home Assistant QR access token.');
|
|
109
|
-
const response = await this.
|
|
136
|
+
const response = await this.request('GET', `/v1.0/m/token/${this.tokenInfo.refresh_token}`, null, null, { skipRefresh: true, skipSignInvalidRetry: true });
|
|
110
137
|
if (response && response.success) {
|
|
111
138
|
const result = response.result || {};
|
|
112
139
|
const tokenInfo = {
|
|
113
140
|
t: response.t || Date.now(),
|
|
114
|
-
expire_time: result.expireTime || result.expire_time ||
|
|
141
|
+
expire_time: result.expireTime || result.expire_time || result.expire || 7200,
|
|
115
142
|
uid: result.uid,
|
|
116
143
|
access_token: result.accessToken || result.access_token,
|
|
117
|
-
refresh_token: result.refreshToken || result.refresh_token,
|
|
144
|
+
refresh_token: result.refreshToken || result.refresh_token || this.tokenInfo.refresh_token,
|
|
118
145
|
};
|
|
119
146
|
this.setTokenInfo(tokenInfo);
|
|
147
|
+
await this.notifyTokenUpdate();
|
|
148
|
+
this.log.info('Tuya Home Assistant QR access token refreshed and saved.');
|
|
149
|
+
return true;
|
|
120
150
|
}
|
|
151
|
+
this.log.warn('Refresh Tuya access token failed. code=%s, msg=%s', response?.code, response?.msg);
|
|
152
|
+
return false;
|
|
121
153
|
}
|
|
122
154
|
catch (error) {
|
|
123
155
|
const msg = error instanceof Error ? error.message : String(error);
|
|
124
156
|
this.log.error('Failed to refresh Tuya access token: %s', msg);
|
|
157
|
+
return false;
|
|
125
158
|
}
|
|
126
159
|
finally {
|
|
127
160
|
this.refreshTokenInProgress = false;
|
|
128
161
|
}
|
|
129
162
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
isSignInvalidResponse(res) {
|
|
164
|
+
return res && res.success === false && (String(res.code) === '-9999999' || String(res.msg || '').toLowerCase().includes('sign invalid'));
|
|
165
|
+
}
|
|
166
|
+
async request(method, path, params, body, requestOptions = {}) {
|
|
167
|
+
if (!requestOptions.skipRefresh && !this.refreshTokenInProgress) {
|
|
168
|
+
await this.refreshAccessTokenIfNeed(false);
|
|
133
169
|
}
|
|
134
170
|
const rid = (0, util_1.generateUUID)();
|
|
135
171
|
const sid = '';
|
|
@@ -199,6 +235,13 @@ class TuyaHACloudAPI {
|
|
|
199
235
|
req.end();
|
|
200
236
|
}), { retriesMax: 5, interval: 300, exponential: true, factor: 2, jitter: 100 });
|
|
201
237
|
this.log.debug('HA encrypted response: %s', JSON.stringify(res));
|
|
238
|
+
if (!requestOptions.skipSignInvalidRetry && this.isSignInvalidResponse(res)) {
|
|
239
|
+
this.log.warn('Tuya returned sign invalid. Forcing token refresh and retrying request once.');
|
|
240
|
+
const refreshed = await this.refreshAccessTokenIfNeed(true);
|
|
241
|
+
if (refreshed) {
|
|
242
|
+
return this.request(method, path, params, body, { ...requestOptions, skipSignInvalidRetry: true });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
202
245
|
return res;
|
|
203
246
|
}
|
|
204
247
|
async get(path, params) {
|
package/dist/platform.js
CHANGED
|
@@ -68,24 +68,45 @@ class TuyaPlatform {
|
|
|
68
68
|
if (!this.options.deviceOverrides) {
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
if (!Array.isArray(this.options.deviceOverrides)) {
|
|
72
|
+
this.log.warn('[Tuya QR] Ignoring invalid deviceOverrides value because it is not an array.');
|
|
73
|
+
this.options.deviceOverrides = [];
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const validOverrides = [];
|
|
78
|
+
const seenIds = new Set();
|
|
79
|
+
let skippedMissingId = 0;
|
|
80
|
+
let skippedDuplicateId = 0;
|
|
81
|
+
|
|
72
82
|
for (const item of this.options.deviceOverrides) {
|
|
73
|
-
if (!item ||
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
if (!item || typeof item !== 'object') {
|
|
84
|
+
skippedMissingId++;
|
|
85
|
+
continue;
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
const id = String(item.id || '').trim();
|
|
88
|
+
if (!id) {
|
|
89
|
+
skippedMissingId++;
|
|
90
|
+
continue;
|
|
81
91
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return false;
|
|
92
|
+
if (seenIds.has(id)) {
|
|
93
|
+
skippedDuplicateId++;
|
|
94
|
+
this.log.warn('[Tuya QR] Ignoring duplicate device override for id "%s". Keeping the first one.', id);
|
|
95
|
+
continue;
|
|
87
96
|
}
|
|
97
|
+
item.id = id;
|
|
98
|
+
seenIds.add(id);
|
|
99
|
+
validOverrides.push(item);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (skippedMissingId > 0) {
|
|
103
|
+
this.log.warn('[Tuya QR] Ignored %d invalid device override(s) without an "id". QR cloud startup will continue.', skippedMissingId);
|
|
88
104
|
}
|
|
105
|
+
if (skippedDuplicateId > 0) {
|
|
106
|
+
this.log.warn('[Tuya QR] Ignored %d duplicate device override(s). QR cloud startup will continue.', skippedDuplicateId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.options.deviceOverrides = validOverrides;
|
|
89
110
|
return true;
|
|
90
111
|
}
|
|
91
112
|
|
|
@@ -97,20 +118,43 @@ class TuyaPlatform {
|
|
|
97
118
|
if (!deviceOverride.schema) {
|
|
98
119
|
continue;
|
|
99
120
|
}
|
|
100
|
-
|
|
121
|
+
if (!Array.isArray(deviceOverride.schema)) {
|
|
122
|
+
this.log.warn('[Tuya QR] Ignoring invalid schema override for device id "%s" because schema is not an array.', deviceOverride.id);
|
|
123
|
+
deviceOverride.schema = undefined;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const validSchema = [];
|
|
127
|
+
const seenCodes = new Set();
|
|
128
|
+
let skippedMissingCode = 0;
|
|
129
|
+
let skippedDuplicateCode = 0;
|
|
130
|
+
|
|
101
131
|
for (const item of deviceOverride.schema) {
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
idMap.set(item.code, [item]);
|
|
132
|
+
if (!item || typeof item !== 'object') {
|
|
133
|
+
skippedMissingCode++;
|
|
134
|
+
continue;
|
|
106
135
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return false;
|
|
136
|
+
const code = String(item.code || '').trim();
|
|
137
|
+
if (!code) {
|
|
138
|
+
skippedMissingCode++;
|
|
139
|
+
continue;
|
|
112
140
|
}
|
|
141
|
+
if (seenCodes.has(code)) {
|
|
142
|
+
skippedDuplicateCode++;
|
|
143
|
+
this.log.warn('[Tuya QR] Ignoring duplicate schema override code "%s" for device id "%s". Keeping the first one.', code, deviceOverride.id);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
item.code = code;
|
|
147
|
+
seenCodes.add(code);
|
|
148
|
+
validSchema.push(item);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (skippedMissingCode > 0) {
|
|
152
|
+
this.log.warn('[Tuya QR] Ignored %d invalid schema override(s) without a "code" for device id "%s".', skippedMissingCode, deviceOverride.id);
|
|
113
153
|
}
|
|
154
|
+
if (skippedDuplicateCode > 0) {
|
|
155
|
+
this.log.warn('[Tuya QR] Ignored %d duplicate schema override(s) for device id "%s".', skippedDuplicateCode, deviceOverride.id);
|
|
156
|
+
}
|
|
157
|
+
deviceOverride.schema = validSchema;
|
|
114
158
|
}
|
|
115
159
|
return true;
|
|
116
160
|
}
|
|
@@ -130,10 +174,17 @@ class TuyaPlatform {
|
|
|
130
174
|
try {
|
|
131
175
|
const raw = await fs.promises.readFile(file, "utf8");
|
|
132
176
|
const data = JSON.parse(raw);
|
|
133
|
-
|
|
177
|
+
const tokenInfo = data.tokenInfo || {};
|
|
178
|
+
if (!data.userCode || !data.endpoint || !data.terminalId || !(tokenInfo.access_token || tokenInfo.accessToken) || !(tokenInfo.refresh_token || tokenInfo.refreshToken)) {
|
|
134
179
|
this.log.warn("[Tuya QR] Existing auth file is incomplete. Clear authentication in the plugin settings and scan again.");
|
|
135
180
|
return undefined;
|
|
136
181
|
}
|
|
182
|
+
data.tokenInfo = {
|
|
183
|
+
...tokenInfo,
|
|
184
|
+
access_token: tokenInfo.access_token || tokenInfo.accessToken,
|
|
185
|
+
refresh_token: tokenInfo.refresh_token || tokenInfo.refreshToken,
|
|
186
|
+
expire_time: tokenInfo.expire_time || tokenInfo.expireTime || tokenInfo.expire || 7200,
|
|
187
|
+
};
|
|
137
188
|
return data;
|
|
138
189
|
} catch {
|
|
139
190
|
return undefined;
|
|
@@ -219,14 +270,22 @@ class TuyaPlatform {
|
|
|
219
270
|
return undefined;
|
|
220
271
|
}
|
|
221
272
|
|
|
222
|
-
const api = new TuyaHACloudAPI(userCode, authData.terminalId, authData.endpoint, authData.tokenInfo, this.log, debugMode)
|
|
273
|
+
const api = new TuyaHACloudAPI(userCode, authData.terminalId, authData.endpoint, authData.tokenInfo, this.log, debugMode, async (tokenInfo) => {
|
|
274
|
+
await this.writeAuthData(userCode, {
|
|
275
|
+
...authData,
|
|
276
|
+
endpoint: api.endpoint,
|
|
277
|
+
tokenInfo,
|
|
278
|
+
savedAt: Date.now(),
|
|
279
|
+
refreshedAt: Date.now(),
|
|
280
|
+
});
|
|
281
|
+
});
|
|
223
282
|
const deviceManager = new TuyaHADeviceManager(api, debugMode);
|
|
224
283
|
|
|
225
284
|
this.log.info("[Tuya QR] Fetching home list.");
|
|
226
285
|
const res = await deviceManager.getHomeList();
|
|
227
286
|
if (res.success === false) {
|
|
228
287
|
this.log.error(`[Tuya QR] Fetching home list failed. code=${res.code}, msg=${res.msg}`);
|
|
229
|
-
this.log.error("[Tuya QR]
|
|
288
|
+
this.log.error("[Tuya QR] Token refresh was attempted automatically. If this continues, clear authentication in the plugin settings and scan again.");
|
|
230
289
|
return undefined;
|
|
231
290
|
}
|
|
232
291
|
|
|
Binary file
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<style>
|
|
2
|
+
.tuya-nodev-logo {
|
|
3
|
+
display: block;
|
|
4
|
+
width: 72px;
|
|
5
|
+
height: 72px;
|
|
6
|
+
margin: 0 auto 12px;
|
|
7
|
+
border-radius: 16px;
|
|
8
|
+
}
|
|
2
9
|
.tuya-nodev-card {
|
|
3
10
|
border: 1px solid rgba(127, 127, 127, 0.25);
|
|
4
11
|
border-radius: 12px;
|
|
@@ -45,7 +52,8 @@
|
|
|
45
52
|
</style>
|
|
46
53
|
|
|
47
54
|
<div class="tuya-nodev-card">
|
|
48
|
-
<
|
|
55
|
+
<img class="tuya-nodev-logo" src="./homebridge-tuya.png" alt="Tuya without developer account logo">
|
|
56
|
+
<div class="tuya-nodev-title" style="text-align:center;">Tuya QR Cloud Authentication</div>
|
|
49
57
|
<p class="tuya-nodev-small mb-3">
|
|
50
58
|
This plugin connects Tuya / Smart Life devices to Homebridge using the same QR authorization style used by the official Home Assistant Tuya integration. It does not need a Tuya IoT developer account, Access ID, Access Secret, cloud project, username, or password.
|
|
51
59
|
</p>
|
package/homebridge-ui/server.js
CHANGED
|
@@ -35,9 +35,16 @@ function normaliseUserCode(userCode) {
|
|
|
35
35
|
try {
|
|
36
36
|
const raw = await fs.promises.readFile(this.getAuthFile(userCode), 'utf8');
|
|
37
37
|
const data = JSON.parse(raw);
|
|
38
|
-
|
|
38
|
+
const tokenInfo = data.tokenInfo || {};
|
|
39
|
+
if (!data.userCode || !data.endpoint || !data.terminalId || !(tokenInfo.access_token || tokenInfo.accessToken) || !(tokenInfo.refresh_token || tokenInfo.refreshToken)) {
|
|
39
40
|
return null;
|
|
40
41
|
}
|
|
42
|
+
data.tokenInfo = {
|
|
43
|
+
...tokenInfo,
|
|
44
|
+
access_token: tokenInfo.access_token || tokenInfo.accessToken,
|
|
45
|
+
refresh_token: tokenInfo.refresh_token || tokenInfo.refreshToken,
|
|
46
|
+
expire_time: tokenInfo.expire_time || tokenInfo.expireTime || tokenInfo.expire || 7200,
|
|
47
|
+
};
|
|
41
48
|
return data;
|
|
42
49
|
} catch {
|
|
43
50
|
return null;
|
|
@@ -178,14 +185,14 @@ function normaliseUserCode(userCode) {
|
|
|
178
185
|
const info = loginResponse.result || {};
|
|
179
186
|
const authData = {
|
|
180
187
|
userCode,
|
|
181
|
-
terminalId: info.terminal_id,
|
|
188
|
+
terminalId: info.terminal_id || info.terminalId,
|
|
182
189
|
endpoint: info.endpoint,
|
|
183
190
|
tokenInfo: {
|
|
184
191
|
t: loginResponse.t || info.t || Date.now(),
|
|
185
192
|
uid: info.uid,
|
|
186
|
-
expire_time: info.expire_time,
|
|
187
|
-
access_token: info.access_token,
|
|
188
|
-
refresh_token: info.refresh_token,
|
|
193
|
+
expire_time: info.expire_time || info.expireTime || info.expire || 7200,
|
|
194
|
+
access_token: info.access_token || info.accessToken,
|
|
195
|
+
refresh_token: info.refresh_token || info.refreshToken,
|
|
189
196
|
},
|
|
190
197
|
username: info.username,
|
|
191
198
|
savedAt: Date.now(),
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-without-developer-account",
|
|
3
3
|
"displayName": "Tuya without developer account for Homebridge",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "Homebridge plugin for Tuya and Smart Life devices using
|
|
4
|
+
"version": "1.0.2",
|
|
5
|
+
"description": "Homebridge plugin for Tuya and Smart Life devices using QR cloud authentication without a Tuya IoT developer account.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Kosztyk",
|
|
8
8
|
"repository": {
|