@alwaysai/device-agent 1.3.0 → 1.3.1-2

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 (123) 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 +5 -5
  10. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  11. package/lib/cloud-connection/device-agent-cloud-connection.js +203 -178
  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 +10 -21
  27. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  28. package/lib/cloud-connection/shadow-handler.js +154 -100
  29. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  30. package/lib/cloud-connection/shadow-handler.test.js +140 -72
  31. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  32. package/lib/cloud-connection/transaction-manager.d.ts +26 -6
  33. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  34. package/lib/cloud-connection/transaction-manager.js +103 -22
  35. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  36. package/lib/cloud-connection/transaction-manager.test.js +179 -13
  37. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  38. package/lib/device-control/device-control.d.ts +2 -2
  39. package/lib/device-control/device-control.d.ts.map +1 -1
  40. package/lib/device-control/device-control.js.map +1 -1
  41. package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
  42. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  43. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  44. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  45. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  46. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  47. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  48. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  49. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  50. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  51. package/lib/secure-tunneling/spawner-detached.js +107 -0
  52. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  53. package/lib/subcommands/app/analytics.d.ts +10 -0
  54. package/lib/subcommands/app/analytics.d.ts.map +1 -0
  55. package/lib/subcommands/app/analytics.js +79 -0
  56. package/lib/subcommands/app/analytics.js.map +1 -0
  57. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  58. package/lib/subcommands/app/env-vars.js +11 -16
  59. package/lib/subcommands/app/env-vars.js.map +1 -1
  60. package/lib/subcommands/app/index.d.ts.map +1 -1
  61. package/lib/subcommands/app/index.js +3 -1
  62. package/lib/subcommands/app/index.js.map +1 -1
  63. package/lib/subcommands/app/models.d.ts +0 -5
  64. package/lib/subcommands/app/models.d.ts.map +1 -1
  65. package/lib/subcommands/app/models.js +16 -56
  66. package/lib/subcommands/app/models.js.map +1 -1
  67. package/lib/subcommands/app/status.d.ts +1 -0
  68. package/lib/subcommands/app/status.d.ts.map +1 -1
  69. package/lib/subcommands/app/status.js +14 -3
  70. package/lib/subcommands/app/status.js.map +1 -1
  71. package/lib/subcommands/app/version.d.ts +2 -1
  72. package/lib/subcommands/app/version.d.ts.map +1 -1
  73. package/lib/subcommands/app/version.js +16 -3
  74. package/lib/subcommands/app/version.js.map +1 -1
  75. package/lib/util/cloud-mode-ready.d.ts +1 -0
  76. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  77. package/lib/util/cloud-mode-ready.js +36 -1
  78. package/lib/util/cloud-mode-ready.js.map +1 -1
  79. package/lib/util/parsing.d.ts +2 -0
  80. package/lib/util/parsing.d.ts.map +1 -0
  81. package/lib/util/parsing.js +17 -0
  82. package/lib/util/parsing.js.map +1 -0
  83. package/package.json +4 -6
  84. package/readme.md +146 -92
  85. package/src/application-control/environment-variables.test.ts +43 -3
  86. package/src/application-control/environment-variables.ts +29 -19
  87. package/src/application-control/install.ts +1 -1
  88. package/src/cloud-connection/device-agent-cloud-connection.ts +272 -247
  89. package/src/cloud-connection/live-updates-handler.test.ts +20 -0
  90. package/src/cloud-connection/live-updates-handler.ts +45 -52
  91. package/src/cloud-connection/messages.ts +1 -14
  92. package/src/cloud-connection/publisher.ts +4 -0
  93. package/src/cloud-connection/shadow-handler.test.ts +150 -73
  94. package/src/cloud-connection/shadow-handler.ts +247 -126
  95. package/src/cloud-connection/transaction-manager.test.ts +193 -18
  96. package/src/cloud-connection/transaction-manager.ts +174 -26
  97. package/src/device-control/device-control.ts +3 -3
  98. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  99. package/src/secure-tunneling/secure-tunneling.ts +606 -0
  100. package/src/secure-tunneling/spawner-detached.ts +123 -0
  101. package/src/subcommands/app/analytics.ts +102 -0
  102. package/src/subcommands/app/env-vars.ts +18 -16
  103. package/src/subcommands/app/index.ts +4 -3
  104. package/src/subcommands/app/models.ts +25 -57
  105. package/src/subcommands/app/status.ts +20 -3
  106. package/src/subcommands/app/version.ts +19 -4
  107. package/src/util/cloud-mode-ready.ts +36 -0
  108. package/src/util/parsing.ts +11 -0
  109. package/lib/cloud-connection/cmd-status.d.ts +0 -8
  110. package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
  111. package/lib/cloud-connection/cmd-status.js +0 -62
  112. package/lib/cloud-connection/cmd-status.js.map +0 -1
  113. package/lib/cloud-connection/message-builder.d.ts +0 -7
  114. package/lib/cloud-connection/message-builder.d.ts.map +0 -1
  115. package/lib/cloud-connection/message-builder.js +0 -63
  116. package/lib/cloud-connection/message-builder.js.map +0 -1
  117. package/lib/secure-tunneling/index.d.ts +0 -5
  118. package/lib/secure-tunneling/index.d.ts.map +0 -1
  119. package/lib/secure-tunneling/index.js +0 -64
  120. package/lib/secure-tunneling/index.js.map +0 -1
  121. package/src/cloud-connection/cmd-status.ts +0 -71
  122. package/src/cloud-connection/message-builder.ts +0 -117
  123. package/src/secure-tunneling/index.ts +0 -74
