@alwaysai/device-agent 1.3.0-1 → 1.3.1-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.
Files changed (115) hide show
  1. package/lib/application-control/environment-variables.d.ts +1 -0
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/application-control/environment-variables.js +22 -20
  4. package/lib/application-control/environment-variables.js.map +1 -1
  5. package/lib/application-control/environment-variables.test.js +37 -2
  6. package/lib/application-control/environment-variables.test.js.map +1 -1
  7. package/lib/application-control/install.js +1 -1
  8. package/lib/application-control/install.js.map +1 -1
  9. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +4 -3
  10. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  11. package/lib/cloud-connection/device-agent-cloud-connection.js +149 -113
  12. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  13. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  14. package/lib/cloud-connection/live-updates-handler.js +30 -25
  15. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  16. package/lib/cloud-connection/live-updates-handler.test.js +15 -0
  17. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  18. package/lib/cloud-connection/messages.d.ts +1 -3
  19. package/lib/cloud-connection/messages.d.ts.map +1 -1
  20. package/lib/cloud-connection/messages.js +1 -9
  21. package/lib/cloud-connection/messages.js.map +1 -1
  22. package/lib/cloud-connection/publisher.d.ts +1 -0
  23. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  24. package/lib/cloud-connection/publisher.js +3 -0
  25. package/lib/cloud-connection/publisher.js.map +1 -1
  26. package/lib/cloud-connection/shadow-handler.d.ts +12 -0
  27. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  28. package/lib/cloud-connection/shadow-handler.js +36 -22
  29. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  30. package/lib/cloud-connection/shadow-handler.test.js +84 -40
  31. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  32. package/lib/cloud-connection/transaction-manager.d.ts +25 -9
  33. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  34. package/lib/cloud-connection/transaction-manager.js +97 -28
  35. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  36. package/lib/cloud-connection/transaction-manager.test.js +169 -22
  37. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  38. package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
  39. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  40. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  41. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  42. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  43. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  44. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  45. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  46. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  47. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  48. package/lib/secure-tunneling/spawner-detached.js +90 -0
  49. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  50. package/lib/subcommands/app/analytics.d.ts +10 -0
  51. package/lib/subcommands/app/analytics.d.ts.map +1 -0
  52. package/lib/subcommands/app/analytics.js +83 -0
  53. package/lib/subcommands/app/analytics.js.map +1 -0
  54. package/lib/subcommands/app/index.d.ts.map +1 -1
  55. package/lib/subcommands/app/index.js +3 -1
  56. package/lib/subcommands/app/index.js.map +1 -1
  57. package/lib/subcommands/app/models.d.ts +0 -5
  58. package/lib/subcommands/app/models.d.ts.map +1 -1
  59. package/lib/subcommands/app/models.js +11 -47
  60. package/lib/subcommands/app/models.js.map +1 -1
  61. package/lib/subcommands/app/status.d.ts +1 -0
  62. package/lib/subcommands/app/status.d.ts.map +1 -1
  63. package/lib/subcommands/app/status.js +14 -3
  64. package/lib/subcommands/app/status.js.map +1 -1
  65. package/lib/subcommands/app/version.d.ts +2 -1
  66. package/lib/subcommands/app/version.d.ts.map +1 -1
  67. package/lib/subcommands/app/version.js +16 -3
  68. package/lib/subcommands/app/version.js.map +1 -1
  69. package/lib/util/cloud-mode-ready.d.ts +1 -0
  70. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  71. package/lib/util/cloud-mode-ready.js +36 -1
  72. package/lib/util/cloud-mode-ready.js.map +1 -1
  73. package/lib/util/parsing.d.ts +2 -0
  74. package/lib/util/parsing.d.ts.map +1 -0
  75. package/lib/util/parsing.js +17 -0
  76. package/lib/util/parsing.js.map +1 -0
  77. package/package.json +4 -6
  78. package/readme.md +146 -92
  79. package/src/application-control/environment-variables.test.ts +43 -3
  80. package/src/application-control/environment-variables.ts +29 -19
  81. package/src/application-control/install.ts +1 -1
  82. package/src/cloud-connection/device-agent-cloud-connection.ts +216 -172
  83. package/src/cloud-connection/live-updates-handler.test.ts +20 -0
  84. package/src/cloud-connection/live-updates-handler.ts +45 -52
  85. package/src/cloud-connection/messages.ts +1 -14
  86. package/src/cloud-connection/publisher.ts +4 -0
  87. package/src/cloud-connection/shadow-handler.test.ts +93 -41
  88. package/src/cloud-connection/shadow-handler.ts +57 -21
  89. package/src/cloud-connection/transaction-manager.test.ts +183 -27
  90. package/src/cloud-connection/transaction-manager.ts +167 -36
  91. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  92. package/src/secure-tunneling/secure-tunneling.ts +606 -0
  93. package/src/secure-tunneling/spawner-detached.ts +107 -0
  94. package/src/subcommands/app/analytics.ts +99 -0
  95. package/src/subcommands/app/index.ts +4 -3
  96. package/src/subcommands/app/models.ts +13 -49
  97. package/src/subcommands/app/status.ts +20 -3
  98. package/src/subcommands/app/version.ts +19 -4
  99. package/src/util/cloud-mode-ready.ts +36 -0
  100. package/src/util/parsing.ts +11 -0
  101. package/lib/cloud-connection/cmd-status.d.ts +0 -8
  102. package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
  103. package/lib/cloud-connection/cmd-status.js +0 -62
  104. package/lib/cloud-connection/cmd-status.js.map +0 -1
  105. package/lib/cloud-connection/message-builder.d.ts +0 -7
  106. package/lib/cloud-connection/message-builder.d.ts.map +0 -1
  107. package/lib/cloud-connection/message-builder.js +0 -63
  108. package/lib/cloud-connection/message-builder.js.map +0 -1
  109. package/lib/secure-tunneling/index.d.ts +0 -5
  110. package/lib/secure-tunneling/index.d.ts.map +0 -1
  111. package/lib/secure-tunneling/index.js +0 -64
  112. package/lib/secure-tunneling/index.js.map +0 -1
  113. package/src/cloud-connection/cmd-status.ts +0 -71
  114. package/src/cloud-connection/message-builder.ts +0 -117
  115. package/src/secure-tunneling/index.ts +0 -74
