node-alarm-dot-com 1.11.0-beta.6 → 2.0.0-beta.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/dist/index.d.ts +5 -3
- package/dist/index.js +159 -133
- package/package.json +10 -9
package/dist/index.d.ts
CHANGED
@@ -31,7 +31,7 @@ export declare function login(username: string, password: string, existingMfaTok
|
|
31
31
|
* @param {Object} authOpts Authentication object returned from the login.
|
32
32
|
* @returns {Promise}
|
33
33
|
*/
|
34
|
-
export declare function getCurrentState(systemID: string, authOpts:
|
34
|
+
export declare function getCurrentState(systemID: string, authOpts: AuthOpts): Promise<FlattenedSystemState>;
|
35
35
|
/**
|
36
36
|
* Get information about groups of components e.g., sensors, lights, locks, garages, etc.
|
37
37
|
*
|
@@ -86,9 +86,10 @@ export declare function disarm(partitionID: string, authOpts: AuthOpts): Promise
|
|
86
86
|
* @param {string} lightID Light ID string.
|
87
87
|
* @param {number} brightness An integer, 1-100, indicating brightness.
|
88
88
|
* @param {Object} authOpts Authentication object returned from the login.
|
89
|
+
* @param {boolean} isDimmer Indicates whether or not light is dimmable.
|
89
90
|
* @returns {Promise}
|
90
91
|
*/
|
91
|
-
export declare function setLightOn(lightID: string, authOpts: AuthOpts, brightness: number): Promise<any>;
|
92
|
+
export declare function setLightOn(lightID: string, authOpts: AuthOpts, brightness: number, isDimmer: boolean): Promise<any>;
|
92
93
|
/**
|
93
94
|
* Convenience Method:
|
94
95
|
* Sets a light to OFF. The brightness level is ignored.
|
@@ -96,9 +97,10 @@ export declare function setLightOn(lightID: string, authOpts: AuthOpts, brightne
|
|
96
97
|
* @param {string} lightID Light ID string.
|
97
98
|
* @param {number} brightness An integer, 1-100, indicating brightness. Ignored.
|
98
99
|
* @param {Object} authOpts Authentication object returned from the login.
|
100
|
+
* @param {boolean} isDimmer Indicates whether or not light is dimmable.
|
99
101
|
* @returns {Promise}
|
100
102
|
*/
|
101
|
-
export declare function setLightOff(lightID: string, authOpts: AuthOpts, brightness: number): Promise<any>;
|
103
|
+
export declare function setLightOff(lightID: string, authOpts: AuthOpts, brightness: number, isDimmer: boolean): Promise<any>;
|
102
104
|
/**
|
103
105
|
* Convenience Method:
|
104
106
|
* Sets a lock to "locked" (SECURED).
|
package/dist/index.js
CHANGED
@@ -46,17 +46,13 @@ const UA = `node-alarm-dot-com/${require('../package.json').version}`;
|
|
46
46
|
* @param {string} existingMfaToken MFA token from browser used to bypass MFA.
|
47
47
|
* @returns {Promise}
|
48
48
|
*/
|
49
|
-
function login(username, password, existingMfaToken) {
|
49
|
+
async function login(username, password, existingMfaToken) {
|
50
50
|
let loginCookies;
|
51
51
|
let ajaxKey;
|
52
52
|
let loginFormBody, identities, systems;
|
53
53
|
// load initial alarm.com page to gather required hidden form fields
|
54
|
-
|
55
|
-
.catch(err => {
|
56
|
-
throw new Error(`GET ${ADCLOGIN_URL} failed: ${err.message || err}`);
|
57
|
-
})
|
54
|
+
await get(ADCLOGIN_URL)
|
58
55
|
.then(res => {
|
59
|
-
// build login form body
|
60
56
|
const loginObj = {
|
61
57
|
'__EVENTTARGET': null,
|
62
58
|
'__EVENTARGUMENT': null,
|
@@ -69,56 +65,57 @@ function login(username, password, existingMfaToken) {
|
|
69
65
|
'ctl00$ContentPlaceHolder1$loginform$txtUserName': username,
|
70
66
|
'txtPassword': password
|
71
67
|
};
|
68
|
+
// build login form body
|
72
69
|
loginFormBody = Object.keys(loginObj).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(loginObj[k])).join('&');
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
headers: {
|
77
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
78
|
-
'User-Agent': UA,
|
79
|
-
'Cookie': `twoFactorAuthenticationId=${existingMfaToken};`
|
80
|
-
},
|
81
|
-
body: loginFormBody,
|
82
|
-
redirect: 'manual'
|
83
|
-
})
|
84
|
-
.catch((err) => {
|
85
|
-
throw new Error(`POST ${ADCFORMLOGIN_URL} failed: ${err.message || err}`);
|
86
|
-
})
|
87
|
-
.then((res) => {
|
88
|
-
// gather cookies for session
|
89
|
-
loginCookies = res.headers.raw()['set-cookie'].map(c => c.split(';')[0]).join('; ');
|
90
|
-
// gather ajaxkey for session headers
|
91
|
-
const re = /afg=([^;]+);/.exec(loginCookies);
|
92
|
-
if (!re)
|
93
|
-
throw new Error(`No afg cookie: ${loginCookies}`);
|
94
|
-
ajaxKey = re[1];
|
95
|
-
// request identities and systems for account
|
96
|
-
return get(IDENTITIES_URL, {
|
97
|
-
headers: {
|
98
|
-
'Accept': 'application/vnd.api+json',
|
99
|
-
'Cookie': loginCookies,
|
100
|
-
'ajaxrequestuniquekey': ajaxKey,
|
101
|
-
'Referer': 'https://www.alarm.com/web/system/home',
|
102
|
-
'User-Agent': UA
|
103
|
-
}
|
104
|
-
})
|
105
|
-
.catch(err => {
|
106
|
-
throw new Error(`GET ${IDENTITIES_URL} failed: ${err.message || err}`);
|
107
|
-
})
|
108
|
-
.then(res => {
|
109
|
-
// gather identities and systems
|
110
|
-
identities = res.body;
|
111
|
-
systems = (identities.data || []).map((d) => getValue(d, 'relationships.selectedSystem.data.id'));
|
112
|
-
// finally return session/account object for polling and manipulation
|
113
|
-
return {
|
114
|
-
cookie: loginCookies,
|
115
|
-
ajaxKey: ajaxKey,
|
116
|
-
systems: systems,
|
117
|
-
identities: identities
|
118
|
-
};
|
119
|
-
});
|
120
|
-
});
|
70
|
+
})
|
71
|
+
.catch(err => {
|
72
|
+
throw new Error(`GET ${ADCLOGIN_URL} failed: ${err.message || err}`);
|
121
73
|
});
|
74
|
+
await (0, node_fetch_1.default)(ADCFORMLOGIN_URL, {
|
75
|
+
method: 'POST',
|
76
|
+
headers: {
|
77
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
78
|
+
'User-Agent': UA,
|
79
|
+
'Cookie': `twoFactorAuthenticationId=${existingMfaToken};`
|
80
|
+
},
|
81
|
+
body: loginFormBody,
|
82
|
+
redirect: 'manual'
|
83
|
+
})
|
84
|
+
.then(res => {
|
85
|
+
loginCookies = res.headers.raw()['set-cookie'].map(c => c.split(';')[0]).join('; ');
|
86
|
+
// gather ajaxkey for session headers
|
87
|
+
const re = /afg=([^;]+);/.exec(loginCookies);
|
88
|
+
if (!re) {
|
89
|
+
throw new Error(`No afg cookie: ${loginCookies}`);
|
90
|
+
}
|
91
|
+
ajaxKey = re[1];
|
92
|
+
})
|
93
|
+
.catch((err) => {
|
94
|
+
throw new Error(`POST ${ADCFORMLOGIN_URL} failed: ${err.message || err}`);
|
95
|
+
});
|
96
|
+
await get(IDENTITIES_URL, {
|
97
|
+
headers: {
|
98
|
+
'Accept': 'application/vnd.api+json',
|
99
|
+
'Cookie': loginCookies,
|
100
|
+
'ajaxrequestuniquekey': ajaxKey,
|
101
|
+
'Referer': 'https://www.alarm.com/web/system/home',
|
102
|
+
'User-Agent': UA
|
103
|
+
}
|
104
|
+
})
|
105
|
+
.then(res => {
|
106
|
+
// gather identities and systems
|
107
|
+
identities = res.body;
|
108
|
+
systems = (identities.data || []).map((d) => getValue(d, 'relationships.selectedSystem.data.id'));
|
109
|
+
})
|
110
|
+
.catch(err => {
|
111
|
+
throw new Error(`GET ${IDENTITIES_URL} failed: ${err.message || err}`);
|
112
|
+
});
|
113
|
+
return {
|
114
|
+
cookie: loginCookies,
|
115
|
+
ajaxKey: ajaxKey,
|
116
|
+
systems: systems,
|
117
|
+
identities: identities
|
118
|
+
};
|
122
119
|
}
|
123
120
|
exports.login = login;
|
124
121
|
/**
|
@@ -131,43 +128,42 @@ exports.login = login;
|
|
131
128
|
* @param {Object} authOpts Authentication object returned from the login.
|
132
129
|
* @returns {Promise}
|
133
130
|
*/
|
134
|
-
function getCurrentState(systemID, authOpts) {
|
131
|
+
async function getCurrentState(systemID, authOpts) {
|
135
132
|
// This call to the systems endpoint retrieves an overview of all devices in a system
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
};
|
133
|
+
const res = await authenticatedGet(SYSTEM_URL + systemID, authOpts);
|
134
|
+
const rels = res.data.relationships;
|
135
|
+
const components = new Map();
|
136
|
+
// push the results of getComponents into the components
|
137
|
+
// Now we go through and get detailed information about all devices
|
138
|
+
const partitionIDs = rels.partitions.data.map(partition => partition.id);
|
139
|
+
if (typeof partitionIDs[0] !== 'undefined') {
|
140
|
+
components.set('partitions', await getComponents(PARTITIONS_URL, partitionIDs, authOpts));
|
141
|
+
}
|
142
|
+
const sensorIDs = rels.sensors.data.map(sensor => sensor.id);
|
143
|
+
if (typeof sensorIDs[0] !== 'undefined') {
|
144
|
+
components.set('sensors', await getComponents(SENSORS_URL, sensorIDs, authOpts));
|
145
|
+
}
|
146
|
+
const lightIDs = rels.lights.data.map(light => light.id);
|
147
|
+
if (typeof lightIDs[0] !== 'undefined') {
|
148
|
+
components.set('lights', await getComponents(LIGHTS_URL, lightIDs, authOpts));
|
149
|
+
}
|
150
|
+
const lockIDs = rels.locks.data.map(lock => lock.id);
|
151
|
+
if (typeof lockIDs[0] !== 'undefined') {
|
152
|
+
components.set('locks', await getComponents(LOCKS_URL, lockIDs, authOpts));
|
153
|
+
}
|
154
|
+
const garageIDs = rels.garageDoors.data.map(garage => garage.id);
|
155
|
+
if (typeof garageIDs[0] !== 'undefined') {
|
156
|
+
components.set('garages', await getComponents(GARAGE_URL, garageIDs, authOpts));
|
157
|
+
}
|
158
|
+
return ({
|
159
|
+
id: res.data.id,
|
160
|
+
attributes: res.data.attributes,
|
161
|
+
partitions: components.has('partitions') ? components.get('partitions').data : [],
|
162
|
+
sensors: components.has('sensors') ? components.get('sensors').data : [],
|
163
|
+
lights: components.has('lights') ? components.get('lights').data : [],
|
164
|
+
locks: components.has('locks') ? components.get('locks').data : [],
|
165
|
+
garages: components.has('garages') ? components.get('garages').data : [],
|
166
|
+
relationships: rels
|
171
167
|
});
|
172
168
|
}
|
173
169
|
exports.getCurrentState = getCurrentState;
|
@@ -266,14 +262,30 @@ exports.disarm = disarm;
|
|
266
262
|
// about any of the components, sensors included.
|
267
263
|
// Light methods ///////////////////////////////////////////////////////////////
|
268
264
|
/**
|
269
|
-
* Perform light actions, e.
|
265
|
+
* Perform non-dimmable light actions, i.e. turn on, turn off
|
266
|
+
*
|
267
|
+
* @param {string} lightID Light ID string.
|
268
|
+
* @param {string} action Action (verb) to perform on the light.
|
269
|
+
* @param {Object} authOpts Authentication object returned from the login.
|
270
|
+
*/
|
271
|
+
function lightAction(lightID, authOpts, action) {
|
272
|
+
const url = `${LIGHTS_URL}${lightID}/${action}`;
|
273
|
+
const postOpts = Object.assign({}, authOpts, {
|
274
|
+
body: {
|
275
|
+
statePollOnly: false
|
276
|
+
}
|
277
|
+
});
|
278
|
+
return authenticatedPost(url, postOpts);
|
279
|
+
}
|
280
|
+
/**
|
281
|
+
* Perform dimmable light actions, e.g., turn on, turn off, change brightness level.
|
270
282
|
*
|
271
283
|
* @param {string} lightID Light ID string.
|
272
284
|
* @param {string} action Action (verb) to perform on the light.
|
273
285
|
* @param {Object} authOpts Authentication object returned from the login.
|
274
286
|
* @param {number} brightness An integer, 1-100, indicating brightness.
|
275
287
|
*/
|
276
|
-
function
|
288
|
+
function dimmerAction(lightID, authOpts, brightness, action) {
|
277
289
|
const url = `${LIGHTS_URL}${lightID}/${action}`;
|
278
290
|
const postOpts = Object.assign({}, authOpts, {
|
279
291
|
body: {
|
@@ -290,10 +302,16 @@ function lightAction(lightID, authOpts, brightness, action) {
|
|
290
302
|
* @param {string} lightID Light ID string.
|
291
303
|
* @param {number} brightness An integer, 1-100, indicating brightness.
|
292
304
|
* @param {Object} authOpts Authentication object returned from the login.
|
305
|
+
* @param {boolean} isDimmer Indicates whether or not light is dimmable.
|
293
306
|
* @returns {Promise}
|
294
307
|
*/
|
295
|
-
function setLightOn(lightID, authOpts, brightness) {
|
296
|
-
|
308
|
+
function setLightOn(lightID, authOpts, brightness, isDimmer) {
|
309
|
+
if (isDimmer) {
|
310
|
+
return dimmerAction(lightID, authOpts, brightness, 'turnOn');
|
311
|
+
}
|
312
|
+
else {
|
313
|
+
return lightAction(lightID, authOpts, 'turnOn');
|
314
|
+
}
|
297
315
|
}
|
298
316
|
exports.setLightOn = setLightOn;
|
299
317
|
/**
|
@@ -303,10 +321,16 @@ exports.setLightOn = setLightOn;
|
|
303
321
|
* @param {string} lightID Light ID string.
|
304
322
|
* @param {number} brightness An integer, 1-100, indicating brightness. Ignored.
|
305
323
|
* @param {Object} authOpts Authentication object returned from the login.
|
324
|
+
* @param {boolean} isDimmer Indicates whether or not light is dimmable.
|
306
325
|
* @returns {Promise}
|
307
326
|
*/
|
308
|
-
function setLightOff(lightID, authOpts, brightness) {
|
309
|
-
|
327
|
+
function setLightOff(lightID, authOpts, brightness, isDimmer) {
|
328
|
+
if (isDimmer) {
|
329
|
+
return dimmerAction(lightID, authOpts, brightness, 'turnOff');
|
330
|
+
}
|
331
|
+
else {
|
332
|
+
return lightAction(lightID, authOpts, 'turnOff');
|
333
|
+
}
|
310
334
|
}
|
311
335
|
exports.setLightOff = setLightOff;
|
312
336
|
// Lock methods ////////////////////////////////////////////////////////////////
|
@@ -360,8 +384,9 @@ exports.setLockUnsecure = setLockUnsecure;
|
|
360
384
|
* @returns {Promise}
|
361
385
|
*/
|
362
386
|
function getGarages(garageIDs, authOpts) {
|
363
|
-
if (!Array.isArray(garageIDs))
|
387
|
+
if (!Array.isArray(garageIDs)) {
|
364
388
|
garageIDs = [garageIDs];
|
389
|
+
}
|
365
390
|
const query = garageIDs.map(id => `ids%5B%5D=${id}`).join('&');
|
366
391
|
const url = `${GARAGE_URL}?${query}`;
|
367
392
|
return authenticatedGet(url, authOpts);
|
@@ -405,13 +430,15 @@ function openGarage(garageID, authOpts) {
|
|
405
430
|
exports.openGarage = openGarage;
|
406
431
|
// Helper methods //////////////////////////////////////////////////////////////
|
407
432
|
function getValue(data, path) {
|
408
|
-
if (typeof path === 'string')
|
433
|
+
if (typeof path === 'string') {
|
409
434
|
path = path.split('.');
|
410
|
-
|
435
|
+
}
|
436
|
+
for (let i = 0; typeof data === 'object' && i < path.length; i++) {
|
411
437
|
data = data[path[i]];
|
438
|
+
}
|
412
439
|
return data;
|
413
440
|
}
|
414
|
-
function authenticatedGet(url, opts) {
|
441
|
+
async function authenticatedGet(url, opts) {
|
415
442
|
opts = opts || {};
|
416
443
|
opts.headers = opts.headers || {};
|
417
444
|
opts.headers.Accept = 'application/vnd.api+json';
|
@@ -419,9 +446,10 @@ function authenticatedGet(url, opts) {
|
|
419
446
|
opts.headers.Cookie = opts.cookie;
|
420
447
|
opts.headers.Referer = HOME_URL;
|
421
448
|
opts.headers['User-Agent'] = UA;
|
422
|
-
|
449
|
+
const res = await get(url, opts);
|
450
|
+
return res.body;
|
423
451
|
}
|
424
|
-
function authenticatedPost(url, opts) {
|
452
|
+
async function authenticatedPost(url, opts) {
|
425
453
|
opts = opts || {};
|
426
454
|
opts.headers = opts.headers || {};
|
427
455
|
opts.headers.Accept = 'application/vnd.api+json';
|
@@ -430,64 +458,62 @@ function authenticatedPost(url, opts) {
|
|
430
458
|
opts.headers.Referer = HOME_URL;
|
431
459
|
opts.headers['User-Agent'] = UA;
|
432
460
|
opts.headers['Content-Type'] = 'application/json; charset=UTF-8';
|
433
|
-
|
461
|
+
const res = await post(url, opts);
|
462
|
+
return res.body;
|
434
463
|
}
|
435
|
-
function get(url, opts) {
|
464
|
+
async function get(url, opts) {
|
436
465
|
opts = opts || {};
|
437
466
|
let status;
|
438
467
|
let resHeaders;
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
468
|
+
try {
|
469
|
+
const res = await (0, node_fetch_1.default)(url, {
|
470
|
+
method: 'GET',
|
471
|
+
redirect: 'manual',
|
472
|
+
headers: opts.headers
|
473
|
+
});
|
445
474
|
status = res.status;
|
446
475
|
resHeaders = res.headers;
|
447
476
|
const type = res.headers.get('content-type') || '';
|
448
|
-
|
449
|
-
return type.indexOf('json') !== -1 ? (res.status === 204 ? {} : res.json()) : res.text();
|
450
|
-
})
|
451
|
-
.then((body) => {
|
477
|
+
const body = await (type.indexOf('json') !== -1 ? (res.status === 204 ? {} : res.json()) : res.text());
|
452
478
|
if (status === 409) {
|
453
479
|
throw new Error('Two factor is enabled on this account but not setup in the plugin.' +
|
454
480
|
' See the wiki for details');
|
455
481
|
}
|
456
|
-
if (status >= 400)
|
482
|
+
if (status >= 400) {
|
457
483
|
throw new Error(body.Message || body || status);
|
484
|
+
}
|
458
485
|
return {
|
459
486
|
headers: resHeaders,
|
460
487
|
body: body
|
461
488
|
};
|
462
|
-
}
|
463
|
-
|
489
|
+
}
|
490
|
+
catch (err) {
|
464
491
|
throw new Error(`GET ${url} failed: ${err.message || err}`);
|
465
|
-
}
|
492
|
+
}
|
466
493
|
}
|
467
|
-
function post(url, opts) {
|
494
|
+
async function post(url, opts) {
|
468
495
|
opts = opts || {};
|
469
496
|
let status;
|
470
497
|
let resHeaders;
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
498
|
+
try {
|
499
|
+
const res = await (0, node_fetch_1.default)(url, {
|
500
|
+
method: 'POST',
|
501
|
+
redirect: 'manual',
|
502
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
503
|
+
headers: opts.headers
|
504
|
+
});
|
478
505
|
status = res.status;
|
479
506
|
resHeaders = res.headers;
|
480
|
-
|
481
|
-
|
482
|
-
.then((json) => {
|
483
|
-
if (status !== 200)
|
507
|
+
const json = await (res.status === 204 ? {} : res.json());
|
508
|
+
if (status !== 200) {
|
484
509
|
throw new Error(json.Message || status);
|
510
|
+
}
|
485
511
|
return {
|
486
512
|
headers: resHeaders,
|
487
513
|
body: json
|
488
514
|
};
|
489
|
-
}
|
490
|
-
|
515
|
+
}
|
516
|
+
catch (err) {
|
491
517
|
throw new Error(`POST ${url} failed: ${err.message || err}`);
|
492
|
-
}
|
518
|
+
}
|
493
519
|
}
|
package/package.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "node-alarm-dot-com",
|
3
|
-
"version": "
|
4
|
-
"betaVersion": "
|
3
|
+
"version": "2.0.0-beta.0",
|
4
|
+
"betaVersion": "2.0.0",
|
5
5
|
"description": "An interface module written in node.js to arm and disarm Alarm.com security systems.",
|
6
6
|
"author": {
|
7
|
-
"name": "
|
8
|
-
"
|
7
|
+
"name": "Chase Lau",
|
8
|
+
"url": "https://github.com/chase9"
|
9
9
|
},
|
10
10
|
"license": "MIT",
|
11
11
|
"keywords": [
|
@@ -45,17 +45,18 @@
|
|
45
45
|
"test": "echo \"Error: no test specified\" && exit 1"
|
46
46
|
},
|
47
47
|
"engines": {
|
48
|
-
"node": "
|
48
|
+
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
49
49
|
},
|
50
50
|
"dependencies": {
|
51
|
-
"@types/node-fetch": "^
|
51
|
+
"@types/node-fetch": "^3.0.2",
|
52
|
+
"@types/node": "^17.0.16",
|
52
53
|
"node-fetch": "^2.6.1",
|
53
54
|
"semver": "^7.3.5"
|
54
55
|
},
|
55
56
|
"devDependencies": {
|
56
57
|
"rimraf": "^3.0.2",
|
57
|
-
"ts-node": "^
|
58
|
-
"typescript": "^4.
|
59
|
-
"yargs": "^17.
|
58
|
+
"ts-node": "^10.5.0",
|
59
|
+
"typescript": "^4.5.5",
|
60
|
+
"yargs": "^17.3.1"
|
60
61
|
}
|
61
62
|
}
|