myenergi-api 0.0.7-0 → 0.0.8-0
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/package.json +1 -1
- package/src/AuthDigest.ts +119 -0
- package/src/Digest.ts +18 -75
- package/src/MyEnergi.ts +6 -8
- package/tests/debug.ts +9 -0
package/package.json
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export class AuthDigest {
|
|
4
|
+
private _password?: string | undefined;
|
|
5
|
+
private _username?: string | undefined;
|
|
6
|
+
private _initialized: boolean;
|
|
7
|
+
|
|
8
|
+
public get username(): string | undefined {
|
|
9
|
+
return this._username;
|
|
10
|
+
}
|
|
11
|
+
private _realm?: string | undefined;
|
|
12
|
+
public get realm(): string | undefined {
|
|
13
|
+
return this._realm;
|
|
14
|
+
}
|
|
15
|
+
private _nonce?: string | undefined;
|
|
16
|
+
public get nonce(): string | undefined {
|
|
17
|
+
return this._nonce;
|
|
18
|
+
}
|
|
19
|
+
private _uri?: string | undefined;
|
|
20
|
+
public get uri(): string | undefined {
|
|
21
|
+
return this._uri;
|
|
22
|
+
}
|
|
23
|
+
private _algorithm?: string | undefined;
|
|
24
|
+
public get algorithm(): string | undefined {
|
|
25
|
+
return this._algorithm;
|
|
26
|
+
}
|
|
27
|
+
private _response?: string | undefined;
|
|
28
|
+
public get response(): string | undefined {
|
|
29
|
+
return this._response;
|
|
30
|
+
}
|
|
31
|
+
private _opaque?: string | undefined;
|
|
32
|
+
public get opaque(): string | undefined {
|
|
33
|
+
return this._opaque;
|
|
34
|
+
}
|
|
35
|
+
private _qop?: string | undefined;
|
|
36
|
+
public get qop(): string | undefined {
|
|
37
|
+
return this._qop;
|
|
38
|
+
}
|
|
39
|
+
private _nc: number;
|
|
40
|
+
public get nc(): string | undefined {
|
|
41
|
+
this._nc++;
|
|
42
|
+
var myHex = ("0000000" + this._nc?.toString(16)).substr(-8);
|
|
43
|
+
return myHex;
|
|
44
|
+
}
|
|
45
|
+
private _cnonce?: string | undefined;
|
|
46
|
+
public get cnonce(): string | undefined {
|
|
47
|
+
this._cnonce = this.md5(String(new Date().getTime()));
|
|
48
|
+
return this._cnonce;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private md5(data: string) {
|
|
52
|
+
let md5 = crypto.createHash('md5');
|
|
53
|
+
let result = md5.update(data).digest('hex');
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
constructor(username: string, password: string) {
|
|
58
|
+
this._username = username;
|
|
59
|
+
this._password = password;
|
|
60
|
+
this._nc = 0;
|
|
61
|
+
this._initialized = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public init(wwwAuthHeader: string) {
|
|
65
|
+
|
|
66
|
+
//let realm, nonce, qop, opaque;
|
|
67
|
+
let authSplit = wwwAuthHeader.split(',') as string[];
|
|
68
|
+
|
|
69
|
+
for (let item of authSplit) {
|
|
70
|
+
if (item.indexOf('realm=') >= 0) {
|
|
71
|
+
let realmSplit = item.split('="');
|
|
72
|
+
this._realm = realmSplit[realmSplit.length - 1];
|
|
73
|
+
this._realm = this._realm.substring(0, this._realm.length - 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (item.indexOf('nonce=') >= 0) {
|
|
77
|
+
let nonceSplit = item.split('="');
|
|
78
|
+
this._nonce = nonceSplit[nonceSplit.length - 1];
|
|
79
|
+
this._nonce = this._nonce.substring(0, this._nonce.length - 1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (item.indexOf('qop=') >= 0) {
|
|
83
|
+
let qopSplit = item.split('="');
|
|
84
|
+
this._qop = qopSplit[qopSplit.length - 1];
|
|
85
|
+
this._qop = this._qop.substring(0, this._qop.length - 1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (item.indexOf('opaque=') >= 0) {
|
|
89
|
+
let opaqueSplit = item.split('="');
|
|
90
|
+
this._opaque = opaqueSplit[opaqueSplit.length - 1];
|
|
91
|
+
this._opaque = this._opaque.substring(0, this._opaque.length - 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this._initialized = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public getAuthorization(httpMethod: string, path: string): string {
|
|
98
|
+
if(!this._initialized)
|
|
99
|
+
return '';
|
|
100
|
+
|
|
101
|
+
const nc = this.nc;
|
|
102
|
+
const cnonce = this.cnonce;
|
|
103
|
+
let HA1 = this.md5(this.username + ":" + this.realm + ":" + this._password);
|
|
104
|
+
let HA2 = this.md5(httpMethod + ":" + path);
|
|
105
|
+
let response = this.md5(HA1 + ":" + this.nonce + ":" + nc + ":" + cnonce + ":" + this.qop + ":" + HA2);
|
|
106
|
+
|
|
107
|
+
let res = `Digest username="${this._username}",`;
|
|
108
|
+
res += `realm="${this.realm}",`;
|
|
109
|
+
res += `nonce="${this.nonce}",`;
|
|
110
|
+
res += `uri="${path}",`;
|
|
111
|
+
res += `cnonce="${cnonce}",`;
|
|
112
|
+
res += `nc=${nc},`;
|
|
113
|
+
res += `algorithm=MD5,`;
|
|
114
|
+
res += `response="${response}",`;
|
|
115
|
+
res += `qop="${this.qop}",`;
|
|
116
|
+
res += `opaque="${this.opaque}"`;
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
119
|
+
}
|
package/src/Digest.ts
CHANGED
|
@@ -1,91 +1,35 @@
|
|
|
1
1
|
import * as https from 'https';
|
|
2
|
-
import * as crypto from 'crypto';
|
|
3
2
|
import { RequestOptions } from 'https';
|
|
4
3
|
import * as url from "url";
|
|
4
|
+
import { AuthDigest } from './AuthDigest';
|
|
5
5
|
|
|
6
6
|
export class Digest {
|
|
7
|
-
private
|
|
8
|
-
private _password: string;
|
|
9
|
-
private _authorization: string | undefined;
|
|
7
|
+
private _authorization?: AuthDigest;
|
|
10
8
|
|
|
11
9
|
constructor(username: string, password: string) {
|
|
12
|
-
this.
|
|
13
|
-
this._password = password;
|
|
14
|
-
this._authorization = '';
|
|
10
|
+
this._authorization = new AuthDigest(username, password);
|
|
15
11
|
}
|
|
16
12
|
|
|
17
|
-
private
|
|
18
|
-
let md5 = crypto.createHash('md5');
|
|
19
|
-
let result = md5.update(data).digest('hex');
|
|
20
|
-
return result;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private request(options: RequestOptions, data?: any): Promise<any> {
|
|
24
|
-
return new Promise<any>((resolve, reject) => {
|
|
25
|
-
const req = https.request(options, (res: any) => {
|
|
26
|
-
let resData: string = '';
|
|
27
|
-
|
|
28
|
-
res.setEncoding('utf8');
|
|
29
|
-
res.on('data', (chunk: string) => {
|
|
30
|
-
resData += chunk;
|
|
31
|
-
});
|
|
32
|
-
res.on('end', () => {
|
|
33
|
-
resolve(resData);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
req.on('error', (e: any) => {
|
|
38
|
-
console.error(`problem with request: ${e.message}`);
|
|
39
|
-
reject(e);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (data) {
|
|
43
|
-
req.write(data);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
req.end();
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private requestDigest(options: RequestOptions, data?: any): Promise<any> {
|
|
13
|
+
private request(options: RequestOptions, data?: any, retryCount: number = 0): Promise<any> {
|
|
51
14
|
return new Promise<any>((resolve, reject) => {
|
|
52
15
|
let resData: string = '';
|
|
16
|
+
if (!options.headers)
|
|
17
|
+
options.headers = {};
|
|
18
|
+
options.headers.Authorization = this._authorization?.getAuthorization(options.method as string, options.path as string);
|
|
53
19
|
const req = https.request(options, (res: any) => {
|
|
54
20
|
|
|
55
21
|
if (res.statusCode == 401) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let authSplit = auth?.split(",") as string[];
|
|
60
|
-
|
|
61
|
-
for (let item of authSplit) {
|
|
62
|
-
if (item.indexOf("realm=") >= 0) {
|
|
63
|
-
let realmSplit = item.split("=\"");
|
|
64
|
-
realm = realmSplit[realmSplit.length - 1];
|
|
65
|
-
realm = realm.substring(0, realm.length - 1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (item.indexOf("nonce=") >= 0) {
|
|
69
|
-
let nonceSplit = item.split("=\"");
|
|
70
|
-
nonce = nonceSplit[nonceSplit.length - 1];
|
|
71
|
-
nonce = nonce.substring(0, nonce.length - 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (item.indexOf("qop=") >= 0) {
|
|
75
|
-
let qopSplit = item.split("=\"");
|
|
76
|
-
qop = qopSplit[qopSplit.length - 1];
|
|
77
|
-
qop = qop.substring(0, qop.length - 1);
|
|
78
|
-
}
|
|
22
|
+
if (retryCount > 1) {
|
|
23
|
+
reject("Authentication failed");
|
|
24
|
+
return;
|
|
79
25
|
}
|
|
80
|
-
|
|
81
|
-
let
|
|
82
|
-
|
|
83
|
-
let response = this.md5(HA1 + ":" + nonce + ":00000001:" + cnonce + ":" + qop + ":" + HA2);
|
|
26
|
+
retryCount++;
|
|
27
|
+
let auth = res.headers["www-authenticate"];
|
|
28
|
+
this._authorization?.init(auth);
|
|
84
29
|
if (!options.headers)
|
|
85
30
|
options.headers = {};
|
|
86
|
-
|
|
87
|
-
options.
|
|
88
|
-
return this.requestDigest(options, data).then(value => {
|
|
31
|
+
options.headers.Authorization = this._authorization?.getAuthorization(options.method as string, options.path as string);
|
|
32
|
+
return this.request(options, data, retryCount).then(value => {
|
|
89
33
|
resolve(value);
|
|
90
34
|
}).catch(resaon => {
|
|
91
35
|
reject(resaon);
|
|
@@ -119,7 +63,7 @@ export class Digest {
|
|
|
119
63
|
}
|
|
120
64
|
|
|
121
65
|
public get(requestUrl: string, data?: any): Promise<any> {
|
|
122
|
-
const uri = url.parse(requestUrl);
|
|
66
|
+
const uri = url.parse(requestUrl, true);
|
|
123
67
|
const options: RequestOptions = {
|
|
124
68
|
hostname: uri.hostname,
|
|
125
69
|
port: uri.port,
|
|
@@ -127,12 +71,11 @@ export class Digest {
|
|
|
127
71
|
method: 'GET',
|
|
128
72
|
headers: {
|
|
129
73
|
'Connection': 'Keep-Alive',
|
|
130
|
-
'Content-Type': '
|
|
74
|
+
'Content-Type': 'application/json',
|
|
131
75
|
'Host': uri.hostname as string,
|
|
132
|
-
'Authorization': this._authorization
|
|
133
76
|
}
|
|
134
77
|
};
|
|
135
|
-
return this.
|
|
78
|
+
return this.request(options, data);
|
|
136
79
|
}
|
|
137
80
|
|
|
138
81
|
}
|
package/src/MyEnergi.ts
CHANGED
|
@@ -23,30 +23,28 @@ export class MyEnergi {
|
|
|
23
23
|
zappi_mode_url: 'https://s18.myenergi.net/cgi-zappi-mode-Z'
|
|
24
24
|
//https://s18.myenergi.net/cgi-jdayhour-Znnnnnnnn-YYYY-MM-DD
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
private
|
|
26
|
+
|
|
27
|
+
private _digest: Digest;
|
|
28
28
|
|
|
29
29
|
constructor(username: string, password: string) {
|
|
30
30
|
this._config.username = username;
|
|
31
31
|
this._config.password = password;
|
|
32
|
+
this._digest = new Digest(this._config.username, this._config.password);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
public async getStatus(): Promise<any> {
|
|
35
|
-
const
|
|
36
|
-
const data = await digest.get(this._config.status_url);
|
|
36
|
+
const data = await this._digest.get(this._config.status_url);
|
|
37
37
|
return data;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
public async getZappi(): Promise<any> {
|
|
41
|
-
const
|
|
42
|
-
const data = await digest.get(this._config.zappi_url);
|
|
41
|
+
const data = await this._digest.get(this._config.zappi_url);
|
|
43
42
|
return data;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
public async setZappiChargeMode(serialNo: string, chargeMode: ZappiChargeMode): Promise<any> {
|
|
47
|
-
const digest = new Digest(this._config.username, this._config.password);
|
|
48
46
|
const url = `${this._config.zappi_mode_url}${serialNo}-${chargeMode}-0-0-0000`;
|
|
49
|
-
const data = await
|
|
47
|
+
const data = await this._digest.get(url);
|
|
50
48
|
return data;
|
|
51
49
|
}
|
|
52
50
|
|
package/tests/debug.ts
CHANGED
|
@@ -13,6 +13,15 @@ myenergi.getStatus().then(val => {
|
|
|
13
13
|
myenergi.setZappiChargeMode(sno, ZappiChargeMode.Fast).then(x => {
|
|
14
14
|
console.log(x);
|
|
15
15
|
exit();
|
|
16
|
+
}).catch(err => {
|
|
17
|
+
console.error(err);
|
|
18
|
+
exit();
|
|
16
19
|
});
|
|
20
|
+
}).catch(err => {
|
|
21
|
+
console.error(err);
|
|
22
|
+
exit();
|
|
17
23
|
});
|
|
24
|
+
}).catch(err => {
|
|
25
|
+
console.error(err);
|
|
26
|
+
exit();
|
|
18
27
|
});
|