@@ -1,74 +1,75 @@
1
1
  // eslint-disable-next-line
2
2
  const awsIot = require('aws-iot-device-sdk');
3
- import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
4
- import { existsSync } from 'fs';
5
- import {
6
- BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
7
- AWS_ROOT_CERTIFICATE_FILE_PATH,
8
- BOOTSTRAP_CERTIFICATES_DIR_PATH,
9
- DEVICE_PRIVATE_KEY_FILE_PATH,
10
- DEVICE_CERTIFICATE_FILE_PATH
11
- } from '../util/directories';
12
3
  import {
13
- keyMirrors,
14
- validateToClientMessage,
15
- SignedUrlsRequestPayload,
16
- getToDeviceTopic,
17
4
  AppInstallResponsePayload,
18
- validateToDeviceAgentMessage,
19
- ToDeviceAgentMessage,
20
- ToCloudMessage,
21
5
  AppStateControlPayload,
22
6
  AppVersionControlInstallPayload,
23
7
  AppVersionControlUninstallPayload,
24
- ToClientMessage,
25
- DeviceActionPayload
8
+ DeviceActionPayload,
9
+ ModelsInstallResponsePayload,
10
+ SignedUrlsRequestPayload,
11
+ ToCloudMessage,
12
+ ToDeviceAgentMessage,
13
+ getToDeviceTopic,
14
+ buildSignedUrlsRequestMessage,
15
+ buildToClientStatusResponseMessage,
16
+ StatusResponsePayload,
17
+ keyMirrors,
18
+ validateToDeviceAgentMessage
26
19
  } from '@alwaysai/device-agent-schemas';
