@lumiastream/tapo-cove 3.12.0 → 3.12.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 +12 -0
- package/dist/index.d.mts +8 -9
- package/dist/index.d.ts +8 -9
- package/dist/index.js +55 -47
- package/dist/index.mjs +55 -47
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.12.2](https://github.com/lumiastream/rgb/compare/v3.12.1...v3.12.2) (2023-10-26)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- tapo cookie error ([5aa2093](https://github.com/lumiastream/rgb/commit/5aa20933547b1782bbd27ea321c3c75dfe046992))
|
|
11
|
+
|
|
12
|
+
## [3.12.1](https://github.com/lumiastream/rgb/compare/v3.12.0...v3.12.1) (2023-10-26)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- tapo host set error ([6437ed3](https://github.com/lumiastream/rgb/commit/6437ed3f0a8f44fbb9ddad2b78d815f71deae7b0))
|
|
17
|
+
|
|
6
18
|
# [3.12.0](https://github.com/lumiastream/rgb/compare/v3.11.2...v3.12.0) (2023-10-25)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/dist/index.d.mts
CHANGED
|
@@ -97,14 +97,13 @@ declare class TapoApi {
|
|
|
97
97
|
email: string;
|
|
98
98
|
password: string;
|
|
99
99
|
}) => Promise<string>;
|
|
100
|
-
setup: (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}) => Promise<Map<string, DeviceSession>>;
|
|
100
|
+
setup: (devices: Array<{
|
|
101
|
+
id: string;
|
|
102
|
+
host: string;
|
|
103
|
+
}>, logger?: any) => Promise<{
|
|
104
|
+
responses: PromiseSettledResult<void>[];
|
|
105
|
+
devices: Map<string, DeviceSession>;
|
|
106
|
+
}>;
|
|
108
107
|
sendState: (config: {
|
|
109
108
|
device: {
|
|
110
109
|
id: string;
|
|
@@ -121,7 +120,7 @@ declare class TapoApi {
|
|
|
121
120
|
power: boolean;
|
|
122
121
|
}) => void;
|
|
123
122
|
private sessionPost;
|
|
124
|
-
needsNewHandshake(): boolean;
|
|
123
|
+
needsNewHandshake(devSession: any): boolean;
|
|
125
124
|
private handshake;
|
|
126
125
|
private firstHandshake;
|
|
127
126
|
private secondHandshake;
|
package/dist/index.d.ts
CHANGED
|
@@ -97,14 +97,13 @@ declare class TapoApi {
|
|
|
97
97
|
email: string;
|
|
98
98
|
password: string;
|
|
99
99
|
}) => Promise<string>;
|
|
100
|
-
setup: (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}) => Promise<Map<string, DeviceSession>>;
|
|
100
|
+
setup: (devices: Array<{
|
|
101
|
+
id: string;
|
|
102
|
+
host: string;
|
|
103
|
+
}>, logger?: any) => Promise<{
|
|
104
|
+
responses: PromiseSettledResult<void>[];
|
|
105
|
+
devices: Map<string, DeviceSession>;
|
|
106
|
+
}>;
|
|
108
107
|
sendState: (config: {
|
|
109
108
|
device: {
|
|
110
109
|
id: string;
|
|
@@ -121,7 +120,7 @@ declare class TapoApi {
|
|
|
121
120
|
power: boolean;
|
|
122
121
|
}) => void;
|
|
123
122
|
private sessionPost;
|
|
124
|
-
needsNewHandshake(): boolean;
|
|
123
|
+
needsNewHandshake(devSession: any): boolean;
|
|
125
124
|
private handshake;
|
|
126
125
|
private firstHandshake;
|
|
127
126
|
private secondHandshake;
|
package/dist/index.js
CHANGED
|
@@ -85,8 +85,8 @@ var base64Decode = (data) => {
|
|
|
85
85
|
// src/shared/helpers.ts
|
|
86
86
|
var throwErrorIfFound = (responseData) => {
|
|
87
87
|
const errorCode = responseData["error_code"];
|
|
88
|
-
console.debug("[Tapo] errorCode: ", errorCode);
|
|
89
88
|
if (errorCode) {
|
|
89
|
+
console.debug("[Tapo] errorCode: ", errorCode);
|
|
90
90
|
switch (errorCode) {
|
|
91
91
|
case 0:
|
|
92
92
|
return;
|
|
@@ -389,26 +389,27 @@ var _TapoApi = class _TapoApi {
|
|
|
389
389
|
this._token = response.data.result.token;
|
|
390
390
|
return this._token;
|
|
391
391
|
});
|
|
392
|
-
this.setup = (
|
|
392
|
+
this.setup = (devices, logger) => __async(this, null, function* () {
|
|
393
393
|
const promises = devices.map((device) => __async(this, null, function* () {
|
|
394
|
-
const deviceSession = yield this.handshake(device.host);
|
|
394
|
+
const deviceSession = yield this.handshake(device.host, void 0, false, logger);
|
|
395
395
|
this._devices.set(device.id, deviceSession);
|
|
396
396
|
return;
|
|
397
397
|
}));
|
|
398
|
+
let responses = [];
|
|
398
399
|
try {
|
|
399
|
-
|
|
400
|
+
responses = yield Promise.allSettled(promises);
|
|
400
401
|
} catch (err) {
|
|
401
402
|
console.error("tapo setup err: ", err);
|
|
402
403
|
}
|
|
403
|
-
return this._devices;
|
|
404
|
+
return { responses, devices: this._devices };
|
|
404
405
|
});
|
|
405
406
|
// Change state using lightstate
|
|
406
407
|
this.sendState = (config) => __async(this, null, function* () {
|
|
407
|
-
const
|
|
408
|
-
if (!
|
|
408
|
+
const devSession = this._devices.get(config.device.id);
|
|
409
|
+
if (!devSession) {
|
|
409
410
|
return Promise.resolve(false);
|
|
410
411
|
}
|
|
411
|
-
yield this.handshake(
|
|
412
|
+
yield this.handshake(devSession == null ? void 0 : devSession.ip, devSession, false);
|
|
412
413
|
let shouldWait = false;
|
|
413
414
|
if (config.fetchConfig && config.fetchConfig.shouldWait) {
|
|
414
415
|
shouldWait = true;
|
|
@@ -419,14 +420,14 @@ var _TapoApi = class _TapoApi {
|
|
|
419
420
|
params: values
|
|
420
421
|
// terminalUUID: uuidv4(),
|
|
421
422
|
});
|
|
422
|
-
const requestData =
|
|
423
|
-
const response = yield this.sessionPost(
|
|
423
|
+
const requestData = devSession.cipher.encrypt(deviceRequest);
|
|
424
|
+
const response = yield this.sessionPost(devSession.ip, "/request", requestData.encrypted, "arraybuffer", devSession.Cookie, {
|
|
424
425
|
seq: requestData.seq.toString()
|
|
425
426
|
});
|
|
426
427
|
if (response.status !== 200) {
|
|
427
428
|
throw new Error("[KLAP] Request failed");
|
|
428
429
|
}
|
|
429
|
-
const data = JSON.parse(
|
|
430
|
+
const data = JSON.parse(devSession.cipher.decrypt(response.data));
|
|
430
431
|
return {
|
|
431
432
|
response,
|
|
432
433
|
body: data
|
|
@@ -447,113 +448,120 @@ var _TapoApi = class _TapoApi {
|
|
|
447
448
|
// Helpers
|
|
448
449
|
sessionPost(deviceIp, path, payload, responseType, cookie, params) {
|
|
449
450
|
return __async(this, null, function* () {
|
|
451
|
+
var _a;
|
|
452
|
+
const headers = {
|
|
453
|
+
Accept: "*/*",
|
|
454
|
+
"Content-Type": "application/octet-stream"
|
|
455
|
+
};
|
|
456
|
+
if (cookie) {
|
|
457
|
+
if ((_a = process == null ? void 0 : process.versions) == null ? void 0 : _a.electron) {
|
|
458
|
+
headers.BypassCookie = cookie;
|
|
459
|
+
} else {
|
|
460
|
+
headers.Cookie = cookie;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
450
463
|
return import_axios.default.post(`http://${deviceIp}/app${path}`, payload, {
|
|
451
464
|
responseType,
|
|
452
465
|
params,
|
|
453
|
-
headers
|
|
454
|
-
Host: deviceIp,
|
|
455
|
-
Accept: "*/*",
|
|
456
|
-
"Content-Type": "application/octet-stream"
|
|
457
|
-
}, cookie && {
|
|
458
|
-
Cookie: cookie
|
|
459
|
-
}),
|
|
466
|
+
headers,
|
|
460
467
|
httpAgent: new import_http.default.Agent({
|
|
461
468
|
keepAlive: false
|
|
462
469
|
})
|
|
463
470
|
});
|
|
464
471
|
});
|
|
465
472
|
}
|
|
466
|
-
needsNewHandshake() {
|
|
467
|
-
if (!
|
|
473
|
+
needsNewHandshake(devSession) {
|
|
474
|
+
if (!devSession) {
|
|
468
475
|
return true;
|
|
469
476
|
}
|
|
470
|
-
if (!
|
|
477
|
+
if (!devSession.cipher) {
|
|
471
478
|
return true;
|
|
472
479
|
}
|
|
473
|
-
if (
|
|
480
|
+
if (devSession.IsExpired) {
|
|
474
481
|
return true;
|
|
475
482
|
}
|
|
476
|
-
if (!
|
|
483
|
+
if (!devSession.Cookie) {
|
|
477
484
|
return true;
|
|
478
485
|
}
|
|
479
486
|
return false;
|
|
480
487
|
}
|
|
481
|
-
handshake(deviceIp, force = false) {
|
|
488
|
+
handshake(deviceIp, devSession, force = false, logger) {
|
|
482
489
|
return __async(this, null, function* () {
|
|
483
|
-
if (!this.needsNewHandshake() && !force) {
|
|
490
|
+
if (!this.needsNewHandshake(devSession) && !force) {
|
|
484
491
|
return;
|
|
485
492
|
}
|
|
486
|
-
const { localSeed, remoteSeed, authHash } = yield this.firstHandshake(deviceIp);
|
|
487
|
-
return yield this.secondHandshake(deviceIp, localSeed, remoteSeed, authHash);
|
|
493
|
+
const { localSeed, remoteSeed, authHash, deviceSession } = yield this.firstHandshake(deviceIp, void 0, logger);
|
|
494
|
+
return yield this.secondHandshake(deviceSession, deviceIp, localSeed, remoteSeed, authHash, logger);
|
|
488
495
|
});
|
|
489
496
|
}
|
|
490
|
-
firstHandshake(deviceIp, seed) {
|
|
497
|
+
firstHandshake(deviceIp, seed, logger) {
|
|
491
498
|
return __async(this, null, function* () {
|
|
492
499
|
var _a;
|
|
493
500
|
const localSeed = seed ? seed : import_crypto4.default.randomBytes(16);
|
|
494
501
|
const handshake1Result = yield this.sessionPost(deviceIp, "/handshake1", localSeed, "arraybuffer");
|
|
502
|
+
logger == null ? void 0 : logger.debug("handshake1Result: ", handshake1Result);
|
|
495
503
|
if (handshake1Result.status !== 200) {
|
|
496
504
|
throw new Error("Handshake1 failed");
|
|
497
505
|
}
|
|
498
506
|
if (handshake1Result.headers["content-length"] !== "48") {
|
|
499
507
|
throw new Error("Handshake1 failed due to invalid content length");
|
|
500
508
|
}
|
|
501
|
-
const cookie = (_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0];
|
|
502
|
-
const data = handshake1Result.data;
|
|
509
|
+
const cookie = handshake1Result.headers["bypass-cookie"] || ((_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0]);
|
|
510
|
+
const data = Buffer.from(new Uint8Array(handshake1Result.data));
|
|
503
511
|
const [cookieValue, timeout] = cookie.split(";");
|
|
504
512
|
const timeoutValue = timeout.split("=").pop();
|
|
505
|
-
|
|
513
|
+
const deviceSession = new DeviceSession(timeoutValue, deviceIp, cookieValue);
|
|
506
514
|
const remoteSeed = data.subarray(0, 16);
|
|
507
515
|
const serverHash = data.subarray(16);
|
|
508
|
-
|
|
516
|
+
logger == null ? void 0 : logger.debug("[KLAP] First handshake decoded successfully:\nRemote Seed:", remoteSeed.toString("hex"), "\nServer Hash:", serverHash.toString("hex"), "\nCookie:", cookieValue);
|
|
509
517
|
const localHash = this.hashAuth(this._config.rawEmail, this._config.rawPassword);
|
|
510
518
|
const localAuthHash = this.sha256(Buffer.concat([localSeed, remoteSeed, localHash]));
|
|
511
519
|
if (Buffer.compare(localAuthHash, serverHash) === 0) {
|
|
512
|
-
|
|
520
|
+
logger == null ? void 0 : logger.debug("[KLAP] Local auth hash matches server hash");
|
|
513
521
|
return {
|
|
514
522
|
localSeed,
|
|
515
523
|
remoteSeed,
|
|
516
|
-
authHash: localHash
|
|
524
|
+
authHash: localHash,
|
|
525
|
+
deviceSession
|
|
517
526
|
};
|
|
518
527
|
}
|
|
519
528
|
const emptyHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth("", "")]));
|
|
520
529
|
if (Buffer.compare(emptyHash, serverHash) === 0) {
|
|
521
|
-
|
|
530
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Empty auth hash matches server hash");
|
|
522
531
|
return {
|
|
523
532
|
localSeed,
|
|
524
533
|
remoteSeed,
|
|
525
|
-
authHash: emptyHash
|
|
534
|
+
authHash: emptyHash,
|
|
535
|
+
deviceSession
|
|
526
536
|
};
|
|
527
537
|
}
|
|
528
538
|
const testHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth(_TapoApi.TP_TEST_USER, _TapoApi.TP_TEST_PASSWORD)]));
|
|
529
539
|
if (Buffer.compare(testHash, serverHash) === 0) {
|
|
530
|
-
|
|
540
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Test auth hash matches server hash");
|
|
531
541
|
return {
|
|
532
542
|
localSeed,
|
|
533
543
|
remoteSeed,
|
|
534
|
-
authHash: testHash
|
|
544
|
+
authHash: testHash,
|
|
545
|
+
deviceSession
|
|
535
546
|
};
|
|
536
547
|
}
|
|
537
|
-
this.session = void 0;
|
|
538
548
|
throw new Error("Failed to verify server hash");
|
|
539
549
|
});
|
|
540
550
|
}
|
|
541
|
-
secondHandshake(deviceIp, localSeed, remoteSeed, authHash) {
|
|
551
|
+
secondHandshake(devSession, deviceIp, localSeed, remoteSeed, authHash, logger) {
|
|
542
552
|
return __async(this, null, function* () {
|
|
543
553
|
const localAuthHash = this.sha256(Buffer.concat([remoteSeed, localSeed, authHash]));
|
|
544
554
|
try {
|
|
545
|
-
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text",
|
|
555
|
+
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text", devSession.Cookie);
|
|
546
556
|
if (handshake2Result.status === 200) {
|
|
547
|
-
|
|
548
|
-
const deviceSession =
|
|
549
|
-
this.session = deviceSession;
|
|
557
|
+
logger == null ? void 0 : logger.debug("[KLAP] Second handshake successful");
|
|
558
|
+
const deviceSession = devSession.completeHandshake(deviceIp, new KlapCipher(localSeed, remoteSeed, authHash));
|
|
550
559
|
return deviceSession;
|
|
551
560
|
}
|
|
552
|
-
|
|
561
|
+
logger.warn("[KLAP] Second handshake failed", handshake2Result.data);
|
|
553
562
|
} catch (e) {
|
|
554
|
-
|
|
563
|
+
logger.error("[KLAP] Second handshake failed:", e.response.data || e.message);
|
|
555
564
|
}
|
|
556
|
-
this.session = void 0;
|
|
557
565
|
return void 0;
|
|
558
566
|
});
|
|
559
567
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -53,8 +53,8 @@ var base64Decode = (data) => {
|
|
|
53
53
|
// src/shared/helpers.ts
|
|
54
54
|
var throwErrorIfFound = (responseData) => {
|
|
55
55
|
const errorCode = responseData["error_code"];
|
|
56
|
-
console.debug("[Tapo] errorCode: ", errorCode);
|
|
57
56
|
if (errorCode) {
|
|
57
|
+
console.debug("[Tapo] errorCode: ", errorCode);
|
|
58
58
|
switch (errorCode) {
|
|
59
59
|
case 0:
|
|
60
60
|
return;
|
|
@@ -357,26 +357,27 @@ var _TapoApi = class _TapoApi {
|
|
|
357
357
|
this._token = response.data.result.token;
|
|
358
358
|
return this._token;
|
|
359
359
|
});
|
|
360
|
-
this.setup = (
|
|
360
|
+
this.setup = (devices, logger) => __async(this, null, function* () {
|
|
361
361
|
const promises = devices.map((device) => __async(this, null, function* () {
|
|
362
|
-
const deviceSession = yield this.handshake(device.host);
|
|
362
|
+
const deviceSession = yield this.handshake(device.host, void 0, false, logger);
|
|
363
363
|
this._devices.set(device.id, deviceSession);
|
|
364
364
|
return;
|
|
365
365
|
}));
|
|
366
|
+
let responses = [];
|
|
366
367
|
try {
|
|
367
|
-
|
|
368
|
+
responses = yield Promise.allSettled(promises);
|
|
368
369
|
} catch (err) {
|
|
369
370
|
console.error("tapo setup err: ", err);
|
|
370
371
|
}
|
|
371
|
-
return this._devices;
|
|
372
|
+
return { responses, devices: this._devices };
|
|
372
373
|
});
|
|
373
374
|
// Change state using lightstate
|
|
374
375
|
this.sendState = (config) => __async(this, null, function* () {
|
|
375
|
-
const
|
|
376
|
-
if (!
|
|
376
|
+
const devSession = this._devices.get(config.device.id);
|
|
377
|
+
if (!devSession) {
|
|
377
378
|
return Promise.resolve(false);
|
|
378
379
|
}
|
|
379
|
-
yield this.handshake(
|
|
380
|
+
yield this.handshake(devSession == null ? void 0 : devSession.ip, devSession, false);
|
|
380
381
|
let shouldWait = false;
|
|
381
382
|
if (config.fetchConfig && config.fetchConfig.shouldWait) {
|
|
382
383
|
shouldWait = true;
|
|
@@ -387,14 +388,14 @@ var _TapoApi = class _TapoApi {
|
|
|
387
388
|
params: values
|
|
388
389
|
// terminalUUID: uuidv4(),
|
|
389
390
|
});
|
|
390
|
-
const requestData =
|
|
391
|
-
const response = yield this.sessionPost(
|
|
391
|
+
const requestData = devSession.cipher.encrypt(deviceRequest);
|
|
392
|
+
const response = yield this.sessionPost(devSession.ip, "/request", requestData.encrypted, "arraybuffer", devSession.Cookie, {
|
|
392
393
|
seq: requestData.seq.toString()
|
|
393
394
|
});
|
|
394
395
|
if (response.status !== 200) {
|
|
395
396
|
throw new Error("[KLAP] Request failed");
|
|
396
397
|
}
|
|
397
|
-
const data = JSON.parse(
|
|
398
|
+
const data = JSON.parse(devSession.cipher.decrypt(response.data));
|
|
398
399
|
return {
|
|
399
400
|
response,
|
|
400
401
|
body: data
|
|
@@ -415,113 +416,120 @@ var _TapoApi = class _TapoApi {
|
|
|
415
416
|
// Helpers
|
|
416
417
|
sessionPost(deviceIp, path, payload, responseType, cookie, params) {
|
|
417
418
|
return __async(this, null, function* () {
|
|
419
|
+
var _a;
|
|
420
|
+
const headers = {
|
|
421
|
+
Accept: "*/*",
|
|
422
|
+
"Content-Type": "application/octet-stream"
|
|
423
|
+
};
|
|
424
|
+
if (cookie) {
|
|
425
|
+
if ((_a = process == null ? void 0 : process.versions) == null ? void 0 : _a.electron) {
|
|
426
|
+
headers.BypassCookie = cookie;
|
|
427
|
+
} else {
|
|
428
|
+
headers.Cookie = cookie;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
418
431
|
return axios.post(`http://${deviceIp}/app${path}`, payload, {
|
|
419
432
|
responseType,
|
|
420
433
|
params,
|
|
421
|
-
headers
|
|
422
|
-
Host: deviceIp,
|
|
423
|
-
Accept: "*/*",
|
|
424
|
-
"Content-Type": "application/octet-stream"
|
|
425
|
-
}, cookie && {
|
|
426
|
-
Cookie: cookie
|
|
427
|
-
}),
|
|
434
|
+
headers,
|
|
428
435
|
httpAgent: new http.Agent({
|
|
429
436
|
keepAlive: false
|
|
430
437
|
})
|
|
431
438
|
});
|
|
432
439
|
});
|
|
433
440
|
}
|
|
434
|
-
needsNewHandshake() {
|
|
435
|
-
if (!
|
|
441
|
+
needsNewHandshake(devSession) {
|
|
442
|
+
if (!devSession) {
|
|
436
443
|
return true;
|
|
437
444
|
}
|
|
438
|
-
if (!
|
|
445
|
+
if (!devSession.cipher) {
|
|
439
446
|
return true;
|
|
440
447
|
}
|
|
441
|
-
if (
|
|
448
|
+
if (devSession.IsExpired) {
|
|
442
449
|
return true;
|
|
443
450
|
}
|
|
444
|
-
if (!
|
|
451
|
+
if (!devSession.Cookie) {
|
|
445
452
|
return true;
|
|
446
453
|
}
|
|
447
454
|
return false;
|
|
448
455
|
}
|
|
449
|
-
handshake(deviceIp, force = false) {
|
|
456
|
+
handshake(deviceIp, devSession, force = false, logger) {
|
|
450
457
|
return __async(this, null, function* () {
|
|
451
|
-
if (!this.needsNewHandshake() && !force) {
|
|
458
|
+
if (!this.needsNewHandshake(devSession) && !force) {
|
|
452
459
|
return;
|
|
453
460
|
}
|
|
454
|
-
const { localSeed, remoteSeed, authHash } = yield this.firstHandshake(deviceIp);
|
|
455
|
-
return yield this.secondHandshake(deviceIp, localSeed, remoteSeed, authHash);
|
|
461
|
+
const { localSeed, remoteSeed, authHash, deviceSession } = yield this.firstHandshake(deviceIp, void 0, logger);
|
|
462
|
+
return yield this.secondHandshake(deviceSession, deviceIp, localSeed, remoteSeed, authHash, logger);
|
|
456
463
|
});
|
|
457
464
|
}
|
|
458
|
-
firstHandshake(deviceIp, seed) {
|
|
465
|
+
firstHandshake(deviceIp, seed, logger) {
|
|
459
466
|
return __async(this, null, function* () {
|
|
460
467
|
var _a;
|
|
461
468
|
const localSeed = seed ? seed : crypto4.randomBytes(16);
|
|
462
469
|
const handshake1Result = yield this.sessionPost(deviceIp, "/handshake1", localSeed, "arraybuffer");
|
|
470
|
+
logger == null ? void 0 : logger.debug("handshake1Result: ", handshake1Result);
|
|
463
471
|
if (handshake1Result.status !== 200) {
|
|
464
472
|
throw new Error("Handshake1 failed");
|
|
465
473
|
}
|
|
466
474
|
if (handshake1Result.headers["content-length"] !== "48") {
|
|
467
475
|
throw new Error("Handshake1 failed due to invalid content length");
|
|
468
476
|
}
|
|
469
|
-
const cookie = (_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0];
|
|
470
|
-
const data = handshake1Result.data;
|
|
477
|
+
const cookie = handshake1Result.headers["bypass-cookie"] || ((_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0]);
|
|
478
|
+
const data = Buffer.from(new Uint8Array(handshake1Result.data));
|
|
471
479
|
const [cookieValue, timeout] = cookie.split(";");
|
|
472
480
|
const timeoutValue = timeout.split("=").pop();
|
|
473
|
-
|
|
481
|
+
const deviceSession = new DeviceSession(timeoutValue, deviceIp, cookieValue);
|
|
474
482
|
const remoteSeed = data.subarray(0, 16);
|
|
475
483
|
const serverHash = data.subarray(16);
|
|
476
|
-
|
|
484
|
+
logger == null ? void 0 : logger.debug("[KLAP] First handshake decoded successfully:\nRemote Seed:", remoteSeed.toString("hex"), "\nServer Hash:", serverHash.toString("hex"), "\nCookie:", cookieValue);
|
|
477
485
|
const localHash = this.hashAuth(this._config.rawEmail, this._config.rawPassword);
|
|
478
486
|
const localAuthHash = this.sha256(Buffer.concat([localSeed, remoteSeed, localHash]));
|
|
479
487
|
if (Buffer.compare(localAuthHash, serverHash) === 0) {
|
|
480
|
-
|
|
488
|
+
logger == null ? void 0 : logger.debug("[KLAP] Local auth hash matches server hash");
|
|
481
489
|
return {
|
|
482
490
|
localSeed,
|
|
483
491
|
remoteSeed,
|
|
484
|
-
authHash: localHash
|
|
492
|
+
authHash: localHash,
|
|
493
|
+
deviceSession
|
|
485
494
|
};
|
|
486
495
|
}
|
|
487
496
|
const emptyHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth("", "")]));
|
|
488
497
|
if (Buffer.compare(emptyHash, serverHash) === 0) {
|
|
489
|
-
|
|
498
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Empty auth hash matches server hash");
|
|
490
499
|
return {
|
|
491
500
|
localSeed,
|
|
492
501
|
remoteSeed,
|
|
493
|
-
authHash: emptyHash
|
|
502
|
+
authHash: emptyHash,
|
|
503
|
+
deviceSession
|
|
494
504
|
};
|
|
495
505
|
}
|
|
496
506
|
const testHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth(_TapoApi.TP_TEST_USER, _TapoApi.TP_TEST_PASSWORD)]));
|
|
497
507
|
if (Buffer.compare(testHash, serverHash) === 0) {
|
|
498
|
-
|
|
508
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Test auth hash matches server hash");
|
|
499
509
|
return {
|
|
500
510
|
localSeed,
|
|
501
511
|
remoteSeed,
|
|
502
|
-
authHash: testHash
|
|
512
|
+
authHash: testHash,
|
|
513
|
+
deviceSession
|
|
503
514
|
};
|
|
504
515
|
}
|
|
505
|
-
this.session = void 0;
|
|
506
516
|
throw new Error("Failed to verify server hash");
|
|
507
517
|
});
|
|
508
518
|
}
|
|
509
|
-
secondHandshake(deviceIp, localSeed, remoteSeed, authHash) {
|
|
519
|
+
secondHandshake(devSession, deviceIp, localSeed, remoteSeed, authHash, logger) {
|
|
510
520
|
return __async(this, null, function* () {
|
|
511
521
|
const localAuthHash = this.sha256(Buffer.concat([remoteSeed, localSeed, authHash]));
|
|
512
522
|
try {
|
|
513
|
-
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text",
|
|
523
|
+
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text", devSession.Cookie);
|
|
514
524
|
if (handshake2Result.status === 200) {
|
|
515
|
-
|
|
516
|
-
const deviceSession =
|
|
517
|
-
this.session = deviceSession;
|
|
525
|
+
logger == null ? void 0 : logger.debug("[KLAP] Second handshake successful");
|
|
526
|
+
const deviceSession = devSession.completeHandshake(deviceIp, new KlapCipher(localSeed, remoteSeed, authHash));
|
|
518
527
|
return deviceSession;
|
|
519
528
|
}
|
|
520
|
-
|
|
529
|
+
logger.warn("[KLAP] Second handshake failed", handshake2Result.data);
|
|
521
530
|
} catch (e) {
|
|
522
|
-
|
|
531
|
+
logger.error("[KLAP] Second handshake failed:", e.response.data || e.message);
|
|
523
532
|
}
|
|
524
|
-
this.session = void 0;
|
|
525
533
|
return void 0;
|
|
526
534
|
});
|
|
527
535
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumiastream/tapo-cove",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "GPL",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"tsup": "*",
|
|
25
25
|
"typescript": "*"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "50bec9272df26e7211708d21cd0f1de4876885d9"
|
|
28
28
|
}
|