@alwaysai/device-agent 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/environment-variables.d.ts +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +22 -20
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +37 -2
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.js +1 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -2
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +116 -99
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +30 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +15 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +1 -3
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +1 -9
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +3 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +10 -3
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +79 -28
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +26 -6
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +103 -22
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +179 -13
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/subcommands/app/analytics.d.ts +10 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -0
- package/lib/subcommands/app/analytics.js +83 -0
- package/lib/subcommands/app/analytics.js.map +1 -0
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +3 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -5
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +11 -47
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.d.ts +1 -0
- package/lib/subcommands/app/status.d.ts.map +1 -1
- package/lib/subcommands/app/status.js +14 -3
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts +2 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +16 -3
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/parsing.d.ts +2 -0
- package/lib/util/parsing.d.ts.map +1 -0
- package/lib/util/parsing.js +17 -0
- package/lib/util/parsing.js.map +1 -0
- package/package.json +4 -6
- package/readme.md +146 -92
- package/src/application-control/environment-variables.test.ts +43 -3
- package/src/application-control/environment-variables.ts +29 -19
- package/src/application-control/install.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +155 -141
- package/src/cloud-connection/live-updates-handler.test.ts +20 -0
- package/src/cloud-connection/live-updates-handler.ts +45 -52
- package/src/cloud-connection/messages.ts +1 -14
- package/src/cloud-connection/publisher.ts +4 -0
- package/src/cloud-connection/shadow-handler.test.ts +88 -28
- package/src/cloud-connection/shadow-handler.ts +13 -3
- package/src/cloud-connection/transaction-manager.test.ts +193 -18
- package/src/cloud-connection/transaction-manager.ts +174 -26
- package/src/subcommands/app/analytics.ts +99 -0
- package/src/subcommands/app/index.ts +4 -3
- package/src/subcommands/app/models.ts +13 -49
- package/src/subcommands/app/status.ts +20 -3
- package/src/subcommands/app/version.ts +19 -4
- package/src/util/parsing.ts +11 -0
- package/lib/cloud-connection/cmd-status.d.ts +0 -8
- package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
- package/lib/cloud-connection/cmd-status.js +0 -62
- package/lib/cloud-connection/cmd-status.js.map +0 -1
- package/lib/cloud-connection/message-builder.d.ts +0 -7
- package/lib/cloud-connection/message-builder.d.ts.map +0 -1
- package/lib/cloud-connection/message-builder.js +0 -63
- package/lib/cloud-connection/message-builder.js.map +0 -1
- package/src/cloud-connection/cmd-status.ts +0 -71
- package/src/cloud-connection/message-builder.ts +0 -117
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// eslint-disable-next-line
|
|
2
|
-
const awsIot = require(
|
|
2
|
+
const awsIot = require("aws-iot-device-sdk");
|
|
3
3
|
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import {
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from '../util/directories';
|
|
12
12
|
import {
|
|
13
13
|
keyMirrors,
|
|
14
|
-
validateToClientMessage,
|
|
15
14
|
SignedUrlsRequestPayload,
|
|
16
15
|
getToDeviceTopic,
|
|
17
16
|
AppInstallResponsePayload,
|
|
@@ -21,8 +20,10 @@ import {
|
|
|
21
20
|
AppStateControlPayload,
|
|
22
21
|
AppVersionControlInstallPayload,
|
|
23
22
|
AppVersionControlUninstallPayload,
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
DeviceActionPayload,
|
|
24
|
+
buildSignedUrlsRequestMessage,
|
|
25
|
+
buildToClientStatusResponseMessage,
|
|
26
|
+
StatusResponsePayload
|
|
26
27
|
} from '@alwaysai/device-agent-schemas';
|
|
27
28
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
28
29
|
import { logger } from '../util/logger';
|
|
@@ -41,32 +42,31 @@ import {
|
|
|
41
42
|
import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
|
|
42
43
|
import { secureTunnelNotifyHandler } from '../secure-tunneling/index';
|
|
43
44
|
import { Publisher } from './publisher';
|
|
44
|
-
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
45
45
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
46
|
-
import { CmdStatusManager } from './cmd-status';
|
|
47
46
|
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
48
47
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
49
|
-
import { getStatusResponsePayload } from './messages';
|
|
50
48
|
import { ModelsInstallResponsePayload } from '@alwaysai/device-agent-schemas';
|
|
51
49
|
import sleep from '../util/sleep';
|
|
52
50
|
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
53
51
|
import { TransactionManager } from './transaction-manager';
|
|
54
|
-
import {
|
|
55
|
-
buildSignedUrlsRequestMessage,
|
|
56
|
-
buildStatusResponseMessage
|
|
57
|
-
} from './message-builder';
|
|
58
52
|
import { reboot } from '../device-control/device-control';
|
|
59
53
|
|
|
54
|
+
import { exec } from 'child_process';
|
|
55
|
+
import { promisify } from 'util';
|
|
56
|
+
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
57
|
+
|
|
58
|
+
const exec_promise = promisify(exec);
|
|
59
|
+
|
|
60
60
|
export class DeviceAgentCloudConnection {
|
|
61
61
|
private shadowHandler: ShadowHandler;
|
|
62
62
|
public publisher: Publisher;
|
|
63
|
-
private cmdStatusMgr: CmdStatusManager;
|
|
64
63
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
65
64
|
private txnMgr: TransactionManager;
|
|
66
65
|
private device = awsIot.device;
|
|
67
66
|
|
|
68
67
|
private clientId = getDeviceUuid();
|
|
69
68
|
private host = getIoTCoreEndpointUrl();
|
|
69
|
+
private port = 8883;
|
|
70
70
|
private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
|
|
71
71
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
72
72
|
// FIXME: Add support for multiple simultaneous project updates
|
|
@@ -106,7 +106,8 @@ export class DeviceAgentCloudConnection {
|
|
|
106
106
|
appReleaseHash
|
|
107
107
|
}
|
|
108
108
|
};
|
|
109
|
-
const message =
|
|
109
|
+
const message = buildSignedUrlsRequestMessage(
|
|
110
|
+
this.clientId,
|
|
110
111
|
signedUrlsRequestPayload,
|
|
111
112
|
txId
|
|
112
113
|
);
|
|
@@ -272,62 +273,6 @@ export class DeviceAgentCloudConnection {
|
|
|
272
273
|
}
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
// eslint-disable-next-line
|
|
276
|
-
private async atomicCmd<T extends any[]>(props: {
|
|
277
|
-
func: (...args: T) => Promise<boolean>;
|
|
278
|
-
args: T;
|
|
279
|
-
projectId: string;
|
|
280
|
-
txId: string;
|
|
281
|
-
}): Promise<boolean> {
|
|
282
|
-
const { func, args, projectId, txId } = props;
|
|
283
|
-
try {
|
|
284
|
-
await this.cmdStatusMgr.start(projectId);
|
|
285
|
-
await this.liveUpdatesHandler.enableTransactionStatus({
|
|
286
|
-
txId
|
|
287
|
-
});
|
|
288
|
-
const completed = await func(...args);
|
|
289
|
-
if (completed) {
|
|
290
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
291
|
-
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
292
|
-
txId
|
|
293
|
-
});
|
|
294
|
-
const successStatusResponsePayload = await getStatusResponsePayload(
|
|
295
|
-
keyMirrors.statusResponse.success,
|
|
296
|
-
''
|
|
297
|
-
);
|
|
298
|
-
// Send final status message
|
|
299
|
-
const message = await buildStatusResponseMessage(
|
|
300
|
-
successStatusResponsePayload,
|
|
301
|
-
txId
|
|
302
|
-
);
|
|
303
|
-
this.publisher.publishToClient(message);
|
|
304
|
-
}
|
|
305
|
-
return completed;
|
|
306
|
-
} catch (e) {
|
|
307
|
-
logger.error(
|
|
308
|
-
`Failed to execute cmd for ${projectId}:\n${e.message}\n${e.stack}`
|
|
309
|
-
);
|
|
310
|
-
const message: string = e.message;
|
|
311
|
-
|
|
312
|
-
// uninstall the failed app to put system back in good state
|
|
313
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
314
|
-
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
315
|
-
txId
|
|
316
|
-
});
|
|
317
|
-
const failureStatusResponsePayload = await getStatusResponsePayload(
|
|
318
|
-
keyMirrors.statusResponse.failure,
|
|
319
|
-
message
|
|
320
|
-
);
|
|
321
|
-
// Send final status message
|
|
322
|
-
const failureStatusResponseMessage = await buildStatusResponseMessage(
|
|
323
|
-
failureStatusResponsePayload,
|
|
324
|
-
txId
|
|
325
|
-
);
|
|
326
|
-
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
276
|
private handleAppConfigUpdate = async (
|
|
332
277
|
update: ShadowUpdate,
|
|
333
278
|
txId: string
|
|
@@ -353,7 +298,8 @@ export class DeviceAgentCloudConnection {
|
|
|
353
298
|
models: updatedModels
|
|
354
299
|
}
|
|
355
300
|
};
|
|
356
|
-
const message =
|
|
301
|
+
const message = buildSignedUrlsRequestMessage(
|
|
302
|
+
this.clientId,
|
|
357
303
|
modelsOnlyUrlsRequestPayload,
|
|
358
304
|
txId
|
|
359
305
|
);
|
|
@@ -397,17 +343,19 @@ export class DeviceAgentCloudConnection {
|
|
|
397
343
|
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
398
344
|
clientId: this.clientId,
|
|
399
345
|
host: this.host,
|
|
400
|
-
port:
|
|
346
|
+
port: this.port,
|
|
401
347
|
keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
402
348
|
});
|
|
403
349
|
this.publisher = new Publisher(this.device, this.clientId);
|
|
404
350
|
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
405
|
-
this.cmdStatusMgr = new CmdStatusManager();
|
|
406
351
|
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
407
352
|
this.publisher,
|
|
408
353
|
this.clientId
|
|
409
354
|
);
|
|
410
|
-
this.txnMgr = new TransactionManager(
|
|
355
|
+
this.txnMgr = new TransactionManager(
|
|
356
|
+
this.publisher,
|
|
357
|
+
this.liveUpdatesHandler
|
|
358
|
+
);
|
|
411
359
|
|
|
412
360
|
this.subscribe(this.toDeviceTopic);
|
|
413
361
|
this.subscribe(this.secureTunnelNotifyTopic);
|
|
@@ -431,7 +379,7 @@ export class DeviceAgentCloudConnection {
|
|
|
431
379
|
}
|
|
432
380
|
|
|
433
381
|
public isCmdInProgress(projectId: string): boolean {
|
|
434
|
-
return this.
|
|
382
|
+
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
435
383
|
}
|
|
436
384
|
|
|
437
385
|
public async updateProjectShadow(projectId: string) {
|
|
@@ -458,23 +406,28 @@ export class DeviceAgentCloudConnection {
|
|
|
458
406
|
return;
|
|
459
407
|
}
|
|
460
408
|
const txId = message.txId;
|
|
409
|
+
const {
|
|
410
|
+
app_state_control,
|
|
411
|
+
app_version_control,
|
|
412
|
+
live_state_updates,
|
|
413
|
+
app_install_response,
|
|
414
|
+
models_install_response,
|
|
415
|
+
status_response
|
|
416
|
+
} = keyMirrors.toDeviceAgentMessageType;
|
|
461
417
|
switch (message.messageType) {
|
|
462
|
-
case
|
|
418
|
+
case app_state_control: {
|
|
463
419
|
// txId sent from cloud, just need to continue it
|
|
464
420
|
const payload = message.payload;
|
|
465
421
|
const projectId = payload.projectId;
|
|
466
422
|
|
|
467
423
|
try {
|
|
468
|
-
this.txnMgr.
|
|
469
|
-
|
|
470
|
-
func: this.handleAppStateControl,
|
|
471
|
-
args: [message.payload],
|
|
424
|
+
await this.txnMgr.runTransactionStep({
|
|
425
|
+
func: () => this.handleAppStateControl(message.payload),
|
|
472
426
|
projectId,
|
|
473
|
-
txId
|
|
427
|
+
txId,
|
|
428
|
+
start: true,
|
|
429
|
+
stepName: payload.baseCommand
|
|
474
430
|
});
|
|
475
|
-
if (completed) {
|
|
476
|
-
this.txnMgr.completeTransaction(txId);
|
|
477
|
-
}
|
|
478
431
|
} catch (e) {
|
|
479
432
|
logger.error(
|
|
480
433
|
`Error processing application state control request: ${e}!`
|
|
@@ -483,40 +436,33 @@ export class DeviceAgentCloudConnection {
|
|
|
483
436
|
|
|
484
437
|
break;
|
|
485
438
|
}
|
|
486
|
-
case
|
|
439
|
+
case app_version_control: {
|
|
487
440
|
// txId sent from cloud, just need to continue it
|
|
488
441
|
const payload = message.payload;
|
|
489
442
|
const projectId = payload.projectId;
|
|
490
|
-
const appReleaseHash =
|
|
491
|
-
payload.baseCommand === keyMirrors.appVersionControl.install
|
|
492
|
-
? payload.appReleaseHash
|
|
493
|
-
: undefined;
|
|
494
443
|
try {
|
|
495
|
-
this.txnMgr.
|
|
496
|
-
|
|
497
|
-
func: this.handleAppVersionControl,
|
|
498
|
-
args: [payload, txId],
|
|
444
|
+
await this.txnMgr.runTransactionStep({
|
|
445
|
+
func: () => this.handleAppVersionControl(payload, txId),
|
|
499
446
|
projectId,
|
|
500
|
-
txId
|
|
447
|
+
txId,
|
|
448
|
+
start: true,
|
|
449
|
+
stepName: payload.baseCommand
|
|
501
450
|
});
|
|
502
|
-
if (completed) {
|
|
503
|
-
this.txnMgr.completeTransaction(txId);
|
|
504
|
-
}
|
|
505
451
|
} catch (e) {
|
|
506
452
|
logger.error(`Error processing application install request: ${e}!`);
|
|
507
453
|
}
|
|
508
454
|
|
|
509
455
|
break;
|
|
510
456
|
}
|
|
511
|
-
case
|
|
457
|
+
case live_state_updates: {
|
|
512
458
|
const payload = message.payload;
|
|
513
459
|
// TODO: Send response?
|
|
514
|
-
|
|
460
|
+
void this.liveUpdatesHandler.handleToggles(payload, txId);
|
|
515
461
|
break;
|
|
516
462
|
}
|
|
517
|
-
case
|
|
463
|
+
case app_install_response: {
|
|
518
464
|
const payload = message.payload;
|
|
519
|
-
const { projectId
|
|
465
|
+
const { projectId } = payload.appInstallResponse;
|
|
520
466
|
if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
521
467
|
throw new Error(
|
|
522
468
|
`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
@@ -524,19 +470,16 @@ export class DeviceAgentCloudConnection {
|
|
|
524
470
|
)})!`
|
|
525
471
|
);
|
|
526
472
|
}
|
|
527
|
-
|
|
528
|
-
func: this.handleAppInstallCloudResponsePayload,
|
|
529
|
-
args: [payload],
|
|
473
|
+
await this.txnMgr.runTransactionStep({
|
|
474
|
+
func: () => this.handleAppInstallCloudResponsePayload(payload),
|
|
530
475
|
projectId,
|
|
531
|
-
txId
|
|
476
|
+
txId,
|
|
477
|
+
start: false,
|
|
478
|
+
stepName: message.messageType
|
|
532
479
|
});
|
|
533
|
-
if (completed) {
|
|
534
|
-
this.txnMgr.completeTransaction(txId);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
480
|
break;
|
|
538
481
|
}
|
|
539
|
-
case
|
|
482
|
+
case models_install_response: {
|
|
540
483
|
// This message doesn't have appReleaseHash in it's payload, but
|
|
541
484
|
// atomicCmd should be able to read it from the installed app
|
|
542
485
|
const payload = message.payload;
|
|
@@ -548,28 +491,46 @@ export class DeviceAgentCloudConnection {
|
|
|
548
491
|
)})!`
|
|
549
492
|
);
|
|
550
493
|
}
|
|
551
|
-
|
|
552
|
-
func: this.handleModelsInstallCloudResponsePayload,
|
|
553
|
-
args: [payload],
|
|
494
|
+
await this.txnMgr.runTransactionStep({
|
|
495
|
+
func: () => this.handleModelsInstallCloudResponsePayload(payload),
|
|
554
496
|
projectId,
|
|
555
|
-
txId
|
|
497
|
+
txId,
|
|
498
|
+
start: false,
|
|
499
|
+
stepName: message.messageType
|
|
556
500
|
});
|
|
557
|
-
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case status_response: {
|
|
504
|
+
const { failure } = keyMirrors.statusResponse;
|
|
505
|
+
if (message.payload.status === failure) {
|
|
558
506
|
this.txnMgr.completeTransaction(txId);
|
|
559
|
-
}
|
|
560
507
|
|
|
508
|
+
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
509
|
+
status: keyMirrors.statusResponse.failure,
|
|
510
|
+
message: message.payload.message
|
|
511
|
+
};
|
|
512
|
+
// Send final status message
|
|
513
|
+
const failureStatusResponseMessage =
|
|
514
|
+
buildToClientStatusResponseMessage(
|
|
515
|
+
this.clientId,
|
|
516
|
+
failureStatusResponsePayload,
|
|
517
|
+
txId
|
|
518
|
+
);
|
|
519
|
+
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
520
|
+
}
|
|
561
521
|
break;
|
|
562
522
|
}
|
|
563
523
|
case keyMirrors.toDeviceAgentMessageType.device_action: {
|
|
564
524
|
try {
|
|
565
|
-
const successStatusResponsePayload =
|
|
566
|
-
keyMirrors.statusResponse.success
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
525
|
+
const successStatusResponsePayload: StatusResponsePayload = {
|
|
526
|
+
status: keyMirrors.statusResponse.success
|
|
527
|
+
};
|
|
528
|
+
const successStatusResponseMessage =
|
|
529
|
+
buildToClientStatusResponseMessage(
|
|
530
|
+
this.clientId,
|
|
531
|
+
successStatusResponsePayload,
|
|
532
|
+
txId
|
|
533
|
+
);
|
|
573
534
|
this.publisher.publishToClient(successStatusResponseMessage);
|
|
574
535
|
|
|
575
536
|
await this.handleDeviceAction(message.payload);
|
|
@@ -577,14 +538,16 @@ export class DeviceAgentCloudConnection {
|
|
|
577
538
|
logger.error(
|
|
578
539
|
`There was a problem performing device action '${message.payload.action}': ${e.message}`
|
|
579
540
|
);
|
|
580
|
-
const failureStatusResponsePayload =
|
|
581
|
-
keyMirrors.statusResponse.failure,
|
|
582
|
-
e.message
|
|
583
|
-
|
|
584
|
-
const failureStatusResponseMessage =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
541
|
+
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
542
|
+
status: keyMirrors.statusResponse.failure,
|
|
543
|
+
message: e.message
|
|
544
|
+
};
|
|
545
|
+
const failureStatusResponseMessage =
|
|
546
|
+
buildToClientStatusResponseMessage(
|
|
547
|
+
this.clientId,
|
|
548
|
+
failureStatusResponsePayload,
|
|
549
|
+
txId
|
|
550
|
+
);
|
|
588
551
|
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
589
552
|
}
|
|
590
553
|
break;
|
|
@@ -609,7 +572,7 @@ export class DeviceAgentCloudConnection {
|
|
|
609
572
|
);
|
|
610
573
|
switch (topic) {
|
|
611
574
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
612
|
-
case this.shadowHandler.shadowTopics.projects.
|
|
575
|
+
case this.shadowHandler.shadowTopics.projects.updateAccepted: {
|
|
613
576
|
const shadowUpdates = await this.shadowHandler.handleShadowTopic({
|
|
614
577
|
topic,
|
|
615
578
|
payload: message.state,
|
|
@@ -620,16 +583,13 @@ export class DeviceAgentCloudConnection {
|
|
|
620
583
|
const projectId = shadowUpdate.projectId;
|
|
621
584
|
const txId = shadowUpdate.txId;
|
|
622
585
|
try {
|
|
623
|
-
this.txnMgr.
|
|
624
|
-
|
|
625
|
-
func: this.handleAppConfigUpdate,
|
|
626
|
-
args: [shadowUpdate, txId],
|
|
586
|
+
await this.txnMgr.runTransactionStep({
|
|
587
|
+
func: () => this.handleAppConfigUpdate(shadowUpdate, txId),
|
|
627
588
|
projectId,
|
|
628
|
-
txId
|
|
589
|
+
txId,
|
|
590
|
+
start: true,
|
|
591
|
+
stepName: topic
|
|
629
592
|
});
|
|
630
|
-
if (completed) {
|
|
631
|
-
this.txnMgr.completeTransaction(txId);
|
|
632
|
-
}
|
|
633
593
|
} catch (e) {
|
|
634
594
|
logger.error(`Error handling shadow message: ${e.message}`);
|
|
635
595
|
}
|
|
@@ -638,7 +598,7 @@ export class DeviceAgentCloudConnection {
|
|
|
638
598
|
break;
|
|
639
599
|
}
|
|
640
600
|
case this.shadowHandler.shadowTopics.projects.getRejected:
|
|
641
|
-
case this.shadowHandler.shadowTopics.projects.
|
|
601
|
+
case this.shadowHandler.shadowTopics.projects.updateDelta:
|
|
642
602
|
case this.shadowHandler.shadowTopics.projects.updateRejected:
|
|
643
603
|
// Not handling these for now
|
|
644
604
|
break;
|
|
@@ -692,9 +652,63 @@ export class DeviceAgentCloudConnection {
|
|
|
692
652
|
|
|
693
653
|
this.device.on('offline', () => {
|
|
694
654
|
logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
|
|
655
|
+
void this.logConnectionInfo();
|
|
695
656
|
});
|
|
696
657
|
}
|
|
697
658
|
|
|
659
|
+
public async logConnectionInfo() {
|
|
660
|
+
try {
|
|
661
|
+
/**
|
|
662
|
+
* We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
|
|
663
|
+
* This command doesn't always exit (see below), so
|
|
664
|
+
* we use timeout to break out of the prompt
|
|
665
|
+
* and catch the resulting error/parse the resulting stderr
|
|
666
|
+
*
|
|
667
|
+
* Sample command for current host and port:
|
|
668
|
+
* nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
669
|
+
*
|
|
670
|
+
* Sample output when port is not blocked and host is reachable:
|
|
671
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
|
|
672
|
+
* Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
|
|
673
|
+
*
|
|
674
|
+
*
|
|
675
|
+
* Sample output when port is blocked (will repeatedly try until ctrl-C out):
|
|
676
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
677
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
678
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
679
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
680
|
+
* ^C
|
|
681
|
+
*
|
|
682
|
+
*
|
|
683
|
+
* Sample command/output when the port isn't enable on that host:
|
|
684
|
+
* $ nc -zv -w 1 localhost 8883
|
|
685
|
+
* nc: connect to localhost port 8883 (tcp) failed: Connection refused
|
|
686
|
+
*/
|
|
687
|
+
await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
|
|
688
|
+
timeout: 2000
|
|
689
|
+
});
|
|
690
|
+
} catch (err) {
|
|
691
|
+
const output = JSON.stringify(err['stderr']);
|
|
692
|
+
if (output.indexOf('not known') !== -1) {
|
|
693
|
+
logger.warn(
|
|
694
|
+
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
695
|
+
);
|
|
696
|
+
} else if (output.indexOf('timed out') !== -1) {
|
|
697
|
+
logger.warn(
|
|
698
|
+
`Internet connection appears fine, however the endpoint was not reachable on the current connection port: ${this.port}\nPlease check if a firewall is in place.`
|
|
699
|
+
);
|
|
700
|
+
} else if (output.indexOf('refused') !== -1) {
|
|
701
|
+
logger.warn(
|
|
702
|
+
`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
|
|
703
|
+
);
|
|
704
|
+
} else {
|
|
705
|
+
logger.warn(
|
|
706
|
+
`Output from checking connection to ${this.host} on ${this.port}: ${output}`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
698
712
|
public async stop() {
|
|
699
713
|
// FIXME: This method is currently only used by the CLI, and shadow messages
|
|
700
714
|
// can be lost since we aren't waiting for responses so sleep for a short
|
|
@@ -3,6 +3,11 @@ import { Publisher } from './publisher';
|
|
|
3
3
|
|
|
4
4
|
global.setTimeout = jest.fn() as unknown as typeof setTimeout;
|
|
5
5
|
|
|
6
|
+
// https://github.com/facebook/react-native/issues/35701
|
|
7
|
+
Object.defineProperty(global, 'performance', {
|
|
8
|
+
writable: true
|
|
9
|
+
});
|
|
10
|
+
|
|
6
11
|
const testTrueToggles = {
|
|
7
12
|
deviceStats: true,
|
|
8
13
|
appState: true
|
|
@@ -66,4 +71,19 @@ describe('Test Live Updates Handler', () => {
|
|
|
66
71
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
67
72
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
68
73
|
});
|
|
74
|
+
|
|
75
|
+
test('timeout turns off live updates', async () => {
|
|
76
|
+
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
77
|
+
|
|
78
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
79
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
80
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
|
|
81
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
82
|
+
|
|
83
|
+
jest.runAllTimers();
|
|
84
|
+
|
|
85
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|
|
86
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
87
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
88
|
+
});
|
|
69
89
|
});
|