27
- import { getDeviceUuid } from '../util/get-device-id';
28
- import { logger } from '../util/logger';
29
- import { cloudModeReady } from '../util/cloud-mode-ready';
30
- import { AgentConfigFile } from '../infrastructure/agent-config';
20
+ import { existsSync } from 'fs';
31
21
  import {
22
+ installApp,
23
+ restartApp,
24
+ setEnv,
32
25
  startApp,
33
26
  stopApp,
34
- restartApp,
35
- updateModelsWithPresignedUrls,
36
- installApp,
37
27
  uninstallApp,
38
28
  updateAppCfg,
39
- setEnv
29
+ updateModelsWithPresignedUrls
40
30
  } from '../application-control';
41
- import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
42
- import { secureTunnelNotifyHandler } from '../secure-tunneling/index';
43
- import { Publisher } from './publisher';
44
- import { LiveUpdatesHandler } from './live-updates-handler';
45
- import { bootstrapProvision } from './bootstrap-provision';
46
- import { CmdStatusManager } from './cmd-status';
47
- import { PassthroughHandler, runChannel } from './passthrough-handler';
31
+ import { createAppBackup, rollbackApp } from '../application-control/backup';
32
+ import { reboot } from '../device-control/device-control';
48
33
  import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
49
- import { getStatusResponsePayload } from './messages';
50
- import { ModelsInstallResponsePayload } from '@alwaysai/device-agent-schemas';
34
+ import { AgentConfigFile } from '../infrastructure/agent-config';
35
+ import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
36
+ import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
37
+ import { cloudModeReady } from '../util/cloud-mode-ready';
38
+ import {
39
+ AWS_ROOT_CERTIFICATE_FILE_PATH,
40
+ BOOTSTRAP_CERTIFICATES_DIR_PATH,
41
+ BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
42
+ DEVICE_CERTIFICATE_FILE_PATH,
43
+ DEVICE_PRIVATE_KEY_FILE_PATH
44
+ } from '../util/directories';
45
+ import { getDeviceUuid } from '../util/get-device-id';
46
+ import { logger } from '../util/logger';
51
47
  import sleep from '../util/sleep';
52
- import { createAppBackup, rollbackApp } from '../application-control/backup';
48
+ import { bootstrapProvision } from './bootstrap-provision';
49
+ import { LiveUpdatesHandler } from './live-updates-handler';
50
+ import { PassthroughHandler, runChannel } from './passthrough-handler';
51
+ import { Publisher } from './publisher';
52
+ import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
53
53
  import { TransactionManager } from './transaction-manager';
54
- import {
55
- buildSignedUrlsRequestMessage,
56
- buildStatusResponseMessage
57
- } from './message-builder';
58
- import { reboot } from '../device-control/device-control';
54
+ import { exec } from 'child_process';
55
+ import { promisify } from 'util';
56
+
57
+ const exec_promise = promisify(exec);
59
58
 
60
59
  export class DeviceAgentCloudConnection {
61
60
  private shadowHandler: ShadowHandler;
62
61
  public publisher: Publisher;
63
- private cmdStatusMgr: CmdStatusManager;
64
62
  private liveUpdatesHandler: LiveUpdatesHandler;
65
63
  private txnMgr: TransactionManager;
66
64
  private device = awsIot.device;
67
65
 
68
66
  private clientId = getDeviceUuid();
69
67
  private host = getIoTCoreEndpointUrl();
68
+ private port = 8883;
70
69
  private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
71
70
  private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
71
+ private readonly secureTunnelHandler =
72
+ SecureTunnelHandlerSingleton.getInstance();
72
73
  // FIXME: Add support for multiple simultaneous project updates
73
74
  private appCfgUpdateQueue: ShadowUpdate[] = [];
74
75
 
@@ -106,7 +107,8 @@ export class DeviceAgentCloudConnection {
106
107
  appReleaseHash
107
108
  }
108
109
  };
