@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.
- package/lib/application-control/environment-variables.d.ts +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +22 -20
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +37 -2
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.js +1 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +203 -178
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +30 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +15 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +1 -3
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +1 -9
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +3 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +10 -21
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +154 -100
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +140 -72
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +26 -6
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +103 -22
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +179 -13
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +2 -2
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +107 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts +10 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -0
- package/lib/subcommands/app/analytics.js +79 -0
- package/lib/subcommands/app/analytics.js.map +1 -0
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +11 -16
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +3 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -5
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +16 -56
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.d.ts +1 -0
- package/lib/subcommands/app/status.d.ts.map +1 -1
- package/lib/subcommands/app/status.js +14 -3
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts +2 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +16 -3
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/lib/util/parsing.d.ts +2 -0
- package/lib/util/parsing.d.ts.map +1 -0
- package/lib/util/parsing.js +17 -0
- package/lib/util/parsing.js.map +1 -0
- package/package.json +4 -6
- package/readme.md +146 -92
- package/src/application-control/environment-variables.test.ts +43 -3
- package/src/application-control/environment-variables.ts +29 -19
- package/src/application-control/install.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +272 -247
- package/src/cloud-connection/live-updates-handler.test.ts +20 -0
- package/src/cloud-connection/live-updates-handler.ts +45 -52
- package/src/cloud-connection/messages.ts +1 -14
- package/src/cloud-connection/publisher.ts +4 -0
- package/src/cloud-connection/shadow-handler.test.ts +150 -73
- package/src/cloud-connection/shadow-handler.ts +247 -126
- package/src/cloud-connection/transaction-manager.test.ts +193 -18
- package/src/cloud-connection/transaction-manager.ts +174 -26
- package/src/device-control/device-control.ts +3 -3
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -0
- package/src/secure-tunneling/spawner-detached.ts +123 -0
- package/src/subcommands/app/analytics.ts +102 -0
- package/src/subcommands/app/env-vars.ts +18 -16
- package/src/subcommands/app/index.ts +4 -3
- package/src/subcommands/app/models.ts +25 -57
- package/src/subcommands/app/status.ts +20 -3
- package/src/subcommands/app/version.ts +19 -4
- package/src/util/cloud-mode-ready.ts +36 -0
- package/src/util/parsing.ts +11 -0
- package/lib/cloud-connection/cmd-status.d.ts +0 -8
- package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
- package/lib/cloud-connection/cmd-status.js +0 -62
- package/lib/cloud-connection/cmd-status.js.map +0 -1
- package/lib/cloud-connection/message-builder.d.ts +0 -7
- package/lib/cloud-connection/message-builder.d.ts.map +0 -1
- package/lib/cloud-connection/message-builder.js +0 -63
- package/lib/cloud-connection/message-builder.js.map +0 -1
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/src/cloud-connection/cmd-status.ts +0 -71
- package/src/cloud-connection/message-builder.ts +0 -117
- 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
|
|
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
|
|
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
|
|
21
|
+
const live_updates_handler_1 = require("./live-updates-handler");
|
|
21
22
|
const passthrough_handler_1 = require("./passthrough-handler");
|
|
22
|
-
const
|
|
23
|
-
const
|
|
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
|
|
28
|
-
const
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.subscribe(this.shadowHandler.shadowTopics.
|
|
162
|
-
this.subscribe(this.shadowHandler.shadowTopics.
|
|
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.
|
|
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,
|
|
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(
|
|
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
|
|
219
|
+
await this.atomicApplicationUninstall(projectId);
|
|
221
220
|
}
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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.
|
|
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
|
|
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.
|
|
297
|
-
|
|
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
|
|
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.
|
|
321
|
-
|
|
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
|
|
313
|
+
case live_state_updates: {
|
|
337
314
|
const payload = message.payload;
|
|
338
315
|
// TODO: Send response?
|
|
339
|
-
|
|
316
|
+
void this.liveUpdatesHandler.handleToggles(payload, txId);
|
|
340
317
|
break;
|
|
341
318
|
}
|
|
342
|
-
case
|
|
319
|
+
case app_install_response: {
|
|
343
320
|
const payload = message.payload;
|
|
344
|
-
const { projectId
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
365
|
+
case device_action: {
|
|
379
366
|
try {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
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 =
|
|
388
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|