@alwaysai/device-agent 2.0.0 → 2.0.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/application-control/environment-variables.d.ts +4 -0
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/application-control/environment-variables.js +17 -13
  4. package/lib/application-control/environment-variables.js.map +1 -1
  5. package/lib/application-control/install.d.ts +4 -1
  6. package/lib/application-control/install.d.ts.map +1 -1
  7. package/lib/application-control/install.js +16 -1
  8. package/lib/application-control/install.js.map +1 -1
  9. package/lib/application-control/utils.d.ts.map +1 -1
  10. package/lib/application-control/utils.js +13 -0
  11. package/lib/application-control/utils.js.map +1 -1
  12. package/lib/cloud-connection/base-message-handler.d.ts +27 -0
  13. package/lib/cloud-connection/base-message-handler.d.ts.map +1 -0
  14. package/lib/cloud-connection/base-message-handler.js +72 -0
  15. package/lib/cloud-connection/base-message-handler.js.map +1 -0
  16. package/lib/cloud-connection/connection-manager.d.ts +20 -0
  17. package/lib/cloud-connection/connection-manager.d.ts.map +1 -0
  18. package/lib/cloud-connection/connection-manager.js +164 -0
  19. package/lib/cloud-connection/connection-manager.js.map +1 -0
  20. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +7 -23
  21. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  22. package/lib/cloud-connection/device-agent-cloud-connection.js +49 -517
  23. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  24. package/lib/cloud-connection/device-agent-message-handler.d.ts +22 -0
  25. package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -0
  26. package/lib/cloud-connection/device-agent-message-handler.js +357 -0
  27. package/lib/cloud-connection/device-agent-message-handler.js.map +1 -0
  28. package/lib/cloud-connection/live-updates-handler.d.ts +1 -0
  29. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  30. package/lib/cloud-connection/live-updates-handler.js +13 -10
  31. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  32. package/lib/cloud-connection/message-dispatcher.d.ts +10 -0
  33. package/lib/cloud-connection/message-dispatcher.d.ts.map +1 -0
  34. package/lib/cloud-connection/message-dispatcher.js +27 -0
  35. package/lib/cloud-connection/message-dispatcher.js.map +1 -0
  36. package/lib/cloud-connection/publisher.d.ts +3 -2
  37. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  38. package/lib/cloud-connection/publisher.js +8 -4
  39. package/lib/cloud-connection/publisher.js.map +1 -1
  40. package/lib/cloud-connection/shadow-handler.d.ts +7 -0
  41. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  42. package/lib/cloud-connection/shadow-handler.js +77 -1
  43. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  44. package/lib/cloud-connection/shadow-handler.test.js +5 -2
  45. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  46. package/lib/cloud-connection/transaction-manager.d.ts +9 -4
  47. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  48. package/lib/cloud-connection/transaction-manager.js +22 -11
  49. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  50. package/lib/cloud-connection/transaction-manager.test.js +43 -14
  51. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  52. package/lib/jobs/job-handler.d.ts +23 -0
  53. package/lib/jobs/job-handler.d.ts.map +1 -0
  54. package/lib/jobs/job-handler.js +131 -0
  55. package/lib/jobs/job-handler.js.map +1 -0
  56. package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts +8 -0
  57. package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts.map +1 -0
  58. package/lib/secure-tunneling/secure-tunnel-message-handler.js +42 -0
  59. package/lib/secure-tunneling/secure-tunnel-message-handler.js.map +1 -0
  60. package/lib/subcommands/app/version.d.ts +2 -0
  61. package/lib/subcommands/app/version.d.ts.map +1 -1
  62. package/lib/subcommands/app/version.js +14 -2
  63. package/lib/subcommands/app/version.js.map +1 -1
  64. package/package.json +2 -2
  65. package/src/application-control/environment-variables.ts +31 -21
  66. package/src/application-control/install.ts +24 -3
  67. package/src/application-control/utils.ts +13 -0
  68. package/src/cloud-connection/base-message-handler.ts +118 -0
  69. package/src/cloud-connection/connection-manager.ts +196 -0
  70. package/src/cloud-connection/device-agent-cloud-connection.ts +104 -817
  71. package/src/cloud-connection/device-agent-message-handler.ts +642 -0
  72. package/src/cloud-connection/live-updates-handler.ts +26 -18
  73. package/src/cloud-connection/message-dispatcher.ts +33 -0
  74. package/src/cloud-connection/publisher.ts +28 -23
  75. package/src/cloud-connection/shadow-handler.test.ts +6 -2
  76. package/src/cloud-connection/shadow-handler.ts +129 -1
  77. package/src/cloud-connection/transaction-manager.test.ts +55 -24
  78. package/src/cloud-connection/transaction-manager.ts +42 -31
  79. package/src/jobs/job-handler.ts +146 -0
  80. package/src/secure-tunneling/secure-tunnel-message-handler.ts +56 -0
  81. package/src/subcommands/app/version.ts +20 -2