109
- const message = await buildSignedUrlsRequestMessage(
110
+ const message = buildSignedUrlsRequestMessage(
111
+ this.clientId,
110
112
  signedUrlsRequestPayload,
111
113
  txId
112
114
  );
@@ -272,62 +274,6 @@ export class DeviceAgentCloudConnection {
272
274
  }
273
275
  }
274
276
 
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
277
  private handleAppConfigUpdate = async (
332
278
  update: ShadowUpdate,
333
279
  txId: string
@@ -353,7 +299,8 @@ export class DeviceAgentCloudConnection {
353
299
  models: updatedModels
354
300
  }
355
301
  };
356
- const message = await buildSignedUrlsRequestMessage(
302
+ const message = buildSignedUrlsRequestMessage(
303
+ this.clientId,
357
304
  modelsOnlyUrlsRequestPayload,
358
305
  txId
359
306
  );
@@ -397,17 +344,19 @@ export class DeviceAgentCloudConnection {
397
344
  caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
398
345
  clientId: this.clientId,
399
346
  host: this.host,
400
- port: 8883,
347
+ port: this.port,
401
348
  keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
402
349
  });
403
350
  this.publisher = new Publisher(this.device, this.clientId);
404
351
  this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
405
- this.cmdStatusMgr = new CmdStatusManager();
406
352
  this.liveUpdatesHandler = new LiveUpdatesHandler(
407
353
  this.publisher,
408
354
  this.clientId
409
355
  );
410
- this.txnMgr = new TransactionManager();
356
+ this.txnMgr = new TransactionManager(
357
+ this.publisher,
358
+ this.liveUpdatesHandler
359
+ );
411
360
 
412
361
  this.subscribe(this.toDeviceTopic);
413
362
  this.subscribe(this.secureTunnelNotifyTopic);
@@ -416,6 +365,8 @@ export class DeviceAgentCloudConnection {
416
365
  this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
417
366
  this.subscribe(this.shadowHandler.shadowTopics.projects.updateAccepted);
418
367
  this.subscribe(this.shadowHandler.shadowTopics.projects.updateRejected);
368
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
369
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
419
370
  }
420
371
 