@@ -3,29 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runDeviceAgentCloudInterface = exports.DeviceAgentCloudConnection = void 0;
4
4
  // eslint-disable-next-line
5
5
  const awsIot = require('aws-iot-device-sdk');
6
- const urls_1 = require("../infrastructure/urls");
6
+ const device_agent_schemas_1 = require("@alwaysai/device-agent-schemas");
7
7
  const fs_1 = require("fs");
8
+ const application_control_1 = require("../application-control");
9
+ const backup_1 = require("../application-control/backup");
10
+ const device_control_1 = require("../device-control/device-control");
11
+ const environment_1 = require("../environment");
12
+ const agent_config_1 = require("../infrastructure/agent-config");
13
+ const urls_1 = require("../infrastructure/urls");
14
+ const secure_tunneling_1 = require("../secure-tunneling/secure-tunneling");
15
+ const cloud_mode_ready_1 = require("../util/cloud-mode-ready");
8
16
  const directories_1 = require("../util/directories");
9
- const device_agent_schemas_1 = require("@alwaysai/device-agent-schemas");
10
17
  const get_device_id_1 = require("../util/get-device-id");
11
18
  const logger_1 = require("../util/logger");
12
- const cloud_mode_ready_1 = require("../util/cloud-mode-ready");
13
- const agent_config_1 = require("../infrastructure/agent-config");
14
- const application_control_1 = require("../application-control");
15
- const shadow_handler_1 = require("./shadow-handler");
16
- const index_1 = require("../secure-tunneling/index");
17
- const publisher_1 = require("./publisher");
18
- const live_updates_handler_1 = require("./live-updates-handler");
19
+ const sleep_1 = require("../util/sleep");
19
20
  const bootstrap_provision_1 = require("./bootstrap-provision");
20
- const cmd_status_1 = require("./cmd-status");
21
+ const live_updates_handler_1 = require("./live-updates-handler");
21
22
  const passthrough_handler_1 = require("./passthrough-handler");
22
- const environment_1 = require("../environment");
23
- const messages_1 = require("./messages");
24
- const sleep_1 = require("../util/sleep");
25
- const backup_1 = require("../application-control/backup");
23
+ const publisher_1 = require("./publisher");
24
+ const shadow_handler_1 = require("./shadow-handler");
26
25
  const transaction_manager_1 = require("./transaction-manager");
