@lumiastream/tapo-cove 0.0.1-alpha.1
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/module/discovery.d.ts +8 -0
- package/dist/module/discovery.js +118 -0
- package/dist/module/index.d.ts +5 -0
- package/dist/module/index.js +12 -0
- package/dist/module/lightstate.d.ts +39 -0
- package/dist/module/lightstate.js +123 -0
- package/dist/module/shared/cipher.d.ts +11 -0
- package/dist/module/shared/cipher.js +66 -0
- package/dist/module/shared/color-transformer.d.ts +8 -0
- package/dist/module/shared/color-transformer.js +42 -0
- package/dist/module/shared/helpers.d.ts +5 -0
- package/dist/module/shared/helpers.js +105 -0
- package/dist/module/shared/tapo-ca-cert.d.ts +2 -0
- package/dist/module/shared/tapo-ca-cert.js +3 -0
- package/dist/module/shared/tapo.constants.d.ts +21 -0
- package/dist/module/shared/tapo.constants.js +25 -0
- package/dist/module/shared/utils.d.ts +23 -0
- package/dist/module/shared/utils.js +152 -0
- package/dist/module/tapo-api.d.ts +48 -0
- package/dist/module/tapo-api.js +142 -0
- package/dist/module/types/interfaces.d.ts +3 -0
- package/dist/module/types/interfaces.js +2 -0
- package/dist/module/types/raw-types.d.ts +84 -0
- package/dist/module/types/raw-types.js +2 -0
- package/lib/cjs/discovery.d.ts +8 -0
- package/lib/cjs/discovery.js +118 -0
- package/lib/cjs/index.d.ts +5 -0
- package/lib/cjs/index.js +12 -0
- package/lib/cjs/lightstate.d.ts +39 -0
- package/lib/cjs/lightstate.js +123 -0
- package/lib/cjs/shared/cipher.d.ts +11 -0
- package/lib/cjs/shared/cipher.js +66 -0
- package/lib/cjs/shared/color-transformer.d.ts +8 -0
- package/lib/cjs/shared/color-transformer.js +42 -0
- package/lib/cjs/shared/helpers.d.ts +5 -0
- package/lib/cjs/shared/helpers.js +105 -0
- package/lib/cjs/shared/tapo-ca-cert.d.ts +2 -0
- package/lib/cjs/shared/tapo-ca-cert.js +3 -0
- package/lib/cjs/shared/tapo.constants.d.ts +21 -0
- package/lib/cjs/shared/tapo.constants.js +25 -0
- package/lib/cjs/shared/utils.d.ts +23 -0
- package/lib/cjs/shared/utils.js +152 -0
- package/lib/cjs/tapo-api.d.ts +48 -0
- package/lib/cjs/tapo-api.js +142 -0
- package/lib/cjs/types/interfaces.d.ts +3 -0
- package/lib/cjs/types/interfaces.js +2 -0
- package/lib/cjs/types/raw-types.d.ts +84 -0
- package/lib/cjs/types/raw-types.js +2 -0
- package/lib/esm/discovery.d.ts +8 -0
- package/lib/esm/discovery.js +116 -0
- package/lib/esm/index.d.ts +5 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/lightstate.d.ts +39 -0
- package/lib/esm/lightstate.js +120 -0
- package/lib/esm/shared/cipher.d.ts +11 -0
- package/lib/esm/shared/cipher.js +56 -0
- package/lib/esm/shared/color-transformer.d.ts +8 -0
- package/lib/esm/shared/color-transformer.js +40 -0
- package/lib/esm/shared/helpers.d.ts +5 -0
- package/lib/esm/shared/helpers.js +98 -0
- package/lib/esm/shared/tapo-ca-cert.d.ts +2 -0
- package/lib/esm/shared/tapo-ca-cert.js +1 -0
- package/lib/esm/shared/tapo.constants.d.ts +21 -0
- package/lib/esm/shared/tapo.constants.js +22 -0
- package/lib/esm/shared/utils.d.ts +23 -0
- package/lib/esm/shared/utils.js +146 -0
- package/lib/esm/tapo-api.d.ts +48 -0
- package/lib/esm/tapo-api.js +140 -0
- package/lib/esm/types/interfaces.d.ts +3 -0
- package/lib/esm/types/interfaces.js +1 -0
- package/lib/esm/types/raw-types.d.ts +84 -0
- package/lib/esm/types/raw-types.js +1 -0
- package/package.json +38 -0
- package/test/auth.test.ts +22 -0
- package/test/discover.test.ts +9 -0
- package/test/light.test.ts +17 -0
- package/test/plug.test.ts +16 -0
- package/tslint.json +53 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { __awaiter, __generator } from "tslib";
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { decrypt, encrypt, generateKeyPair, readDeviceKey } from './cipher';
|
|
4
|
+
export var throwErrorIfFound = function (responseData) {
|
|
5
|
+
var errorCode = responseData['error_code'];
|
|
6
|
+
if (errorCode) {
|
|
7
|
+
switch (errorCode) {
|
|
8
|
+
case 0:
|
|
9
|
+
return;
|
|
10
|
+
case -1010:
|
|
11
|
+
throw new Error('Invalid public key length');
|
|
12
|
+
case -1012:
|
|
13
|
+
throw new Error('Invalid terminal UUID');
|
|
14
|
+
case -1501:
|
|
15
|
+
throw new Error('Invalid request or credentials');
|
|
16
|
+
case -1002:
|
|
17
|
+
throw new Error('Incorrect request');
|
|
18
|
+
case -1003:
|
|
19
|
+
throw new Error('JSON format error');
|
|
20
|
+
case -20601:
|
|
21
|
+
throw new Error('Incorrect email or password');
|
|
22
|
+
case -20675:
|
|
23
|
+
throw new Error('Cloud token expired or invalid');
|
|
24
|
+
case 9999:
|
|
25
|
+
throw new Error('Device token expired or invalid');
|
|
26
|
+
default:
|
|
27
|
+
throw new Error("Unexpected Error Code: ".concat(errorCode, " (").concat(responseData['msg'], ")"));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export var throwTapoCareErrorIfFound = function (responseData) {
|
|
32
|
+
var errorCode = responseData === null || responseData === void 0 ? void 0 : responseData.code;
|
|
33
|
+
if (errorCode) {
|
|
34
|
+
throw new Error("Unexpected Error Code: ".concat(errorCode, " (").concat(responseData['message'], ")"));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export var handshake = function (deviceIp) { return __awaiter(void 0, void 0, void 0, function () {
|
|
38
|
+
var keyPair, handshakeRequest, response, setCookieHeader, sessionCookie, deviceKey;
|
|
39
|
+
return __generator(this, function (_a) {
|
|
40
|
+
switch (_a.label) {
|
|
41
|
+
case 0: return [4, generateKeyPair()];
|
|
42
|
+
case 1:
|
|
43
|
+
keyPair = _a.sent();
|
|
44
|
+
handshakeRequest = {
|
|
45
|
+
method: 'handshake',
|
|
46
|
+
params: {
|
|
47
|
+
key: keyPair.publicKey,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return [4, axios({
|
|
51
|
+
method: 'post',
|
|
52
|
+
url: "http://".concat(deviceIp, "/app"),
|
|
53
|
+
data: handshakeRequest,
|
|
54
|
+
})];
|
|
55
|
+
case 2:
|
|
56
|
+
response = _a.sent();
|
|
57
|
+
throwErrorIfFound(response.data);
|
|
58
|
+
setCookieHeader = response.headers['set-cookie'][0];
|
|
59
|
+
sessionCookie = setCookieHeader.substring(0, setCookieHeader.indexOf(';'));
|
|
60
|
+
deviceKey = readDeviceKey(response.data.result.key, keyPair.privateKey);
|
|
61
|
+
return [2, {
|
|
62
|
+
key: deviceKey.subarray(0, 16),
|
|
63
|
+
iv: deviceKey.subarray(16, 32),
|
|
64
|
+
deviceIp: deviceIp,
|
|
65
|
+
sessionCookie: sessionCookie,
|
|
66
|
+
}];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}); };
|
|
70
|
+
export var securePassthrough = function (deviceRequest, deviceKey) { return __awaiter(void 0, void 0, void 0, function () {
|
|
71
|
+
var encryptedRequest, securePassthroughRequest, response, decryptedResponse;
|
|
72
|
+
return __generator(this, function (_a) {
|
|
73
|
+
switch (_a.label) {
|
|
74
|
+
case 0:
|
|
75
|
+
encryptedRequest = encrypt(deviceRequest, deviceKey);
|
|
76
|
+
securePassthroughRequest = {
|
|
77
|
+
method: 'securePassthrough',
|
|
78
|
+
params: {
|
|
79
|
+
request: encryptedRequest,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
return [4, axios({
|
|
83
|
+
method: 'post',
|
|
84
|
+
url: "http://".concat(deviceKey.deviceIp, "/app?token=").concat(deviceKey.token),
|
|
85
|
+
data: securePassthroughRequest,
|
|
86
|
+
headers: {
|
|
87
|
+
Cookie: deviceKey.sessionCookie,
|
|
88
|
+
},
|
|
89
|
+
})];
|
|
90
|
+
case 1:
|
|
91
|
+
response = _a.sent();
|
|
92
|
+
throwErrorIfFound(response.data);
|
|
93
|
+
decryptedResponse = decrypt(response.data.result.response, deviceKey);
|
|
94
|
+
throwErrorIfFound(decryptedResponse);
|
|
95
|
+
return [2, decryptedResponse.result];
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}); };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: "-----BEGIN CERTIFICATE-----\n\nMIIDBzCCAe+gAwIBAgIQT5x0ma7QnINHCQvhnmzR9zANBgkqhkiG9w0BAQsFADAV\n \nMRMwEQYDVQQDEwp0cC1saW5rLUNBMCAXDTE4MDExOTA4Mjc1MloYDzIwNjgwMTE5\n \nMDgzNzUyWjAVMRMwEQYDVQQDEwp0cC1saW5rLUNBMIIBIjANBgkqhkiG9w0BAQEF\n \nAAOCAQ8AMIIBCgKCAQEAuGG8n5zEUN1j5wuvUz4pAIMurhKHbpfUUu+b2acFHKS6\n \niU9hNJWvDyhXcihY5Wz6aq9m4D5SZcgW3k31YoNNtrztDjdg2qw7AaX85S99/G0B\n \nVbIXktrhs34OW19WA/haDwut3dFhLem+gCRRKUXcmuqchZc84dY7JFVfhPcJci4m\n \nsRjLCFNO0ho9OX+MZwfO4BLaeAqKVoAor6rf4BXVtO0xjYHDKO0fb3AWLLJ4EjGe\n \nq6YieqPiYlPFEqRm5PrvBXTm0IuQogygyVpK4LHr/K207ZLyV33DxLLbsUgSEJVn\n \npZUv/WUujXjlIDgxIvyZZCYiXO3dle2/MEvpmZk6JQIDAQABo1EwTzALBgNVHQ8E\n \nBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUxu2iBRTsef5iNnsADVhM\n \nJDQWi6kwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQELBQADggEBAB52Majd\n \n+wo3cb5BsTo63z2Psbbyl4ACMUaw68NxUMy61Oihx3mcLzLJqiIZcKePiHskLqLJ\n \nF7QfT9TqjvizMjFJVgsLuVubUBXKBzqyN+3KKlQci0PO3mH+ObhyaE7BzV+qrS3P\n \ndVTgsCWFv8DkgLTRudSWxL7VwVoedc7lRz5EroGgJ33nRGCR0ngcW919tLTARDQO\n \npULmzulcdWeZgG+0PLX0xjJQIjFEvbOxR1Z+gxMupBz0rWFokmWYrcga8eWiWzjQ\n \nIa3/ASBVJ69srV77trWlfLumkChbXk9i64NXBKnce0Jmll0Y9OC1nMPqrbQKnzcn\n \ndSAA4fejD/qMQn0=\n\n-----END CERTIFICATE-----";
|
|
2
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "-----BEGIN CERTIFICATE-----\n\nMIIDBzCCAe+gAwIBAgIQT5x0ma7QnINHCQvhnmzR9zANBgkqhkiG9w0BAQsFADAV\n \nMRMwEQYDVQQDEwp0cC1saW5rLUNBMCAXDTE4MDExOTA4Mjc1MloYDzIwNjgwMTE5\n \nMDgzNzUyWjAVMRMwEQYDVQQDEwp0cC1saW5rLUNBMIIBIjANBgkqhkiG9w0BAQEF\n \nAAOCAQ8AMIIBCgKCAQEAuGG8n5zEUN1j5wuvUz4pAIMurhKHbpfUUu+b2acFHKS6\n \niU9hNJWvDyhXcihY5Wz6aq9m4D5SZcgW3k31YoNNtrztDjdg2qw7AaX85S99/G0B\n \nVbIXktrhs34OW19WA/haDwut3dFhLem+gCRRKUXcmuqchZc84dY7JFVfhPcJci4m\n \nsRjLCFNO0ho9OX+MZwfO4BLaeAqKVoAor6rf4BXVtO0xjYHDKO0fb3AWLLJ4EjGe\n \nq6YieqPiYlPFEqRm5PrvBXTm0IuQogygyVpK4LHr/K207ZLyV33DxLLbsUgSEJVn\n \npZUv/WUujXjlIDgxIvyZZCYiXO3dle2/MEvpmZk6JQIDAQABo1EwTzALBgNVHQ8E\n \nBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUxu2iBRTsef5iNnsADVhM\n \nJDQWi6kwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQELBQADggEBAB52Majd\n \n+wo3cb5BsTo63z2Psbbyl4ACMUaw68NxUMy61Oihx3mcLzLJqiIZcKePiHskLqLJ\n \nF7QfT9TqjvizMjFJVgsLuVubUBXKBzqyN+3KKlQci0PO3mH+ObhyaE7BzV+qrS3P\n \ndVTgsCWFv8DkgLTRudSWxL7VwVoedc7lRz5EroGgJ33nRGCR0ngcW919tLTARDQO\n \npULmzulcdWeZgG+0PLX0xjJQIjFEvbOxR1Z+gxMupBz0rWFokmWYrcga8eWiWzjQ\n \nIa3/ASBVJ69srV77trWlfLumkChbXk9i64NXBKnce0Jmll0Y9OC1nMPqrbQKnzcn\n \ndSAA4fejD/qMQn0=\n\n-----END CERTIFICATE-----";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const TapoDeviceTypes: {
|
|
2
|
+
ALL: string;
|
|
3
|
+
BULBS: string;
|
|
4
|
+
LIGHTSTRIPS: string;
|
|
5
|
+
PLUGS: string;
|
|
6
|
+
};
|
|
7
|
+
export declare enum ETapoDeviceTypes {
|
|
8
|
+
ALL = "all",
|
|
9
|
+
BULBS = "bulb",
|
|
10
|
+
LIGHTSTRIPS = "lightstrip",
|
|
11
|
+
PLUGS = "plug"
|
|
12
|
+
}
|
|
13
|
+
export declare const DeviceResTypes: {
|
|
14
|
+
BULB: string;
|
|
15
|
+
PLUG: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const DeviceSendValues: {
|
|
18
|
+
BULB: string;
|
|
19
|
+
LIGHTSTRIP: string;
|
|
20
|
+
PLUG: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export var TapoDeviceTypes = {
|
|
2
|
+
ALL: 'all',
|
|
3
|
+
BULBS: 'bulb',
|
|
4
|
+
LIGHTSTRIPS: 'lightstrip',
|
|
5
|
+
PLUGS: 'plug',
|
|
6
|
+
};
|
|
7
|
+
export var ETapoDeviceTypes;
|
|
8
|
+
(function (ETapoDeviceTypes) {
|
|
9
|
+
ETapoDeviceTypes["ALL"] = "all";
|
|
10
|
+
ETapoDeviceTypes["BULBS"] = "bulb";
|
|
11
|
+
ETapoDeviceTypes["LIGHTSTRIPS"] = "lightstrip";
|
|
12
|
+
ETapoDeviceTypes["PLUGS"] = "plug";
|
|
13
|
+
})(ETapoDeviceTypes || (ETapoDeviceTypes = {}));
|
|
14
|
+
export var DeviceResTypes = {
|
|
15
|
+
BULB: 'IOT.SMARTPLUGSWITCH',
|
|
16
|
+
PLUG: 'IOT.SMARTPLUGSWITCH',
|
|
17
|
+
};
|
|
18
|
+
export var DeviceSendValues = {
|
|
19
|
+
BULB: 'smartlife.iot.smartbulb.lightingservice',
|
|
20
|
+
LIGHTSTRIP: 'smartlife.iot.lightStrip',
|
|
21
|
+
PLUG: 'system',
|
|
22
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare const isTruly: (value: any) => boolean;
|
|
3
|
+
export declare const isNully: (value: any) => boolean;
|
|
4
|
+
declare function encryptWithHeader(input: any, firstKey?: number): Buffer;
|
|
5
|
+
declare function decrypt(input: any, firstKey?: number): Buffer;
|
|
6
|
+
declare function decryptWithHeader(input: any, firstKey?: number): Buffer;
|
|
7
|
+
export declare function uuidv4(): string;
|
|
8
|
+
declare const _default: {
|
|
9
|
+
checkHost: (host: any) => any;
|
|
10
|
+
createRandomRgb: (min: any, max: any) => [number, number, number];
|
|
11
|
+
bigEndianToLilEndian: (value: any, pad?: any) => any;
|
|
12
|
+
rgbToHsb: (rgbObj: any) => {
|
|
13
|
+
h: number;
|
|
14
|
+
s: number;
|
|
15
|
+
b: number;
|
|
16
|
+
};
|
|
17
|
+
encrypt: (input: any, firstKey?: number) => Buffer;
|
|
18
|
+
encryptWithHeader: typeof encryptWithHeader;
|
|
19
|
+
decrypt: typeof decrypt;
|
|
20
|
+
decryptWithHeader: typeof decryptWithHeader;
|
|
21
|
+
uuidv4: typeof uuidv4;
|
|
22
|
+
};
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
var checkHost = function (host) {
|
|
2
|
+
if (host && host.indexOf('http') === -1) {
|
|
3
|
+
return 'http://' + host;
|
|
4
|
+
}
|
|
5
|
+
else {
|
|
6
|
+
return host;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
export var isTruly = function (value) {
|
|
10
|
+
return value !== undefined && value !== null;
|
|
11
|
+
};
|
|
12
|
+
export var isNully = function (value) {
|
|
13
|
+
return value === undefined || value === null;
|
|
14
|
+
};
|
|
15
|
+
var getRandomNumber = function (min, max) {
|
|
16
|
+
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
17
|
+
};
|
|
18
|
+
var createRandomRgb = function (min, max) {
|
|
19
|
+
return [getRandomNumber(min, max), getRandomNumber(min, max), getRandomNumber(min, max)];
|
|
20
|
+
};
|
|
21
|
+
var bigEndianToLilEndian = function (value, pad) {
|
|
22
|
+
var bigEndian = value.toString(16);
|
|
23
|
+
if (pad) {
|
|
24
|
+
bigEndian = bigEndian.padStart(pad, '0');
|
|
25
|
+
}
|
|
26
|
+
var lilEndian;
|
|
27
|
+
if (pad && pad === 4) {
|
|
28
|
+
lilEndian = Buffer.from(bigEndian, 'hex').readUInt16LE(0).toString(16);
|
|
29
|
+
}
|
|
30
|
+
else if (pad && pad === 8) {
|
|
31
|
+
lilEndian = Buffer.from(bigEndian, 'hex').readUInt32LE(0).toString(16);
|
|
32
|
+
}
|
|
33
|
+
else if (pad) {
|
|
34
|
+
lilEndian = Buffer.from(bigEndian, 'hex').readUIntLE(0, pad).toString(16);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lilEndian = Buffer.from(bigEndian, 'hex').readUInt16LE(0).toString(16);
|
|
38
|
+
}
|
|
39
|
+
if (pad) {
|
|
40
|
+
lilEndian = lilEndian.padStart(pad, '0');
|
|
41
|
+
}
|
|
42
|
+
return lilEndian;
|
|
43
|
+
};
|
|
44
|
+
var rgbToHsb = function (rgbObj) {
|
|
45
|
+
var red = rgbObj.r / 255;
|
|
46
|
+
var green = rgbObj.g / 255;
|
|
47
|
+
var blue = rgbObj.b / 255;
|
|
48
|
+
var rgb = [red, green, blue];
|
|
49
|
+
var hsb = { h: 0, s: 0, b: 0 };
|
|
50
|
+
var max = maxNumberInArray(rgb);
|
|
51
|
+
var min = minNumberInArray(rgb);
|
|
52
|
+
var c = max - min;
|
|
53
|
+
var hue;
|
|
54
|
+
if (c === 0) {
|
|
55
|
+
hue = 0;
|
|
56
|
+
}
|
|
57
|
+
else if (max === red) {
|
|
58
|
+
hue = (green - blue) / c;
|
|
59
|
+
if (hue < 0) {
|
|
60
|
+
hue += 6;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (max === green) {
|
|
64
|
+
hue = 2 + (blue - red) / c;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
hue = 4 + (red - green) / c;
|
|
68
|
+
}
|
|
69
|
+
hsb.h = Math.round(60 * hue);
|
|
70
|
+
var lightness = max;
|
|
71
|
+
hsb.b = Math.round(lightness * 100);
|
|
72
|
+
var saturation;
|
|
73
|
+
if (c === 0) {
|
|
74
|
+
saturation = 0;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
saturation = c / lightness;
|
|
78
|
+
}
|
|
79
|
+
hsb.s = Math.round(saturation * 100);
|
|
80
|
+
return hsb;
|
|
81
|
+
};
|
|
82
|
+
var minNumberInArray = function (array) {
|
|
83
|
+
var sortedCopy = array.slice();
|
|
84
|
+
sortedCopy.sort(function (a, b) {
|
|
85
|
+
return a - b;
|
|
86
|
+
});
|
|
87
|
+
return sortedCopy[0];
|
|
88
|
+
};
|
|
89
|
+
var maxNumberInArray = function (array) {
|
|
90
|
+
var sortedCopy = array.slice();
|
|
91
|
+
sortedCopy.sort(function (a, b) {
|
|
92
|
+
return a - b;
|
|
93
|
+
});
|
|
94
|
+
return sortedCopy[sortedCopy.length - 1];
|
|
95
|
+
};
|
|
96
|
+
var encrypt = function (input, firstKey) {
|
|
97
|
+
if (firstKey === void 0) { firstKey = 0xab; }
|
|
98
|
+
var buf = Buffer.from(input);
|
|
99
|
+
var key = firstKey;
|
|
100
|
+
for (var i = 0; i < buf.length; i++) {
|
|
101
|
+
buf[i] = buf[i] ^ key;
|
|
102
|
+
key = buf[i];
|
|
103
|
+
}
|
|
104
|
+
return buf;
|
|
105
|
+
};
|
|
106
|
+
function encryptWithHeader(input, firstKey) {
|
|
107
|
+
if (firstKey === void 0) { firstKey = 0xab; }
|
|
108
|
+
var msgBuf = encrypt(input, firstKey);
|
|
109
|
+
var outBuf = Buffer.alloc(msgBuf.length + 4);
|
|
110
|
+
outBuf.writeUInt32BE(msgBuf.length, 0);
|
|
111
|
+
msgBuf.copy(outBuf, 4);
|
|
112
|
+
return outBuf;
|
|
113
|
+
}
|
|
114
|
+
function decrypt(input, firstKey) {
|
|
115
|
+
if (firstKey === void 0) { firstKey = 0xab; }
|
|
116
|
+
var buf = Buffer.from(input);
|
|
117
|
+
var key = firstKey;
|
|
118
|
+
var nextKey;
|
|
119
|
+
for (var i = 0; i < buf.length; i++) {
|
|
120
|
+
nextKey = buf[i];
|
|
121
|
+
buf[i] = buf[i] ^ key;
|
|
122
|
+
key = nextKey;
|
|
123
|
+
}
|
|
124
|
+
return buf;
|
|
125
|
+
}
|
|
126
|
+
function decryptWithHeader(input, firstKey) {
|
|
127
|
+
if (firstKey === void 0) { firstKey = 0xab; }
|
|
128
|
+
return decrypt(input.slice(4));
|
|
129
|
+
}
|
|
130
|
+
export function uuidv4() {
|
|
131
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
132
|
+
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
133
|
+
return v.toString(16);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export default {
|
|
137
|
+
checkHost: checkHost,
|
|
138
|
+
createRandomRgb: createRandomRgb,
|
|
139
|
+
bigEndianToLilEndian: bigEndianToLilEndian,
|
|
140
|
+
rgbToHsb: rgbToHsb,
|
|
141
|
+
encrypt: encrypt,
|
|
142
|
+
encryptWithHeader: encryptWithHeader,
|
|
143
|
+
decrypt: decrypt,
|
|
144
|
+
decryptWithHeader: decryptWithHeader,
|
|
145
|
+
uuidv4: uuidv4,
|
|
146
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { LightState } from './lightstate';
|
|
2
|
+
import { TapoDeviceKey } from './types/raw-types';
|
|
3
|
+
export default class TapoApi {
|
|
4
|
+
_baseUrl: string;
|
|
5
|
+
_baseTapoCareUrl: string;
|
|
6
|
+
private axiosInstance;
|
|
7
|
+
private _config;
|
|
8
|
+
private _token;
|
|
9
|
+
private _devices;
|
|
10
|
+
constructor(config?: {
|
|
11
|
+
token?: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
httpTimeout?: number;
|
|
14
|
+
});
|
|
15
|
+
auth: ({ email, password }: {
|
|
16
|
+
email: string;
|
|
17
|
+
password: string;
|
|
18
|
+
}) => Promise<string>;
|
|
19
|
+
setup: ({ email, password, devices }: {
|
|
20
|
+
email: string;
|
|
21
|
+
password: string;
|
|
22
|
+
devices: Array<{
|
|
23
|
+
id: string;
|
|
24
|
+
ip: string;
|
|
25
|
+
}>;
|
|
26
|
+
}) => Promise<Map<string, TapoDeviceKey>>;
|
|
27
|
+
getAllInfo: ({ host }: {
|
|
28
|
+
host: any;
|
|
29
|
+
}) => Promise<any>;
|
|
30
|
+
getInfo: ({ host }: {
|
|
31
|
+
host: any;
|
|
32
|
+
}) => Promise<any>;
|
|
33
|
+
sendState: (config: {
|
|
34
|
+
device: {
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
state: LightState;
|
|
38
|
+
fetchConfig?: {
|
|
39
|
+
shouldWait: boolean;
|
|
40
|
+
};
|
|
41
|
+
}) => Promise<any>;
|
|
42
|
+
sendPower: (config: {
|
|
43
|
+
device: {
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
power: boolean;
|
|
47
|
+
}) => void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { __assign, __awaiter, __generator } from "tslib";
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { LightState } from './lightstate';
|
|
4
|
+
import { fetchWork } from '@lumiastream/fetch-cove';
|
|
5
|
+
import { uuidv4 } from './shared/utils';
|
|
6
|
+
import { handshake, securePassthrough, throwErrorIfFound } from './shared/helpers';
|
|
7
|
+
import { base64Encode, encrypt, shaDigest } from './shared/cipher';
|
|
8
|
+
var TapoApi = (function () {
|
|
9
|
+
function TapoApi(config) {
|
|
10
|
+
var _this = this;
|
|
11
|
+
this._baseUrl = 'https://eu-wap.tplinkcloud.com/';
|
|
12
|
+
this._baseTapoCareUrl = 'https://euw1-app-tapo-care.i.tplinknbu.com';
|
|
13
|
+
this._config = {
|
|
14
|
+
authToken: null,
|
|
15
|
+
timeout: 10000,
|
|
16
|
+
httpTimeout: 4000,
|
|
17
|
+
};
|
|
18
|
+
this._devices = new Map();
|
|
19
|
+
this.auth = function (_a) {
|
|
20
|
+
var email = _a.email, password = _a.password;
|
|
21
|
+
return __awaiter(_this, void 0, void 0, function () {
|
|
22
|
+
var loginRequest, response;
|
|
23
|
+
return __generator(this, function (_b) {
|
|
24
|
+
switch (_b.label) {
|
|
25
|
+
case 0:
|
|
26
|
+
loginRequest = {
|
|
27
|
+
method: 'login',
|
|
28
|
+
params: {
|
|
29
|
+
appType: 'Tapo_Android',
|
|
30
|
+
cloudPassword: password,
|
|
31
|
+
cloudUserName: email,
|
|
32
|
+
terminalUUID: uuidv4(),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
return [4, axios({
|
|
36
|
+
method: 'post',
|
|
37
|
+
url: this._baseUrl,
|
|
38
|
+
data: loginRequest,
|
|
39
|
+
})];
|
|
40
|
+
case 1:
|
|
41
|
+
response = _b.sent();
|
|
42
|
+
throwErrorIfFound(response.data);
|
|
43
|
+
this._token = response.data.result.token;
|
|
44
|
+
return [2, this._token];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
this.setup = function (_a) {
|
|
50
|
+
var email = _a.email, password = _a.password, devices = _a.devices;
|
|
51
|
+
return __awaiter(_this, void 0, void 0, function () {
|
|
52
|
+
var promises;
|
|
53
|
+
var _this = this;
|
|
54
|
+
return __generator(this, function (_b) {
|
|
55
|
+
switch (_b.label) {
|
|
56
|
+
case 0:
|
|
57
|
+
promises = devices.map(function (device) { return __awaiter(_this, void 0, void 0, function () {
|
|
58
|
+
var deviceKey, loginDeviceRequest, loginDeviceResponse;
|
|
59
|
+
return __generator(this, function (_a) {
|
|
60
|
+
switch (_a.label) {
|
|
61
|
+
case 0: return [4, handshake(device.ip)];
|
|
62
|
+
case 1:
|
|
63
|
+
deviceKey = _a.sent();
|
|
64
|
+
loginDeviceRequest = {
|
|
65
|
+
method: 'login_device',
|
|
66
|
+
params: {
|
|
67
|
+
username: base64Encode(shaDigest(email)),
|
|
68
|
+
password: base64Encode(password),
|
|
69
|
+
},
|
|
70
|
+
requestTimeMils: 0,
|
|
71
|
+
};
|
|
72
|
+
return [4, securePassthrough(loginDeviceRequest, deviceKey)];
|
|
73
|
+
case 2:
|
|
74
|
+
loginDeviceResponse = _a.sent();
|
|
75
|
+
deviceKey.token = loginDeviceResponse.token;
|
|
76
|
+
this._devices.set(device.id, deviceKey);
|
|
77
|
+
return [2];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}); });
|
|
81
|
+
return [4, Promise.all(promises)];
|
|
82
|
+
case 1:
|
|
83
|
+
_b.sent();
|
|
84
|
+
return [2, this._devices];
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
this.getAllInfo = function (_a) {
|
|
90
|
+
var _this = this;
|
|
91
|
+
var host = _a.host;
|
|
92
|
+
return new Promise(function (resolve, reject) {
|
|
93
|
+
return _this.axiosInstance
|
|
94
|
+
.get(host + '/json')
|
|
95
|
+
.then(function (res) { return resolve(res.data); })
|
|
96
|
+
.catch(function (err) { return reject(err); });
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
this.getInfo = this.getAllInfo;
|
|
100
|
+
this.sendState = function (config) {
|
|
101
|
+
var deviceKey = _this._devices.get(config.device.id);
|
|
102
|
+
var shouldWait = false;
|
|
103
|
+
if (config.fetchConfig && config.fetchConfig.shouldWait) {
|
|
104
|
+
shouldWait = true;
|
|
105
|
+
}
|
|
106
|
+
var values = config.state.getValues();
|
|
107
|
+
var deviceRequest = {
|
|
108
|
+
method: 'set_device_info',
|
|
109
|
+
params: values,
|
|
110
|
+
terminalUUID: uuidv4(),
|
|
111
|
+
};
|
|
112
|
+
var encryptedRequest = encrypt(deviceRequest, deviceKey);
|
|
113
|
+
var securePassthroughRequest = {
|
|
114
|
+
method: 'securePassthrough',
|
|
115
|
+
params: {
|
|
116
|
+
request: encryptedRequest,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
return fetchWork({
|
|
120
|
+
url: "http://".concat(deviceKey.deviceIp, "/app?token=").concat(deviceKey.token),
|
|
121
|
+
data: {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: securePassthroughRequest,
|
|
124
|
+
headers: {
|
|
125
|
+
Cookie: deviceKey.sessionCookie,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
shouldWait: shouldWait,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
this.sendPower = function (config) {
|
|
132
|
+
_this.sendState({ device: config.device, state: new LightState({ on: config.power }) });
|
|
133
|
+
};
|
|
134
|
+
this.axiosInstance = axios.create();
|
|
135
|
+
this.axiosInstance.defaults.timeout = (config === null || config === void 0 ? void 0 : config.httpTimeout) || 4000;
|
|
136
|
+
this._config = __assign(__assign({}, this._config), config);
|
|
137
|
+
}
|
|
138
|
+
return TapoApi;
|
|
139
|
+
}());
|
|
140
|
+
export default TapoApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export type RawDeviceTypes = 'SMART.TAPOBULB' | 'SMART.IPCAMERA' | 'SMART.TAPOPLUG' | 'IOT.SMARTBULB' | 'IOT.IPCAMERA' | 'IOT.SMARTPLUGSWITCH';
|
|
3
|
+
export type RawTapoDevice = {
|
|
4
|
+
deviceType: RawDeviceTypes;
|
|
5
|
+
fwVer: string;
|
|
6
|
+
appServerUrl: string;
|
|
7
|
+
deviceRegion: string;
|
|
8
|
+
deviceId: string;
|
|
9
|
+
deviceName: string;
|
|
10
|
+
deviceHwVer: string;
|
|
11
|
+
alias: string;
|
|
12
|
+
deviceMac: string;
|
|
13
|
+
oemId: string;
|
|
14
|
+
deviceModel: string;
|
|
15
|
+
hwId: string;
|
|
16
|
+
fwId: string;
|
|
17
|
+
isSameRegion: boolean;
|
|
18
|
+
status: number;
|
|
19
|
+
ip?: string;
|
|
20
|
+
};
|
|
21
|
+
export type RawTapoDeviceInfo = {
|
|
22
|
+
device_id: string;
|
|
23
|
+
fw_ver: string;
|
|
24
|
+
hw_ver: string;
|
|
25
|
+
type: string;
|
|
26
|
+
model: string;
|
|
27
|
+
mac: string;
|
|
28
|
+
hw_id: string;
|
|
29
|
+
fw_id: string;
|
|
30
|
+
oem_id: string;
|
|
31
|
+
specs: string;
|
|
32
|
+
device_on: boolean;
|
|
33
|
+
on_time: number;
|
|
34
|
+
overheated: boolean;
|
|
35
|
+
nickname: string;
|
|
36
|
+
location: string;
|
|
37
|
+
avatar: string;
|
|
38
|
+
time_usage_today: string;
|
|
39
|
+
time_usage_past7: string;
|
|
40
|
+
time_usage_past30: string;
|
|
41
|
+
longitude: string;
|
|
42
|
+
latitude: string;
|
|
43
|
+
has_set_location_info: boolean;
|
|
44
|
+
ip: string;
|
|
45
|
+
ssid: string;
|
|
46
|
+
signal_level: number;
|
|
47
|
+
rssi: number;
|
|
48
|
+
region: string;
|
|
49
|
+
time_diff: number;
|
|
50
|
+
lang: string;
|
|
51
|
+
};
|
|
52
|
+
export type TapoDeviceKey = {
|
|
53
|
+
key: string | Buffer;
|
|
54
|
+
iv: string | Buffer;
|
|
55
|
+
deviceIp: string;
|
|
56
|
+
sessionCookie: string;
|
|
57
|
+
token?: string;
|
|
58
|
+
};
|
|
59
|
+
export type TapoVideoImage = {
|
|
60
|
+
uri: string;
|
|
61
|
+
length: number;
|
|
62
|
+
uriExpiresAt: number;
|
|
63
|
+
};
|
|
64
|
+
export type TapoVideo = {
|
|
65
|
+
uri: string;
|
|
66
|
+
duration: number;
|
|
67
|
+
m3u8: string;
|
|
68
|
+
startTimestamp: number;
|
|
69
|
+
uriExpiresAt: number;
|
|
70
|
+
};
|
|
71
|
+
export type TapoVideoPageItem = {
|
|
72
|
+
uuid: string;
|
|
73
|
+
video: TapoVideo[];
|
|
74
|
+
image: TapoVideoImage[];
|
|
75
|
+
createdTime: number;
|
|
76
|
+
eventLocalTime: string;
|
|
77
|
+
};
|
|
78
|
+
export type TapoVideoList = {
|
|
79
|
+
deviceId: string;
|
|
80
|
+
total: number;
|
|
81
|
+
page: number;
|
|
82
|
+
pageSize: number;
|
|
83
|
+
index: TapoVideoPageItem[];
|
|
84
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumiastream/tapo-cove",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "",
|
|
6
|
+
"main": "./lib/cjs/index.js",
|
|
7
|
+
"directories": {
|
|
8
|
+
"test": "test"
|
|
9
|
+
},
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "npm run clean && tsc && tsc --module commonjs --outDir lib/cjs && tsc --module es6 --outDir lib/esm",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"lint": "tslint --project tsconfig.json \"src/**/*.ts\"",
|
|
16
|
+
"lint:fix": "tslint --project tsconfig.json --fix \"src/**/*.ts\"",
|
|
17
|
+
"clean": "rm -rf lib lib/esm lib/cjs"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/lumiastream/tapo-cove.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/lumiastream/tapo-cove/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/lumiastream/tapo-cove#readme",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@lumiastream/lumia-rgb-types": "^0.0.4",
|
|
29
|
+
"axios": "^1.3.3",
|
|
30
|
+
"local-devices": "^4.0.0",
|
|
31
|
+
"network-cove": "0.1.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^18.14.0",
|
|
35
|
+
"tslib": "^2.3.0",
|
|
36
|
+
"typescript": "^4.0.5"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { discover, TapoApi } from '../src';
|
|
2
|
+
|
|
3
|
+
const email = '';
|
|
4
|
+
const password = '';
|
|
5
|
+
const deviceId = '';
|
|
6
|
+
const deviceIp = '';
|
|
7
|
+
const token = '';
|
|
8
|
+
|
|
9
|
+
(async () => {
|
|
10
|
+
const api = new TapoApi({ token });
|
|
11
|
+
// const token = await api.auth({ email, password });
|
|
12
|
+
// console.log('token: ', token);
|
|
13
|
+
|
|
14
|
+
const devices = await discover({ token });
|
|
15
|
+
console.log('devices: ', devices);
|
|
16
|
+
|
|
17
|
+
// const deviceKey = await TapoApi.loginDevice(email, password, '192.168.5.204');
|
|
18
|
+
// const devices = await api.setup({ email, password, devices: [{ id: deviceId, ip: deviceIp }] });
|
|
19
|
+
// console.log('devices: ', devices);
|
|
20
|
+
|
|
21
|
+
// await api.sendPower({ device: { id: deviceId }, power: false });
|
|
22
|
+
})();
|