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 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: any): Promise<FlattenedSystemState>;
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
- return get(ADCLOGIN_URL)
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
- // submit login form and gather cookies/keys for session
74
- return 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
- .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
- return authenticatedGet(SYSTEM_URL + systemID, authOpts).then(async (res) => {
137
- const rels = res.data.relationships;
138
- const components = new Map();
139
- // push the results of getComponents into the components
140
- // Now we go through and get detailed information about all devices
141
- const partitionIDs = rels.partitions.data.map(p => p.id);
142
- if (typeof partitionIDs[0] != 'undefined') {
143
- components.set('partitions', await getComponents(PARTITIONS_URL, partitionIDs, authOpts));
144
- }
145
- const sensorIDs = rels.sensors.data.map(s => s.id);
146
- if (typeof sensorIDs[0] != 'undefined') {
147
- components.set('sensors', await getComponents(SENSORS_URL, sensorIDs, authOpts));
148
- }
149
- const lightIDs = rels.lights.data.map(l => l.id);
150
- if (typeof lightIDs[0] != 'undefined') {
151
- components.set('lights', await getComponents(LIGHTS_URL, lightIDs, authOpts));
152
- }
153
- const lockIDs = rels.locks.data.map(l => l.id);
154
- if (typeof lockIDs[0] != 'undefined') {
155
- components.set('locks', await getComponents(LOCKS_URL, lockIDs, authOpts));
156
- }
157
- const garageIDs = rels.garageDoors.data.map(g => g.id);
158
- if (typeof garageIDs[0] != 'undefined') {
159
- components.set('garages', await getComponents(GARAGE_URL, garageIDs, authOpts));
160
- }
161
- return {
162
- id: res.data.id,
163
- attributes: res.data.attributes,
164
- partitions: components.has('partitions') ? components.get('partitions').data : [],
165
- sensors: components.has('sensors') ? components.get('sensors').data : [],
166
- lights: components.has('lights') ? components.get('lights').data : [],
167
- locks: components.has('locks') ? components.get('locks').data : [],
168
- garages: components.has('garages') ? components.get('garages').data : [],
169
- relationships: rels
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.g., turn on, turn off, change brightness level.
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 lightAction(lightID, authOpts, brightness, action) {
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
- return lightAction(lightID, authOpts, brightness, 'turnOn');
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
- return lightAction(lightID, authOpts, brightness, 'turnOff');
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
- for (let i = 0; typeof data === 'object' && i < path.length; i++)
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
- return get(url, opts).then(res => res.body);
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
- return post(url, opts).then(res => res.body);
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
- return node_fetch_1.default(url, {
440
- method: 'GET',
441
- redirect: 'manual',
442
- headers: opts.headers
443
- })
444
- .then(res => {
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
- // If response is type JSON,
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
- .catch(err => {
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
- return node_fetch_1.default(url, {
472
- method: 'POST',
473
- redirect: 'manual',
474
- body: opts.body ? JSON.stringify(opts.body) : undefined,
475
- headers: opts.headers
476
- })
477
- .then(res => {
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
- return (res.status === 204 ? {} : res.json());
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
- .catch(err => {
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": "1.11.0-beta.6",
4
- "betaVersion": "1.11.0",
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": "Mike Kormendy",
8
- "email": "mike@somethinginteractive.com"
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": ">=0.12.0"
48
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@types/node-fetch": "^2.5.10",
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": "^9.1.1",
58
- "typescript": "^4.2.4",
59
- "yargs": "^17.0.1"
58
+ "ts-node": "^10.5.0",
59
+ "typescript": "^4.5.5",
60
+ "yargs": "^17.3.1"
60
61
  }
61
62
  }