@mega-yfue/eufy-sdk 0.5.0-beta.50 → 0.5.0-beta.51
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/dist/index.js +82 -19
- package/dist/index.js.map +4 -4
- package/dist/transport/tuya/account.d.ts +8 -5
- package/dist/transport/tuya/client.d.ts +16 -3
- package/dist/transport/tuya/command-router.d.ts +1 -1
- package/dist/transport/tuya/et3.d.ts +22 -0
- package/dist/transport/tuya/request.d.ts +16 -6
- package/dist/transport/tuya/sign.d.ts +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25210,7 +25210,7 @@ var MqttCommandRouter = class _MqttCommandRouter {
|
|
|
25210
25210
|
|
|
25211
25211
|
// dist/transport/tuya/account.js
|
|
25212
25212
|
import { createCipheriv as createCipheriv5 } from "node:crypto";
|
|
25213
|
-
var TUYA_USERNAME_PREFIX = "
|
|
25213
|
+
var TUYA_USERNAME_PREFIX = "eh-";
|
|
25214
25214
|
var TUYA_PASSWORD_KEY = Buffer.from([
|
|
25215
25215
|
36,
|
|
25216
25216
|
78,
|
|
@@ -25398,10 +25398,13 @@ var StubSigner = class {
|
|
|
25398
25398
|
}
|
|
25399
25399
|
};
|
|
25400
25400
|
var TUYA_SIGN_K = "com.oceanwing.battery.cam_16:6C:23:45:57:B7:76:CA:D8:AC:94:C9:79:37:9E:48:DF:38:7D:4D:8F:96:A3:43:DF:40:FC:D9:05:BF:F6:86_dn9erpyp7nmeuvah8ktghqsgpay87maa_pt585qhmt75hwcynchnps9dnxh9suhwd";
|
|
25401
|
+
function resolveSignKey(override) {
|
|
25402
|
+
return override ?? process.env.TUYA_SIGN_KEY ?? TUYA_SIGN_K;
|
|
25403
|
+
}
|
|
25401
25404
|
var HmacSigner = class {
|
|
25402
25405
|
key;
|
|
25403
25406
|
constructor(key) {
|
|
25404
|
-
this.key = key
|
|
25407
|
+
this.key = resolveSignKey(key);
|
|
25405
25408
|
}
|
|
25406
25409
|
sign(preimage) {
|
|
25407
25410
|
return createHmac4("sha256", this.key).update(preimage, "utf-8").digest("hex");
|
|
@@ -25410,7 +25413,37 @@ var HmacSigner = class {
|
|
|
25410
25413
|
var TUYA_CHKEY = "7cbfe6d8";
|
|
25411
25414
|
|
|
25412
25415
|
// dist/transport/tuya/client.js
|
|
25413
|
-
import { randomBytes as
|
|
25416
|
+
import { randomBytes as randomBytes5, createHash as createHash6, createPublicKey, publicEncrypt, constants as constants2 } from "node:crypto";
|
|
25417
|
+
|
|
25418
|
+
// dist/transport/tuya/et3.js
|
|
25419
|
+
import { createCipheriv as createCipheriv6, createDecipheriv as createDecipheriv8, createHmac as createHmac5, randomBytes as randomBytes4 } from "node:crypto";
|
|
25420
|
+
import { gunzipSync } from "node:zlib";
|
|
25421
|
+
function deriveBodyKey(requestId, ecode) {
|
|
25422
|
+
const k = resolveSignKey();
|
|
25423
|
+
const msg = ecode === null ? k : `${k}_${ecode}`;
|
|
25424
|
+
const hex = createHmac5("sha256", requestId).update(msg, "utf-8").digest("hex");
|
|
25425
|
+
return Buffer.from(hex.slice(0, 16), "ascii");
|
|
25426
|
+
}
|
|
25427
|
+
function encryptBody2(key, json) {
|
|
25428
|
+
const nonce = randomBytes4(12);
|
|
25429
|
+
const cipher = createCipheriv6("aes-128-gcm", key, nonce);
|
|
25430
|
+
const ciphertext = Buffer.concat([cipher.update(Buffer.from(json, "utf-8")), cipher.final()]);
|
|
25431
|
+
return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]).toString("base64");
|
|
25432
|
+
}
|
|
25433
|
+
function decryptReply(key, resultB64) {
|
|
25434
|
+
const blob = Buffer.from(resultB64, "base64");
|
|
25435
|
+
if (blob.length < 12 + 16) {
|
|
25436
|
+
throw new Error("tuya et=3 reply too short to hold a 12-byte nonce + 16-byte tag");
|
|
25437
|
+
}
|
|
25438
|
+
const nonce = blob.subarray(0, 12);
|
|
25439
|
+
const tag2 = blob.subarray(blob.length - 16);
|
|
25440
|
+
const ciphertext = blob.subarray(12, blob.length - 16);
|
|
25441
|
+
const decipher = createDecipheriv8("aes-128-gcm", key, nonce);
|
|
25442
|
+
decipher.setAuthTag(tag2);
|
|
25443
|
+
const plain = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
25444
|
+
const text2 = plain.length >= 2 && plain[0] === 31 && plain[1] === 139 ? gunzipSync(plain) : plain;
|
|
25445
|
+
return text2.toString("utf-8");
|
|
25446
|
+
}
|
|
25414
25447
|
|
|
25415
25448
|
// dist/transport/tuya/request.js
|
|
25416
25449
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -25465,8 +25498,9 @@ function buildApiParams(action, opts) {
|
|
|
25465
25498
|
nd: env.nd,
|
|
25466
25499
|
deviceCoreVersion: env.deviceCoreVersion
|
|
25467
25500
|
};
|
|
25468
|
-
if (action.postData !== void 0)
|
|
25469
|
-
params.postData = action.postData;
|
|
25501
|
+
if (action.postData !== void 0) {
|
|
25502
|
+
params.postData = env.et === "3" ? encryptBody2(deriveBodyKey(params.requestId, opts.session.ecode ?? null), action.postData) : action.postData;
|
|
25503
|
+
}
|
|
25470
25504
|
const sign = opts.signer.sign(buildSignPreimage(params));
|
|
25471
25505
|
return { ...params, sign };
|
|
25472
25506
|
}
|
|
@@ -25486,14 +25520,14 @@ async function sendApiRequest(params, opts = {}) {
|
|
|
25486
25520
|
}
|
|
25487
25521
|
function buildPublishDpsAction(devId, gwId, dps) {
|
|
25488
25522
|
return {
|
|
25489
|
-
a: "
|
|
25523
|
+
a: "smartlife.m.device.dp.publish",
|
|
25490
25524
|
v: "2.0",
|
|
25491
25525
|
postData: JSON.stringify({ gwId, devId, dps: JSON.stringify(dps) })
|
|
25492
25526
|
};
|
|
25493
25527
|
}
|
|
25494
25528
|
function buildGetDeviceDpsAction(devId, dpCacheType = 1) {
|
|
25495
25529
|
return {
|
|
25496
|
-
a: "
|
|
25530
|
+
a: "smartlife.m.device.cache.dp.get",
|
|
25497
25531
|
v: "2.0",
|
|
25498
25532
|
postData: JSON.stringify({ devId, dpCacheType })
|
|
25499
25533
|
};
|
|
@@ -25509,7 +25543,15 @@ function buildPasswordLoginRegAction(countryCode, uid, passwd, token) {
|
|
|
25509
25543
|
return {
|
|
25510
25544
|
a: "smartlife.m.user.uid.password.login.reg",
|
|
25511
25545
|
v: "1.0",
|
|
25512
|
-
postData: JSON.stringify({
|
|
25546
|
+
postData: JSON.stringify({
|
|
25547
|
+
countryCode,
|
|
25548
|
+
uid,
|
|
25549
|
+
passwd,
|
|
25550
|
+
token,
|
|
25551
|
+
ifencrypt: 1,
|
|
25552
|
+
createGroup: true,
|
|
25553
|
+
options: '{"group": 1}'
|
|
25554
|
+
})
|
|
25513
25555
|
};
|
|
25514
25556
|
}
|
|
25515
25557
|
function buildUidTokenCreateAction(countryCode, uid) {
|
|
@@ -25522,7 +25564,7 @@ function buildUidTokenCreateAction(countryCode, uid) {
|
|
|
25522
25564
|
|
|
25523
25565
|
// dist/transport/tuya/client.js
|
|
25524
25566
|
function genDeviceId() {
|
|
25525
|
-
return
|
|
25567
|
+
return randomBytes5(22).toString("hex");
|
|
25526
25568
|
}
|
|
25527
25569
|
function rsaEncryptPassword(password, publicKeyDecimal, exponentDecimal) {
|
|
25528
25570
|
const bigintToBuffer = (n) => {
|
|
@@ -25572,9 +25614,29 @@ var TuyaClient = class {
|
|
|
25572
25614
|
buildRequest(action) {
|
|
25573
25615
|
return buildApiParams(action, { session: this.session, signer: this.signer, env: this.env });
|
|
25574
25616
|
}
|
|
25575
|
-
/**
|
|
25617
|
+
/**
|
|
25618
|
+
* Build + POST an action, returning the parsed envelope. Requires a working signer.
|
|
25619
|
+
*
|
|
25620
|
+
* On an `et=3` reply the envelope's `result` is a base64 AES-128-GCM blob, not an object; this
|
|
25621
|
+
* decrypts it (keyed off the request's `requestId` and the session's `ecode`) and returns the
|
|
25622
|
+
* decrypted inner envelope, so callers read `result` as an object regardless. A non-string `result`
|
|
25623
|
+
* (an error, or an already-plain reply) passes through untouched.
|
|
25624
|
+
*/
|
|
25576
25625
|
async call(action) {
|
|
25577
|
-
|
|
25626
|
+
const params = this.buildRequest(action);
|
|
25627
|
+
const env = await sendApiRequest(params, { endpoint: this.endpoint, http: this.http });
|
|
25628
|
+
return this.decodeReply(env, params.requestId);
|
|
25629
|
+
}
|
|
25630
|
+
/**
|
|
25631
|
+
* Decode an et=3 reply. When `result` is a non-empty string it is the encrypted blob: decrypt and
|
|
25632
|
+
* parse it (a wrong key or tampered blob fails the GCM tag in {@link decryptReply} and throws). The
|
|
25633
|
+
* plaintext is itself the standard `{success, t, result}` envelope, which becomes the returned one.
|
|
25634
|
+
*/
|
|
25635
|
+
decodeReply(env, requestId) {
|
|
25636
|
+
if (typeof env.result !== "string" || env.result === "")
|
|
25637
|
+
return env;
|
|
25638
|
+
const key = deriveBodyKey(requestId, this.session.ecode ?? null);
|
|
25639
|
+
return JSON.parse(decryptReply(key, env.result));
|
|
25578
25640
|
}
|
|
25579
25641
|
/**
|
|
25580
25642
|
* Log into the Tuya cloud from a eufy user id (wire-confirmed from the eufy Security app).
|
|
@@ -25607,7 +25669,7 @@ var TuyaClient = class {
|
|
|
25607
25669
|
const encPasswd = rsaEncryptPassword(password, publicKey, exponent);
|
|
25608
25670
|
const loginRes = await this.call(buildPasswordLoginRegAction(account.countryCode, account.username, encPasswd, token));
|
|
25609
25671
|
if (loginRes.success && loginRes.result?.sid && loginRes.result?.uid) {
|
|
25610
|
-
return { sid: loginRes.result.sid, uid: loginRes.result.uid };
|
|
25672
|
+
return { sid: loginRes.result.sid, uid: loginRes.result.uid, ecode: loginRes.result.ecode };
|
|
25611
25673
|
}
|
|
25612
25674
|
if ((loginRes.errorCode ?? loginRes.errorMsg) === "USER_PASSWD_WRONG")
|
|
25613
25675
|
return "PASSWD_WRONG";
|
|
@@ -25619,18 +25681,18 @@ var TuyaClient = class {
|
|
|
25619
25681
|
if (result === "PASSWD_WRONG") {
|
|
25620
25682
|
throw new Error("tuya login: USER_PASSWD_WRONG on both derived password and fallback '12345678'");
|
|
25621
25683
|
}
|
|
25622
|
-
this.session = { ...this.session, sid: result.sid };
|
|
25623
|
-
return result;
|
|
25684
|
+
this.session = { ...this.session, sid: result.sid, ecode: result.ecode };
|
|
25685
|
+
return { sid: result.sid, uid: result.uid };
|
|
25624
25686
|
}
|
|
25625
25687
|
/**
|
|
25626
|
-
* READ/dump a device's cached data-points (`
|
|
25688
|
+
* READ/dump a device's cached data-points (`smartlife.m.device.cache.dp.get`).
|
|
25627
25689
|
* Builds the request without needing a working signer; the signer is only exercised on send.
|
|
25628
25690
|
*/
|
|
25629
25691
|
async getDeviceDps(devId, dpCacheType) {
|
|
25630
25692
|
return this.call(buildGetDeviceDpsAction(devId, dpCacheType));
|
|
25631
25693
|
}
|
|
25632
25694
|
/**
|
|
25633
|
-
* CONTROL: publish data-points to a device (`
|
|
25695
|
+
* CONTROL: publish data-points to a device (`smartlife.m.device.dp.publish`). `dps` is
|
|
25634
25696
|
* `{ "<dpId>": <value> }`. `gwId` is the gateway/parent id (equals `devId` for a standalone gw).
|
|
25635
25697
|
*
|
|
25636
25698
|
* ⚠️ UNVERIFIED write — refuses to send by default. The `dp.publish` param shape
|
|
@@ -25769,7 +25831,7 @@ var TuyaCommandRouter = class {
|
|
|
25769
25831
|
return this.loginOnce;
|
|
25770
25832
|
}
|
|
25771
25833
|
/**
|
|
25772
|
-
* Fetch a device's cached DPs from the Tuya cloud (`
|
|
25834
|
+
* Fetch a device's cached DPs from the Tuya cloud (`smartlife.m.device.cache.dp.get`) and deliver
|
|
25773
25835
|
* the raw DP map to the caller. Used for initial state hydration after MQTT subscribe — gets the
|
|
25774
25836
|
* last-known state without waiting for the first realtime push.
|
|
25775
25837
|
*
|
|
@@ -26647,7 +26709,7 @@ var PushClient = class _PushClient extends EventEmitter12 {
|
|
|
26647
26709
|
};
|
|
26648
26710
|
|
|
26649
26711
|
// dist/transport/push/fcm.js
|
|
26650
|
-
import { randomBytes as
|
|
26712
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
26651
26713
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
26652
26714
|
var FCM = {
|
|
26653
26715
|
PROJECT_ID: "batterycam-3250a",
|
|
@@ -26660,7 +26722,7 @@ var FCM = {
|
|
|
26660
26722
|
SDK_VERSION: "a:16.3.1"
|
|
26661
26723
|
};
|
|
26662
26724
|
function generateFid() {
|
|
26663
|
-
const b =
|
|
26725
|
+
const b = randomBytes6(17);
|
|
26664
26726
|
b[0] = 112 + b[0] % 16;
|
|
26665
26727
|
return b.toString("base64url").slice(0, 22);
|
|
26666
26728
|
}
|
|
@@ -30787,6 +30849,7 @@ __export(tuya_exports, {
|
|
|
30787
30849
|
genDeviceId: () => genDeviceId,
|
|
30788
30850
|
isoToDialCode: () => isoToDialCode,
|
|
30789
30851
|
resolveCountryCode: () => resolveCountryCode,
|
|
30852
|
+
resolveSignKey: () => resolveSignKey,
|
|
30790
30853
|
sendApiRequest: () => sendApiRequest,
|
|
30791
30854
|
swapMd5: () => swapMd5,
|
|
30792
30855
|
tuyaUsername: () => tuyaUsername
|