@@ -1,180 +1,78 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runDeviceAgentCloudInterface = exports.DeviceAgentCloudConnection = void 0;
4
- // eslint-disable-next-line
5
- const awsIot = require('aws-iot-device-sdk');
6
4
  const device_agent_schemas_1 = require("@alwaysai/device-agent-schemas");
7
- const infrastructure_1 = require("alwaysai/lib/infrastructure");
8
- const util_1 = require("alwaysai/lib/util");
9
- const child_process_1 = require("child_process");
10
5
  const fs_1 = require("fs");
11
- const util_2 = require("util");
12
- const application_control_1 = require("../application-control");
13
- const backup_1 = require("../application-control/backup");
14
- const models_1 = require("../application-control/models");
15
- const device_control_1 = require("../device-control/device-control");
16
6
  const environment_1 = require("../environment");
17
- const agent_config_1 = require("../infrastructure/agent-config");
18
7
  const device_certificate_1 = require("../infrastructure/device-certificate");
19
8
  const legacy_migration_1 = require("../infrastructure/legacy-migration/legacy-migration");
20
9
  const required_config_checks_1 = require("../infrastructure/required-config-checks");
21
10
  const urls_1 = require("../infrastructure/urls");
22
11
  const secure_tunneling_1 = require("../secure-tunneling/secure-tunneling");
23
- const aai_error_1 = require("../util/aai-error");
12
+ const secure_tunnel_message_handler_1 = require("../secure-tunneling/secure-tunnel-message-handler");
24
13
  const check_for_updates_1 = require("../util/check-for-updates");
25
- const directories_1 = require("../util/directories");
26
14
  const get_device_id_1 = require("../util/get-device-id");
27
15
  const logger_1 = require("../util/logger");
28
16
  const sleep_1 = require("../util/sleep");
29
17
  const bootstrap_provision_1 = require("./bootstrap-provision");
30
18
  const live_updates_handler_1 = require("./live-updates-handler");
31
- const messages_1 = require("./messages");
32
19
  const passthrough_handler_1 = require("./passthrough-handler");
33
20
  const publisher_1 = require("./publisher");
34
21
  const shadow_handler_1 = require("./shadow-handler");
22
+ const job_handler_1 = require("../jobs/job-handler");
35
23
  const transaction_manager_1 = require("./transaction-manager");
