homebridge-adt-pulse 3.0.0-beta.4 → 3.0.0-beta.6

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/src/lib/api.ts DELETED
@@ -1,3483 +0,0 @@
1
- import axios from 'axios';
2
- import { wrapper } from 'axios-cookiejar-support';
3
- import { JSDOM } from 'jsdom';
4
- import _ from 'lodash';
5
- import { serializeError } from 'serialize-error';
6
- import { CookieJar } from 'tough-cookie';
7
-
8
- import {
9
- detectedNewDoSubmitHandlers,
10
- detectedNewGatewayInformation,
11
- detectedNewOrbSecurityButtons,
12
- detectedNewPanelInformation,
13
- detectedNewPanelStatus,
14
- detectedNewPortalVersion,
15
- detectedNewSensorsInformation,
16
- detectedNewSensorsStatus,
17
- } from '@/lib/detect.js';
18
- import {
19
- paramNetworkId,
20
- paramSat,
21
- requestPathAccessSignIn,
22
- requestPathAccessSignInENsPartnerAdt,
23
- requestPathAccessSignInNetworkIdXxPartnerAdt,
24
- requestPathAjaxSyncCheckServTXx,
25
- requestPathKeepAlive,
26
- requestPathMfaMfaSignInWorkflowChallenge,
27
- requestPathQuickControlArmDisarm,
28
- requestPathQuickControlServRunRraCommand,
29
- requestPathSummarySummary,
30
- requestPathSystemDeviceId1,
31
- requestPathSystemGateway,
32
- requestPathSystemSystem,
33
- textPanelEmergencyKeys,
34
- } from '@/lib/regex.js';
35
- import {
36
- debugLog,
37
- fetchErrorMessage,
38
- fetchMissingSatCode,
39
- fetchTableCells,
40
- findNullKeys,
41
- generateDynatracePCHeaderValue,
42
- generateHash,
43
- isPortalSyncCode,
44
- parseArmDisarmMessage,
45
- parseDoSubmitHandlers,
46
- parseOrbSecurityButtons,
47
- parseOrbSensors,
48
- parseOrbTextSummary,
49
- parseSensorsTable,
50
- sleep,
51
- stackTracer,
52
- } from '@/lib/utility.js';
53
- import type {
54
- ADTPulseArmDisarmHandlerArm,
55
- ADTPulseArmDisarmHandlerArmState,
56
- ADTPulseArmDisarmHandlerHref,
57
- ADTPulseArmDisarmHandlerReadyButton,
58
- ADTPulseArmDisarmHandlerRelativeUrl,
59
- ADTPulseArmDisarmHandlerReturns,
60
- ADTPulseArmDisarmHandlerSat,
61
- ADTPulseArmDisarmHandlerSessions,
62
- ADTPulseConstructorConfig,
63
- ADTPulseConstructorInternalConfig,
64
- ADTPulseCredentials,
65
- ADTPulseForceArmHandlerRelativeUrl,
66
- ADTPulseForceArmHandlerResponse,
67
- ADTPulseForceArmHandlerReturns,
68
- ADTPulseForceArmHandlerSessions,
69
- ADTPulseForceArmHandlerTracker,
70
- ADTPulseGetGatewayInformationReturns,
71
- ADTPulseGetGatewayInformationReturnsStatus,
72
- ADTPulseGetGatewayInformationSessions,
73
- ADTPulseGetPanelInformationReturns,
74
- ADTPulseGetPanelInformationReturnsStatus,
75
- ADTPulseGetPanelInformationSessions,
76
- ADTPulseGetPanelStatusReturns,
77
- ADTPulseGetPanelStatusSessions,
78
- ADTPulseGetRequestConfigDefaultConfig,
79
- ADTPulseGetRequestConfigExtraConfig,
80
- ADTPulseGetRequestConfigReturns,
81
- ADTPulseGetSensorsInformationReturns,
82
- ADTPulseGetSensorsInformationSessions,
83
- ADTPulseGetSensorsStatusReturns,
84
- ADTPulseGetSensorsStatusSessions,
85
- ADTPulseHandleLoginFailureRequestPath,
86
- ADTPulseHandleLoginFailureReturns,
87
- ADTPulseHandleLoginFailureSession,
88
- ADTPulseInternal,
89
- ADTPulseIsAuthenticatedReturns,
90
- ADTPulseIsPortalAccessibleReturns,
91
- ADTPulseLoginPortalVersion,
92
- ADTPulseLoginReturns,
93
- ADTPulseLoginSessions,
94
- ADTPulseLogoutReturns,
95
- ADTPulseLogoutSessions,
96
- ADTPulseNewInformationDispatcherData,
97
- ADTPulseNewInformationDispatcherReturns,
98
- ADTPulseNewInformationDispatcherType,
99
- ADTPulsePerformKeepAliveReturns,
100
- ADTPulsePerformKeepAliveSessions,
101
- ADTPulsePerformSyncCheckReturns,
102
- ADTPulsePerformSyncCheckSessions,
103
- ADTPulseResetSessionReturns,
104
- ADTPulseSession,
105
- ADTPulseSetPanelStatusArmTo,
106
- ADTPulseSetPanelStatusReadyButton,
107
- ADTPulseSetPanelStatusReturns,
108
- ADTPulseSetPanelStatusSessions,
109
- } from '@/types/index.d.ts';
110
-
111
- /**
112
- * ADT Pulse.
113
- *
114
- * @since 1.0.0
115
- */
116
- export class ADTPulse {
117
- /**
118
- * ADT Pulse - Credentials.
119
- *
120
- * @private
121
- *
122
- * @since 1.0.0
123
- */
124
- #credentials: ADTPulseCredentials;
125
-
126
- /**
127
- * ADT Pulse - Internal.
128
- *
129
- * @private
130
- *
131
- * @since 1.0.0
132
- */
133
- #internal: ADTPulseInternal;
134
-
135
- /**
136
- * ADT Pulse - Session.
137
- *
138
- * @private
139
- *
140
- * @since 1.0.0
141
- */
142
- #session: ADTPulseSession;
143
-
144
- /**
145
- * ADT Pulse - Constructor.
146
- *
147
- * @param {ADTPulseConstructorConfig} config - Config.
148
- * @param {ADTPulseConstructorInternalConfig} internalConfig - Internal config.
149
- *
150
- * @since 1.0.0
151
- */
152
- constructor(config: ADTPulseConstructorConfig, internalConfig: ADTPulseConstructorInternalConfig) {
153
- // Set config options.
154
- this.#credentials = {
155
- fingerprint: config.fingerprint,
156
- password: config.password,
157
- subdomain: config.subdomain,
158
- username: config.username,
159
- };
160
-
161
- // Set internal config options.
162
- this.#internal = {
163
- baseUrl: internalConfig.baseUrl ?? `https://${this.#credentials.subdomain}.adtpulse.com`,
164
- debug: internalConfig.debug ?? false,
165
- logger: internalConfig.logger ?? null,
166
- reportedHashes: [],
167
- testMode: {
168
- enabled: internalConfig.testMode?.enabled ?? false,
169
- isDisarmChecked: internalConfig.testMode?.isDisarmChecked ?? false,
170
- },
171
- };
172
-
173
- // Set session information to defaults.
174
- this.#session = {
175
- backupSatCode: null,
176
- httpClient: wrapper(axios.create({
177
- jar: new CookieJar(),
178
- })),
179
- isAuthenticated: false,
180
- isCleanState: true,
181
- networkId: null,
182
- portalVersion: null,
183
- };
184
- }
185
-
186
- /**
187
- * ADT Pulse - Login.
188
- *
189
- * @returns {ADTPulseLoginReturns}
190
- *
191
- * @since 1.0.0
192
- */
193
- async login(): ADTPulseLoginReturns {
194
- let errorObject;
195
-
196
- if (this.#internal.debug) {
197
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'info', `Attempting to login to "${this.#internal.baseUrl}"`);
198
- }
199
-
200
- try {
201
- const internet = await this.isPortalAccessible();
202
- const sessions: ADTPulseLoginSessions = {};
203
-
204
- // Check if portal is accessible.
205
- if (!internet.success) {
206
- return {
207
- action: 'LOGIN',
208
- success: false,
209
- info: internet.info,
210
- };
211
- }
212
-
213
- // Check if "this instance" has already authenticated.
214
- if (this.isAuthenticated()) {
215
- if (this.#internal.debug) {
216
- debugLog(
217
- this.#internal.logger,
218
- 'api.ts / ADTPulse.login()',
219
- 'info',
220
- [
221
- 'Already logged in',
222
- [
223
- '(',
224
- [
225
- `backup sat code: ${this.#session.backupSatCode}`,
226
- `network id: ${this.#session.networkId}`,
227
- `portal version: ${this.#session.portalVersion}`,
228
- ].join(', '),
229
- ')',
230
- ].join(''),
231
- ].join(' '),
232
- );
233
- }
234
-
235
- return {
236
- action: 'LOGIN',
237
- success: true,
238
- info: {
239
- backupSatCode: this.#session.backupSatCode,
240
- networkId: this.#session.networkId,
241
- portalVersion: this.#session.portalVersion,
242
- },
243
- };
244
- }
245
-
246
- // sessions.axiosIndex: Load the homepage.
247
- sessions.axiosIndex = await this.#session.httpClient.get<unknown>(
248
- `${this.#internal.baseUrl}/`,
249
- this.getRequestConfig(),
250
- );
251
-
252
- // If the "ClientRequest" object does not exist in the Axios response.
253
- if (typeof sessions.axiosIndex?.request === 'undefined') {
254
- if (this.#internal.debug) {
255
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', 'The HTTP client responded without the "request" object');
256
- }
257
-
258
- return {
259
- action: 'LOGIN',
260
- success: false,
261
- info: {
262
- message: 'The HTTP client responded without the "request" object',
263
- },
264
- };
265
- }
266
-
267
- const axiosIndexRequestPath = sessions.axiosIndex.request.path;
268
- const axiosIndexRequestPathValid = requestPathAccessSignIn.test(axiosIndexRequestPath);
269
-
270
- if (this.#internal.debug) {
271
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'info', `Request path ➜ ${axiosIndexRequestPath}`);
272
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'info', `Request path valid ➜ ${axiosIndexRequestPathValid}`);
273
- }
274
-
275
- // If the final URL of sessions.axiosIndex is not the sign-in page.
276
- if (!axiosIndexRequestPathValid) {
277
- if (this.#internal.debug) {
278
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', `"${axiosIndexRequestPath} is not the sign-in page`);
279
- }
280
-
281
- // Check if "this instance" was not signed in during this time.
282
- this.handleLoginFailure(axiosIndexRequestPath, sessions.axiosIndex);
283
-
284
- return {
285
- action: 'LOGIN',
286
- success: false,
287
- info: {
288
- message: `"${axiosIndexRequestPath} is not the sign-in page`,
289
- },
290
- };
291
- }
292
-
293
- // Build an "application/x-www-form-urlencoded" form for use with logging in.
294
- const loginForm = new URLSearchParams();
295
- loginForm.append('usernameForm', this.#credentials.username);
296
- loginForm.append('passwordForm', this.#credentials.password);
297
- loginForm.append('sun', 'yes'); // Remember my username.
298
- loginForm.append('networkid', ''); // Blank if URL does not have the "networkid" param.
299
- loginForm.append('fingerprint', this.#credentials.fingerprint);
300
-
301
- /**
302
- * Detailed parsing information for "portalVersion".
303
- *
304
- * NOTICE: Responses may be inaccurate or missing.
305
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
306
- *
307
- * How the data may be displayed:
308
- * ➜ /myhome/16.0.0-131/access/signin.jsp
309
- *
310
- * Example data after being processed by "replace()" function/method:
311
- * ➜ 16.0.0-131
312
- *
313
- * @since 1.0.0
314
- */
315
- this.#session.portalVersion = axiosIndexRequestPath.replace(requestPathAccessSignIn, '$2') as ADTPulseLoginPortalVersion;
316
-
317
- /**
318
- * Check if "portalVersion" needs documenting or testing.
319
- *
320
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
321
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
322
- *
323
- * version: '16.0.0-131'
324
- * '17.0.0-69'
325
- * '18.0.0-78'
326
- * '19.0.0-89'
327
- * '20.0.0-221'
328
- * '20.0.0-244'
329
- * '21.0.0-344'
330
- * '21.0.0-353'
331
- * '21.0.0-354'
332
- * '22.0.0-233'
333
- * '23.0.0-99'
334
- * '24.0.0-117'
335
- * '25.0.0-21'
336
- * '26.0.0-32'
337
- * '27.0.0-140'
338
- *
339
- * @since 1.0.0
340
- */
341
- await this.newInformationDispatcher('portal-version', { version: this.#session.portalVersion });
342
-
343
- // sessions.axiosSignin: Emulate a sign-in request.
344
- sessions.axiosSignin = await this.#session.httpClient.post<unknown>(
345
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/signin.jsp?e=ns&partner=adt`,
346
- loginForm,
347
- this.getRequestConfig({
348
- headers: {
349
- 'Cache-Control': 'max-age=0',
350
- 'Content-Type': 'application/x-www-form-urlencoded',
351
- Origin: this.#internal.baseUrl,
352
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/signin.jsp?e=ns&partner=adt`,
353
- 'Sec-Fetch-Site': 'same-origin',
354
- },
355
- }),
356
- );
357
-
358
- // If the "ClientRequest" object does not exist in the Axios response.
359
- if (typeof sessions.axiosSignin?.request === 'undefined') {
360
- if (this.#internal.debug) {
361
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', 'The HTTP client responded without the "request" object');
362
- }
363
-
364
- return {
365
- action: 'LOGIN',
366
- success: false,
367
- info: {
368
- message: 'The HTTP client responded without the "request" object',
369
- },
370
- };
371
- }
372
-
373
- const axiosSigninRequestPath = sessions.axiosSignin.request.path;
374
- const axiosSigninRequestPathValid = requestPathSummarySummary.test(axiosSigninRequestPath);
375
-
376
- if (this.#internal.debug) {
377
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'info', `Request path ➜ ${axiosSigninRequestPath}`);
378
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'info', `Request path valid ➜ ${axiosSigninRequestPathValid}`);
379
- }
380
-
381
- // If the final URL of sessions.axiosSignin is not the summary page.
382
- if (!axiosSigninRequestPathValid) {
383
- if (this.#internal.debug) {
384
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', `"${axiosSigninRequestPath}" is not the summary page`);
385
- }
386
-
387
- // Check if "this instance" was not signed in during this time.
388
- this.handleLoginFailure(axiosSigninRequestPath, sessions.axiosSignin);
389
-
390
- return {
391
- action: 'LOGIN',
392
- success: false,
393
- info: {
394
- message: `"${axiosSigninRequestPath}" is not the summary page`,
395
- },
396
- };
397
- }
398
-
399
- // Make sure we are able to use the "String.prototype.match()" method on the response data.
400
- if (typeof sessions.axiosSignin.data !== 'string') {
401
- if (this.#internal.debug) {
402
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', 'The response body of the summary page is not of type "string"');
403
- }
404
-
405
- return {
406
- action: 'LOGIN',
407
- success: false,
408
- info: {
409
- message: 'The response body of the summary page is not of type "string"',
410
- },
411
- };
412
- }
413
-
414
- /**
415
- * Original matches for the network ID (site ID).
416
- *
417
- * - "?networkid=1234567890"
418
- * - "1234567890"
419
- *
420
- * Only need to store the network ID (site ID), and should be
421
- * two elements. It is loosely matched for more to take unexpected
422
- * changes into account. Used for logout links.
423
- *
424
- * @since 1.0.0
425
- */
426
- const matchNetworkId = sessions.axiosSignin.data.match(paramNetworkId);
427
- this.#session.networkId = (matchNetworkId !== null && matchNetworkId.length >= 2) ? matchNetworkId[1] : null;
428
-
429
- /**
430
- * Original matches for the sat code.
431
- *
432
- * - "sat=3b59d412-0dcb-41fb-b925-3fcfe3144633"
433
- * - "3b59d412-0dcb-41fb-b925-3fcfe3144633"
434
- *
435
- * Only need to store the sat code, and should be two elements.
436
- * It is loosely matched for more to take unexpected changes into
437
- * account. Used in case sat code is not found.
438
- *
439
- * If during login, the system status was unavailable, this value
440
- * will be null, and things like creating a fake Disarm button would not
441
- * work. Will try to recover on "summary/summary.jsp" page loads.
442
- *
443
- * @since 1.0.0
444
- */
445
- const matchSatCode = sessions.axiosSignin.data.match(paramSat);
446
- this.#session.backupSatCode = (matchSatCode !== null && matchSatCode.length >= 2) ? matchSatCode[1] : null;
447
-
448
- // If backup sat code was unavailable at this time.
449
- if (this.#session.backupSatCode === null && this.#internal.debug) {
450
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'warn', 'Unable to backup sat code, will try again when system becomes available');
451
- }
452
-
453
- // Mark the session for "this instance" as authenticated.
454
- this.#session.isAuthenticated = true;
455
-
456
- if (this.#internal.debug) {
457
- debugLog(
458
- this.#internal.logger,
459
- 'api.ts / ADTPulse.login()',
460
- 'success',
461
- [
462
- 'Login successful',
463
- [
464
- '(',
465
- [
466
- `backup sat code: ${this.#session.backupSatCode}`,
467
- `network id: ${this.#session.networkId}`,
468
- `portal version: ${this.#session.portalVersion}`,
469
- ].join(', '),
470
- ')',
471
- ].join(''),
472
- ].join(' '),
473
- );
474
- }
475
-
476
- return {
477
- action: 'LOGIN',
478
- success: true,
479
- info: {
480
- backupSatCode: this.#session.backupSatCode,
481
- networkId: this.#session.networkId,
482
- portalVersion: this.#session.portalVersion,
483
- },
484
- };
485
- } catch (error) {
486
- errorObject = serializeError(error);
487
- }
488
-
489
- if (this.#internal.debug) {
490
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.login()', 'error', 'Method encountered an error during execution');
491
- stackTracer('serialize-error', errorObject);
492
- }
493
-
494
- return {
495
- action: 'LOGIN',
496
- success: false,
497
- info: {
498
- error: errorObject,
499
- },
500
- };
501
- }
502
-
503
- /**
504
- * ADT Pulse - Logout.
505
- *
506
- * @returns {ADTPulseLogoutReturns}
507
- *
508
- * @since 1.0.0
509
- */
510
- async logout(): ADTPulseLogoutReturns {
511
- let errorObject;
512
-
513
- if (this.#internal.debug) {
514
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'info', `Attempting to logout of "${this.#internal.baseUrl}"`);
515
- }
516
-
517
- try {
518
- const internet = await this.isPortalAccessible();
519
- const sessions: ADTPulseLogoutSessions = {};
520
-
521
- // Check if portal is accessible.
522
- if (!internet.success) {
523
- return {
524
- action: 'LOGOUT',
525
- success: false,
526
- info: internet.info,
527
- };
528
- }
529
-
530
- // Check if "this instance" has already de-authenticated.
531
- if (!this.isAuthenticated()) {
532
- if (this.#internal.debug) {
533
- debugLog(
534
- this.#internal.logger,
535
- 'api.ts / ADTPulse.logout()',
536
- 'info',
537
- [
538
- 'Already logged out',
539
- [
540
- '(',
541
- [
542
- `backup sat code: ${this.#session.backupSatCode}`,
543
- `network id: ${this.#session.networkId}`,
544
- `portal version: ${this.#session.portalVersion}`,
545
- ].join(', '),
546
- ')',
547
- ].join(''),
548
- ].join(' '),
549
- );
550
- }
551
-
552
- return {
553
- action: 'LOGOUT',
554
- success: true,
555
- info: {
556
- backupSatCode: this.#session.backupSatCode,
557
- networkId: this.#session.networkId,
558
- portalVersion: this.#session.portalVersion,
559
- },
560
- };
561
- }
562
-
563
- // sessions.axiosSignout: Emulate a sign-out request.
564
- sessions.axiosSignout = await this.#session.httpClient.get<unknown>(
565
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/signout.jsp?networkid=${this.#session.networkId}&partner=adt`,
566
- this.getRequestConfig({
567
- headers: {
568
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
569
- 'Sec-Fetch-Site': 'same-origin',
570
- },
571
- }),
572
- );
573
-
574
- // If the "ClientRequest" object does not exist in the Axios response.
575
- if (typeof sessions.axiosSignout?.request === 'undefined') {
576
- if (this.#internal.debug) {
577
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'error', 'The HTTP client responded without the "request" object');
578
- }
579
-
580
- return {
581
- action: 'LOGOUT',
582
- success: false,
583
- info: {
584
- message: 'The HTTP client responded without the "request" object',
585
- },
586
- };
587
- }
588
-
589
- const axiosSignoutRequestPath = sessions.axiosSignout.request.path;
590
- const axiosSignoutRequestPathValid = requestPathAccessSignInNetworkIdXxPartnerAdt.test(axiosSignoutRequestPath);
591
-
592
- if (this.#internal.debug) {
593
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'info', `Request path ➜ ${axiosSignoutRequestPath}`);
594
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'info', `Request path valid ➜ ${axiosSignoutRequestPathValid}`);
595
- }
596
-
597
- // If the final URL of sessions.axiosSignout is not the sign-in page with "networkid" and "partner=adt" parameters.
598
- if (!axiosSignoutRequestPathValid) {
599
- if (this.#internal.debug) {
600
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'error', `"${axiosSignoutRequestPath}" is not the sign-in page with "networkid" and "partner=adt" parameters`);
601
- }
602
-
603
- // Check if "this instance" was not signed in during this time.
604
- this.handleLoginFailure(axiosSignoutRequestPath, sessions.axiosSignout);
605
-
606
- return {
607
- action: 'LOGOUT',
608
- success: false,
609
- info: {
610
- message: `"${axiosSignoutRequestPath}" is not the sign-in page with "networkid" and "partner=adt" parameters`,
611
- },
612
- };
613
- }
614
-
615
- // Reset the session state for "this instance".
616
- this.resetSession();
617
-
618
- if (this.#internal.debug) {
619
- debugLog(
620
- this.#internal.logger,
621
- 'api.ts / ADTPulse.logout()',
622
- 'success',
623
- [
624
- 'Logout successful',
625
- [
626
- '(',
627
- [
628
- `backup sat code: ${this.#session.backupSatCode}`,
629
- `network id: ${this.#session.networkId}`,
630
- `portal version: ${this.#session.portalVersion}`,
631
- ].join(', '),
632
- ')',
633
- ].join(''),
634
- ].join(' '),
635
- );
636
- }
637
-
638
- return {
639
- action: 'LOGOUT',
640
- success: true,
641
- info: {
642
- backupSatCode: this.#session.backupSatCode,
643
- networkId: this.#session.networkId,
644
- portalVersion: this.#session.portalVersion,
645
- },
646
- };
647
- } catch (error) {
648
- errorObject = serializeError(error);
649
- }
650
-
651
- if (this.#internal.debug) {
652
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.logout()', 'error', 'Method encountered an error during execution');
653
- stackTracer('serialize-error', errorObject);
654
- }
655
-
656
- return {
657
- action: 'LOGOUT',
658
- success: false,
659
- info: {
660
- error: errorObject,
661
- },
662
- };
663
- }
664
-
665
- /**
666
- * ADT Pulse - Get gateway information.
667
- *
668
- * @returns {ADTPulseGetGatewayInformationReturns}
669
- *
670
- * @since 1.0.0
671
- */
672
- async getGatewayInformation(): ADTPulseGetGatewayInformationReturns {
673
- let errorObject;
674
-
675
- if (this.#internal.debug) {
676
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'info', `Attempting to retrieve gateway information from "${this.#internal.baseUrl}"`);
677
- }
678
-
679
- try {
680
- const internet = await this.isPortalAccessible();
681
- const sessions: ADTPulseGetGatewayInformationSessions = {};
682
-
683
- // Check if portal is accessible.
684
- if (!internet.success) {
685
- return {
686
- action: 'GET_GATEWAY_INFORMATION',
687
- success: false,
688
- info: internet.info,
689
- };
690
- }
691
-
692
- // sessions.axiosSystemGateway: Load the system gateway page.
693
- sessions.axiosSystemGateway = await this.#session.httpClient.get<unknown>(
694
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/gateway.jsp`,
695
- this.getRequestConfig({
696
- headers: {
697
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/system.jsp`,
698
- 'Sec-Fetch-Site': 'same-origin',
699
- 'Sec-Fetch-User': undefined,
700
- },
701
- }),
702
- );
703
-
704
- // If the "ClientRequest" object does not exist in the Axios response.
705
- if (typeof sessions.axiosSystemGateway?.request === 'undefined') {
706
- if (this.#internal.debug) {
707
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'error', 'The HTTP client responded without the "request" object');
708
- }
709
-
710
- return {
711
- action: 'GET_GATEWAY_INFORMATION',
712
- success: false,
713
- info: {
714
- message: 'The HTTP client responded without the "request" object',
715
- },
716
- };
717
- }
718
-
719
- const axiosSystemGatewayRequestPath = sessions.axiosSystemGateway.request.path;
720
- const axiosSystemGatewayRequestPathValid = requestPathSystemGateway.test(axiosSystemGatewayRequestPath);
721
-
722
- if (this.#internal.debug) {
723
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'info', `Request path ➜ ${axiosSystemGatewayRequestPath}`);
724
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'info', `Request path valid ➜ ${axiosSystemGatewayRequestPathValid}`);
725
- }
726
-
727
- // If the final URL of sessions.axiosSystemGateway is not the system gateway page.
728
- if (!axiosSystemGatewayRequestPathValid) {
729
- if (this.#internal.debug) {
730
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'error', `"${axiosSystemGatewayRequestPath}" is not the system gateway page`);
731
- }
732
-
733
- // Check if "this instance" was not signed in during this time.
734
- this.handleLoginFailure(axiosSystemGatewayRequestPath, sessions.axiosSystemGateway);
735
-
736
- return {
737
- action: 'GET_GATEWAY_INFORMATION',
738
- success: false,
739
- info: {
740
- message: `"${axiosSystemGatewayRequestPath}" is not the system gateway page`,
741
- },
742
- };
743
- }
744
-
745
- // Make sure we are able to use JSDOM on the response data.
746
- if (typeof sessions.axiosSystemGateway.data !== 'string') {
747
- if (this.#internal.debug) {
748
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'error', 'The response body of the system gateway page is not of type "string"');
749
- }
750
-
751
- return {
752
- action: 'GET_GATEWAY_INFORMATION',
753
- success: false,
754
- info: {
755
- message: 'The response body of the system gateway page is not of type "string"',
756
- },
757
- };
758
- }
759
-
760
- // sessions.jsdomSystemGateway: Parse the system gateway page.
761
- sessions.jsdomSystemGateway = new JSDOM(
762
- sessions.axiosSystemGateway.data,
763
- {
764
- url: sessions.axiosSystemGateway.config.url,
765
- referrer: sessions.axiosSystemGateway.config.headers.Referer,
766
- contentType: 'text/html',
767
- pretendToBeVisual: true,
768
- },
769
- );
770
-
771
- /**
772
- * Detailed parsing information for "gatewayInformation".
773
- *
774
- * NOTICE: Responses may be inaccurate or missing.
775
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
776
- *
777
- * How the data may be displayed:
778
- * ➜ <tr>
779
- * <td>Manufacturer:</td>
780
- * <td>ADT Pulse Gateway</td>
781
- * </tr>
782
- * <tr>
783
- * <td>Model:</td>
784
- * <td>1234567890</td>
785
- * </tr>
786
- *
787
- * Example data after being processed by "fetchTableCells()" function/method:
788
- * ➜ {
789
- * 'Manufacturer:': [
790
- * 'ADT Pulse Gateway',
791
- * ],
792
- * 'Model:': [
793
- * '1234567890',
794
- * ],
795
- * }
796
- *
797
- * @since 1.0.0
798
- */
799
- const jsdomSystemGatewayTableCells = sessions.jsdomSystemGateway.window.document.querySelectorAll('td');
800
- const fetchedTableCells = fetchTableCells(
801
- jsdomSystemGatewayTableCells,
802
- [
803
- 'Broadband Connection Status:',
804
- 'Broadband LAN IP Address:',
805
- 'Broadband LAN MAC:',
806
- 'Cellular Connection Status:',
807
- 'Cellular Signal Strength:',
808
- 'Device LAN IP Address:',
809
- 'Device LAN MAC:',
810
- 'Firmware Version:',
811
- 'Hardware Version:',
812
- 'Last Update:',
813
- 'Manufacturer:',
814
- 'Model:',
815
- 'Next Update:',
816
- 'Primary Connection Type:',
817
- 'Router LAN IP Address:',
818
- 'Router WAN IP Address:',
819
- 'Serial Number:',
820
- 'Status:',
821
- ],
822
- 1,
823
- 1,
824
- );
825
- const gatewayInformation = {
826
- communication: {
827
- primaryConnectionType: _.get(fetchedTableCells, ['Primary Connection Type:', 0], null),
828
- broadbandConnectionStatus: _.get(fetchedTableCells, ['Broadband Connection Status:', 0], null),
829
- cellularConnectionStatus: _.get(fetchedTableCells, ['Cellular Connection Status:', 0], null),
830
- cellularSignalStrength: _.get(fetchedTableCells, ['Cellular Signal Strength:', 0], null),
831
- },
832
- manufacturer: _.get(fetchedTableCells, ['Manufacturer:', 0], null),
833
- model: _.get(fetchedTableCells, ['Model:', 0], null),
834
- network: {
835
- broadband: {
836
- ip: _.get(fetchedTableCells, ['Broadband LAN IP Address:', 0], null),
837
- mac: _.get(fetchedTableCells, ['Broadband LAN MAC:', 0], null),
838
- },
839
- device: {
840
- ip: _.get(fetchedTableCells, ['Device LAN IP Address:', 0], null),
841
- mac: _.get(fetchedTableCells, ['Device LAN MAC:', 0], null),
842
- },
843
- router: {
844
- lanIp: _.get(fetchedTableCells, ['Router LAN IP Address:', 0], null),
845
- wanIp: _.get(fetchedTableCells, ['Router WAN IP Address:', 0], null),
846
- },
847
- },
848
- serialNumber: _.get(fetchedTableCells, ['Serial Number:', 0], null),
849
- status: _.get(fetchedTableCells, ['Status:', 0], null) as ADTPulseGetGatewayInformationReturnsStatus,
850
- update: {
851
- last: _.get(fetchedTableCells, ['Last Update:', 0], null),
852
- next: _.get(fetchedTableCells, ['Next Update:', 0], null),
853
- },
854
- versions: {
855
- firmware: _.get(fetchedTableCells, ['Firmware Version:', 0], null),
856
- hardware: _.get(fetchedTableCells, ['Hardware Version:', 0], null),
857
- },
858
- };
859
-
860
- /**
861
- * Check if "gatewayInformation" needs documenting or testing.
862
- *
863
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
864
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
865
- *
866
- * status: 'Online'
867
- * 'Status Unknown'
868
- *
869
- * @since 1.0.0
870
- */
871
- await this.newInformationDispatcher('gateway-information', gatewayInformation);
872
-
873
- if (this.#internal.debug) {
874
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'success', `Successfully retrieved gateway information from "${this.#internal.baseUrl}"`);
875
- }
876
-
877
- return {
878
- action: 'GET_GATEWAY_INFORMATION',
879
- success: true,
880
- info: gatewayInformation,
881
- };
882
- } catch (error) {
883
- errorObject = serializeError(error);
884
- }
885
-
886
- if (this.#internal.debug) {
887
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getGatewayInformation()', 'error', 'Method encountered an error during execution');
888
- stackTracer('serialize-error', errorObject);
889
- }
890
-
891
- return {
892
- action: 'GET_GATEWAY_INFORMATION',
893
- success: false,
894
- info: {
895
- error: errorObject,
896
- },
897
- };
898
- }
899
-
900
- /**
901
- * ADT Pulse - Get panel information.
902
- *
903
- * @returns {ADTPulseGetPanelInformationReturns}
904
- *
905
- * @since 1.0.0
906
- */
907
- async getPanelInformation(): ADTPulseGetPanelInformationReturns {
908
- let errorObject;
909
-
910
- if (this.#internal.debug) {
911
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'info', `Attempting to retrieve panel information from "${this.#internal.baseUrl}"`);
912
- }
913
-
914
- try {
915
- const internet = await this.isPortalAccessible();
916
- const sessions: ADTPulseGetPanelInformationSessions = {};
917
-
918
- // Check if portal is accessible.
919
- if (!internet.success) {
920
- return {
921
- action: 'GET_PANEL_INFORMATION',
922
- success: false,
923
- info: internet.info,
924
- };
925
- }
926
-
927
- // sessions.axiosSystemDeviceId1: Load the system device id 1 page.
928
- sessions.axiosSystemDeviceId1 = await this.#session.httpClient.get<unknown>(
929
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/device.jsp?id=1`,
930
- this.getRequestConfig({
931
- headers: {
932
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/system.jsp`,
933
- 'Sec-Fetch-Site': 'same-origin',
934
- },
935
- }),
936
- );
937
-
938
- // If the "ClientRequest" object does not exist in the Axios response.
939
- if (typeof sessions.axiosSystemDeviceId1?.request === 'undefined') {
940
- if (this.#internal.debug) {
941
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'error', 'The HTTP client responded without the "request" object');
942
- }
943
-
944
- return {
945
- action: 'GET_PANEL_INFORMATION',
946
- success: false,
947
- info: {
948
- message: 'The HTTP client responded without the "request" object',
949
- },
950
- };
951
- }
952
-
953
- const axiosSystemDeviceId1RequestPath = sessions.axiosSystemDeviceId1.request.path;
954
- const axiosSystemDeviceId1RequestPathValid = requestPathSystemDeviceId1.test(axiosSystemDeviceId1RequestPath);
955
-
956
- if (this.#internal.debug) {
957
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'info', `Request path ➜ ${axiosSystemDeviceId1RequestPath}`);
958
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'info', `Request path valid ➜ ${axiosSystemDeviceId1RequestPathValid}`);
959
- }
960
-
961
- // If the final URL of sessions.axiosSystemDeviceId1 is not the system device id 1 page.
962
- if (!axiosSystemDeviceId1RequestPathValid) {
963
- if (this.#internal.debug) {
964
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'error', `"${axiosSystemDeviceId1RequestPath}" is not the system device id 1 page`);
965
- }
966
-
967
- // Check if "this instance" was not signed in during this time.
968
- this.handleLoginFailure(axiosSystemDeviceId1RequestPath, sessions.axiosSystemDeviceId1);
969
-
970
- return {
971
- action: 'GET_PANEL_INFORMATION',
972
- success: false,
973
- info: {
974
- message: `"${axiosSystemDeviceId1RequestPath}" is not the system device id 1 page`,
975
- },
976
- };
977
- }
978
-
979
- // Make sure we are able to use JSDOM on the response data.
980
- if (typeof sessions.axiosSystemDeviceId1.data !== 'string') {
981
- if (this.#internal.debug) {
982
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'error', 'The response body of the system device id 1 page is not of type "string"');
983
- }
984
-
985
- return {
986
- action: 'GET_PANEL_INFORMATION',
987
- success: false,
988
- info: {
989
- message: 'The response body of the system device id 1 page is not of type "string"',
990
- },
991
- };
992
- }
993
-
994
- // sessions.jsdomSystemDeviceId1: Parse the system device id 1 page.
995
- sessions.jsdomSystemDeviceId1 = new JSDOM(
996
- sessions.axiosSystemDeviceId1.data,
997
- {
998
- url: sessions.axiosSystemDeviceId1.config.url,
999
- referrer: sessions.axiosSystemDeviceId1.config.headers.Referer,
1000
- contentType: 'text/html',
1001
- pretendToBeVisual: true,
1002
- },
1003
- );
1004
-
1005
- /**
1006
- * Detailed parsing information for "panelInformation".
1007
- *
1008
- * NOTICE: Responses may be inaccurate or missing.
1009
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1010
- *
1011
- * How the data may be displayed:
1012
- * ➜ <tr>
1013
- * <td>Manufacturer/Provider:</td>
1014
- * <td>ADT</td>
1015
- * </tr>
1016
- * <tr>
1017
- * <td>Emergency Keys:</td>
1018
- * <td>
1019
- * <div>Button:&nbsp;Sample&nbsp;Alarm&nbsp;(Zone&nbsp;99)</div>
1020
- * <div>Button:&nbsp;Sample&nbsp;Alarm&nbsp;(Zone&nbsp;99)</div>
1021
- * </td>
1022
- * </tr>
1023
- *
1024
- * Example data after being processed by "fetchTableCells()" function/method:
1025
- * ➜ {
1026
- * 'Manufacturer/Provider:': [
1027
- * 'ADT Pulse Gateway',
1028
- * ],
1029
- * 'Emergency Keys:': [
1030
- * 'Button: Sample Alarm (Zone 99) Button: Sample Alarm (Zone 99)',
1031
- * ],
1032
- * }
1033
- *
1034
- * @since 1.0.0
1035
- */
1036
- const jsdomSystemDeviceId1TableCells = sessions.jsdomSystemDeviceId1.window.document.querySelectorAll('td');
1037
- const fetchedTableCells = fetchTableCells(
1038
- jsdomSystemDeviceId1TableCells,
1039
- [
1040
- 'Emergency Keys:',
1041
- 'Manufacturer/Provider:',
1042
- 'Security Panel Master Code:',
1043
- 'Status:',
1044
- 'Type/Model:',
1045
- ],
1046
- 1,
1047
- 1,
1048
- );
1049
- const emergencyKeys = _.get(fetchedTableCells, ['Emergency Keys:', 0], null);
1050
- const parsedEmergencyKeys = (typeof emergencyKeys === 'string') ? emergencyKeys.match(textPanelEmergencyKeys) : null;
1051
- const manufacturerProvider = _.get(fetchedTableCells, ['Manufacturer/Provider:', 0], null);
1052
- const parsedManufacturer = (typeof manufacturerProvider === 'string') ? manufacturerProvider.split(' - ')[0] ?? null : null;
1053
- const parsedProvider = (typeof manufacturerProvider === 'string') ? manufacturerProvider.split(' - ')[1] ?? null : null;
1054
- const typeModel = _.get(fetchedTableCells, ['Type/Model:', 0], null);
1055
- const parsedType = (typeof typeModel === 'string') ? typeModel.split(' - ')[0] ?? null : null;
1056
- const parsedModel = (typeof typeModel === 'string') ? typeModel.split(' - ')[1] ?? null : null;
1057
- const panelInformation = {
1058
- emergencyKeys: parsedEmergencyKeys,
1059
- manufacturer: parsedManufacturer,
1060
- masterCode: _.get(fetchedTableCells, ['Security Panel Master Code:', 0], null),
1061
- provider: parsedProvider,
1062
- type: parsedType,
1063
- model: parsedModel,
1064
- status: _.get(fetchedTableCells, ['Status:', 0], null) as ADTPulseGetPanelInformationReturnsStatus,
1065
- };
1066
-
1067
- /**
1068
- * Check if "panelInformation" needs documenting or testing.
1069
- *
1070
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
1071
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1072
- *
1073
- * status: 'Online'
1074
- * 'Status Unknown'
1075
- *
1076
- * @since 1.0.0
1077
- */
1078
- await this.newInformationDispatcher('panel-information', panelInformation);
1079
-
1080
- if (this.#internal.debug) {
1081
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'success', `Successfully retrieved panel information from "${this.#internal.baseUrl}"`);
1082
- }
1083
-
1084
- return {
1085
- action: 'GET_PANEL_INFORMATION',
1086
- success: true,
1087
- info: panelInformation,
1088
- };
1089
- } catch (error) {
1090
- errorObject = serializeError(error);
1091
- }
1092
-
1093
- if (this.#internal.debug) {
1094
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelInformation()', 'error', 'Method encountered an error during execution');
1095
- stackTracer('serialize-error', errorObject);
1096
- }
1097
-
1098
- return {
1099
- action: 'GET_PANEL_INFORMATION',
1100
- success: false,
1101
- info: {
1102
- error: errorObject,
1103
- },
1104
- };
1105
- }
1106
-
1107
- /**
1108
- * ADT Pulse - Get panel status.
1109
- *
1110
- * @returns {ADTPulseGetPanelStatusReturns}
1111
- *
1112
- * @since 1.0.0
1113
- */
1114
- async getPanelStatus(): ADTPulseGetPanelStatusReturns {
1115
- let errorObject;
1116
-
1117
- if (this.#internal.debug) {
1118
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'info', `Attempting to retrieve panel status from "${this.#internal.baseUrl}"`);
1119
- }
1120
-
1121
- try {
1122
- const internet = await this.isPortalAccessible();
1123
- const sessions: ADTPulseGetPanelStatusSessions = {};
1124
-
1125
- // Check if portal is accessible.
1126
- if (!internet.success) {
1127
- return {
1128
- action: 'GET_PANEL_STATUS',
1129
- success: false,
1130
- info: internet.info,
1131
- };
1132
- }
1133
-
1134
- // sessions.axiosSummary: Load the summary page.
1135
- sessions.axiosSummary = await this.#session.httpClient.get<unknown>(
1136
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1137
- this.getRequestConfig({
1138
- headers: {
1139
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1140
- 'Sec-Fetch-Site': 'same-origin',
1141
- },
1142
- }),
1143
- );
1144
-
1145
- // If the "ClientRequest" object does not exist in the Axios response.
1146
- if (typeof sessions.axiosSummary?.request === 'undefined') {
1147
- if (this.#internal.debug) {
1148
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'error', 'The HTTP client responded without the "request" object');
1149
- }
1150
-
1151
- return {
1152
- action: 'GET_PANEL_STATUS',
1153
- success: false,
1154
- info: {
1155
- message: 'The HTTP client responded without the "request" object',
1156
- },
1157
- };
1158
- }
1159
-
1160
- const axiosSummaryRequestPath = sessions.axiosSummary.request.path;
1161
- const axiosSummaryRequestPathValid = requestPathSummarySummary.test(axiosSummaryRequestPath);
1162
-
1163
- if (this.#internal.debug) {
1164
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'info', `Request path ➜ ${axiosSummaryRequestPath}`);
1165
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'info', `Request path valid ➜ ${axiosSummaryRequestPathValid}`);
1166
- }
1167
-
1168
- // If the final URL of sessions.axiosSummary is not the summary page.
1169
- if (!axiosSummaryRequestPathValid) {
1170
- if (this.#internal.debug) {
1171
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'error', `"${axiosSummaryRequestPath}" is not the summary page`);
1172
- }
1173
-
1174
- // Check if "this instance" was not signed in during this time.
1175
- this.handleLoginFailure(axiosSummaryRequestPath, sessions.axiosSummary);
1176
-
1177
- return {
1178
- action: 'GET_PANEL_STATUS',
1179
- success: false,
1180
- info: {
1181
- message: `"${axiosSummaryRequestPath}" is not the summary page`,
1182
- },
1183
- };
1184
- }
1185
-
1186
- // Make sure we are able to use JSDOM on the response data.
1187
- if (typeof sessions.axiosSummary.data !== 'string') {
1188
- if (this.#internal.debug) {
1189
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'error', 'The response body of the summary page is not of type "string"');
1190
- }
1191
-
1192
- return {
1193
- action: 'GET_PANEL_STATUS',
1194
- success: false,
1195
- info: {
1196
- message: 'The response body of the summary page is not of type "string"',
1197
- },
1198
- };
1199
- }
1200
-
1201
- // Recover sat code if it was missing during login.
1202
- if (this.#session.backupSatCode === null) {
1203
- const missingSatCode = fetchMissingSatCode(sessions.axiosSummary);
1204
-
1205
- if (missingSatCode !== null) {
1206
- if (this.#internal.debug) {
1207
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'success', 'Backup sat code was successfully recovered from previous failed retrieval');
1208
- }
1209
-
1210
- this.#session.backupSatCode = missingSatCode;
1211
- }
1212
- }
1213
-
1214
- // sessions.jsdomSummary: Parse the summary page.
1215
- sessions.jsdomSummary = new JSDOM(
1216
- sessions.axiosSummary.data,
1217
- {
1218
- url: sessions.axiosSummary.config.url,
1219
- referrer: sessions.axiosSummary.config.headers.Referer,
1220
- contentType: 'text/html',
1221
- pretendToBeVisual: true,
1222
- },
1223
- );
1224
-
1225
- /**
1226
- * Detailed parsing information for "panelStatus".
1227
- *
1228
- * NOTICE: Responses may be inaccurate or missing.
1229
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1230
- *
1231
- * How the data may be displayed:
1232
- * ➜ "Disarmed. All Quiet."
1233
- * ➜ "Status Unavailable. "
1234
- *
1235
- * Example data after being processed by "parseOrbTextSummary()" function/method:
1236
- * ➜ {
1237
- * state: 'Disarmed',
1238
- * status: 'All Quiet',
1239
- * }
1240
- * ➜ {
1241
- * state: 'Status Unavailable',
1242
- * status: null,
1243
- * }
1244
- *
1245
- * @since 1.0.0
1246
- */
1247
- const jsdomSummaryOrbTextSummary = sessions.jsdomSummary.window.document.querySelector('#divOrbTextSummary');
1248
- const parsedOrbTextSummary = parseOrbTextSummary(jsdomSummaryOrbTextSummary);
1249
-
1250
- /**
1251
- * Check if "panelStatus" needs documenting or testing.
1252
- *
1253
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
1254
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1255
- *
1256
- * state: 'Armed Away'
1257
- * 'Armed Night'
1258
- * 'Armed Stay'
1259
- * 'Disarmed'
1260
- * 'Status Unavailable'
1261
- * null
1262
- *
1263
- * status: '1 Sensor Open'
1264
- * '[# of sensors open] Sensors Open'
1265
- * 'All Quiet'
1266
- * 'BURGLARY ALARM'
1267
- * 'Carbon Monoxide Alarm'
1268
- * 'FIRE ALARM'
1269
- * 'Motion'
1270
- * 'Sensor Bypassed'
1271
- * 'Sensor Problem'
1272
- * 'Sensors Bypassed'
1273
- * 'Sensors Tripped'
1274
- * 'Sensor Tripped'
1275
- * 'Uncleared Alarm'
1276
- * 'WATER ALARM'
1277
- * null
1278
- *
1279
- * @since 1.0.0
1280
- */
1281
- await this.newInformationDispatcher('panel-status', parsedOrbTextSummary);
1282
-
1283
- if (this.#internal.debug) {
1284
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'success', `Successfully retrieved panel status from "${this.#internal.baseUrl}"`);
1285
- }
1286
-
1287
- return {
1288
- action: 'GET_PANEL_STATUS',
1289
- success: true,
1290
- info: parsedOrbTextSummary,
1291
- };
1292
- } catch (error) {
1293
- errorObject = serializeError(error);
1294
- }
1295
-
1296
- if (this.#internal.debug) {
1297
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getPanelStatus()', 'error', 'Method encountered an error during execution');
1298
- stackTracer('serialize-error', errorObject);
1299
- }
1300
-
1301
- return {
1302
- action: 'GET_PANEL_STATUS',
1303
- success: false,
1304
- info: {
1305
- error: errorObject,
1306
- },
1307
- };
1308
- }
1309
-
1310
- /**
1311
- * ADT Pulse - Set panel status.
1312
- *
1313
- * @param {ADTPulseSetPanelStatusArmTo} armTo - Arm to.
1314
- *
1315
- * @returns {ADTPulseSetPanelStatusReturns}
1316
- *
1317
- * @since 1.0.0
1318
- */
1319
- async setPanelStatus(armTo: ADTPulseSetPanelStatusArmTo): ADTPulseSetPanelStatusReturns {
1320
- let errorObject;
1321
-
1322
- if (this.#internal.debug) {
1323
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'info', `Attempting to update panel status to "${armTo}" at "${this.#internal.baseUrl}"`);
1324
- }
1325
-
1326
- if (
1327
- armTo !== 'away'
1328
- && armTo !== 'night'
1329
- && armTo !== 'off'
1330
- && armTo !== 'stay'
1331
- ) {
1332
- return {
1333
- action: 'SET_PANEL_STATUS',
1334
- success: false,
1335
- info: {
1336
- message: `"${armTo}" is an invalid arm to state`,
1337
- },
1338
- };
1339
- }
1340
-
1341
- try {
1342
- const internet = await this.isPortalAccessible();
1343
- const sessions: ADTPulseSetPanelStatusSessions = {};
1344
-
1345
- // Check if portal is accessible.
1346
- if (!internet.success) {
1347
- return {
1348
- action: 'SET_PANEL_STATUS',
1349
- success: false,
1350
- info: internet.info,
1351
- };
1352
- }
1353
-
1354
- // sessions.axiosSummary: Load the summary page.
1355
- sessions.axiosSummary = await this.#session.httpClient.get<unknown>(
1356
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1357
- this.getRequestConfig({
1358
- headers: {
1359
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1360
- 'Sec-Fetch-Site': 'same-origin',
1361
- },
1362
- }),
1363
- );
1364
-
1365
- // If the "ClientRequest" object does not exist in the Axios response.
1366
- if (typeof sessions.axiosSummary?.request === 'undefined') {
1367
- if (this.#internal.debug) {
1368
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'The HTTP client responded without the "request" object');
1369
- }
1370
-
1371
- return {
1372
- action: 'SET_PANEL_STATUS',
1373
- success: false,
1374
- info: {
1375
- message: 'The HTTP client responded without the "request" object',
1376
- },
1377
- };
1378
- }
1379
-
1380
- const axiosSummaryRequestPath = sessions.axiosSummary.request.path;
1381
- const axiosSummaryRequestPathValid = requestPathSummarySummary.test(axiosSummaryRequestPath);
1382
-
1383
- if (this.#internal.debug) {
1384
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'info', `Request path ➜ ${axiosSummaryRequestPath}`);
1385
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'info', `Request path valid ➜ ${axiosSummaryRequestPathValid}`);
1386
- }
1387
-
1388
- // If the final URL of sessions.axiosSummary is not the summary page.
1389
- if (!axiosSummaryRequestPathValid) {
1390
- if (this.#internal.debug) {
1391
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', `"${axiosSummaryRequestPath}" is not the summary page`);
1392
- }
1393
-
1394
- // Check if "this instance" was not signed in during this time.
1395
- this.handleLoginFailure(axiosSummaryRequestPath, sessions.axiosSummary);
1396
-
1397
- return {
1398
- action: 'SET_PANEL_STATUS',
1399
- success: false,
1400
- info: {
1401
- message: `"${axiosSummaryRequestPath}" is not the summary page`,
1402
- },
1403
- };
1404
- }
1405
-
1406
- // Make sure we are able to use JSDOM on the response data.
1407
- if (typeof sessions.axiosSummary.data !== 'string') {
1408
- if (this.#internal.debug) {
1409
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'The response body of the summary page is not of type "string"');
1410
- }
1411
-
1412
- return {
1413
- action: 'SET_PANEL_STATUS',
1414
- success: false,
1415
- info: {
1416
- message: 'The response body of the summary page is not of type "string"',
1417
- },
1418
- };
1419
- }
1420
-
1421
- // Recover sat code if it was missing during login.
1422
- if (this.#session.backupSatCode === null) {
1423
- const missingSatCode = fetchMissingSatCode(sessions.axiosSummary);
1424
-
1425
- if (missingSatCode !== null) {
1426
- if (this.#internal.debug) {
1427
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'success', 'Backup sat code was successfully recovered from previous failed retrieval');
1428
- }
1429
-
1430
- this.#session.backupSatCode = missingSatCode;
1431
- }
1432
- }
1433
-
1434
- // sessions.jsdomSummary: Parse the summary page.
1435
- sessions.jsdomSummary = new JSDOM(
1436
- sessions.axiosSummary.data,
1437
- {
1438
- url: sessions.axiosSummary.config.url,
1439
- referrer: sessions.axiosSummary.config.headers.Referer,
1440
- contentType: 'text/html',
1441
- pretendToBeVisual: true,
1442
- },
1443
- );
1444
-
1445
- /**
1446
- * Detailed parsing information for "orbSecurityButtons".
1447
- *
1448
- * NOTICE: Responses may be inaccurate or missing.
1449
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1450
- *
1451
- * How the data may be displayed:
1452
- * ➜ <input id="security_button_1" value="Arm Stay" onclick="setArmState('quickcontrol/armDisarm.jsp','Arming Stay','1','2','false','href=rest/adt/ui/client/security/setArmState&amp;armstate=off&amp;arm=stay&amp;sat=21580428-e539-4075-8237-5c58b6c6fec8')">
1453
- * ➜ <input id="security_button_1" value="Arming Stay" disabled="">
1454
- *
1455
- * Example data after being processed by "parseOrbSecurityButtons()" function/method:
1456
- * ➜ [
1457
- * {
1458
- * buttonDisabled: false,
1459
- * buttonId: 'security_button_1',
1460
- * buttonIndex: 1,
1461
- * buttonText: 'Arm Stay',
1462
- * changeAccessCode: false,
1463
- * loadingText: 'Arming Stay',
1464
- * relativeUrl: 'quickcontrol/armDisarm.jsp',
1465
- * totalButtons: 2,
1466
- * urlParams: {
1467
- * arm: 'stay',
1468
- * armState: 'off',
1469
- * href: 'rest/adt/ui/client/security/setArmState',
1470
- * sat: '21580428-e539-4075-8237-5c58b6c6fec8',
1471
- * },
1472
- * },
1473
- * ]
1474
- * ➜ [
1475
- * {
1476
- * buttonDisabled: true,
1477
- * buttonId: 'security_button_1',
1478
- * buttonText: 'Arming Stay',
1479
- * },
1480
- * ]
1481
- *
1482
- * Notes I've gathered during the process:
1483
- * - After disarming, "armState" will be set to "disarmed". It will be set to "off" after re-login.¹
1484
- * - After turning off siren, "armState" will be set to "disarmed+with+alarm". It will be set to "disarmed_with_alarm" after re-login.¹²
1485
- * - After arming night, "armState" will be set to "night+stay". It will be set to "night" after re-login.¹
1486
- * - The "sat" code is required for all arm/disarm actions (UUID, generated on every login).
1487
- * - If "armState" is not "off" or "disarmed", you must disarm first before setting to other modes.
1488
- *
1489
- * Footnotes:
1490
- * ¹ States are synced across an entire site (per home). If one account arms, every user signed in during that phase becomes "dirty"
1491
- * ² Turning off siren means system is in "Uncleared Alarm" mode, not truly "Disarmed" mode.
1492
- *
1493
- * @since 1.0.0
1494
- */
1495
- const jsdomSummaryOrbSecurityButtons = sessions.jsdomSummary.window.document.querySelectorAll('#divOrbSecurityButtons input');
1496
- const parsedOrbSecurityButtons = parseOrbSecurityButtons(jsdomSummaryOrbSecurityButtons);
1497
-
1498
- /**
1499
- * Check if "orbSecurityButtons" needs documenting or testing.
1500
- *
1501
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
1502
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1503
- *
1504
- * buttonText: 'Arm Away'
1505
- * 'Arm Night'
1506
- * 'Arm Stay'
1507
- * 'Clear Alarm'
1508
- * 'Disarm'
1509
- *
1510
- * loadingText: 'Arming Away'
1511
- * 'Arming Night'
1512
- * 'Arming Stay'
1513
- * 'Disarming'
1514
- *
1515
- * relativeUrl: 'quickcontrol/armDisarm.jsp'
1516
- *
1517
- * urlParams.arm: 'away'
1518
- * 'night'
1519
- * 'off'
1520
- * 'stay'
1521
- *
1522
- * urlParams.armState: 'away'
1523
- * 'disarmed'
1524
- * 'disarmed_with_alarm'
1525
- * 'disarmed+with+alarm'
1526
- * 'night'
1527
- * 'night+stay'
1528
- * 'off'
1529
- * 'stay'
1530
- *
1531
- * urlParams.href: 'rest/adt/ui/client/security/setArmState'
1532
- *
1533
- * Notes I've gathered during the process:
1534
- * - When a button is in pending (disabled) state, the "buttonText" will be the "loadingText".
1535
- * - Currently, "disarmed+with+alarm" and "night+stay" are considered dirty states.
1536
- *
1537
- * @since 1.0.0
1538
- */
1539
- await this.newInformationDispatcher('orb-security-buttons', parsedOrbSecurityButtons);
1540
-
1541
- // WORKAROUND FOR ARM NIGHT BUTTON BUG: Find the "Arming Night" button location.
1542
- const armingNightButtonIndex = parsedOrbSecurityButtons.findIndex((parsedOrbSecurityButton) => {
1543
- const parsedOrbSecurityButtonButtonDisabled = parsedOrbSecurityButton.buttonDisabled;
1544
- const parsedOrbSecurityButtonButtonText = parsedOrbSecurityButton.buttonText;
1545
-
1546
- return (parsedOrbSecurityButtonButtonDisabled && parsedOrbSecurityButtonButtonText === 'Arming Night');
1547
- });
1548
-
1549
- // WORKAROUND FOR ARM NIGHT BUTTON BUG: Replace the "Arming Night" button with a fake "Disarm" button.
1550
- if (
1551
- this.#session.backupSatCode !== null // Backup sat code must be available.
1552
- && armingNightButtonIndex >= 0 // Make sure that the pending "Arming Night" button is there.
1553
- ) {
1554
- if (this.#internal.debug) {
1555
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'warn', 'Replacing the stuck "Arming Night" button with a fake "Disarm" button');
1556
- }
1557
-
1558
- parsedOrbSecurityButtons[armingNightButtonIndex] = {
1559
- buttonDisabled: false,
1560
- buttonId: 'security_button_0',
1561
- buttonIndex: 0,
1562
- buttonText: 'Disarm',
1563
- changeAccessCode: false,
1564
- loadingText: 'Disarming',
1565
- relativeUrl: 'quickcontrol/armDisarm.jsp',
1566
- totalButtons: 1,
1567
- urlParams: {
1568
- arm: 'off',
1569
- armState: (this.#session.isCleanState) ? 'night' : 'night+stay',
1570
- href: 'rest/adt/ui/client/security/setArmState',
1571
- sat: this.#session.backupSatCode,
1572
- },
1573
- };
1574
- }
1575
-
1576
- // Only keep all ready (enabled) orb security buttons.
1577
- let readyButtons = parsedOrbSecurityButtons.filter((parsedOrbSecurityButton): parsedOrbSecurityButton is ADTPulseSetPanelStatusReadyButton => !parsedOrbSecurityButton.buttonDisabled);
1578
-
1579
- // Make sure there is at least 1 security button available.
1580
- if (readyButtons.length < 1) {
1581
- if (this.#internal.debug) {
1582
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'Security buttons are not found on the summary page');
1583
- }
1584
-
1585
- return {
1586
- action: 'SET_PANEL_STATUS',
1587
- success: false,
1588
- info: {
1589
- message: 'Security buttons are not found on the summary page',
1590
- },
1591
- };
1592
- }
1593
-
1594
- // In test mode, system must be disarmed first.
1595
- if (
1596
- this.#internal.testMode.enabled
1597
- && !this.#internal.testMode.isDisarmChecked
1598
- ) {
1599
- // If system is not disarmed, end the test.
1600
- if (!['off', 'disarmed'].includes(readyButtons[0].urlParams.armState)) {
1601
- if (this.#internal.debug) {
1602
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'Test mode is active and system is not disarmed');
1603
- }
1604
-
1605
- return {
1606
- action: 'SET_PANEL_STATUS',
1607
- success: false,
1608
- info: {
1609
- message: 'Test mode is active and system is not disarmed',
1610
- },
1611
- };
1612
- }
1613
-
1614
- // If system is disarmed, set "isDisarmChecked" to true, so it does not check again.
1615
- this.#internal.testMode.isDisarmChecked = true;
1616
- }
1617
-
1618
- // If current arm state is not truly "disarmed", disarm it first.
1619
- while (!['off', 'disarmed'].includes(readyButtons[0].urlParams.armState)) {
1620
- // Accessing index 0 is guaranteed, because of the check above.
1621
- const armDisarmResponse = await this.armDisarmHandler(
1622
- readyButtons[0].relativeUrl,
1623
- readyButtons[0].urlParams.href,
1624
- readyButtons[0].urlParams.armState,
1625
- 'off',
1626
- readyButtons[0].urlParams.sat,
1627
- );
1628
-
1629
- if (!armDisarmResponse.success) {
1630
- if (this.#internal.debug) {
1631
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'An error occurred in the arm disarm handler (while disarming)');
1632
- }
1633
-
1634
- return {
1635
- action: 'SET_PANEL_STATUS',
1636
- success: false,
1637
- info: armDisarmResponse.info,
1638
- };
1639
- }
1640
-
1641
- // Make sure there is at least 1 security button available.
1642
- if (armDisarmResponse.info.readyButtons.length < 1) {
1643
- if (this.#internal.debug) {
1644
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'Arm disarm handler failed to find new security buttons');
1645
- }
1646
-
1647
- return {
1648
- action: 'SET_PANEL_STATUS',
1649
- success: false,
1650
- info: {
1651
- message: 'Arm disarm handler failed to find new security buttons',
1652
- },
1653
- };
1654
- }
1655
-
1656
- // Update the ready buttons to the latest known state.
1657
- readyButtons = armDisarmResponse.info.readyButtons;
1658
- }
1659
-
1660
- // Track if force arming was required.
1661
- let forceArmRequired = false;
1662
-
1663
- // Set the arm state based on "armTo" if system is not being disarmed.
1664
- if (armTo !== 'off') {
1665
- // Accessing index 0 is guaranteed, because of the check above.
1666
- const armDisarmResponse = await this.armDisarmHandler(
1667
- readyButtons[0].relativeUrl,
1668
- readyButtons[0].urlParams.href,
1669
- readyButtons[0].urlParams.armState,
1670
- armTo,
1671
- readyButtons[0].urlParams.sat,
1672
- );
1673
-
1674
- if (!armDisarmResponse.success) {
1675
- if (this.#internal.debug) {
1676
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'An error occurred in the arm disarm handler (while arming)');
1677
- }
1678
-
1679
- return {
1680
- action: 'SET_PANEL_STATUS',
1681
- success: false,
1682
- info: armDisarmResponse.info,
1683
- };
1684
- }
1685
-
1686
- forceArmRequired = armDisarmResponse.info.forceArmRequired;
1687
- }
1688
-
1689
- if (this.#internal.debug) {
1690
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'success', `Successfully updated panel status to "${armTo}" at "${this.#internal.baseUrl}"`);
1691
- }
1692
-
1693
- return {
1694
- action: 'SET_PANEL_STATUS',
1695
- success: true,
1696
- info: {
1697
- forceArmRequired,
1698
- },
1699
- };
1700
- } catch (error) {
1701
- errorObject = serializeError(error);
1702
- }
1703
-
1704
- if (this.#internal.debug) {
1705
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.setPanelStatus()', 'error', 'Method encountered an error during execution');
1706
- stackTracer('serialize-error', errorObject);
1707
- }
1708
-
1709
- return {
1710
- action: 'SET_PANEL_STATUS',
1711
- success: false,
1712
- info: {
1713
- error: errorObject,
1714
- },
1715
- };
1716
- }
1717
-
1718
- /**
1719
- * ADT Pulse - Get sensors information.
1720
- *
1721
- * @returns {ADTPulseGetSensorsInformationReturns}
1722
- *
1723
- * @since 1.0.0
1724
- */
1725
- async getSensorsInformation(): ADTPulseGetSensorsInformationReturns {
1726
- let errorObject;
1727
-
1728
- if (this.#internal.debug) {
1729
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'info', `Attempting to retrieve sensors information from "${this.#internal.baseUrl}"`);
1730
- }
1731
-
1732
- try {
1733
- const internet = await this.isPortalAccessible();
1734
- const sessions: ADTPulseGetSensorsInformationSessions = {};
1735
-
1736
- // Check if portal is accessible.
1737
- if (!internet.success) {
1738
- return {
1739
- action: 'GET_SENSORS_INFORMATION',
1740
- success: false,
1741
- info: internet.info,
1742
- };
1743
- }
1744
-
1745
- // sessions.axiosSystem: Load the system page.
1746
- sessions.axiosSystem = await this.#session.httpClient.get<unknown>(
1747
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/system.jsp`,
1748
- this.getRequestConfig({
1749
- headers: {
1750
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1751
- 'Sec-Fetch-Site': 'same-origin',
1752
- },
1753
- }),
1754
- );
1755
-
1756
- // If the "ClientRequest" object does not exist in the Axios response.
1757
- if (typeof sessions.axiosSystem?.request === 'undefined') {
1758
- if (this.#internal.debug) {
1759
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'error', 'The HTTP client responded without the "request" object');
1760
- }
1761
-
1762
- return {
1763
- action: 'GET_SENSORS_INFORMATION',
1764
- success: false,
1765
- info: {
1766
- message: 'The HTTP client responded without the "request" object',
1767
- },
1768
- };
1769
- }
1770
-
1771
- const axiosSystemRequestPath = sessions.axiosSystem.request.path;
1772
- const axiosSystemRequestPathValid = requestPathSystemSystem.test(axiosSystemRequestPath);
1773
-
1774
- if (this.#internal.debug) {
1775
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'info', `Request path ➜ ${axiosSystemRequestPath}`);
1776
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'info', `Request path valid ➜ ${axiosSystemRequestPathValid}`);
1777
- }
1778
-
1779
- // If the final URL of sessions.axiosSystem is not the system page.
1780
- if (!axiosSystemRequestPathValid) {
1781
- if (this.#internal.debug) {
1782
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'error', `"${axiosSystemRequestPath}" is not the system page`);
1783
- }
1784
-
1785
- // Check if "this instance" was not signed in during this time.
1786
- this.handleLoginFailure(axiosSystemRequestPath, sessions.axiosSystem);
1787
-
1788
- return {
1789
- action: 'GET_SENSORS_INFORMATION',
1790
- success: false,
1791
- info: {
1792
- message: `"${axiosSystemRequestPath}" is not the system page`,
1793
- },
1794
- };
1795
- }
1796
-
1797
- // Make sure we are able to use JSDOM on the response data.
1798
- if (typeof sessions.axiosSystem.data !== 'string') {
1799
- if (this.#internal.debug) {
1800
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'error', 'The response body of the system page is not of type "string"');
1801
- }
1802
-
1803
- return {
1804
- action: 'GET_SENSORS_INFORMATION',
1805
- success: false,
1806
- info: {
1807
- message: 'The response body of the system page is not of type "string"',
1808
- },
1809
- };
1810
- }
1811
-
1812
- // sessions.jsdomSystem: Parse the system page.
1813
- sessions.jsdomSystem = new JSDOM(
1814
- sessions.axiosSystem.data,
1815
- {
1816
- url: sessions.axiosSystem.config.url,
1817
- referrer: sessions.axiosSystem.config.headers.Referer,
1818
- contentType: 'text/html',
1819
- pretendToBeVisual: true,
1820
- },
1821
- );
1822
-
1823
- /**
1824
- * Detailed parsing information for "sensorsInformation".
1825
- *
1826
- * NOTICE: Responses may be inaccurate or missing.
1827
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1828
- *
1829
- * How the data may be displayed:
1830
- * ➜ <tr onclick="goToUrl('device.jsp?id=2');">
1831
- * <td>
1832
- * <canvas title="Online"></canvas>
1833
- * </td>
1834
- * <td>
1835
- * <a>Sensor 1</a>
1836
- * </td>
1837
- * <td> 1</td>
1838
- * <td>&nbsp;</td>
1839
- * <td>Door/Window Sensor</td>
1840
- * </tr>
1841
- *
1842
- * Example data after being processed by "parseSensorsTable()" function/method:
1843
- * ➜ [
1844
- * {
1845
- * deviceId: 2,
1846
- * deviceType: 'Door/Window Sensor',
1847
- * name: 'Sensor 1',
1848
- * status: 'Online',
1849
- * zone: 1,
1850
- * },
1851
- * ]
1852
- *
1853
- * @since 1.0.0
1854
- */
1855
- const jsdomSystemSensorsTable = sessions.jsdomSystem.window.document.querySelectorAll('#systemContentList tr[onclick^="goToUrl(\'device.jsp?id="]');
1856
- const parsedSensorsTable = parseSensorsTable(jsdomSystemSensorsTable);
1857
-
1858
- /**
1859
- * Check if "sensorsInformation" needs documenting or testing.
1860
- *
1861
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
1862
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
1863
- *
1864
- * deviceType: 'Door Sensor'
1865
- * 'Door/Window Sensor'
1866
- * 'Carbon Monoxide Detector'
1867
- * 'Fire (Smoke/Heat) Detector'
1868
- * 'Glass Break Detector'
1869
- * 'Motion Sensor'
1870
- * 'Motion Sensor (Notable Events Only)'
1871
- * 'Temperature Sensor'
1872
- * 'Water/Flood Sensor'
1873
- * 'Window Sensor'
1874
- *
1875
- * status: 'Online'
1876
- * 'Status Unknown'
1877
- *
1878
- * @since 1.0.0
1879
- */
1880
- await this.newInformationDispatcher('sensors-information', parsedSensorsTable);
1881
-
1882
- if (this.#internal.debug) {
1883
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'success', `Successfully retrieved sensors information from "${this.#internal.baseUrl}"`);
1884
- }
1885
-
1886
- return {
1887
- action: 'GET_SENSORS_INFORMATION',
1888
- success: true,
1889
- info: {
1890
- sensors: parsedSensorsTable,
1891
- },
1892
- };
1893
- } catch (error) {
1894
- errorObject = serializeError(error);
1895
- }
1896
-
1897
- if (this.#internal.debug) {
1898
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsInformation()', 'error', 'Method encountered an error during execution');
1899
- stackTracer('serialize-error', errorObject);
1900
- }
1901
-
1902
- return {
1903
- action: 'GET_SENSORS_INFORMATION',
1904
- success: false,
1905
- info: {
1906
- error: errorObject,
1907
- },
1908
- };
1909
- }
1910
-
1911
- /**
1912
- * ADT Pulse - Get sensors status.
1913
- *
1914
- * @returns {ADTPulseGetSensorsStatusReturns}
1915
- *
1916
- * @since 1.0.0
1917
- */
1918
- async getSensorsStatus(): ADTPulseGetSensorsStatusReturns {
1919
- let errorObject;
1920
-
1921
- if (this.#internal.debug) {
1922
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'info', `Attempting to retrieve sensors status from "${this.#internal.baseUrl}"`);
1923
- }
1924
-
1925
- try {
1926
- const internet = await this.isPortalAccessible();
1927
- const sessions: ADTPulseGetSensorsStatusSessions = {};
1928
-
1929
- // Check if portal is accessible.
1930
- if (!internet.success) {
1931
- return {
1932
- action: 'GET_SENSORS_STATUS',
1933
- success: false,
1934
- info: internet.info,
1935
- };
1936
- }
1937
-
1938
- // sessions.axiosSummary: Load the summary page.
1939
- sessions.axiosSummary = await this.#session.httpClient.get<unknown>(
1940
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1941
- this.getRequestConfig({
1942
- headers: {
1943
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
1944
- 'Sec-Fetch-Site': 'same-origin',
1945
- },
1946
- }),
1947
- );
1948
-
1949
- // If the "ClientRequest" object does not exist in the Axios response.
1950
- if (typeof sessions.axiosSummary?.request === 'undefined') {
1951
- if (this.#internal.debug) {
1952
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'error', 'The HTTP client responded without the "request" object');
1953
- }
1954
-
1955
- return {
1956
- action: 'GET_SENSORS_STATUS',
1957
- success: false,
1958
- info: {
1959
- message: 'The HTTP client responded without the "request" object',
1960
- },
1961
- };
1962
- }
1963
-
1964
- const axiosSummaryRequestPath = sessions.axiosSummary.request.path;
1965
- const axiosSummaryRequestPathValid = requestPathSummarySummary.test(axiosSummaryRequestPath);
1966
-
1967
- if (this.#internal.debug) {
1968
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'info', `Request path ➜ ${axiosSummaryRequestPath}`);
1969
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'info', `Request path valid ➜ ${axiosSummaryRequestPathValid}`);
1970
- }
1971
-
1972
- // If the final URL of sessions.axiosSummary is not the summary page.
1973
- if (!axiosSummaryRequestPathValid) {
1974
- if (this.#internal.debug) {
1975
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'error', `"${axiosSummaryRequestPath}" is not the summary page`);
1976
- }
1977
-
1978
- // Check if "this instance" was not signed in during this time.
1979
- this.handleLoginFailure(axiosSummaryRequestPath, sessions.axiosSummary);
1980
-
1981
- return {
1982
- action: 'GET_SENSORS_STATUS',
1983
- success: false,
1984
- info: {
1985
- message: `"${axiosSummaryRequestPath}" is not the summary page`,
1986
- },
1987
- };
1988
- }
1989
-
1990
- // Make sure we are able to use JSDOM on the response data.
1991
- if (typeof sessions.axiosSummary.data !== 'string') {
1992
- if (this.#internal.debug) {
1993
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'error', 'The response body of the summary page is not of type "string"');
1994
- }
1995
-
1996
- return {
1997
- action: 'GET_SENSORS_STATUS',
1998
- success: false,
1999
- info: {
2000
- message: 'The response body of the summary page is not of type "string"',
2001
- },
2002
- };
2003
- }
2004
-
2005
- // Recover sat code if it was missing during login.
2006
- if (this.#session.backupSatCode === null) {
2007
- const missingSatCode = fetchMissingSatCode(sessions.axiosSummary);
2008
-
2009
- if (missingSatCode !== null) {
2010
- if (this.#internal.debug) {
2011
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'success', 'Backup sat code was successfully recovered from previous failed retrieval');
2012
- }
2013
-
2014
- this.#session.backupSatCode = missingSatCode;
2015
- }
2016
- }
2017
-
2018
- // sessions.jsdomSummary: Parse the summary page.
2019
- sessions.jsdomSummary = new JSDOM(
2020
- sessions.axiosSummary.data,
2021
- {
2022
- url: sessions.axiosSummary.config.url,
2023
- referrer: sessions.axiosSummary.config.headers.Referer,
2024
- contentType: 'text/html',
2025
- pretendToBeVisual: true,
2026
- },
2027
- );
2028
-
2029
- /**
2030
- * Detailed parsing information for "sensorsStatus".
2031
- *
2032
- * NOTICE: Responses may be inaccurate or missing.
2033
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2034
- *
2035
- * How the data may be displayed:
2036
- * ➜ <tr>
2037
- * <td>
2038
- * <span>
2039
- * <canvas icon="devStatOK"></canvas>
2040
- * </span>
2041
- * </td>
2042
- * <td>
2043
- * <img src="/myhome/16.0.0-131/images/spacer.gif">
2044
- * </td>
2045
- * <td>
2046
- * <a class="p_deviceNameText">Sensor 1</a>
2047
- * &nbsp;
2048
- * <span class="p_grayNormalText">Zone&nbsp;1</div>
2049
- * </td>
2050
- * <td>
2051
- * Closed&nbsp;
2052
- * </td>
2053
- * </tr>
2054
- * ➜ <tr>
2055
- * <td>
2056
- * <span>
2057
- * <canvas icon="devStatMotion"></canvas>
2058
- * </span>
2059
- * </td>
2060
- * <td>
2061
- * <img src="/myhome/16.0.0-131/images/spacer.gif">
2062
- * </td>
2063
- * <td>
2064
- * <a class="p_deviceNameText">Sensor 2</a>
2065
- * &nbsp;
2066
- * <div class="p_grayNormalText">Zone&nbsp;2</div>
2067
- * </td>
2068
- * <td>
2069
- * Motion&nbsp;
2070
- * </td>
2071
- * </tr>
2072
- *
2073
- * Example data after being processed by "parseOrbSensors()" function/method:
2074
- * ➜ [
2075
- * {
2076
- * icon: 'devStatOK',
2077
- * name: 'Sensor 1',
2078
- * status: 'Closed',
2079
- * zone: 1,
2080
- * },
2081
- * ]
2082
- * ➜ [
2083
- * {
2084
- * icon: 'devStatMotion',
2085
- * name: 'Sensor 2',
2086
- * status: 'Motion',
2087
- * zone: 2,
2088
- * },
2089
- * ]
2090
- *
2091
- * @since 1.0.0
2092
- */
2093
- const jsdomSummaryOrbSensors = sessions.jsdomSummary.window.document.querySelectorAll('#orbSensorsList tr.p_listRow');
2094
- const parsedOrbSensors = parseOrbSensors(jsdomSummaryOrbSensors);
2095
-
2096
- /**
2097
- * Check if "sensorsStatus" needs documenting or testing.
2098
- *
2099
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
2100
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2101
- *
2102
- * icon: 'devStatAlarm'
2103
- * 'devStatLowBatt'
2104
- * 'devStatMotion'
2105
- * 'devStatOK'
2106
- * 'devStatOpen'
2107
- * 'devStatTamper'
2108
- * 'devStatUnknown'
2109
- *
2110
- * status: 'ALARM, Okay'
2111
- * 'Closed'
2112
- * 'Motion'
2113
- * 'No Motion'
2114
- * 'Okay'
2115
- * 'Open'
2116
- * 'Tripped'
2117
- * 'Unknown'
2118
- *
2119
- * @since 1.0.0
2120
- */
2121
- await this.newInformationDispatcher('sensors-status', parsedOrbSensors);
2122
-
2123
- if (this.#internal.debug) {
2124
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'success', `Successfully retrieved sensors status from "${this.#internal.baseUrl}"`);
2125
- }
2126
-
2127
- return {
2128
- action: 'GET_SENSORS_STATUS',
2129
- success: true,
2130
- info: {
2131
- sensors: parsedOrbSensors,
2132
- },
2133
- };
2134
- } catch (error) {
2135
- errorObject = serializeError(error);
2136
- }
2137
-
2138
- if (this.#internal.debug) {
2139
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.getSensorsStatus()', 'error', 'Method encountered an error during execution');
2140
- stackTracer('serialize-error', errorObject);
2141
- }
2142
-
2143
- return {
2144
- action: 'GET_SENSORS_STATUS',
2145
- success: false,
2146
- info: {
2147
- error: errorObject,
2148
- },
2149
- };
2150
- }
2151
-
2152
- /**
2153
- * ADT Pulse - Perform sync check.
2154
- *
2155
- * @returns {ADTPulsePerformSyncCheckReturns}
2156
- *
2157
- * @since 1.0.0
2158
- */
2159
- async performSyncCheck(): ADTPulsePerformSyncCheckReturns {
2160
- let errorObject;
2161
-
2162
- if (this.#internal.debug) {
2163
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'info', `Attempting to perform a sync check from "${this.#internal.baseUrl}"`);
2164
- }
2165
-
2166
- try {
2167
- const internet = await this.isPortalAccessible();
2168
- const sessions: ADTPulsePerformSyncCheckSessions = {};
2169
-
2170
- // Check if portal is accessible.
2171
- if (!internet.success) {
2172
- return {
2173
- action: 'PERFORM_SYNC_CHECK',
2174
- success: false,
2175
- info: internet.info,
2176
- };
2177
- }
2178
-
2179
- // sessions.axiosSyncCheck: Load the sync check page.
2180
- sessions.axiosSyncCheck = await this.#session.httpClient.get<unknown>(
2181
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/Ajax/SyncCheckServ?t=${Date.now()}`,
2182
- this.getRequestConfig({
2183
- headers: {
2184
- Accept: '*/*',
2185
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
2186
- 'Sec-Fetch-Dest': 'empty',
2187
- 'Sec-Fetch-Mode': 'cors',
2188
- 'Sec-Fetch-Site': 'same-origin',
2189
- 'Sec-Fetch-User': undefined,
2190
- 'Upgrade-Insecure-Requests': undefined,
2191
- },
2192
- }),
2193
- );
2194
-
2195
- // If the "ClientRequest" object does not exist in the Axios response.
2196
- if (typeof sessions.axiosSyncCheck?.request === 'undefined') {
2197
- if (this.#internal.debug) {
2198
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'error', 'The HTTP client responded without the "request" object');
2199
- }
2200
-
2201
- return {
2202
- action: 'PERFORM_SYNC_CHECK',
2203
- success: false,
2204
- info: {
2205
- message: 'The HTTP client responded without the "request" object',
2206
- },
2207
- };
2208
- }
2209
-
2210
- const syncCheckRequestPath = sessions.axiosSyncCheck.request.path;
2211
- const syncCheckRequestPathValid = requestPathAjaxSyncCheckServTXx.test(syncCheckRequestPath);
2212
-
2213
- if (this.#internal.debug) {
2214
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'info', `Request path ➜ ${syncCheckRequestPath}`);
2215
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'info', `Request path valid ➜ ${syncCheckRequestPathValid}`);
2216
- }
2217
-
2218
- // If the final URL of sessions.axios_syncCheck is not the sync check page.
2219
- if (!syncCheckRequestPathValid) {
2220
- if (this.#internal.debug) {
2221
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'error', `"${syncCheckRequestPath}" is not the sync check page`);
2222
- }
2223
-
2224
- // Check if "this instance" was not signed in during this time.
2225
- this.handleLoginFailure(syncCheckRequestPath, sessions.axiosSyncCheck);
2226
-
2227
- return {
2228
- action: 'PERFORM_SYNC_CHECK',
2229
- success: false,
2230
- info: {
2231
- message: `"${syncCheckRequestPath}" is not the sync check page`,
2232
- },
2233
- };
2234
- }
2235
-
2236
- // Make sure we are able to pass on the response data.
2237
- if (typeof sessions.axiosSyncCheck.data !== 'string') {
2238
- if (this.#internal.debug) {
2239
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'error', 'The response body of the sync check page is not of type "string"');
2240
- }
2241
-
2242
- return {
2243
- action: 'PERFORM_SYNC_CHECK',
2244
- success: false,
2245
- info: {
2246
- message: 'The response body of the sync check page is not of type "string"',
2247
- },
2248
- };
2249
- }
2250
-
2251
- // Make sure the sync code is valid.
2252
- if (!isPortalSyncCode(sessions.axiosSyncCheck.data)) {
2253
- if (this.#internal.debug) {
2254
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'error', 'The sync code structure is invalid');
2255
- }
2256
-
2257
- return {
2258
- action: 'PERFORM_SYNC_CHECK',
2259
- success: false,
2260
- info: {
2261
- message: 'The sync code structure is invalid',
2262
- },
2263
- };
2264
- }
2265
-
2266
- if (this.#internal.debug) {
2267
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'success', `Successfully performed a sync check from "${this.#internal.baseUrl}"`);
2268
- }
2269
-
2270
- return {
2271
- action: 'PERFORM_SYNC_CHECK',
2272
- success: true,
2273
- info: {
2274
- /**
2275
- * A breakdown of the responses when parsing the "syncCheckServ" response body.
2276
- *
2277
- * NOTE: Responses may be inaccurate or missing.
2278
- * LINK: https://patents.google.com/patent/US20170070361A1/en
2279
- *
2280
- * - 1-0-0
2281
- * - 2-0-0
2282
- * - [integer]-0-0
2283
- * - [integer]-[integer]-0
2284
- *
2285
- * @since 1.0.0
2286
- */
2287
- syncCode: sessions.axiosSyncCheck.data,
2288
- },
2289
- };
2290
- } catch (error) {
2291
- errorObject = serializeError(error);
2292
- }
2293
-
2294
- if (this.#internal.debug) {
2295
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performSyncCheck()', 'error', 'Method encountered an error during execution');
2296
- stackTracer('serialize-error', errorObject);
2297
- }
2298
-
2299
- return {
2300
- action: 'PERFORM_SYNC_CHECK',
2301
- success: false,
2302
- info: {
2303
- error: errorObject,
2304
- },
2305
- };
2306
- }
2307
-
2308
- /**
2309
- * ADT Pulse - Perform keep alive.
2310
- *
2311
- * @returns {ADTPulsePerformKeepAliveReturns}
2312
- *
2313
- * @since 1.0.0
2314
- */
2315
- async performKeepAlive(): ADTPulsePerformKeepAliveReturns {
2316
- let errorObject;
2317
-
2318
- if (this.#internal.debug) {
2319
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'info', `Attempting to perform a keep alive from "${this.#internal.baseUrl}"`);
2320
- }
2321
-
2322
- try {
2323
- const internet = await this.isPortalAccessible();
2324
- const sessions: ADTPulsePerformKeepAliveSessions = {};
2325
-
2326
- // Check if portal is accessible.
2327
- if (!internet.success) {
2328
- return {
2329
- action: 'PERFORM_KEEP_ALIVE',
2330
- success: false,
2331
- info: internet.info,
2332
- };
2333
- }
2334
-
2335
- // sessions.axiosKeepAlive: Load the keep alive page.
2336
- sessions.axiosKeepAlive = await this.#session.httpClient.post<unknown>(
2337
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/KeepAlive`,
2338
- '',
2339
- this.getRequestConfig({
2340
- headers: {
2341
- Accept: '*/*',
2342
- 'Content-type': 'application/x-www-form-urlencoded',
2343
- Origin: this.#internal.baseUrl,
2344
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
2345
- 'Sec-Fetch-Dest': 'empty',
2346
- 'Sec-Fetch-Mode': 'cors',
2347
- 'Sec-Fetch-Site': 'same-origin',
2348
- 'Sec-Fetch-User': undefined,
2349
- 'Upgrade-Insecure-Requests': undefined,
2350
- 'x-dtpc': generateDynatracePCHeaderValue('keep-alive'),
2351
- },
2352
- }),
2353
- );
2354
-
2355
- // If the "ClientRequest" object does not exist in the Axios response.
2356
- if (typeof sessions.axiosKeepAlive?.request === 'undefined') {
2357
- if (this.#internal.debug) {
2358
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'error', 'The HTTP client responded without the "request" object');
2359
- }
2360
-
2361
- return {
2362
- action: 'PERFORM_KEEP_ALIVE',
2363
- success: false,
2364
- info: {
2365
- message: 'The HTTP client responded without the "request" object',
2366
- },
2367
- };
2368
- }
2369
-
2370
- const axiosKeepAliveRequestPath = sessions.axiosKeepAlive.request.path;
2371
- const axiosKeepAliveRequestPathValid = requestPathKeepAlive.test(axiosKeepAliveRequestPath);
2372
-
2373
- if (this.#internal.debug) {
2374
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'info', `Request path ➜ ${axiosKeepAliveRequestPath}`);
2375
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'info', `Request path valid ➜ ${axiosKeepAliveRequestPathValid}`);
2376
- }
2377
-
2378
- // If the final URL of sessions.axiosKeepAlive is not the keep alive page.
2379
- if (!axiosKeepAliveRequestPathValid) {
2380
- if (this.#internal.debug) {
2381
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'error', `"${axiosKeepAliveRequestPath}" is not the keep alive page`);
2382
- }
2383
-
2384
- // Check if "this instance" was not signed in during this time.
2385
- this.handleLoginFailure(axiosKeepAliveRequestPath, sessions.axiosKeepAlive);
2386
-
2387
- return {
2388
- action: 'PERFORM_KEEP_ALIVE',
2389
- success: false,
2390
- info: {
2391
- message: `"${axiosKeepAliveRequestPath}" is not the keep alive page`,
2392
- },
2393
- };
2394
- }
2395
-
2396
- if (this.#internal.debug) {
2397
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'success', `Successfully performed a keep alive from "${this.#internal.baseUrl}"`);
2398
- }
2399
-
2400
- return {
2401
- action: 'PERFORM_KEEP_ALIVE',
2402
- success: true,
2403
- info: null,
2404
- };
2405
- } catch (error) {
2406
- errorObject = serializeError(error);
2407
- }
2408
-
2409
- if (this.#internal.debug) {
2410
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.performKeepAlive()', 'error', 'Method encountered an error during execution');
2411
- stackTracer('serialize-error', errorObject);
2412
- }
2413
-
2414
- return {
2415
- action: 'PERFORM_KEEP_ALIVE',
2416
- success: false,
2417
- info: {
2418
- error: errorObject,
2419
- },
2420
- };
2421
- }
2422
-
2423
- /**
2424
- * ADT Pulse - Is authenticated.
2425
- *
2426
- * @returns {ADTPulseIsAuthenticatedReturns}
2427
- *
2428
- * @since 1.0.0
2429
- */
2430
- isAuthenticated(): ADTPulseIsAuthenticatedReturns {
2431
- return this.#session.isAuthenticated;
2432
- }
2433
-
2434
- /**
2435
- * ADT Pulse - Arm disarm handler.
2436
- *
2437
- * @param {ADTPulseArmDisarmHandlerRelativeUrl} relativeUrl - Relative url.
2438
- * @param {ADTPulseArmDisarmHandlerHref} href - Href.
2439
- * @param {ADTPulseArmDisarmHandlerArmState} armState - Arm state.
2440
- * @param {ADTPulseArmDisarmHandlerArm} arm - Arm.
2441
- * @param {ADTPulseArmDisarmHandlerSat} sat - Sat.
2442
- *
2443
- * @private
2444
- *
2445
- * @returns {ADTPulseArmDisarmHandlerReturns}
2446
- *
2447
- * @since 1.0.0
2448
- */
2449
- private async armDisarmHandler(relativeUrl: ADTPulseArmDisarmHandlerRelativeUrl, href: ADTPulseArmDisarmHandlerHref, armState: ADTPulseArmDisarmHandlerArmState, arm: ADTPulseArmDisarmHandlerArm, sat: ADTPulseArmDisarmHandlerSat): ADTPulseArmDisarmHandlerReturns {
2450
- let errorObject;
2451
-
2452
- if (this.#internal.debug) {
2453
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `Attempting to update arm state from "${armState}" to "${arm}" on "${this.#internal.baseUrl}"`);
2454
- }
2455
-
2456
- // If system is being set to the current arm state (e.g. disarmed to off).
2457
- if (
2458
- armState === arm
2459
- || (
2460
- armState === 'disarmed'
2461
- && arm === 'off'
2462
- )
2463
- ) {
2464
- if (this.#internal.debug) {
2465
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `No need to change arm state from "${armState}" to "${arm}" due to its equivalence`);
2466
- }
2467
-
2468
- return {
2469
- action: 'ARM_DISARM_HANDLER',
2470
- success: true,
2471
- info: {
2472
- forceArmRequired: false,
2473
- readyButtons: [],
2474
- },
2475
- };
2476
- }
2477
-
2478
- try {
2479
- const internet = await this.isPortalAccessible();
2480
- const sessions: ADTPulseArmDisarmHandlerSessions = {};
2481
-
2482
- // Check if portal is accessible.
2483
- if (!internet.success) {
2484
- return {
2485
- action: 'ARM_DISARM_HANDLER',
2486
- success: false,
2487
- info: internet.info,
2488
- };
2489
- }
2490
-
2491
- // Build an "application/x-www-form-urlencoded" form for use with arming and disarming.
2492
- const armDisarmForm = new URLSearchParams();
2493
- armDisarmForm.append('href', href);
2494
- armDisarmForm.append('armstate', armState);
2495
- armDisarmForm.append('arm', arm);
2496
- armDisarmForm.append('sat', sat);
2497
-
2498
- // sessions.axiosSetArmMode: Emulate an arm state update request.
2499
- sessions.axiosSetArmMode = await this.#session.httpClient.post<unknown>(
2500
- /**
2501
- * A breakdown of the links to set arm mode.
2502
- *
2503
- * NOTE: Responses may be inaccurate or missing.
2504
- * LINK: https://patents.google.com/patent/US20170070361A1/en
2505
- *
2506
- * - When "Disarmed" mode:
2507
- * - Clicking the "Arm Away" button:
2508
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=off&arm=away&sat=<sat>
2509
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=disarmed&arm=away&sat=<sat>
2510
- * - Clicking the "Arm Stay" button:
2511
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=off&arm=stay&sat=<sat>
2512
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=disarmed&arm=stay&sat=<sat>
2513
- * - Clicking the "Arm Night" button:
2514
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=off&arm=night&sat=<sat>
2515
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=disarmed&arm=night&sat=<sat>
2516
- *
2517
- * - When "Armed Away" mode:
2518
- * - Clicking the "Disarm" button:
2519
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=away&arm=off&sat=<sat>
2520
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=away&arm=off&sat=<sat>
2521
- *
2522
- * - When "Armed Stay" mode:
2523
- * - Clicking the "Disarm" button:
2524
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=stay&arm=off&sat=<sat>
2525
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=stay&arm=off&sat=<sat>
2526
- *
2527
- * - When "Armed Night" mode:
2528
- * - Clicking the "Disarm" button:
2529
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=night&arm=off&sat=<sat>
2530
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=night+stay&arm=off&sat=<sat>
2531
- *
2532
- * - When alarm is triggered (when siren is SCREAMING REALLY LOUD):
2533
- * - Clicking the "Disarm" button when "Armed Away":
2534
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=away&arm=off&sat=<sat>
2535
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=away&arm=off&sat=<sat>
2536
- * - Clicking the "Disarm" button when "Armed Stay":
2537
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=stay&arm=off&sat=<sat>
2538
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=stay&arm=off&sat=<sat>
2539
- * - Clicking the "Disarm" button when "Armed Night":
2540
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=night&arm=off&sat=<sat>
2541
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=night+stay&arm=off&sat=<sat>
2542
- *
2543
- * - When alarm is triggered (when siren is done screaming at you):
2544
- * - Clicking the "Clear Alarm" button:
2545
- * - Clean mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=disarmed_with_alarm&arm=off&sat=<sat>
2546
- * - Dirty mode: https://<subdomain>.adtpulse.com/myhome/<version>/<relativeUrl>?href=<href>&armstate=disarmed+with+alarm&arm=off&sat=<sat>
2547
- *
2548
- * Notes I've gathered during the process:
2549
- * - States are synced across an entire site (per home). If one account arms, every user signed in during that phase becomes "dirty"
2550
- * - When arming and disarming in the portal, POST requests are made. However, GET requests still work when URL is pasted in.
2551
- *
2552
- * @since 1.0.0
2553
- */
2554
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/${relativeUrl}`,
2555
- armDisarmForm,
2556
- this.getRequestConfig({
2557
- headers: {
2558
- 'Cache-Control': 'max-age=0',
2559
- 'Content-Type': 'application/x-www-form-urlencoded',
2560
- Origin: this.#internal.baseUrl,
2561
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
2562
- 'Sec-Fetch-Dest': 'iframe',
2563
- 'Sec-Fetch-Mode': 'navigate',
2564
- 'Sec-Fetch-Site': 'same-origin',
2565
- 'Sec-Fetch-User': undefined,
2566
- },
2567
- }),
2568
- );
2569
-
2570
- // If the "ClientRequest" object does not exist in the Axios response.
2571
- if (typeof sessions.axiosSetArmMode?.request === 'undefined') {
2572
- if (this.#internal.debug) {
2573
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', 'The HTTP client responded without the "request" object');
2574
- }
2575
-
2576
- return {
2577
- action: 'ARM_DISARM_HANDLER',
2578
- success: false,
2579
- info: {
2580
- message: 'The HTTP client responded without the "request" object',
2581
- },
2582
- };
2583
- }
2584
-
2585
- const axiosSetArmModeRequestPath = sessions.axiosSetArmMode.request.path;
2586
- const axiosSetArmModeRequestPathValid = requestPathQuickControlArmDisarm.test(axiosSetArmModeRequestPath);
2587
-
2588
- if (this.#internal.debug) {
2589
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `Request path ➜ ${axiosSetArmModeRequestPath}`);
2590
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `Request path valid ➜ ${axiosSetArmModeRequestPathValid}`);
2591
- }
2592
-
2593
- // If the final URL of sessions.axiosSetArmMode is not the arm disarm page.
2594
- if (!axiosSetArmModeRequestPathValid) {
2595
- if (this.#internal.debug) {
2596
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', `"${axiosSetArmModeRequestPath}" is not the arm disarm page`);
2597
- }
2598
-
2599
- // Check if "this instance" was not signed in during this time.
2600
- this.handleLoginFailure(axiosSetArmModeRequestPath, sessions.axiosSetArmMode);
2601
-
2602
- return {
2603
- action: 'ARM_DISARM_HANDLER',
2604
- success: false,
2605
- info: {
2606
- message: `"${axiosSetArmModeRequestPath}" is not the arm disarm page`,
2607
- },
2608
- };
2609
- }
2610
-
2611
- // Track if force arming was required.
2612
- let forceArmRequired = false;
2613
-
2614
- // No need to force arm if system is not being set to arm.
2615
- if (arm !== 'off') {
2616
- // Passing the force arming task to the handler.
2617
- const forceArmResponse = await this.forceArmHandler(sessions.axiosSetArmMode, relativeUrl);
2618
-
2619
- if (!forceArmResponse.success) {
2620
- if (this.#internal.debug) {
2621
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', 'An error occurred in the force arm handler');
2622
- }
2623
-
2624
- return {
2625
- action: 'ARM_DISARM_HANDLER',
2626
- success: false,
2627
- info: forceArmResponse.info,
2628
- };
2629
- }
2630
-
2631
- forceArmRequired = forceArmResponse.info.forceArmRequired;
2632
- }
2633
-
2634
- // After changing any arm state, the "armState" may be different from when you logged into the portal.
2635
- this.#session.isCleanState = false;
2636
-
2637
- // Allow the security orb buttons to refresh (usually takes around 6 seconds).
2638
- await sleep(6000);
2639
-
2640
- // sessions.axiosSummary: Load the summary page.
2641
- sessions.axiosSummary = await this.#session.httpClient.get<unknown>(
2642
- `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
2643
- this.getRequestConfig({
2644
- headers: {
2645
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`,
2646
- 'Sec-Fetch-Site': 'same-origin',
2647
- },
2648
- }),
2649
- );
2650
-
2651
- // If the "ClientRequest" object does not exist in the Axios response.
2652
- if (typeof sessions.axiosSummary?.request === 'undefined') {
2653
- if (this.#internal.debug) {
2654
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', 'The HTTP client responded without the "request" object');
2655
- }
2656
-
2657
- return {
2658
- action: 'ARM_DISARM_HANDLER',
2659
- success: false,
2660
- info: {
2661
- message: 'The HTTP client responded without the "request" object',
2662
- },
2663
- };
2664
- }
2665
-
2666
- const axiosSummaryRequestPath = sessions.axiosSummary.request.path;
2667
- const axiosSummaryRequestPathValid = requestPathSummarySummary.test(axiosSummaryRequestPath);
2668
-
2669
- if (this.#internal.debug) {
2670
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `Request path ➜ ${axiosSummaryRequestPath}`);
2671
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'info', `Request path valid ➜ ${axiosSummaryRequestPathValid}`);
2672
- }
2673
-
2674
- // If the final URL of sessions.axios[1] is not the summary page.
2675
- if (!axiosSummaryRequestPathValid) {
2676
- if (this.#internal.debug) {
2677
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', `"${axiosSummaryRequestPath}" is not the summary page`);
2678
- }
2679
-
2680
- // Check if "this instance" was not signed in during this time.
2681
- this.handleLoginFailure(axiosSummaryRequestPath, sessions.axiosSummary);
2682
-
2683
- return {
2684
- action: 'ARM_DISARM_HANDLER',
2685
- success: false,
2686
- info: {
2687
- message: `"${axiosSummaryRequestPath}" is not the summary page`,
2688
- },
2689
- };
2690
- }
2691
-
2692
- // Make sure we are able to use JSDOM on the response data.
2693
- if (typeof sessions.axiosSummary.data !== 'string') {
2694
- if (this.#internal.debug) {
2695
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', 'The response body of the summary page is not of type "string"');
2696
- }
2697
-
2698
- return {
2699
- action: 'ARM_DISARM_HANDLER',
2700
- success: false,
2701
- info: {
2702
- message: 'The response body of the summary page is not of type "string"',
2703
- },
2704
- };
2705
- }
2706
-
2707
- // Recover sat code if it was missing during login.
2708
- if (this.#session.backupSatCode === null) {
2709
- const missingSatCode = fetchMissingSatCode(sessions.axiosSummary);
2710
-
2711
- if (missingSatCode !== null) {
2712
- if (this.#internal.debug) {
2713
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'success', 'Backup sat code was successfully recovered from previous failed retrieval');
2714
- }
2715
-
2716
- this.#session.backupSatCode = missingSatCode;
2717
- }
2718
- }
2719
-
2720
- // sessions.jsdomSummary: Parse the summary page.
2721
- sessions.jsdomSummary = new JSDOM(
2722
- sessions.axiosSummary.data,
2723
- {
2724
- url: sessions.axiosSummary.config.url,
2725
- referrer: sessions.axiosSummary.config.headers.Referer,
2726
- contentType: 'text/html',
2727
- pretendToBeVisual: true,
2728
- },
2729
- );
2730
-
2731
- /**
2732
- * Detailed parsing information for "orbSecurityButtons".
2733
- *
2734
- * NOTICE: Responses may be inaccurate or missing.
2735
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2736
- *
2737
- * How the data may be displayed:
2738
- * ➜ <input id="security_button_1" value="Arm Stay" onclick="setArmState('quickcontrol/armDisarm.jsp','Arming Stay','1','2','false','href=rest/adt/ui/client/security/setArmState&amp;armstate=off&amp;arm=stay&amp;sat=21580428-e539-4075-8237-5c58b6c6fec8')">
2739
- * ➜ <input id="security_button_1" value="Arming Stay" disabled="">
2740
- *
2741
- * Example data after being processed by "parseOrbSecurityButtons()" function/method:
2742
- * ➜ [
2743
- * {
2744
- * buttonDisabled: false,
2745
- * buttonId: 'security_button_1',
2746
- * buttonIndex: 1,
2747
- * buttonText: 'Arm Stay',
2748
- * changeAccessCode: false,
2749
- * loadingText: 'Arming Stay',
2750
- * relativeUrl: 'quickcontrol/armDisarm.jsp',
2751
- * totalButtons: 2,
2752
- * urlParams: {
2753
- * arm: 'stay',
2754
- * armState: 'off',
2755
- * href: 'rest/adt/ui/client/security/setArmState',
2756
- * sat: '21580428-e539-4075-8237-5c58b6c6fec8',
2757
- * },
2758
- * },
2759
- * ]
2760
- * ➜ [
2761
- * {
2762
- * buttonDisabled: true,
2763
- * buttonId: 'security_button_1',
2764
- * buttonText: 'Arming Stay',
2765
- * },
2766
- * ]
2767
- *
2768
- * Notes I've gathered during the process:
2769
- * - After disarming, "armState" will be set to "disarmed". It will be set to "off" after re-login.¹
2770
- * - After turning off siren, "armState" will be set to "disarmed+with+alarm". It will be set to "disarmed_with_alarm" after re-login.¹²
2771
- * - After arming night, "armState" will be set to "night+stay". It will be set to "night" after re-login.¹
2772
- * - The "sat" code is required for all arm/disarm actions (UUID, generated on every login).
2773
- * - If "armState" is not "off" or "disarmed", you must disarm first before setting to other modes.
2774
- *
2775
- * Footnotes:
2776
- * ¹ States are synced across an entire site (per home). If one account arms, every user signed in during that phase becomes "dirty"
2777
- * ² Turning off siren means system is in "Uncleared Alarm" mode, not truly "Disarmed" mode.
2778
- *
2779
- * @since 1.0.0
2780
- */
2781
- const jsdomSummaryOrbSecurityButtons = sessions.jsdomSummary.window.document.querySelectorAll('#divOrbSecurityButtons input');
2782
- const parsedOrbSecurityButtons = parseOrbSecurityButtons(jsdomSummaryOrbSecurityButtons);
2783
-
2784
- /**
2785
- * Check if "orbSecurityButtons" needs documenting or testing.
2786
- *
2787
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
2788
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2789
- *
2790
- * buttonText: 'Arm Away'
2791
- * 'Arm Night'
2792
- * 'Arm Stay'
2793
- * 'Clear Alarm'
2794
- * 'Disarm'
2795
- *
2796
- * loadingText: 'Arming Away'
2797
- * 'Arming Night'
2798
- * 'Arming Stay'
2799
- * 'Disarming'
2800
- *
2801
- * relativeUrl: 'quickcontrol/armDisarm.jsp'
2802
- *
2803
- * urlParams.arm: 'away'
2804
- * 'night'
2805
- * 'off'
2806
- * 'stay'
2807
- *
2808
- * urlParams.armState: 'away'
2809
- * 'disarmed'
2810
- * 'disarmed_with_alarm'
2811
- * 'disarmed+with+alarm'
2812
- * 'night'
2813
- * 'night+stay'
2814
- * 'off'
2815
- * 'stay'
2816
- *
2817
- * urlParams.href: 'rest/adt/ui/client/security/setArmState'
2818
- *
2819
- * Notes I've gathered during the process:
2820
- * - When a button is in pending (disabled) state, the "buttonText" will be the "loadingText".
2821
- * - Currently, "disarmed+with+alarm" and "night+stay" are considered dirty states.
2822
- *
2823
- * @since 1.0.0
2824
- */
2825
- await this.newInformationDispatcher('orb-security-buttons', parsedOrbSecurityButtons);
2826
-
2827
- let readyButtons = parsedOrbSecurityButtons.filter((parsedOrbSecurityButton): parsedOrbSecurityButton is ADTPulseArmDisarmHandlerReadyButton => !parsedOrbSecurityButton.buttonDisabled);
2828
-
2829
- // WORKAROUND FOR ARM NIGHT BUTTON BUG: Generate a fake "parsedOrbSecurityButtons" response after system has been set to "night" mode if "Arming Night" is stuck.
2830
- if (
2831
- ['disarmed', 'off'].includes(armState) // Checks if state was "disarmed" (dirty) or "off" (clean).
2832
- && ['night'].includes(arm) // Checks if system was trying to change to "night" mode.
2833
- && readyButtons.length === 0 // Check if there are no ready (enabled) buttons.
2834
- ) {
2835
- readyButtons = [
2836
- {
2837
- buttonDisabled: false,
2838
- buttonId: 'security_button_0',
2839
- buttonIndex: 0,
2840
- buttonText: 'Disarm',
2841
- changeAccessCode: false,
2842
- loadingText: 'Disarming',
2843
- relativeUrl,
2844
- totalButtons: 1,
2845
- urlParams: {
2846
- arm: 'off',
2847
- armState: (this.#session.isCleanState) ? 'night' : 'night+stay',
2848
- href,
2849
- sat,
2850
- },
2851
- },
2852
- ];
2853
- }
2854
-
2855
- if (this.#internal.debug) {
2856
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'success', `Successfully updated arm state from "${armState}" to "${arm}" on "${this.#internal.baseUrl}"`);
2857
- }
2858
-
2859
- return {
2860
- action: 'ARM_DISARM_HANDLER',
2861
- success: true,
2862
- info: {
2863
- forceArmRequired,
2864
- readyButtons,
2865
- },
2866
- };
2867
- } catch (error) {
2868
- errorObject = serializeError(error);
2869
- }
2870
-
2871
- if (this.#internal.debug) {
2872
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.armDisarmHandler()', 'error', 'Method encountered an error during execution');
2873
- stackTracer('serialize-error', errorObject);
2874
- }
2875
-
2876
- return {
2877
- action: 'ARM_DISARM_HANDLER',
2878
- success: false,
2879
- info: {
2880
- error: errorObject,
2881
- },
2882
- };
2883
- }
2884
-
2885
- /**
2886
- * ADT Pulse - Force arm handler.
2887
- *
2888
- * @param {ADTPulseForceArmHandlerResponse} response - Response.
2889
- * @param {ADTPulseForceArmHandlerRelativeUrl} relativeUrl - Relative url.
2890
- *
2891
- * @private
2892
- *
2893
- * @returns {ADTPulseForceArmHandlerReturns}
2894
- *
2895
- * @since 1.0.0
2896
- */
2897
- private async forceArmHandler(response: ADTPulseForceArmHandlerResponse, relativeUrl: ADTPulseForceArmHandlerRelativeUrl): ADTPulseForceArmHandlerReturns {
2898
- let errorObject;
2899
-
2900
- if (this.#internal.debug) {
2901
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'info', `Attempting to force arm on "${this.#internal.baseUrl}"`);
2902
- }
2903
-
2904
- try {
2905
- const internet = await this.isPortalAccessible();
2906
- const sessions: ADTPulseForceArmHandlerSessions = {};
2907
-
2908
- // Check if portal is accessible.
2909
- if (!internet.success) {
2910
- return {
2911
- action: 'FORCE_ARM_HANDLER',
2912
- success: false,
2913
- info: internet.info,
2914
- };
2915
- }
2916
-
2917
- // Make sure we are able to use JSDOM on the response data.
2918
- if (typeof response.data !== 'string') {
2919
- if (this.#internal.debug) {
2920
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', 'The response body of the arm disarm page is not of type "string"');
2921
- }
2922
-
2923
- return {
2924
- action: 'FORCE_ARM_HANDLER',
2925
- success: false,
2926
- info: {
2927
- message: 'The response body of the arm disarm page is not of type "string"',
2928
- },
2929
- };
2930
- }
2931
-
2932
- // sessions.jsdomArmDisarm: Parse the arm disarm page.
2933
- sessions.jsdomArmDisarm = new JSDOM(
2934
- response.data,
2935
- {
2936
- url: response.config.url,
2937
- referrer: response.config.headers.Referer,
2938
- contentType: 'text/html',
2939
- pretendToBeVisual: true,
2940
- },
2941
- );
2942
-
2943
- /**
2944
- * Detailed parsing information for "doSubmitHandlers".
2945
- *
2946
- * NOTICE: Responses may be inaccurate or missing.
2947
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2948
- *
2949
- * How the data may be displayed:
2950
- * ➜ <input onclick="doSubmit( '/myhome/16.0.0-131/quickcontrol/serv/RunRRACommand?sat=f8e7c824-a88c-4fd2-ad4d-9b4b69039b09&href=rest\/adt\/ui\/client\/security\/setForceArm&armstate=forcearm&arm=stay' )">
2951
- * ➜ <input onclick="doSubmit( '/myhome/16.0.0-131/quickcontrol/serv/RunRRACommand?sat=f8e7c824-a88c-4fd2-ad4d-9b4b69039b09&href=rest\/adt\/ui\/client\/security\/setCancelProtest' )">
2952
- *
2953
- * Example data after being processed by "parseDoSubmitHandlers()" function/method:
2954
- * ➜ [
2955
- * {
2956
- * relativeUrl: '/myhome/16.0.0-131/quickcontrol/serv/RunRRACommand',
2957
- * urlParams: {
2958
- * arm: 'stay',
2959
- * armState: 'forcearm',
2960
- * href: 'rest/adt/ui/client/security/setForceArm',
2961
- * sat: 'f8e7c824-a88c-4fd2-ad4d-9b4b69039b09',
2962
- * },
2963
- * },
2964
- * ]
2965
- * ➜ [
2966
- * {
2967
- * relativeUrl: '/myhome/16.0.0-131/quickcontrol/serv/RunRRACommand',
2968
- * urlParams: {
2969
- * arm: null,
2970
- * armState: null,
2971
- * href: 'rest/adt/ui/client/security/setCancelProtest',
2972
- * sat: 'f8e7c824-a88c-4fd2-ad4d-9b4b69039b09',
2973
- * },
2974
- * },
2975
- * ]
2976
- *
2977
- * Notes I've gathered during the process:
2978
- * - The "sat" code is required for all force arm actions (UUID, generated on every login).
2979
- *
2980
- * @since 1.0.0
2981
- */
2982
- const jsdomArmDisarmDoSubmitHandlers = sessions.jsdomArmDisarm.window.document.querySelectorAll('.p_armDisarmWrapper input');
2983
- const jsdomArmDisarmArmDisarmMessage = sessions.jsdomArmDisarm.window.document.querySelector('.p_armDisarmWrapper div:first-child');
2984
- const parsedArmDisarmMessage = parseArmDisarmMessage(jsdomArmDisarmArmDisarmMessage);
2985
- const parsedDoSubmitHandlers = parseDoSubmitHandlers(jsdomArmDisarmDoSubmitHandlers);
2986
-
2987
- /**
2988
- * Check if "doSubmitHandlers" needs documenting or testing.
2989
- *
2990
- * NOTICE: Parts NOT SHOWN below will NOT be tracked, documented, or tested.
2991
- * PATENT: https://patents.google.com/patent/US20170070361A1/en
2992
- *
2993
- * relativeUrl: '/myhome/16.0.0-131/quickcontrol/serv/RunRRACommand'
2994
- *
2995
- * urlParams.arm: 'away'
2996
- * 'night'
2997
- * 'stay'
2998
- *
2999
- * urlParams.armState: 'forcearm'
3000
- *
3001
- * urlParams.href: 'rest/adt/ui/client/security/setForceArm'
3002
- * 'rest/adt/ui/client/security/setCancelProtest'
3003
- *
3004
- * @since 1.0.0
3005
- */
3006
- await this.newInformationDispatcher('do-submit-handlers', parsedDoSubmitHandlers);
3007
-
3008
- // Check if there are no force arm buttons available.
3009
- if (
3010
- parsedDoSubmitHandlers.length === 0
3011
- || parsedArmDisarmMessage === null
3012
- ) {
3013
- // In test mode, system must detect at least 1 door or window open.
3014
- if (this.#internal.testMode.enabled) {
3015
- if (this.#internal.debug) {
3016
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', 'Test mode is active but no doors or windows were open');
3017
- }
3018
-
3019
- return {
3020
- action: 'FORCE_ARM_HANDLER',
3021
- success: false,
3022
- info: {
3023
- message: 'Test mode is active but no doors or windows were open',
3024
- },
3025
- };
3026
- }
3027
-
3028
- if (this.#internal.debug) {
3029
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'info', 'Force arming not required');
3030
- }
3031
-
3032
- return {
3033
- action: 'FORCE_ARM_HANDLER',
3034
- success: true,
3035
- info: {
3036
- forceArmRequired: false,
3037
- },
3038
- };
3039
- }
3040
-
3041
- // Helps track the latest force arming response because the use of the loop.
3042
- const tracker: ADTPulseForceArmHandlerTracker = {
3043
- complete: false,
3044
- errorMessage: null,
3045
- requestUrl: null,
3046
- };
3047
-
3048
- if (this.#internal.debug) {
3049
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'warn', `Portal message ➜ "${parsedArmDisarmMessage}"`);
3050
- }
3051
-
3052
- // Purpose of this loop is to determine the correct button position for force arming.
3053
- for (let i = 0; i < parsedDoSubmitHandlers.length; i += 1) {
3054
- const forceArmRelativeUrl = parsedDoSubmitHandlers[i].relativeUrl;
3055
- const forceArmSat = parsedDoSubmitHandlers[i].urlParams.sat;
3056
- const forceArmHref = parsedDoSubmitHandlers[i].urlParams.href;
3057
- const forceArmArmState = parsedDoSubmitHandlers[i].urlParams.armState;
3058
- const forceArmArm = parsedDoSubmitHandlers[i].urlParams.arm;
3059
-
3060
- if (
3061
- (
3062
- tracker.complete // If force arm already completed.
3063
- && tracker.errorMessage === null // If there is no error message.
3064
- )
3065
- || forceArmArmState === null // If "armState" does not exist, it is not an "Arm Anyway" button.
3066
- || forceArmArm === null // If "arm" does not exist, it is not an "Arm Anyway" button.
3067
- ) {
3068
- continue;
3069
- }
3070
-
3071
- // Build an "application/x-www-form-urlencoded" form for use with force arming.
3072
- const forceArmForm = new URLSearchParams();
3073
- forceArmForm.append('sat', forceArmSat);
3074
- forceArmForm.append('href', forceArmHref);
3075
- forceArmForm.append('armstate', forceArmArmState);
3076
- forceArmForm.append('arm', forceArmArm);
3077
-
3078
- // sessions.axiosForceArm: Emulate a force arm state update request.
3079
- sessions.axiosForceArm = await this.#session.httpClient.post<unknown>(
3080
- /**
3081
- * A breakdown of the links to force set arm mode.
3082
- *
3083
- * NOTE: Responses may be inaccurate or missing.
3084
- * LINK: https://patents.google.com/patent/US20170070361A1/en
3085
- *
3086
- * - When trying to "Arm Away":
3087
- * - "Arm Anyway" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>&armstate=forcearm&arm=away
3088
- * - "Cancel" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>
3089
- * - When trying to "Arm Stay":
3090
- * - "Arm Anyway" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>&armstate=forcearm&arm=stay
3091
- * - "Cancel" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>
3092
- * - When trying to "Arm Night":
3093
- * - "Arm Anyway" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>&armstate=forcearm&arm=night
3094
- * - "Cancel" button link: https://<subdomain>.adtpulse.com<relativeUrl>?sat=<sat>&href=<href>
3095
- *
3096
- * Notes I've gathered during the process:
3097
- * - When arming and disarming in the portal, POST requests are made. However, GET requests still work when URL is pasted in.
3098
- *
3099
- * @since 1.0.0
3100
- */
3101
- this.#internal.baseUrl + forceArmRelativeUrl,
3102
- forceArmForm,
3103
- this.getRequestConfig({
3104
- headers: {
3105
- Accept: '*/*',
3106
- 'Content-type': 'application/x-www-form-urlencoded',
3107
- Origin: this.#internal.baseUrl,
3108
- Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/${relativeUrl}`,
3109
- 'Sec-Fetch-Dest': 'empty',
3110
- 'Sec-Fetch-Mode': 'cors',
3111
- 'Sec-Fetch-Site': 'same-origin',
3112
- 'Sec-Fetch-User': undefined,
3113
- 'x-dtpc': generateDynatracePCHeaderValue('force-arm'),
3114
- },
3115
- }),
3116
- );
3117
-
3118
- // If the "ClientRequest" object does not exist in the Axios response.
3119
- if (typeof sessions.axiosForceArm?.request === 'undefined') {
3120
- if (this.#internal.debug) {
3121
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', 'The HTTP client responded without the "request" object');
3122
- }
3123
-
3124
- return {
3125
- action: 'FORCE_ARM_HANDLER',
3126
- success: false,
3127
- info: {
3128
- message: 'The HTTP client responded without the "request" object',
3129
- },
3130
- };
3131
- }
3132
-
3133
- const axiosForceArmRequestPath = sessions.axiosForceArm.request.path;
3134
- const axiosForceArmRequestPathValid = requestPathQuickControlServRunRraCommand.test(axiosForceArmRequestPath);
3135
-
3136
- if (this.#internal.debug) {
3137
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'info', `Request path ➜ ${axiosForceArmRequestPath}`);
3138
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'info', `Request path valid ➜ ${axiosForceArmRequestPathValid}`);
3139
- }
3140
-
3141
- // If the final URL of sessions.axiosForceArm is not the run rra command page.
3142
- if (!axiosForceArmRequestPathValid) {
3143
- tracker.errorMessage = `"${axiosForceArmRequestPath}" is not the run rra command page`;
3144
- tracker.requestUrl = axiosForceArmRequestPath;
3145
-
3146
- continue;
3147
- }
3148
-
3149
- // Make sure we are able to use the "String.prototype.includes()" method on the response data.
3150
- if (typeof sessions.axiosForceArm.data !== 'string') {
3151
- tracker.errorMessage = 'The response body of the run rra command page is not of type "string"';
3152
- tracker.requestUrl = axiosForceArmRequestPath;
3153
-
3154
- continue;
3155
- }
3156
-
3157
- // The server reported that the force arm failed.
3158
- if (!sessions.axiosForceArm.data.includes('1.0-OKAY')) {
3159
- /**
3160
- * A breakdown of the responses when parsing the "RunRRACommand" response body.
3161
- *
3162
- * NOTE: Responses may be inaccurate or missing.
3163
- * LINK: https://patents.google.com/patent/US20170070361A1/en
3164
- *
3165
- * When force arming is successful:
3166
- * - "Could not process the request!</br></br>Error: 1.0-OKAY"
3167
- *
3168
- * When force arming is not successful:
3169
- * - "Could not process the request!</br></br>Error: Method not allowed. Allowed methods GET, HEAD"
3170
- *
3171
- * Notes I've gathered during the process:
3172
- * - "Method not allowed" error appeared when "parseDoSubmitHandlers().href" has escaped forward slashes (e.g. rest\/adt\/ui\/client\/security\/setForceArm).
3173
- * - POST method is allowed. The error message steers in to the wrong direction. Probably for security reasons.
3174
- *
3175
- * @since 1.0.0
3176
- */
3177
- tracker.errorMessage = 'The response body of the run rra command page does not include "1.0-OKAY"';
3178
- tracker.requestUrl = axiosForceArmRequestPath;
3179
-
3180
- continue;
3181
- }
3182
-
3183
- // Mark the force arm as complete.
3184
- tracker.complete = true;
3185
- tracker.errorMessage = null;
3186
- tracker.requestUrl = null;
3187
- }
3188
-
3189
- // If "tracker.errorMessage" has a pending error message to display.
3190
- if (tracker.errorMessage !== null) {
3191
- if (this.#internal.debug) {
3192
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', tracker.errorMessage);
3193
- }
3194
-
3195
- // If "this instance" was not signed in at this time.
3196
- this.handleLoginFailure(tracker.requestUrl, sessions.axiosForceArm);
3197
-
3198
- return {
3199
- action: 'FORCE_ARM_HANDLER',
3200
- success: false,
3201
- info: {
3202
- message: tracker.errorMessage,
3203
- },
3204
- };
3205
- }
3206
-
3207
- // If force arming failed because the "Arm Anyway" button was not found.
3208
- if (!tracker.complete) {
3209
- if (this.#internal.debug) {
3210
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', 'Force arming failed because the "Arm Anyway" button was not found');
3211
- }
3212
-
3213
- return {
3214
- action: 'FORCE_ARM_HANDLER',
3215
- success: false,
3216
- info: {
3217
- message: 'Force arming failed because the "Arm Anyway" button was not found',
3218
- },
3219
- };
3220
- }
3221
-
3222
- if (this.#internal.debug) {
3223
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'success', `Successfully forced arm on "${this.#internal.baseUrl}"`);
3224
- }
3225
-
3226
- return {
3227
- action: 'FORCE_ARM_HANDLER',
3228
- success: true,
3229
- info: {
3230
- forceArmRequired: true,
3231
- },
3232
- };
3233
- } catch (error) {
3234
- errorObject = serializeError(error);
3235
- }
3236
-
3237
- if (this.#internal.debug) {
3238
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.forceArmHandler()', 'error', 'Method encountered an error during execution');
3239
- stackTracer('serialize-error', errorObject);
3240
- }
3241
-
3242
- return {
3243
- action: 'FORCE_ARM_HANDLER',
3244
- success: false,
3245
- info: {
3246
- error: errorObject,
3247
- },
3248
- };
3249
- }
3250
-
3251
- /**
3252
- * ADT Pulse - Is portal accessible.
3253
- *
3254
- * @private
3255
- *
3256
- * @returns {ADTPulseIsPortalAccessibleReturns}
3257
- *
3258
- * @since 1.0.0
3259
- */
3260
- private async isPortalAccessible(): ADTPulseIsPortalAccessibleReturns {
3261
- let errorObject;
3262
-
3263
- if (this.#internal.debug) {
3264
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'info', `Attempting to check if "${this.#internal.baseUrl}" is accessible`);
3265
- }
3266
-
3267
- try {
3268
- // Send request using a new instance to prevent cookie jar cross-contamination.
3269
- const response = await axios.head(
3270
- this.#internal.baseUrl,
3271
- this.getRequestConfig(),
3272
- );
3273
-
3274
- if (response.status !== 200 || response.statusText !== 'OK') {
3275
- if (this.#internal.debug) {
3276
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'error', `The portal at "${this.#internal.baseUrl}" is not accessible`);
3277
- }
3278
-
3279
- return {
3280
- action: 'IS_PORTAL_ACCESSIBLE',
3281
- success: false,
3282
- info: {
3283
- message: `The portal at "${this.#internal.baseUrl}" is not accessible`,
3284
- },
3285
- };
3286
- }
3287
-
3288
- if (this.#internal.debug) {
3289
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'success', `Successfully checked if "${this.#internal.baseUrl}" is accessible`);
3290
- }
3291
-
3292
- return {
3293
- action: 'IS_PORTAL_ACCESSIBLE',
3294
- success: true,
3295
- info: null,
3296
- };
3297
- } catch (error) {
3298
- errorObject = serializeError(error);
3299
- }
3300
-
3301
- if (this.#internal.debug) {
3302
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'error', 'Method encountered an error during execution');
3303
- stackTracer('serialize-error', errorObject);
3304
- }
3305
-
3306
- return {
3307
- action: 'IS_PORTAL_ACCESSIBLE',
3308
- success: false,
3309
- info: {
3310
- error: errorObject,
3311
- },
3312
- };
3313
- }
3314
-
3315
- /**
3316
- * ADT Pulse - New information dispatcher.
3317
- *
3318
- * @param {ADTPulseNewInformationDispatcherType} type - Type.
3319
- * @param {ADTPulseNewInformationDispatcherData} data - Data.
3320
- *
3321
- * @private
3322
- *
3323
- * @returns {ADTPulseNewInformationDispatcherReturns}
3324
- *
3325
- * @since 1.0.0
3326
- */
3327
- private async newInformationDispatcher(type: ADTPulseNewInformationDispatcherType, data: ADTPulseNewInformationDispatcherData<ADTPulseNewInformationDispatcherType>): ADTPulseNewInformationDispatcherReturns {
3328
- const dataHash = generateHash(`${type}: ${JSON.stringify(data)}`);
3329
-
3330
- // If the detector has not reported this event before.
3331
- if (this.#internal.reportedHashes.find((reportedHash) => dataHash === reportedHash) === undefined) {
3332
- let detectedNew = false;
3333
-
3334
- // Determine what information needs to be checked.
3335
- switch (type) {
3336
- case 'do-submit-handlers':
3337
- detectedNew = await detectedNewDoSubmitHandlers(data as ADTPulseNewInformationDispatcherData<'do-submit-handlers'>, this.#internal.logger, this.#internal.debug);
3338
- break;
3339
- case 'gateway-information':
3340
- detectedNew = await detectedNewGatewayInformation(data as ADTPulseNewInformationDispatcherData<'gateway-information'>, this.#internal.logger, this.#internal.debug);
3341
- break;
3342
- case 'orb-security-buttons':
3343
- detectedNew = await detectedNewOrbSecurityButtons(data as ADTPulseNewInformationDispatcherData<'orb-security-buttons'>, this.#internal.logger, this.#internal.debug);
3344
- break;
3345
- case 'panel-information':
3346
- detectedNew = await detectedNewPanelInformation(data as ADTPulseNewInformationDispatcherData<'panel-information'>, this.#internal.logger, this.#internal.debug);
3347
- break;
3348
- case 'panel-status':
3349
- detectedNew = await detectedNewPanelStatus(data as ADTPulseNewInformationDispatcherData<'panel-status'>, this.#internal.logger, this.#internal.debug);
3350
- break;
3351
- case 'portal-version':
3352
- detectedNew = await detectedNewPortalVersion(data as ADTPulseNewInformationDispatcherData<'portal-version'>, this.#internal.logger, this.#internal.debug);
3353
- break;
3354
- case 'sensors-information':
3355
- detectedNew = await detectedNewSensorsInformation(data as ADTPulseNewInformationDispatcherData<'sensors-information'>, this.#internal.logger, this.#internal.debug);
3356
- break;
3357
- case 'sensors-status':
3358
- detectedNew = await detectedNewSensorsStatus(data as ADTPulseNewInformationDispatcherData<'sensors-status'>, this.#internal.logger, this.#internal.debug);
3359
- break;
3360
- default:
3361
- break;
3362
- }
3363
-
3364
- // Save this hash so the detector does not detect the same thing multiple times.
3365
- if (detectedNew) {
3366
- this.#internal.reportedHashes.push(dataHash);
3367
- }
3368
- }
3369
- }
3370
-
3371
- /**
3372
- * ADT Pulse - Get request config.
3373
- *
3374
- * @param {ADTPulseGetRequestConfigExtraConfig} extraConfig - Extra config.
3375
- *
3376
- * @private
3377
- *
3378
- * @returns {ADTPulseGetRequestConfigReturns}
3379
- *
3380
- * @since 1.0.0
3381
- */
3382
- private getRequestConfig(extraConfig?: ADTPulseGetRequestConfigExtraConfig): ADTPulseGetRequestConfigReturns {
3383
- const defaultConfig: ADTPulseGetRequestConfigDefaultConfig = {
3384
- family: 4,
3385
- headers: {
3386
- Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
3387
- 'Accept-Encoding': 'gzip, deflate, br',
3388
- 'Accept-Language': 'en-US,en;q=0.9',
3389
- Connection: 'keep-alive',
3390
- Host: `${this.#credentials.subdomain}.adtpulse.com`,
3391
- 'Sec-Fetch-Dest': 'document',
3392
- 'Sec-Fetch-Mode': 'navigate',
3393
- 'Sec-Fetch-Site': 'none',
3394
- 'Sec-Fetch-User': '?1',
3395
- 'Upgrade-Insecure-Requests': '1',
3396
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
3397
- 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
3398
- 'sec-ch-ua-mobile': '?0',
3399
- 'sec-ch-ua-platform': '"macOS"',
3400
- },
3401
- timeout: 15000, // 15 seconds.
3402
- validateStatus: undefined,
3403
- };
3404
-
3405
- if (extraConfig === undefined) {
3406
- return defaultConfig;
3407
- }
3408
-
3409
- // If one or more of "extraConfig" keys are "undefined" or "null", omit those keys for both configs.
3410
- return _.merge(
3411
- _.omit(defaultConfig, findNullKeys(extraConfig)),
3412
- _.omit(extraConfig, findNullKeys(extraConfig)),
3413
- );
3414
- }
3415
-
3416
- /**
3417
- * ADT Pulse - Handle login failure.
3418
- *
3419
- * @param {ADTPulseHandleLoginFailureRequestPath} requestPath - Request path.
3420
- * @param {ADTPulseHandleLoginFailureSession} session - Session.
3421
- *
3422
- * @private
3423
- *
3424
- * @returns {ADTPulseHandleLoginFailureReturns}
3425
- *
3426
- * @since 1.0.0
3427
- */
3428
- private handleLoginFailure(requestPath: ADTPulseHandleLoginFailureRequestPath, session: ADTPulseHandleLoginFailureSession): ADTPulseHandleLoginFailureReturns {
3429
- if (requestPath === null) {
3430
- return;
3431
- }
3432
-
3433
- if (
3434
- requestPathAccessSignIn.test(requestPath)
3435
- || requestPathAccessSignInENsPartnerAdt.test(requestPath)
3436
- || requestPathMfaMfaSignInWorkflowChallenge.test(requestPath)
3437
- ) {
3438
- if (this.#internal.debug) {
3439
- const errorMessage = fetchErrorMessage(session);
3440
-
3441
- // Determine if "this instance" was redirected to the sign-in page.
3442
- if (requestPathAccessSignIn.test(requestPath) || requestPathAccessSignInENsPartnerAdt.test(requestPath)) {
3443
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.handleLoginFailure()', 'error', 'Either the username or password is incorrect, fingerprint format is invalid, or was signed out due to inactivity');
3444
- }
3445
-
3446
- // Determine if "this instance" was redirected to the MFA challenge page.
3447
- if (requestPathMfaMfaSignInWorkflowChallenge.test(requestPath)) {
3448
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.handleLoginFailure()', 'error', 'Either the fingerprint expired or "Trust this device" was not selected after completing MFA challenge');
3449
- }
3450
-
3451
- // Show the portal error message if it exists.
3452
- if (errorMessage !== null) {
3453
- debugLog(this.#internal.logger, 'api.ts / ADTPulse.handleLoginFailure()', 'warn', `Portal message ➜ "${errorMessage}"`);
3454
- }
3455
- }
3456
-
3457
- // Reset the session state for "this instance".
3458
- this.resetSession();
3459
- }
3460
- }
3461
-
3462
- /**
3463
- * ADT Pulse - Reset session.
3464
- *
3465
- * @private
3466
- *
3467
- * @returns {ADTPulseResetSessionReturns}
3468
- *
3469
- * @since 1.0.0
3470
- */
3471
- private resetSession(): ADTPulseResetSessionReturns {
3472
- this.#session = {
3473
- backupSatCode: null,
3474
- httpClient: wrapper(axios.create({
3475
- jar: new CookieJar(),
3476
- })),
3477
- isAuthenticated: false,
3478
- isCleanState: true,
3479
- networkId: null,
3480
- portalVersion: null,
3481
- };
3482
- }
3483
- }