@lumiastream/tapo-cove 3.12.1 → 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 +6 -0
- package/dist/index.d.mts +8 -9
- package/dist/index.d.ts +8 -9
- package/dist/index.js +55 -46
- package/dist/index.mjs +55 -46
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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
|
+
|
|
6
12
|
## [3.12.1](https://github.com/lumiastream/rgb/compare/v3.12.0...v3.12.1) (2023-10-26)
|
|
7
13
|
|
|
8
14
|
### 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,112 +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
|
-
Accept: "*/*",
|
|
455
|
-
"Content-Type": "application/octet-stream"
|
|
456
|
-
}, cookie && {
|
|
457
|
-
Cookie: cookie
|
|
458
|
-
}),
|
|
466
|
+
headers,
|
|
459
467
|
httpAgent: new import_http.default.Agent({
|
|
460
468
|
keepAlive: false
|
|
461
469
|
})
|
|
462
470
|
});
|
|
463
471
|
});
|
|
464
472
|
}
|
|
465
|
-
needsNewHandshake() {
|
|
466
|
-
if (!
|
|
473
|
+
needsNewHandshake(devSession) {
|
|
474
|
+
if (!devSession) {
|
|
467
475
|
return true;
|
|
468
476
|
}
|
|
469
|
-
if (!
|
|
477
|
+
if (!devSession.cipher) {
|
|
470
478
|
return true;
|
|
471
479
|
}
|
|
472
|
-
if (
|
|
480
|
+
if (devSession.IsExpired) {
|
|
473
481
|
return true;
|
|
474
482
|
}
|
|
475
|
-
if (!
|
|
483
|
+
if (!devSession.Cookie) {
|
|
476
484
|
return true;
|
|
477
485
|
}
|
|
478
486
|
return false;
|
|
479
487
|
}
|
|
480
|
-
handshake(deviceIp, force = false) {
|
|
488
|
+
handshake(deviceIp, devSession, force = false, logger) {
|
|
481
489
|
return __async(this, null, function* () {
|
|
482
|
-
if (!this.needsNewHandshake() && !force) {
|
|
490
|
+
if (!this.needsNewHandshake(devSession) && !force) {
|
|
483
491
|
return;
|
|
484
492
|
}
|
|
485
|
-
const { localSeed, remoteSeed, authHash } = yield this.firstHandshake(deviceIp);
|
|
486
|
-
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);
|
|
487
495
|
});
|
|
488
496
|
}
|
|
489
|
-
firstHandshake(deviceIp, seed) {
|
|
497
|
+
firstHandshake(deviceIp, seed, logger) {
|
|
490
498
|
return __async(this, null, function* () {
|
|
491
499
|
var _a;
|
|
492
500
|
const localSeed = seed ? seed : import_crypto4.default.randomBytes(16);
|
|
493
501
|
const handshake1Result = yield this.sessionPost(deviceIp, "/handshake1", localSeed, "arraybuffer");
|
|
502
|
+
logger == null ? void 0 : logger.debug("handshake1Result: ", handshake1Result);
|
|
494
503
|
if (handshake1Result.status !== 200) {
|
|
495
504
|
throw new Error("Handshake1 failed");
|
|
496
505
|
}
|
|
497
506
|
if (handshake1Result.headers["content-length"] !== "48") {
|
|
498
507
|
throw new Error("Handshake1 failed due to invalid content length");
|
|
499
508
|
}
|
|
500
|
-
const cookie = (_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0];
|
|
501
|
-
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));
|
|
502
511
|
const [cookieValue, timeout] = cookie.split(";");
|
|
503
512
|
const timeoutValue = timeout.split("=").pop();
|
|
504
|
-
|
|
513
|
+
const deviceSession = new DeviceSession(timeoutValue, deviceIp, cookieValue);
|
|
505
514
|
const remoteSeed = data.subarray(0, 16);
|
|
506
515
|
const serverHash = data.subarray(16);
|
|
507
|
-
|
|
516
|
+
logger == null ? void 0 : logger.debug("[KLAP] First handshake decoded successfully:\nRemote Seed:", remoteSeed.toString("hex"), "\nServer Hash:", serverHash.toString("hex"), "\nCookie:", cookieValue);
|
|
508
517
|
const localHash = this.hashAuth(this._config.rawEmail, this._config.rawPassword);
|
|
509
518
|
const localAuthHash = this.sha256(Buffer.concat([localSeed, remoteSeed, localHash]));
|
|
510
519
|
if (Buffer.compare(localAuthHash, serverHash) === 0) {
|
|
511
|
-
|
|
520
|
+
logger == null ? void 0 : logger.debug("[KLAP] Local auth hash matches server hash");
|
|
512
521
|
return {
|
|
513
522
|
localSeed,
|
|
514
523
|
remoteSeed,
|
|
515
|
-
authHash: localHash
|
|
524
|
+
authHash: localHash,
|
|
525
|
+
deviceSession
|
|
516
526
|
};
|
|
517
527
|
}
|
|
518
528
|
const emptyHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth("", "")]));
|
|
519
529
|
if (Buffer.compare(emptyHash, serverHash) === 0) {
|
|
520
|
-
|
|
530
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Empty auth hash matches server hash");
|
|
521
531
|
return {
|
|
522
532
|
localSeed,
|
|
523
533
|
remoteSeed,
|
|
524
|
-
authHash: emptyHash
|
|
534
|
+
authHash: emptyHash,
|
|
535
|
+
deviceSession
|
|
525
536
|
};
|
|
526
537
|
}
|
|
527
538
|
const testHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth(_TapoApi.TP_TEST_USER, _TapoApi.TP_TEST_PASSWORD)]));
|
|
528
539
|
if (Buffer.compare(testHash, serverHash) === 0) {
|
|
529
|
-
|
|
540
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Test auth hash matches server hash");
|
|
530
541
|
return {
|
|
531
542
|
localSeed,
|
|
532
543
|
remoteSeed,
|
|
533
|
-
authHash: testHash
|
|
544
|
+
authHash: testHash,
|
|
545
|
+
deviceSession
|
|
534
546
|
};
|
|
535
547
|
}
|
|
536
|
-
this.session = void 0;
|
|
537
548
|
throw new Error("Failed to verify server hash");
|
|
538
549
|
});
|
|
539
550
|
}
|
|
540
|
-
secondHandshake(deviceIp, localSeed, remoteSeed, authHash) {
|
|
551
|
+
secondHandshake(devSession, deviceIp, localSeed, remoteSeed, authHash, logger) {
|
|
541
552
|
return __async(this, null, function* () {
|
|
542
553
|
const localAuthHash = this.sha256(Buffer.concat([remoteSeed, localSeed, authHash]));
|
|
543
554
|
try {
|
|
544
|
-
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text",
|
|
555
|
+
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text", devSession.Cookie);
|
|
545
556
|
if (handshake2Result.status === 200) {
|
|
546
|
-
|
|
547
|
-
const deviceSession =
|
|
548
|
-
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));
|
|
549
559
|
return deviceSession;
|
|
550
560
|
}
|
|
551
|
-
|
|
561
|
+
logger.warn("[KLAP] Second handshake failed", handshake2Result.data);
|
|
552
562
|
} catch (e) {
|
|
553
|
-
|
|
563
|
+
logger.error("[KLAP] Second handshake failed:", e.response.data || e.message);
|
|
554
564
|
}
|
|
555
|
-
this.session = void 0;
|
|
556
565
|
return void 0;
|
|
557
566
|
});
|
|
558
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,112 +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
|
-
Accept: "*/*",
|
|
423
|
-
"Content-Type": "application/octet-stream"
|
|
424
|
-
}, cookie && {
|
|
425
|
-
Cookie: cookie
|
|
426
|
-
}),
|
|
434
|
+
headers,
|
|
427
435
|
httpAgent: new http.Agent({
|
|
428
436
|
keepAlive: false
|
|
429
437
|
})
|
|
430
438
|
});
|
|
431
439
|
});
|
|
432
440
|
}
|
|
433
|
-
needsNewHandshake() {
|
|
434
|
-
if (!
|
|
441
|
+
needsNewHandshake(devSession) {
|
|
442
|
+
if (!devSession) {
|
|
435
443
|
return true;
|
|
436
444
|
}
|
|
437
|
-
if (!
|
|
445
|
+
if (!devSession.cipher) {
|
|
438
446
|
return true;
|
|
439
447
|
}
|
|
440
|
-
if (
|
|
448
|
+
if (devSession.IsExpired) {
|
|
441
449
|
return true;
|
|
442
450
|
}
|
|
443
|
-
if (!
|
|
451
|
+
if (!devSession.Cookie) {
|
|
444
452
|
return true;
|
|
445
453
|
}
|
|
446
454
|
return false;
|
|
447
455
|
}
|
|
448
|
-
handshake(deviceIp, force = false) {
|
|
456
|
+
handshake(deviceIp, devSession, force = false, logger) {
|
|
449
457
|
return __async(this, null, function* () {
|
|
450
|
-
if (!this.needsNewHandshake() && !force) {
|
|
458
|
+
if (!this.needsNewHandshake(devSession) && !force) {
|
|
451
459
|
return;
|
|
452
460
|
}
|
|
453
|
-
const { localSeed, remoteSeed, authHash } = yield this.firstHandshake(deviceIp);
|
|
454
|
-
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);
|
|
455
463
|
});
|
|
456
464
|
}
|
|
457
|
-
firstHandshake(deviceIp, seed) {
|
|
465
|
+
firstHandshake(deviceIp, seed, logger) {
|
|
458
466
|
return __async(this, null, function* () {
|
|
459
467
|
var _a;
|
|
460
468
|
const localSeed = seed ? seed : crypto4.randomBytes(16);
|
|
461
469
|
const handshake1Result = yield this.sessionPost(deviceIp, "/handshake1", localSeed, "arraybuffer");
|
|
470
|
+
logger == null ? void 0 : logger.debug("handshake1Result: ", handshake1Result);
|
|
462
471
|
if (handshake1Result.status !== 200) {
|
|
463
472
|
throw new Error("Handshake1 failed");
|
|
464
473
|
}
|
|
465
474
|
if (handshake1Result.headers["content-length"] !== "48") {
|
|
466
475
|
throw new Error("Handshake1 failed due to invalid content length");
|
|
467
476
|
}
|
|
468
|
-
const cookie = (_a = handshake1Result.headers["set-cookie"]) == null ? void 0 : _a[0];
|
|
469
|
-
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));
|
|
470
479
|
const [cookieValue, timeout] = cookie.split(";");
|
|
471
480
|
const timeoutValue = timeout.split("=").pop();
|
|
472
|
-
|
|
481
|
+
const deviceSession = new DeviceSession(timeoutValue, deviceIp, cookieValue);
|
|
473
482
|
const remoteSeed = data.subarray(0, 16);
|
|
474
483
|
const serverHash = data.subarray(16);
|
|
475
|
-
|
|
484
|
+
logger == null ? void 0 : logger.debug("[KLAP] First handshake decoded successfully:\nRemote Seed:", remoteSeed.toString("hex"), "\nServer Hash:", serverHash.toString("hex"), "\nCookie:", cookieValue);
|
|
476
485
|
const localHash = this.hashAuth(this._config.rawEmail, this._config.rawPassword);
|
|
477
486
|
const localAuthHash = this.sha256(Buffer.concat([localSeed, remoteSeed, localHash]));
|
|
478
487
|
if (Buffer.compare(localAuthHash, serverHash) === 0) {
|
|
479
|
-
|
|
488
|
+
logger == null ? void 0 : logger.debug("[KLAP] Local auth hash matches server hash");
|
|
480
489
|
return {
|
|
481
490
|
localSeed,
|
|
482
491
|
remoteSeed,
|
|
483
|
-
authHash: localHash
|
|
492
|
+
authHash: localHash,
|
|
493
|
+
deviceSession
|
|
484
494
|
};
|
|
485
495
|
}
|
|
486
496
|
const emptyHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth("", "")]));
|
|
487
497
|
if (Buffer.compare(emptyHash, serverHash) === 0) {
|
|
488
|
-
|
|
498
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Empty auth hash matches server hash");
|
|
489
499
|
return {
|
|
490
500
|
localSeed,
|
|
491
501
|
remoteSeed,
|
|
492
|
-
authHash: emptyHash
|
|
502
|
+
authHash: emptyHash,
|
|
503
|
+
deviceSession
|
|
493
504
|
};
|
|
494
505
|
}
|
|
495
506
|
const testHash = this.sha256(Buffer.concat([localSeed, remoteSeed, this.hashAuth(_TapoApi.TP_TEST_USER, _TapoApi.TP_TEST_PASSWORD)]));
|
|
496
507
|
if (Buffer.compare(testHash, serverHash) === 0) {
|
|
497
|
-
|
|
508
|
+
logger == null ? void 0 : logger.debug("[KLAP] [WARN] Test auth hash matches server hash");
|
|
498
509
|
return {
|
|
499
510
|
localSeed,
|
|
500
511
|
remoteSeed,
|
|
501
|
-
authHash: testHash
|
|
512
|
+
authHash: testHash,
|
|
513
|
+
deviceSession
|
|
502
514
|
};
|
|
503
515
|
}
|
|
504
|
-
this.session = void 0;
|
|
505
516
|
throw new Error("Failed to verify server hash");
|
|
506
517
|
});
|
|
507
518
|
}
|
|
508
|
-
secondHandshake(deviceIp, localSeed, remoteSeed, authHash) {
|
|
519
|
+
secondHandshake(devSession, deviceIp, localSeed, remoteSeed, authHash, logger) {
|
|
509
520
|
return __async(this, null, function* () {
|
|
510
521
|
const localAuthHash = this.sha256(Buffer.concat([remoteSeed, localSeed, authHash]));
|
|
511
522
|
try {
|
|
512
|
-
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text",
|
|
523
|
+
const handshake2Result = yield this.sessionPost(deviceIp, "/handshake2", localAuthHash, "text", devSession.Cookie);
|
|
513
524
|
if (handshake2Result.status === 200) {
|
|
514
|
-
|
|
515
|
-
const deviceSession =
|
|
516
|
-
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));
|
|
517
527
|
return deviceSession;
|
|
518
528
|
}
|
|
519
|
-
|
|
529
|
+
logger.warn("[KLAP] Second handshake failed", handshake2Result.data);
|
|
520
530
|
} catch (e) {
|
|
521
|
-
|
|
531
|
+
logger.error("[KLAP] Second handshake failed:", e.response.data || e.message);
|
|
522
532
|
}
|
|
523
|
-
this.session = void 0;
|
|
524
533
|
return void 0;
|
|
525
534
|
});
|
|
526
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
|
}
|