36
- const exec_promise = (0, util_2.promisify)(child_process_1.exec);
24
+ const connection_manager_1 = require("./connection-manager");
25
+ const device_agent_message_handler_1 = require("./device-agent-message-handler");
37
26
  class DeviceAgentCloudConnection {
38
27
  constructor() {
39
- this.device = awsIot.device;
40
28
  this.clientId = (0, get_device_id_1.getDeviceUuid)();
41
29
  this.host = (0, urls_1.getIoTCoreEndpointUrl)();
42
30
  this.port = 8883;
43
31
  this.toDeviceTopic = (0, device_agent_schemas_1.getToDeviceTopic)(this.clientId);
44
- this.secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
45
32
  this.secureTunnelHandler = secure_tunneling_1.SecureTunnelHandlerSingleton.getInstance();
46
- this.handleAppStateControl = async (payload) => {
47
- const { baseCommand, projectId } = payload;
48
- switch (baseCommand) {
49
- case device_agent_schemas_1.keyMirrors.appStateControl.start:
50
- await (0, application_control_1.startApp)({ projectId });
51
- break;
52
- case device_agent_schemas_1.keyMirrors.appStateControl.stop:
53
- await (0, application_control_1.stopApp)({ projectId });
54
- break;
55
- case device_agent_schemas_1.keyMirrors.appStateControl.restart:
56
- await (0, application_control_1.restartApp)({ projectId });
57
- break;
58
- }
59
- return true;
60
- };
61
- this.handleAppVersionControl = async (payload, txId) => {
62
- switch (payload.baseCommand) {
63
- case device_agent_schemas_1.keyMirrors.appVersionControl.install: {
64
- const { projectId, appReleaseHash } = payload;
65
- const signedUrlsRequestPayload = {
66
- signedUrlsRequest: {
67
- projectId,
68
- appReleaseHash
69
- }
70
- };
71
- const message = (0, device_agent_schemas_1.buildSignedUrlsRequestMessage)(this.clientId, signedUrlsRequestPayload, txId);
72
- await this.publishCloudRequest(message);
73
- return false;
74
- }
75
- case device_agent_schemas_1.keyMirrors.appVersionControl.uninstall: {
76
- const { projectId } = payload;
77
- await this.atomicApplicationUninstall(projectId);
78
- return true;
79
- }
80
- default:
81
- logger_1.logger.warn(`Ignore App Version Control packet: ${JSON.stringify(payload, null, 2)}`);
82
- return true;
83
- }
84
- };
85
- this.handleAppInstallCloudResponsePayload = async (payload) => {
86
- const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } = payload.appInstallResponse;
87
- const signedUrlsPayload = {
88
- appInstallPayload,
89
- modelsInstallPayload
90
- };
91
- await this.atomicApplicationUpdate(async () => {
92
- this.shadowHandler.clearProjectShadow(projectId);
93
- await (0, application_control_1.installApp)({ projectId, appReleaseHash, signedUrlsPayload });
94
- }, projectId);
95
- return true;
96
- };
97
- this.handleModelsInstallCloudResponsePayload = async (payload, txId) => {
98
- const projectId = payload.modelsInstallResponse.projectId;
99
- const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
100
- if (update === undefined) {
101
- throw new Error('Unknown error while updating models via application config! No config present for model update.');
102
- }
103
- const { appCfgUpdate, envVarUpdate } = update;
104
- if (appCfgUpdate) {
105
- await this.atomicApplicationUpdate(async () => await (0, application_control_1.updateModelsWithPresignedUrls)({
106
- projectId,
107
- modelInstallPayloads: payload.modelsInstallResponse.newModels,
108
- newAppCfg: appCfgUpdate.newAppCfg
109
- }), projectId);
110
- }
111
- if (envVarUpdate) {
112
- await this.atomicApplicationUpdate(async () => await this.shadowHandler.updateProjectEnvVars({
113
- projectId,
114
- envVars: envVarUpdate.envVars
115
- }), projectId, true);
116
- }
117
- return true;
118
- };
119
- this.handleProjectShadowConfigUpdate = async (update, txId) => {
120
- const { projectId, appCfgUpdate, envVarUpdate } = update;
121
- if ((appCfgUpdate === null || appCfgUpdate === void 0 ? void 0 : appCfgUpdate.updatedModels) &&
122
- Object.keys(appCfgUpdate.updatedModels).length) {
123
- // When there are model updates request signed URLs and wait to apply config changes
124
- const { updatedModels } = appCfgUpdate;
125
- logger_1.logger.debug(`Requesting presigned urls from cloud for model versions: ${JSON.stringify(updatedModels)}`);
126
- const modelsOnlyUrlsRequestPayload = {
127
- modelsOnlyUrlsRequest: {
128
- projectId,
129
- models: updatedModels
130
- }
131
- };
132
- const message = (0, device_agent_schemas_1.buildSignedUrlsRequestMessage)(this.clientId, modelsOnlyUrlsRequestPayload, txId);
133
- this.publisher.publishToCloud(message);
134
- this.txnMgr.setAppCfgUpdateToTx(txId, update);
135
- return false;
136
- }
137
- if (appCfgUpdate) {
138
- await this.atomicApplicationUpdate(async () => {
139
- await (0, models_1.pruneModels)({
140
- projectId,
141
- appCfg: appCfgUpdate.newAppCfg
142
- });
143
- await (0, application_control_1.updateAppCfg)({
144
- projectId,
145
- newAppCfg: appCfgUpdate.newAppCfg
146
- });
147
- }, projectId);
148
- }
149
- if (envVarUpdate) {
150
- await this.atomicApplicationUpdate(async () => await this.shadowHandler.updateProjectEnvVars({
151
- projectId,
152
- envVars: envVarUpdate.envVars
153
- }), projectId, true);
154
- }
155
- return true;
156
- };
157
- this.device = awsIot.device({
158
- keyPath: infrastructure_1.DEVICE_PRIVATE_KEY_FILE_PATH,
159
- certPath: infrastructure_1.DEVICE_CERTIFICATE_FILE_PATH,
160
- caPath: directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
33
+ this.liveUpdatesHandler = new live_updates_handler_1.LiveUpdatesHandler();
34
+ // Initialize & setup the connection
35
+ this.connectionManager = new connection_manager_1.ConnectionManager(this.clientId, this.host, this.port);
36
+ this.publisher = new publisher_1.Publisher(this.connectionManager, this.clientId);
37
+ this.shadowHandler = new shadow_handler_1.ShadowHandler(this.clientId, this.publisher);
38
+ this.connectionManager.connect(() => {
39
+ void this.shadowHandler.initShadows();
40
+ this.publisher.publish(JOB_HANDLER_TOPICS.START_NEXT, JSON.stringify({}));
41
+ });
42
+ this.transactionManager = new transaction_manager_1.TransactionManager(this.liveUpdatesHandler);
43
+ // Construct a HandlerContext used by all the message handlers
44
+ const handlerContext = {
161
45
  clientId: this.clientId,
162
- host: this.host,
163
- port: this.port,
164
- keepalive: 10 // time before re-connect attempt on dropped connection, default is 400 seconds
46
+ txnMgr: this.transactionManager,
47
+ publisher: this.publisher,
48
+ shadowHandler: this.shadowHandler,
49
+ liveUpdatesHandler: this.liveUpdatesHandler,
50
+ secureTunnelHandler: this.secureTunnelHandler
51
+ };
52
+ // Instantiate & register message handlers for Project Shadow topics
53
+ const projectShadowMessageHandler = new shadow_handler_1.ProjectShadowMessageHandler(handlerContext);
54
+ this.shadowHandler.projectShadowTopics.forEach((topic) => {
55
+ this.connectionManager.registerHandler(topic, projectShadowMessageHandler);
165
56
  });
166
- this.publisher = new publisher_1.Publisher(this.device, this.clientId);
167
- this.shadowHandler = new shadow_handler_1.ShadowHandler(this.clientId, this.publisher);
168
- this.liveUpdatesHandler = new live_updates_handler_1.LiveUpdatesHandler();
169
- this.txnMgr = new transaction_manager_1.TransactionManager(this.publisher, this.liveUpdatesHandler);
170
- this.subscribe(this.toDeviceTopic);
171
- this.subscribe(this.secureTunnelNotifyTopic);
172
- for (const topic of this.shadowHandler.projectShadowTopics) {
173
- this.subscribe(topic);
174
- }
175
- this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
176
- this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
177
- this.setupHandlers();
57
+ // Instantiate & register message handlers for to-device and secureTunnel topics
58
+ this.connectionManager.registerHandler(this.toDeviceTopic, new device_agent_message_handler_1.DeviceAgentMessageHandler(handlerContext, (txId, errorMsg) => {
59
+ const msg = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.publisher.getClientId(), {
60
+ status: device_agent_schemas_1.keyMirrors.statusResponse.failure,
61
+ message: errorMsg
62
+ }, txId);
63
+ this.publisher.publishToClient(msg);
64
+ }, (txId) => {
65
+ const msg = (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.publisher.getClientId(), { status: device_agent_schemas_1.keyMirrors.statusResponse.success }, txId);
66
+ this.publisher.publishToClient(msg);
67
+ }));
68
+ const secureTunnelMessageHandler = new secure_tunnel_message_handler_1.SecureTunnelMessageHandler(handlerContext);
69
+ this.connectionManager.registerHandler(secureTunnelMessageHandler.getNotifyTopic(), secureTunnelMessageHandler);
70
+ this.connectionManager.registerHandler(this.shadowHandler.shadowTopics.secureTunnel.updateDelta, secureTunnelMessageHandler);
71
+ this.connectionManager.registerHandler(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted, secureTunnelMessageHandler);
72
+ const jobHandler = new job_handler_1.JobHandler(handlerContext);
73
+ const JOB_HANDLER_TOPICS = jobHandler.getJobTopic();
74
+ this.connectionManager.registerHandler(JOB_HANDLER_TOPICS.NOTIFY_NEXT, jobHandler);
75
+ this.connectionManager.registerHandler(JOB_HANDLER_TOPICS.START_NEXT_ACCEPTED, jobHandler);
178
76
  }
179
77
  /*=================================================================
180
78
  Public interface
@@ -183,383 +81,17 @@ class DeviceAgentCloudConnection {
183
81
  return this.clientId;
184
82
  }
185
83
  isCmdInProgress(projectId) {
186
- return this.txnMgr.isOngoingTransactionForProjectID(projectId);
187
- }
188
- async handleMessage(topic, message) {
189
- logger_1.logger.debug(`Received message: ${JSON.stringify({ topic, message }, null, 2)}`);
190
- // ProjectShadow messages
191
- if (this.shadowHandler.projectShadowTopics.includes(topic)) {
192
- await this.handleProjectShadowMessage(topic, message);
193
- }
194
- else if (topic === this.toDeviceTopic) {
195
- await this.handleDeviceAgentMessage({
196
- topic,
197
- message
198
- });
199
- // SecureTunnelNotify messages
200
- }
201
- else if (topic === this.secureTunnelNotifyTopic) {
202
- await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
203
- // SecureTunnel messages
204
- }
205
- else if (topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta) {
206
- await this.handleSecureTunnelMessage(message);
207
- }
208
- else if (topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted) {
209
- logger_1.logger.info(`Received secure tunnel deleteAccepted: ${message}`);
210
- await this.secureTunnelHandler.destroy();
211
- }
212
- else {
213
- logger_1.logger.error(`Unexpected topic, ignoring! ${topic}`);
214
- }
84
+ return this.transactionManager.isOngoingTransactionForProjectID(projectId);
215
85
  }
216
86
  async stop() {
217
87
  // This method is currently only used by the CLI, and shadow messages can be
218
88
  // lost since we aren't waiting for responses so sleep for a short time to
219
89
  // receive them
220
90
  await (0, sleep_1.default)(1000);
221
- this.device.end();
222
- }
223
- /*=================================================================
224
- Private interface
225
- =================================================================*/
226
- setupHandlers() {
227
- this.device.on('connect', (connack) => {
228
- logger_1.logger.info('Device Agent has connected to the cloud');
229
- // FIXME: EI-709 Skip this request for now to prevent kicking off another
230
- // shadow update process if IoT Core disconnect occurs during app config update
231
- //this.shadowHandler.getShadowUpdates();
232
- void this.shadowHandler.updateSystemInfoShadow();
233
- });
234
- this.device.on('disconnect', () => {
235
- logger_1.logger.warn('Device Agent has been disconnected from the cloud');
236
- });
237
- this.device.on('reconnect', () => {
238
- logger_1.logger.info(`Device Agent attempting to re-connect ${new Date().toLocaleString()}`);
239
- });
240
- this.device.on('error', function (e) {
241
- logger_1.logger.error(`Error connecting to cloud!\n${(0, util_1.stringifyError)(e)}`);
242
- });
243
- this.device.on('message', async (topic, payload) => {
244
- try {
245
- const jsonPacket = JSON.parse(payload);
246
- await this.handleMessage(topic, jsonPacket);
247
- }
248
- catch (e) {
249
- logger_1.logger.error(`Error parsing message!\n${(0, util_1.stringifyError)(e)}`);
250
- }
251
- });
252
- this.device.on('offline', () => {
253
- logger_1.logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
254
- void this.logConnectionInfo();
255
- });
256
- }
257
- async logConnectionInfo() {
258
- try {
259
- /**
260
- * We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
261
- * This command doesn't always exit (see below), so
262
- * we use timeout to break out of the prompt
263
- * and catch the resulting error/parse the resulting stderr
264
- *
265
- * Sample command for current host and port:
266
- * nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
267
- *
268
- * Sample output when port is not blocked and host is reachable:
269
- * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
270
- * Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
271
- *
272
- *
273
- * Sample output when port is blocked (will repeatedly try until ctrl-C out):
274
- * $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
275
- * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
276
- * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
277
- * nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
278
- * ^C
279
- *
280
- *
281
- * Sample command/output when the port isn't enable on that host:
282
- * $ nc -zv -w 1 localhost 8883
283
- * nc: connect to localhost port 8883 (tcp) failed: Connection refused
284
- */
285
- await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
286
- timeout: 2000
287
- });
288
- }
289
- catch (err) {
290
- const output = JSON.stringify(err['stderr']);
291
- if (output.indexOf('not known') !== -1) {
292
- logger_1.logger.warn('Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.');
293
- }
294
- else if (output.indexOf('timed out') !== -1) {
295
- 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.`);
296
- }
297
- else if (output.indexOf('refused') !== -1) {
298
- logger_1.logger.warn(`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`);
299
- }
300
- else {
301
- logger_1.logger.warn(`Output from checking connection to ${this.host} on ${this.port}: ${output}`);
302
- }
303
- }
304
- }
305
- async handleDeviceAgentMessage({ topic, message }) {
306
- const valid = (0, device_agent_schemas_1.validateToDeviceAgentMessage)(message);
307
- if (!valid) {
308
- logger_1.logger.error(`Error validating message: ${JSON.stringify({ topic, message, errors: device_agent_schemas_1.validateToDeviceAgentMessage.errors }, null, 2)}`);
309
- // TODO: Send generic error response
310
- return;
311
- }
312
- const txId = message.txId;
313
- 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;
314
- switch (message.messageType) {
315
- case app_state_control: {
316
- // txId sent from cloud, just need to continue it
317
- const payload = message.payload;
318
- const projectId = payload.projectId;
319
- try {
320
- await this.txnMgr.runTransactionStep({
321
- func: () => this.handleAppStateControl(message.payload),
322
- projectId,
323
- txId,
324
- start: true,
325
- liveUpdatesPublishFn: async () => this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, { status: device_agent_schemas_1.keyMirrors.statusResponse.in_progress }, txId), logger_1.logger.silly),
326
- stepName: payload.baseCommand
327
- });
328
- }
329
- catch (e) {
330
- logger_1.logger.error(`Error processing application state control request for ${projectId}!\n${(0, util_1.stringifyError)(e)}`);
331
- }
332
- break;
333
- }
334
- case app_version_control: {
335
- // txId sent from cloud, just need to continue it
336
- const payload = message.payload;
337
- const projectId = payload.projectId;
338
- try {
339
- await this.txnMgr.runTransactionStep({
340
- func: () => this.handleAppVersionControl(payload, txId),
341
- projectId,
342
- txId,
343
- start: true,
344
- liveUpdatesPublishFn: async () => this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, { status: device_agent_schemas_1.keyMirrors.statusResponse.in_progress }, txId), logger_1.logger.silly),
345
- stepName: payload.baseCommand
346
- });
347
- }
348
- catch (e) {
349
- logger_1.logger.error(`Error processing application install request for ${projectId}!\n${(0, util_1.stringifyError)(e)}`);
350
- }
351
- break;
352
- }
353
- case live_state_updates: {
354
- const { deviceStats, appState, appLogs } = message.payload;
355
- if (deviceStats !== undefined) {
356
- if (deviceStats) {
357
- await this.liveUpdatesHandler.enable(device_agent_schemas_1.keyMirrors.toClientMessageType.device_stats, async () => this.publisher.publishToClient((0, device_agent_schemas_1.buildDeviceStatsMessage)(this.clientId, await (0, messages_1.getDeviceStatsPayload)(), txId), logger_1.logger.silly));
358
- }
359
- else {
360
- this.liveUpdatesHandler.disable(device_agent_schemas_1.keyMirrors.toClientMessageType.device_stats);
361
- }
362
- }
363
- if (appState !== undefined) {
364
- if (appState) {
365
- await this.liveUpdatesHandler.enable(device_agent_schemas_1.keyMirrors.toClientMessageType.app_state, async () => this.publisher.publishToClient((0, device_agent_schemas_1.buildAppStateMessage)(this.clientId, await (0, messages_1.getAppStatePayload)(), txId), logger_1.logger.silly));
366
- }
367
- else {
368
- this.liveUpdatesHandler.disable(device_agent_schemas_1.keyMirrors.toClientMessageType.app_state);
369
- }
370
- }
371
- if (appLogs !== undefined) {
372
- if (appLogs.toggle) {
373
- await this.liveUpdatesHandler.startStream(appLogs.projectId, async () => await (0, application_control_1.getAppLogs)({
374
- projectId: appLogs.projectId,
375
- args: ['--tail', '100', '--no-log-prefix']
376
- }), async (logChunk) => this.publisher.publishToClient((0, device_agent_schemas_1.buildAppLogsMessage)(this.clientId, {
377
- projectId: appLogs.projectId,
378
- logChunk
379
- }, txId), logger_1.logger.silly));
380
- }
381
- else {
382
- this.liveUpdatesHandler.stopStream(appLogs.projectId);
383
- }
384
- }
385
- break;
386
- }
387
- case app_install_response: {
388
- const payload = message.payload;
389
- const { projectId } = payload.appInstallResponse;
390
- if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
391
- throw new Error(`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(projectId)})!`);
392
- }
393
- await this.txnMgr.runTransactionStep({
394
- func: () => this.handleAppInstallCloudResponsePayload(payload),
395
- projectId,
396
- txId,
397
- start: false,
398
- stepName: message.messageType
399
- });
400
- break;
401
- }
402
- case models_install_response: {
403
- // This message doesn't have appReleaseHash in it's payload, but
404
- // atomicCmd should be able to read it from the installed app
405
- const payload = message.payload;
406
- const { projectId } = payload.modelsInstallResponse;
407
- if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
408
- throw new Error(`Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(projectId)})!`);
409
- }
410
- await this.txnMgr.runTransactionStep({
411
- func: () => this.handleModelsInstallCloudResponsePayload(payload, txId),
412
- projectId,
413
- txId,
414
- start: false,
415
- stepName: message.messageType
416
- });
417
- break;
418
- }
419
- case status_response: {
420
- const { failure } = device_agent_schemas_1.keyMirrors.statusResponse;
421
- if (message.payload.status === failure) {
422
- this.txnMgr.completeTransaction(txId, (0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, {
423
- status: device_agent_schemas_1.keyMirrors.statusResponse.failure,
424
- message: message.payload.message
425
- }, txId));
426
- }
427
- break;
428
- }
429
- case device_action: {
430
- try {
431
- this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, {
432
- status: device_agent_schemas_1.keyMirrors.statusResponse.in_progress
433
- }, txId));
434
- await this.handleDeviceAction(message.payload);
435
- this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, {
436
- status: device_agent_schemas_1.keyMirrors.statusResponse.success
437
- }, txId));
438
- }
439
- catch (e) {
440
- logger_1.logger.error(`There was a problem performing device action '${message.payload.action}'!\n${(0, util_1.stringifyError)(e)}`);
441
- this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, {
442
- status: device_agent_schemas_1.keyMirrors.statusResponse.failure,
443
- message: e.message
444
- }, txId));
445
- }
446
- break;
447
- }
448
- default:
449
- logger_1.logger.error(`Invalid client message: '${JSON.stringify({ topic, message, txId }, null, 2)}'`);
450
- }
451
- }
452
- async handleDeviceAction(payload) {
453
- const { system_restart } = device_agent_schemas_1.keyMirrors.deviceAction;
454
- switch (payload.action) {
455
- case system_restart: {
456
- await (0, device_control_1.reboot)();
457
- break;
458
- }
459
- default: {
460
- logger_1.logger.info(`Unrecognized device action requested: '${payload.action}'.`);
461
- }
462
- }
463
- }
464
- async publishCloudRequest(message) {
465
- this.publisher.publishToCloud(message);
466
- }
467
- subscribe(topic) {
468
- logger_1.logger.info(`Subscribing to ${topic}`);
469
- this.device.subscribe(topic);
91
+ this.connectionManager.disconnect();
470
92
  }
471
- async atomicApplicationUninstall(projectId) {
472
- try {
473
- await (0, application_control_1.uninstallApp)({ projectId });
474
- this.shadowHandler.clearProjectShadow(projectId);
475
- }
476
- catch (e) {
477
- logger_1.logger.error(`Failed to uninstall ${projectId}!\n${(0, util_1.stringifyError)(e)}`);
478
- throw e;
479
- }
480
- }
481
- // eslint-disable-next-line
482
- async atomicApplicationUpdate(func, projectId, skipUpdateShadow) {
483
- if (await (0, agent_config_1.AgentConfigFile)().isAppPresent({ projectId })) {
484
- // Reject the application update if app is present but not ready
485
- if (!(await (0, agent_config_1.AgentConfigFile)().isAppReady({ projectId }))) {
486
- throw new Error('Application already has installation in progress!');
487
- }
488
- // Try to create a backup, so that there is one available if something goes wrong in the next try:catch.
489
- try {
490
- await (0, backup_1.createAppBackup)({ projectId });
491
- }
492
- catch (e) {
493
- logger_1.logger.error(`Could not create a backup for the project: ${projectId}!\n${(0, util_1.stringifyError)(e)}`);
494
- }
495
- }
496
- try {
497
- const out = await func();
498
- if (!skipUpdateShadow)
499
- await this.shadowHandler.updateProjectShadow(projectId);
500
- return out;
501
- }
502
- catch (errorAppUpdate) {
503
- logger_1.logger.error(`Failed to update ${projectId}!\n${(0, util_1.stringifyError)(errorAppUpdate)}`);
504
- // If something goes wrong, first try to rollback
505
- try {
506
- await (0, backup_1.rollbackApp)({ projectId });
507
- }
508
- catch (errorRollbackApp) {
509
- logger_1.logger.error(`Application rollback failed for ${projectId}!\n${(0, util_1.stringifyError)(errorRollbackApp)}`);
510
- // and if that fails, uninstall the app as a last resort.
511
- try {
512
- await this.atomicApplicationUninstall(projectId);
513
- }
514
- catch (_a) {
515
- // atomicApplicationUninstall logs failure, so there's nothing to do here.
516
- }
517
- throw new aai_error_1.default('Application update and rollback failed, uninstalled the application!', { cause: errorAppUpdate });
518
- }
519
- throw new Error('Application update failed, rolled the application back!', { cause: errorAppUpdate });
520
- }
521
- }
522
- async handleProjectShadowMessage(topic, message) {
523
- const shadowUpdates = await this.shadowHandler.handleProjectShadow({
524
- topic,
525
- payload: message,
526
- clientToken: message.clientToken
527
- });
528
- if (shadowUpdates.length) {
529
- const shadowUpdatePromises = [];
530
- for (const shadowUpdate of shadowUpdates) {
531
- const projectId = shadowUpdate.projectId;
532
- const txId = shadowUpdate.txId;
533
- shadowUpdatePromises.push(this.txnMgr
534
- .runTransactionStep({
535
- func: () => this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
536
- projectId,
537
- txId,
538
- start: true,
539
- liveUpdatesPublishFn: async () => this.publisher.publishToClient((0, device_agent_schemas_1.buildToClientStatusResponseMessage)(this.clientId, { status: device_agent_schemas_1.keyMirrors.statusResponse.in_progress }, txId), logger_1.logger.silly),
540
- stepName: topic
541
- })
542
- .catch((e) => {
543
- logger_1.logger.error(`There was an issue updating project shadow config for ${projectId}!\n${(0, util_1.stringifyError)(e)}`);
544
- }));
545
- }
546
- await Promise.all(shadowUpdatePromises);
547
- }
548
- }
549
- async handleSecureTunnelMessage(payload) {
550
- logger_1.logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
551
- const state = (0, device_agent_schemas_1.getUpdateDeltaStateFromMessage)(payload);
552
- if (!state) {
553
- logger_1.logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
554
- return;
555
- }
556
- const valid = (0, device_agent_schemas_1.validateSecureTunnelShadowUpdate)(state);
557
- if (!valid) {
558
- logger_1.logger.error(`Error validating message: ${JSON.stringify({ payload, errors: device_agent_schemas_1.validateSecureTunnelShadowUpdate.errors }, null, 2)}`);
559
- return;
560
- }
561
- const secureTunnelUpdate = await this.secureTunnelHandler.syncShadowToDeviceState(payload);
562
- await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
93
+ async handleMessage(topic, message) {
94
+ this.connectionManager.dispatch(topic, message);
563
95
  }
564
96
  }
565
97
  exports.DeviceAgentCloudConnection = DeviceAgentCloudConnection;