421
372
  public getClientId(): string {
@@ -431,7 +382,7 @@ export class DeviceAgentCloudConnection {
431
382
  }
432
383
 
433
384
  public isCmdInProgress(projectId: string): boolean {
434
- return this.cmdStatusMgr.isCmdInProgress(projectId);
385
+ return this.txnMgr.isOngoingTransactionForProjectID(projectId);
435
386
  }
436
387
 
437
388
  public async updateProjectShadow(projectId: string) {
@@ -458,23 +409,29 @@ export class DeviceAgentCloudConnection {
458
409
  return;
459
410
  }
460
411
  const txId = message.txId;
412
+ const {
413
+ app_state_control,
414
+ app_version_control,
415
+ live_state_updates,
416
+ app_install_response,
417
+ models_install_response,
418
+ status_response,
419
+ device_action
420
+ } = keyMirrors.toDeviceAgentMessageType;
461
421
  switch (message.messageType) {
462
- case keyMirrors.toDeviceAgentMessageType.app_state_control: {
422
+ case app_state_control: {
463
423
  // txId sent from cloud, just need to continue it
464
424
  const payload = message.payload;
465
425
  const projectId = payload.projectId;
466
426
 
467
427
  try {
468
- this.txnMgr.addTransaction(txId, projectId, payload.baseCommand);
469
- const completed = await this.atomicCmd({
470
- func: this.handleAppStateControl,
471
- args: [message.payload],
428
+ await this.txnMgr.runTransactionStep({
429
+ func: () => this.handleAppStateControl(message.payload),
472
430
  projectId,
473
- txId
431
+ txId,
432
+ start: true,
433
+ stepName: payload.baseCommand
474
434
  });
475
- if (completed) {
476
- this.txnMgr.completeTransaction(txId);
477
- }
478
435
  } catch (e) {
479
436
  logger.error(
480
437
  `Error processing application state control request: ${e}!`
@@ -483,40 +440,33 @@ export class DeviceAgentCloudConnection {
483
440
 
484
441
  break;
485
442
  }
486
- case keyMirrors.toDeviceAgentMessageType.app_version_control: {
443
+ case app_version_control: {
487
444
  // txId sent from cloud, just need to continue it
488
445
  const payload = message.payload;
489
446
  const projectId = payload.projectId;
490
- const appReleaseHash =
491
- payload.baseCommand === keyMirrors.appVersionControl.install
492
- ? payload.appReleaseHash
493
- : undefined;
494
447
  try {
495
- this.txnMgr.addTransaction(txId, projectId, payload.baseCommand);
496
- const completed = await this.atomicCmd({
497
- func: this.handleAppVersionControl,
498
- args: [payload, txId],
448
+ await this.txnMgr.runTransactionStep({
449
+ func: () => this.handleAppVersionControl(payload, txId),
499
450
  projectId,
500
- txId
451
+ txId,
452
+ start: true,
453
+ stepName: payload.baseCommand
501
454
  });
502
- if (completed) {
503
- this.txnMgr.completeTransaction(txId);
504
- }
505
455
  } catch (e) {
506
456
  logger.error(`Error processing application install request: ${e}!`);
507
457
  }
508
458
 
509
459
  break;
510
460
  }
511
- case keyMirrors.toDeviceAgentMessageType.live_state_updates: {
461
+ case live_state_updates: {
512
462
  const payload = message.payload;
513
463
  // TODO: Send response?
514
- await this.liveUpdatesHandler.handleToggles(payload, txId);
464
+ void this.liveUpdatesHandler.handleToggles(payload, txId);
515
465
  break;
516
466
  }
517
- case keyMirrors.toDeviceAgentMessageType.app_install_response: {
467
+ case app_install_response: {
518
468
  const payload = message.payload;
519
- const { projectId, appReleaseHash } = payload.appInstallResponse;
469
+ const { projectId } = payload.appInstallResponse;
520
470
  if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
521
471
  throw new Error(
522
472
  `App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
@@ -524,19 +474,16 @@ export class DeviceAgentCloudConnection {
524
474
  )})!`
525
475
  );
526
476
  }
527
- const completed = await this.atomicCmd({
528
- func: this.handleAppInstallCloudResponsePayload,
529
- args: [payload],
477
+ await this.txnMgr.runTransactionStep({
478
+ func: () => this.handleAppInstallCloudResponsePayload(payload),
530
479
  projectId,
531
- txId
480
+ txId,
481
+ start: false,
482
+ stepName: message.messageType
532
483
  });
533
- if (completed) {
534
- this.txnMgr.completeTransaction(txId);
535
- }
536
-
537
484
  break;
538
485
  }
539
- case keyMirrors.toDeviceAgentMessageType.models_install_response: {
486
+ case models_install_response: {
540
487
  // This message doesn't have appReleaseHash in it's payload, but
541
488
  // atomicCmd should be able to read it from the installed app
542
489
  const payload = message.payload;
@@ -548,43 +495,73 @@ export class DeviceAgentCloudConnection {
548
495
  )})!`
549
496
  );
550
497
  }
551
- const completed = await this.atomicCmd({
552
- func: this.handleModelsInstallCloudResponsePayload,
553
- args: [payload],
498
+ await this.txnMgr.runTransactionStep({
499
+ func: () => this.handleModelsInstallCloudResponsePayload(payload),
554
500
  projectId,
555
- txId
501
+ txId,
502
+ start: false,
503
+ stepName: message.messageType
556
504
  });
557
- if (completed) {
505
+ break;
506
+ }
507
+ case status_response: {
508
+ const { failure } = keyMirrors.statusResponse;
509
+ if (message.payload.status === failure) {
558
510
  this.txnMgr.completeTransaction(txId);
559
- }
560
511
 
512
+ const failureStatusResponsePayload: StatusResponsePayload = {
513
+ status: keyMirrors.statusResponse.failure,
514
+ message: message.payload.message
515
+ };
516
+ // Send final status message
517
+ const failureStatusResponseMessage =
518
+ buildToClientStatusResponseMessage(
519
+ this.clientId,
520
+ failureStatusResponsePayload,
521
+ txId
522
+ );
523
+ this.publisher.publishToClient(failureStatusResponseMessage);
524
+ }
561
525
  break;
562
526
  }
563
- case keyMirrors.toDeviceAgentMessageType.device_action: {
527
+ case device_action: {
564
528
  try {
565
- const successStatusResponsePayload = await getStatusResponsePayload(
566
- keyMirrors.statusResponse.success,
567
- ''
568
- );
569
- const successStatusResponseMessage = await buildStatusResponseMessage(
570
- successStatusResponsePayload,
529
+ const statusResponsePayload: StatusResponsePayload = {
530
+ status: keyMirrors.statusResponse.in_progress
531
+ };
532
+ const statusResponseMessage = buildToClientStatusResponseMessage(
533
+ this.clientId,
534
+ statusResponsePayload,
571
535
  txId
572
536
  );
573
- this.publisher.publishToClient(successStatusResponseMessage);
537
+ this.publisher.publishToClient(statusResponseMessage);
574
538
 
575
539
  await this.handleDeviceAction(message.payload);
540
+
541
+ const successStatusResponsePayload: StatusResponsePayload = {
542
+ status: keyMirrors.statusResponse.success
543
+ };
544
+ const successStatusResponseMessage =
545
+ buildToClientStatusResponseMessage(
546
+ this.clientId,
547
+ successStatusResponsePayload,
548
+ txId
549
+ );
550
+ this.publisher.publishToClient(successStatusResponseMessage);
576
551
  } catch (e) {
577
552
  logger.error(
578
553
  `There was a problem performing device action '${message.payload.action}': ${e.message}`
579
554
  );
580
- const failureStatusResponsePayload = await getStatusResponsePayload(
581
- keyMirrors.statusResponse.failure,
582
- e.message
583
- );
584
- const failureStatusResponseMessage = await buildStatusResponseMessage(
585
- failureStatusResponsePayload,
586
- txId
587
- );
555
+ const failureStatusResponsePayload: StatusResponsePayload = {
556
+ status: keyMirrors.statusResponse.failure,
557
+ message: e.message
558
+ };
559
+ const failureStatusResponseMessage =
560
+ buildToClientStatusResponseMessage(
561
+ this.clientId,
562
+ failureStatusResponsePayload,
563
+ txId
564
+ );
588
565
  this.publisher.publishToClient(failureStatusResponseMessage);
589
566
  }
590
567
  break;
@@ -609,7 +586,7 @@ export class DeviceAgentCloudConnection {
609
586
  );
610
587
  switch (topic) {
611
588
  case this.shadowHandler.shadowTopics.projects.getAccepted:
612
- case this.shadowHandler.shadowTopics.projects.updateDelta: {
589
+ case this.shadowHandler.shadowTopics.projects.updateAccepted: {
613
590
  const shadowUpdates = await this.shadowHandler.handleShadowTopic({
614
591
  topic,
615
592
  payload: message.state,
@@ -620,16 +597,13 @@ export class DeviceAgentCloudConnection {
620
597
  const projectId = shadowUpdate.projectId;
621
598
  const txId = shadowUpdate.txId;
622
599
  try {
623
- this.txnMgr.addTransaction(txId, projectId, topic);
624
- const completed = await this.atomicCmd({
625
- func: this.handleAppConfigUpdate,
626
- args: [shadowUpdate, txId],
600
+ await this.txnMgr.runTransactionStep({
601
+ func: () => this.handleAppConfigUpdate(shadowUpdate, txId),
627
602
  projectId,
628
- txId
603
+ txId,
604
+ start: true,
605
+ stepName: topic
629
606
  });
630
- if (completed) {
631
- this.txnMgr.completeTransaction(txId);
632
- }
633
607
  } catch (e) {
634
608
  logger.error(`Error handling shadow message: ${e.message}`);
635
609
  }
@@ -638,7 +612,7 @@ export class DeviceAgentCloudConnection {
638
612
  break;
639
613
  }
640
614
  case this.shadowHandler.shadowTopics.projects.getRejected:
641
- case this.shadowHandler.shadowTopics.projects.updateAccepted:
615
+ case this.shadowHandler.shadowTopics.projects.updateDelta:
642
616
  case this.shadowHandler.shadowTopics.projects.updateRejected:
643
617
  // Not handling these for now
644
618
  break;
@@ -650,8 +624,24 @@ export class DeviceAgentCloudConnection {
650
624
  break;
651
625
 
652
626
  case this.secureTunnelNotifyTopic:
653
- await secureTunnelNotifyHandler(message);
627
+ await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
628
+ break;
629
+ case this.shadowHandler.shadowTopics.secureTunnel.updateDelta: {
630
+ logger.info(`Received secure tunnel update: ${message}`);
631
+ const reported = await this.secureTunnelHandler.syncShadowToDeviceState(
632
+ message
633
+ );
634
+ this.publisher.publish(
635
+ this.shadowHandler.shadowTopics.secureTunnel.update,
636
+ JSON.stringify({ state: { reported } })
637
+ );
638
+ break;
639
+ }
640
+ case this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted: {
641
+ logger.info(`Received secure tunnel deleteAccepted: ${message}`);
642
+ await this.secureTunnelHandler.destroy();
654
643
  break;
644
+ }
655
645
  default:
656
646
  logger.error(`Unexpected topic, ignoring! ${topic}`);
657
647
  }
@@ -692,9 +682,63 @@ export class DeviceAgentCloudConnection {
692
682
 
693
683
  this.device.on('offline', () => {
694
684
  logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
685
+ void this.logConnectionInfo();
695
686
  });
696
687
  }
697
688
 
689
+ public async logConnectionInfo() {
690
+ try {
691
+ /**
692
+ * We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
693
+ * This command doesn't always exit (see below), so
694
+ * we use timeout to break out of the prompt
695
+ * and catch the resulting error/parse the resulting stderr
696
+ *
697
+ * Sample command for current host and port:
698
+ * nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
699
+ *
700
+ * Sample output when port is not blocked and host is reachable:
701
+ * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
702
+ * Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
703
+ *
704
+ *
705
+ * Sample output when port is blocked (will repeatedly try until ctrl-C out):
706
+ * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
707
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
708
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
709
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
710
+ * ^C
711
+ *
712
+ *
713
+ * Sample command/output when the port isn't enable on that host:
714
+ * $ nc -zv -w 1 localhost 8883
715
+ * nc: connect to localhost port 8883 (tcp) failed: Connection refused
716
+ */
717
+ await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
718
+ timeout: 2000
719
+ });
720
+ } catch (err) {
721
+ const output = JSON.stringify(err['stderr']);
722
+ if (output.indexOf('not known') !== -1) {
723
+ logger.warn(
724
+ 'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
725
+ );
726
+ } else if (output.indexOf('timed out') !== -1) {
727
+ logger.warn(
728
+ `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.`
729
+ );
730
+ } else if (output.indexOf('refused') !== -1) {
731
+ logger.warn(
732
+ `The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
733
+ );
734
+ } else {
735
+ logger.warn(
736
+ `Output from checking connection to ${this.host} on ${this.port}: ${output}`
737
+ );
738
+ }
739
+ }
740
+ }
741
+
698
742
  public async stop() {
699
743
  // FIXME: This method is currently only used by the CLI, and shadow messages
700
744
  // 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
  });