27
- const message_builder_1 = require("./message-builder");
28
- const device_control_1 = require("../device-control/device-control");
26
+ const child_process_1 = require("child_process");
27
+ const util_1 = require("util");
28
+ const exec_promise = (0, util_1.promisify)(child_process_1.exec);
29
29
  class DeviceAgentCloudConnection {
30
30
  /*=================================================================
31
31
  Public interface
@@ -34,8 +34,10 @@ class DeviceAgentCloudConnection {
34
34
  this.device = awsIot.device;
35
35
  this.clientId = (0, get_device_id_1.getDeviceUuid)();
36
36
  this.host = (0, urls_1.getIoTCoreEndpointUrl)();
37
+ this.port = 8883;
37
38
  this.toDeviceTopic = (0, device_agent_schemas_1.getToDeviceTopic)(this.clientId);
38
39
  this.secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
40
+ this.secureTunnelHandler = secure_tunneling_1.SecureTunnelHandlerSingleton.getInstance();
39
41
  // FIXME: Add support for multiple simultaneous project updates
40
42
  this.appCfgUpdateQueue = [];
41
43
  this.handleAppStateControl = async (payload) => {
@@ -63,7 +65,7 @@ class DeviceAgentCloudConnection {
63
65
  appReleaseHash
64
66
  }
65
67
  };
66
- const message = await (0, message_builder_1.buildSignedUrlsRequestMessage)(signedUrlsRequestPayload, txId);
68
+ const message = (0, device_agent_schemas_1.buildSignedUrlsRequestMessage)(this.clientId, signedUrlsRequestPayload, txId);
67
69
  await this.publishCloudRequest(message);
68
70
  return false;
69
71
  }
@@ -83,7 +85,10 @@ class DeviceAgentCloudConnection {
83
85
  appInstallPayload,
84
86
  modelsInstallPayload
85
87
  };
86
- await this.atomicApplicationUpdate(application_control_1.installApp, [{ projectId, appReleaseHash, signedUrlsPayload }], projectId);
88
+ await this.atomicApplicationUpdate(async () => {
89
+ await this.shadowHandler.clearProjectShadow(projectId);
90
+ await (0, application_control_1.installApp)({ projectId, appReleaseHash, signedUrlsPayload });
91
+ }, projectId);
87
92
  return true;
88
93
  };
89
94
  this.handleModelsInstallCloudResponsePayload = async (payload) => {
@@ -94,20 +99,18 @@ class DeviceAgentCloudConnection {
94
99
  const { appCfgUpdate, envVarUpdate } = update;
95
100
  const projectId = payload.modelsInstallResponse.projectId;
96
101
  if (appCfgUpdate) {
97
- await this.atomicApplicationUpdate(application_control_1.updateModelsWithPresignedUrls, [
98
- {
99
- projectId,
100
- modelInstallPayloads: payload.modelsInstallResponse.newModels,
101
- newAppCfg: appCfgUpdate.newAppCfg
102
- }
103
- ], projectId);
102
+ await this.atomicApplicationUpdate(async () => await (0, application_control_1.updateModelsWithPresignedUrls)({
103
+ projectId,
104
+ modelInstallPayloads: payload.modelsInstallResponse.newModels,
105
+ newAppCfg: appCfgUpdate.newAppCfg
106
+ }), projectId);
104
107
  }
105
108
  if (envVarUpdate) {
106
- await this.atomicApplicationUpdate(application_control_1.setEnv, [{ projectId, envVars: envVarUpdate.envVars }], projectId);
109
+ await this.atomicApplicationUpdate(async () => await (0, application_control_1.setEnv)({ projectId, envVars: envVarUpdate.envVars }), projectId);
107
110
  }
108
111
  return true;
109
112
  };
110
- this.handleAppConfigUpdate = async (update, txId) => {
113
+ this.handleProjectShadowConfigUpdate = async (update, txId) => {
111
114
  const { projectId, appCfgUpdate, envVarUpdate } = update;
112
115
  if (appCfgUpdate &&
113
116
  appCfgUpdate.updatedModels &&
@@ -121,21 +124,19 @@ class DeviceAgentCloudConnection {
121
124
  models: updatedModels
122
125
  }
123
126
  };
124
- const message = await (0, message_builder_1.buildSignedUrlsRequestMessage)(modelsOnlyUrlsRequestPayload, txId);
127
+ const message = (0, device_agent_schemas_1.buildSignedUrlsRequestMessage)(this.clientId, modelsOnlyUrlsRequestPayload, txId);
125
128
  this.publisher.publishToCloud(message);
126
129
  this.appCfgUpdateQueue.push(update);
127
130
  return false;
128
131
  }
129
132
  if (appCfgUpdate) {
130
- await this.atomicApplicationUpdate(application_control_1.updateAppCfg, [
131
- {
132
- projectId,
133
- newAppCfg: appCfgUpdate.newAppCfg
134
- }
135
- ], projectId);
133
+ await this.atomicApplicationUpdate(async () => await (0, application_control_1.updateAppCfg)({
134
+ projectId,
135
+ newAppCfg: appCfgUpdate.newAppCfg
136
+ }), projectId);
136
137
  }
137
138
  if (envVarUpdate) {
138
- await this.atomicApplicationUpdate(application_control_1.setEnv, [{ projectId, envVars: envVarUpdate.envVars }], projectId);
139
+ await this.atomicApplicationUpdate(async () => await (0, application_control_1.setEnv)({ projectId, envVars: envVarUpdate.envVars }), projectId);
139
140
  }
140
141
  return true;
141
142
  };
@@ -145,21 +146,20 @@ class DeviceAgentCloudConnection {
145
146
  caPath: directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
146
147
  clientId: this.clientId,
147
148
  host: this.host,
148
- port: 8883,
149
+ port: this.port,
149
150
  keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
150
151
  });
151
152
  this.publisher = new publisher_1.Publisher(this.device, this.clientId);
152
153
  this.shadowHandler = new shadow_handler_1.ShadowHandler(this.clientId, this.publisher);
153
- this.cmdStatusMgr = new cmd_status_1.CmdStatusManager();
154
154
  this.liveUpdatesHandler = new live_updates_handler_1.LiveUpdatesHandler(this.publisher, this.clientId);
155
- this.txnMgr = new transaction_manager_1.TransactionManager();
155
+ this.txnMgr = new transaction_manager_1.TransactionManager(this.publisher, this.liveUpdatesHandler);
156
156
  this.subscribe(this.toDeviceTopic);
157
157
  this.subscribe(this.secureTunnelNotifyTopic);
158
- this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
159
- this.subscribe(this.shadowHandler.shadowTopics.projects.getRejected);
160
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
161
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateAccepted);
162
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateRejected);
158
+ for (const topic of this.shadowHandler.projectShadowTopics) {
159
+ this.subscribe(topic);
160
+ }
161
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
162
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
163
163
  }
164
164
  async handleDeviceAction(payload) {
165
165
  const { system_restart } = device_agent_schemas_1.keyMirrors.deviceAction;
@@ -183,7 +183,7 @@ class DeviceAgentCloudConnection {
183
183
  async atomicApplicationUninstall(projectId) {
184
184
  try {
185
185
  await (0, application_control_1.uninstallApp)({ projectId });
186
- this.shadowHandler.clearAppConfig(projectId);
186
+ await this.shadowHandler.clearProjectShadow(projectId);
187
187
  }
188
188
  catch (e) {
189
189
  logger_1.logger.error(`Failed to uninstall ${projectId}: ${e.message}`);
@@ -191,7 +191,7 @@ class DeviceAgentCloudConnection {
191
191
  }
192
192
  }
193
193
  // eslint-disable-next-line
194
- async atomicApplicationUpdate(func, args, projectId) {
194
+ async atomicApplicationUpdate(func, projectId) {
195
195
  // First try to create a backup, so that there is one available if something goes wrong in the next try:catch.
196
196
  if (await (0, agent_config_1.AgentConfigFile)().isAppPresent({ projectId })) {
197
197
  try {
@@ -202,8 +202,7 @@ class DeviceAgentCloudConnection {
202
202
  }
203
203
  }
204
204
  try {
205
- const out = await func(...args);
206
- this.shadowHandler.clearAppConfig(projectId);
205
+ const out = await func();
207
206
  await this.shadowHandler.updateProjectShadow(projectId);
208
207
  return out;
209
208
  }
@@ -217,10 +216,10 @@ class DeviceAgentCloudConnection {
217
216
  catch (errorRollbackApp) {
218
217
  // and if that fails, uninstall the app as a last resort.
219
218
  try {
220
- await (0, application_control_1.uninstallApp)({ projectId });
219
+ await this.atomicApplicationUninstall(projectId);
221
220
  }
222
- finally {
223
- this.shadowHandler.clearAppConfig(projectId);
221
+ catch (_a) {
222
+ // atomicApplicationUninstall handles failing, so there's nothing to handle here.
224
223
  }
225
224
  logger_1.logger.error(`Application update failed, rolled back to previous version: ${errorAppUpdate}`);
226
225
  throw new Error(`Application update and rollback failed, uninstalled the application: ${errorAppUpdate}`);
@@ -228,40 +227,29 @@ class DeviceAgentCloudConnection {
228
227
  throw new Error(`Application update failed, rolled the application back: ${errorAppUpdate}`);
229
228
  }
230
229
  }
231
- // eslint-disable-next-line
232
- async atomicCmd(props) {
233
- const { func, args, projectId, txId } = props;
234
- try {
235
- await this.cmdStatusMgr.start(projectId);
236
- await this.liveUpdatesHandler.enableTransactionStatus({
237
- txId
238
- });
239
- const completed = await func(...args);
240
- if (completed) {
241
- await this.cmdStatusMgr.stop(projectId);
242
- await this.liveUpdatesHandler.disableTransactionStatus({
243
- txId
244
- });
245
- const successStatusResponsePayload = await (0, messages_1.getStatusResponsePayload)(device_agent_schemas_1.keyMirrors.statusResponse.success, '');
246
- // Send final status message
247
- const message = await (0, message_builder_1.buildStatusResponseMessage)(successStatusResponsePayload, txId);
248
- this.publisher.publishToClient(message);
230
+ async handleProjectShadowMessage(topic, message) {
231
+ const shadowUpdates = await this.shadowHandler.handleProjectShadow({
232
+ topic,
233
+ payload: message,
234
+ clientToken: message.clientToken
235
+ });
236
+ if (shadowUpdates.length) {
237
+ for (const shadowUpdate of shadowUpdates) {
238
+ const projectId = shadowUpdate.projectId;
239
+ const txId = shadowUpdate.txId;
240
+ try {
241
+ await this.txnMgr.runTransactionStep({
242
+ func: () => this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
243
+ projectId,
244
+ txId,
245
+ start: true,
246
+ stepName: topic
247
+ });
248
+ }
249
+ catch (e) {
250
+ logger_1.logger.error(`Error handling shadow message: ${e.message}`);
251
+ }
249
252
  }
250
- return completed;
251
- }
252
- catch (e) {
253
- logger_1.logger.error(`Failed to execute cmd for ${projectId}:\n${e.message}\n${e.stack}`);
254
- const message = e.message;
255
- // uninstall the failed app to put system back in good state
256
- await this.cmdStatusMgr.stop(projectId);
257
- await this.liveUpdatesHandler.disableTransactionStatus({
258
- txId
259
- });
260
- const failureStatusResponsePayload = await (0, messages_1.getStatusResponsePayload)(device_agent_schemas_1.keyMirrors.statusResponse.failure, message);
261
- // Send final status message
262
- const failureStatusResponseMessage = await (0, message_builder_1.buildStatusResponseMessage)(failureStatusResponsePayload, txId);
263
- this.publisher.publishToClient(failureStatusResponseMessage);
264
- return true;
265
253
  }
266
254
  }
267
255
  getClientId() {
@@ -270,11 +258,8 @@ class DeviceAgentCloudConnection {
270
258
  getToDeviceTopic() {
271
259
  return this.toDeviceTopic;
272
260
  }
273
- getShadowTopics() {
274
- return this.shadowHandler.shadowTopics;
275
- }
276
261
  isCmdInProgress(projectId) {
277
- return this.cmdStatusMgr.isCmdInProgress(projectId);
262
+ return this.txnMgr.isOngoingTransactionForProjectID(projectId);
278
263
  }
279
264
  async updateProjectShadow(projectId) {
280
265
  await this.shadowHandler.updateProjectShadow(projectId);
@@ -287,76 +272,66 @@ class DeviceAgentCloudConnection {
287
272
  return;
288
273
  }
289
274
  const txId = message.txId;
275
+ const { app_state_control, app_version_control, live_state_updates, app_install_response, models_install_response, status_response, device_action } = device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType;
290
276
  switch (message.messageType) {
291
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.app_state_control: {
277
+ case app_state_control: {
292
278
  // txId sent from cloud, just need to continue it
293
279
  const payload = message.payload;
294
280
  const projectId = payload.projectId;
295
281
  try {
296
- this.txnMgr.addTransaction(txId, projectId);
297
- const completed = await this.atomicCmd({
298
- func: this.handleAppStateControl,
299
- args: [message.payload],
282
+ await this.txnMgr.runTransactionStep({
283
+ func: () => this.handleAppStateControl(message.payload),
300
284
  projectId,
301
- txId
285
+ txId,
286
+ start: true,
287
+ stepName: payload.baseCommand
302
288
  });
303
- if (completed) {
304
- this.txnMgr.completeTransaction(txId);
305
- }
306
289
  }
307
290
  catch (e) {
308
291
  logger_1.logger.error(`Error processing application state control request: ${e}!`);
309
292
  }
310
293
  break;
311
294
  }
312
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.app_version_control: {
295
+ case app_version_control: {
313
296
  // txId sent from cloud, just need to continue it
314
297
  const payload = message.payload;
315
298
  const projectId = payload.projectId;
316
- const appReleaseHash = payload.baseCommand === device_agent_schemas_1.keyMirrors.appVersionControl.install
317
- ? payload.appReleaseHash
318
- : undefined;
319
299
  try {
320
- this.txnMgr.addTransaction(txId, projectId);
321
- const completed = await this.atomicCmd({
322
- func: this.handleAppVersionControl,
323
- args: [payload, txId],
300
+ await this.txnMgr.runTransactionStep({
301
+ func: () => this.handleAppVersionControl(payload, txId),
324
302
  projectId,
325
- txId
303
+ txId,
304
+ start: true,
305
+ stepName: payload.baseCommand
326
306
  });
327
- if (completed) {
328
- this.txnMgr.completeTransaction(txId);
329
- }
330
307
  }
331
308
  catch (e) {
332
309
  logger_1.logger.error(`Error processing application install request: ${e}!`);
333
310
  }
334
311
  break;
335
312
  }
336
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.live_state_updates: {
313
+ case live_state_updates: {
337
314
  const payload = message.payload;
338
315
  // TODO: Send response?
339
- await this.liveUpdatesHandler.handleToggles(payload, txId);
316
+ void this.liveUpdatesHandler.handleToggles(payload, txId);
340
317
  break;
341
318
  }
342
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.app_install_response: {
319
+ case app_install_response: {
343
320
  const payload = message.payload;
344
- const { projectId, appReleaseHash } = payload.appInstallResponse;
321
+ const { projectId } = payload.appInstallResponse;
345
322
  if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
346
323
  throw new Error(`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(projectId)})!`);
347
324
  }
348
- const completed = await this.atomicCmd({
349
- func: this.handleAppInstallCloudResponsePayload,
350
- args: [payload],
325
+ await this.txnMgr.runTransactionStep({
326
+ func: () => this.handleAppInstallCloudResponsePayload(payload),
351
327
  projectId,
352
- txId
328
+ txId,
329
+ start: false,
330
+ stepName: message.messageType
353
331
  });
354
- if (completed) {
355
- this.txnMgr.completeTransaction(txId);
356
- }
357
332
  break;
358
333
  }
359
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.models_install_response: {
334
+ case models_install_response: {
360
335
  // This message doesn't have appReleaseHash in it's payload, but
361
336
  // atomicCmd should be able to read it from the installed app
362
337
  const payload = message.payload;
@@ -364,28 +339,50 @@ class DeviceAgentCloudConnection {
364
339
  if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
365
340
  throw new Error(`Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(projectId)})!`);
366
341
  }
367
- const completed = await this.atomicCmd({
368
- func: this.handleModelsInstallCloudResponsePayload,
369
- args: [payload],
342
+ await this.txnMgr.runTransactionStep({
343
+ func: () => this.handleModelsInstallCloudResponsePayload(payload),
370
344
  projectId,
371
- txId
345
+ txId,
346
+ start: false,
347
+ stepName: message.messageType
372
348
  });
373
- if (completed) {
349
+ break;
350
+ }
351
+ case status_response: {
352
+ const { failure } = device_agent_schemas_1.keyMirrors.statusResponse;
353
+ if (message.payload.status === failure) {
374
354
  this.txnMgr.completeTransaction(txId);
355
+ const failureStatusResponsePayload = {
356
+ status: device_agent_schemas_1.keyMirrors.statusResponse.failure,
357
+ message: message.payload.message
358
+ };
359
+ // Send final status message
360
+ const failureStatusResponseMessage = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, failureStatusResponsePayload, txId);
361
+ this.publisher.publishToClient(failureStatusResponseMessage);
375
362
  }
376
363
  break;
377
364
  }
378
- case device_agent_schemas_1.keyMirrors.toDeviceAgentMessageType.device_action: {
365
+ case device_action: {
379
366
  try {
380
- const successStatusResponsePayload = await (0, messages_1.getStatusResponsePayload)(device_agent_schemas_1.keyMirrors.statusResponse.success, '');
381
- const successStatusResponseMessage = await (0, message_builder_1.buildStatusResponseMessage)(successStatusResponsePayload, txId);
382
- this.publisher.publishToClient(successStatusResponseMessage);
367
+ const statusResponsePayload = {
368
+ status: device_agent_schemas_1.keyMirrors.statusResponse.in_progress
369
+ };
370
+ const statusResponseMessage = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, statusResponsePayload, txId);
371
+ this.publisher.publishToClient(statusResponseMessage);
383
372
  await this.handleDeviceAction(message.payload);
373
+ const successStatusResponsePayload = {
374
+ status: device_agent_schemas_1.keyMirrors.statusResponse.success
375
+ };
376
+ const successStatusResponseMessage = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, successStatusResponsePayload, txId);
377
+ this.publisher.publishToClient(successStatusResponseMessage);
384
378
  }
385
379
  catch (e) {
386
380
  logger_1.logger.error(`There was a problem performing device action '${message.payload.action}': ${e.message}`);
387
- const failureStatusResponsePayload = await (0, messages_1.getStatusResponsePayload)(device_agent_schemas_1.keyMirrors.statusResponse.failure, e.message);
388
- const failureStatusResponseMessage = await (0, message_builder_1.buildStatusResponseMessage)(failureStatusResponsePayload, txId);
381
+ const failureStatusResponsePayload = {
382
+ status: device_agent_schemas_1.keyMirrors.statusResponse.failure,
383
+ message: e.message
384
+ };
385
+ const failureStatusResponseMessage = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, failureStatusResponsePayload, txId);
389
386
  this.publisher.publishToClient(failureStatusResponseMessage);
390
387
  }
391
388
  break;
@@ -396,53 +393,32 @@ class DeviceAgentCloudConnection {
396
393
  }
397
394
  async handleMessage(topic, message) {
398
395
  logger_1.logger.debug(`Received message: ${JSON.stringify({ topic, message }, null, 2)}`);
399
- switch (topic) {
400
- case this.shadowHandler.shadowTopics.projects.getAccepted:
401
- case this.shadowHandler.shadowTopics.projects.updateDelta: {
402
- const shadowUpdates = await this.shadowHandler.handleShadowTopic({
403
- topic,
404
- payload: message.state,
405
- clientToken: message.clientToken
406
- });
407
- if (shadowUpdates.length) {
408
- for (const shadowUpdate of shadowUpdates) {
409
- const projectId = shadowUpdate.projectId;
410
- const txId = shadowUpdate.txId;
411
- try {
412
- this.txnMgr.addTransaction(txId, projectId);
413
- const completed = await this.atomicCmd({
414
- func: this.handleAppConfigUpdate,
415
- args: [shadowUpdate, txId],
416
- projectId,
417
- txId
418
- });
419
- if (completed) {
420
- this.txnMgr.completeTransaction(txId);
421
- }
422
- }
423
- catch (e) {
424
- logger_1.logger.error(`Error handling shadow message: ${e.message}`);
425
- }
426
- }
427
- }
428
- break;
429
- }
430
- case this.shadowHandler.shadowTopics.projects.getRejected:
431
- case this.shadowHandler.shadowTopics.projects.updateAccepted:
432
- case this.shadowHandler.shadowTopics.projects.updateRejected:
433
- // Not handling these for now
434
- break;
435
- case this.toDeviceTopic:
436
- await this.handleDeviceAgentMessage({
437
- topic,
438
- message
439
- });
440
- break;
441
- case this.secureTunnelNotifyTopic:
442
- await (0, index_1.secureTunnelNotifyHandler)(message);
443
- break;
444
- default:
445
- logger_1.logger.error(`Unexpected topic, ignoring! ${topic}`);
396
+ // ProjectShadow messages
397
+ if (this.shadowHandler.projectShadowTopics.includes(topic)) {
398
+ await this.handleProjectShadowMessage(topic, message);
399
+ }
400
+ else if (topic === this.toDeviceTopic) {
401
+ await this.handleDeviceAgentMessage({
402
+ topic,
403
+ message
404
+ });
405
+ // SecureTunnelNotify messages
406
+ }
407
+ else if (topic === this.secureTunnelNotifyTopic) {
408
+ await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
409
+ // SecureTunnel messages
410
+ }
411
+ else if (topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta) {
412
+ logger_1.logger.info(`Received secure tunnel update: ${message}`);
413
+ const reported = await this.secureTunnelHandler.syncShadowToDeviceState(message);
414
+ this.publisher.publish(this.shadowHandler.shadowTopics.secureTunnel.update, JSON.stringify({ state: { reported } }));
415
+ }
416
+ else if (topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted) {
417
+ logger_1.logger.info(`Received secure tunnel deleteAccepted: ${message}`);
418
+ await this.secureTunnelHandler.destroy();
419
+ }
420
+ else {
421
+ logger_1.logger.error(`Unexpected topic, ignoring! ${topic}`);
446
422
  }
447
423
  }
448
424
  async setupHandlers() {
@@ -474,8 +450,57 @@ class DeviceAgentCloudConnection {
474
450
  });
475
451
  this.device.on('offline', () => {
476
452
  logger_1.logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
453
+ void this.logConnectionInfo();
477
454
  });
478
455
  }
456
+ async logConnectionInfo() {
457
+ try {
458
+ /**
459
+ * We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
460
+ * This command doesn't always exit (see below), so
461
+ * we use timeout to break out of the prompt
462
+ * and catch the resulting error/parse the resulting stderr
463
+ *
464
+ * Sample command for current host and port:
465
+ * nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
466
+ *
467
+ * Sample output when port is not blocked and host is reachable:
468
+ * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
469
+ * Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
470
+ *
471
+ *
472
+ * Sample output when port is blocked (will repeatedly try until ctrl-C out):
473
+ * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
474
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
475
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
476
+ * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
477
+ * ^C
478
+ *
479
+ *
480
+ * Sample command/output when the port isn't enable on that host:
481
+ * $ nc -zv -w 1 localhost 8883
482
+ * nc: connect to localhost port 8883 (tcp) failed: Connection refused
483
+ */
484
+ await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
485
+ timeout: 2000
486
+ });
487
+ }
488
+ catch (err) {
489
+ const output = JSON.stringify(err['stderr']);
490
+ if (output.indexOf('not known') !== -1) {
491
+ logger_1.logger.warn('Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.');
492
+ }
493
+ else if (output.indexOf('timed out') !== -1) {
494
+ logger_1.logger.warn(`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.`);
495
+ }
496
+ else if (output.indexOf('refused') !== -1) {
497
+ logger_1.logger.warn(`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`);
498
+ }
499
+ else {
500
+ logger_1.logger.warn(`Output from checking connection to ${this.host} on ${this.port}: ${output}`);
501
+ }
502
+ }
503
+ }
479
504
  async stop() {
480
505
  // FIXME: This method is currently only used by the CLI, and shadow messages
481
506
  // can be lost since we aren't waiting for responses so sleep for a short