@alwaysai/device-agent 0.0.7 → 0.0.9

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 (77) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +2 -0
  3. package/lib/application-control/backup.js.map +1 -1
  4. package/lib/application-control/config.d.ts +17 -0
  5. package/lib/application-control/config.d.ts.map +1 -0
  6. package/lib/application-control/config.js +62 -0
  7. package/lib/application-control/config.js.map +1 -0
  8. package/lib/application-control/environment-variables.d.ts.map +1 -1
  9. package/lib/application-control/environment-variables.js +6 -14
  10. package/lib/application-control/environment-variables.js.map +1 -1
  11. package/lib/application-control/index.d.ts +2 -1
  12. package/lib/application-control/index.d.ts.map +1 -1
  13. package/lib/application-control/index.js +6 -1
  14. package/lib/application-control/index.js.map +1 -1
  15. package/lib/application-control/install.d.ts +16 -10
  16. package/lib/application-control/install.d.ts.map +1 -1
  17. package/lib/application-control/install.js +95 -57
  18. package/lib/application-control/install.js.map +1 -1
  19. package/lib/application-control/models.d.ts +3 -0
  20. package/lib/application-control/models.d.ts.map +1 -1
  21. package/lib/application-control/models.js +96 -20
  22. package/lib/application-control/models.js.map +1 -1
  23. package/lib/application-control/status.d.ts +3 -2
  24. package/lib/application-control/status.d.ts.map +1 -1
  25. package/lib/application-control/status.js +8 -6
  26. package/lib/application-control/status.js.map +1 -1
  27. package/lib/application-control/utils.d.ts +5 -0
  28. package/lib/application-control/utils.d.ts.map +1 -1
  29. package/lib/application-control/utils.js +47 -13
  30. package/lib/application-control/utils.js.map +1 -1
  31. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +42 -15
  32. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  33. package/lib/cloud-connection/device-agent-cloud-connection.js +357 -195
  34. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  35. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  36. package/lib/infrastructure/agent-config.js +7 -18
  37. package/lib/infrastructure/agent-config.js.map +1 -1
  38. package/lib/infrastructure/agent-config.test.js +47 -0
  39. package/lib/infrastructure/agent-config.test.js.map +1 -1
  40. package/lib/subcommands/app/app.js +1 -1
  41. package/lib/subcommands/app/app.js.map +1 -1
  42. package/lib/subcommands/login.d.ts.map +1 -1
  43. package/lib/subcommands/login.js +4 -3
  44. package/lib/subcommands/login.js.map +1 -1
  45. package/lib/util/copy-dir.d.ts.map +1 -1
  46. package/lib/util/copy-dir.js +3 -1
  47. package/lib/util/copy-dir.js.map +1 -1
  48. package/lib/util/run-in-dir.d.ts +2 -0
  49. package/lib/util/run-in-dir.d.ts.map +1 -0
  50. package/lib/util/run-in-dir.js +17 -0
  51. package/lib/util/run-in-dir.js.map +1 -0
  52. package/lib/util/sleep.d.ts +2 -0
  53. package/lib/util/sleep.d.ts.map +1 -0
  54. package/lib/util/sleep.js +9 -0
  55. package/lib/util/sleep.js.map +1 -0
  56. package/package.json +4 -3
  57. package/src/application-control/backup.ts +3 -0
  58. package/src/application-control/config.ts +61 -0
  59. package/src/application-control/environment-variables.ts +6 -12
  60. package/src/application-control/index.ts +5 -0
  61. package/src/application-control/install.ts +147 -68
  62. package/src/application-control/models.ts +136 -23
  63. package/src/application-control/status.ts +19 -9
  64. package/src/application-control/utils.ts +58 -13
  65. package/src/cloud-connection/device-agent-cloud-connection.ts +459 -216
  66. package/src/infrastructure/agent-config.test.ts +56 -0
  67. package/src/infrastructure/agent-config.ts +10 -19
  68. package/src/subcommands/app/app.ts +1 -1
  69. package/src/subcommands/login.ts +6 -4
  70. package/src/util/copy-dir.ts +3 -1
  71. package/src/util/run-in-dir.ts +15 -0
  72. package/src/util/sleep.ts +5 -0
  73. package/lib/util/run-cli-cmd.d.ts +0 -5
  74. package/lib/util/run-cli-cmd.d.ts.map +0 -1
  75. package/lib/util/run-cli-cmd.js +0 -24
  76. package/lib/util/run-cli-cmd.js.map +0 -1
  77. package/src/util/run-cli-cmd.ts +0 -18
@@ -1,32 +1,61 @@
1
1
  "use strict";
2
- var __asyncValues = (this && this.__asyncValues) || function (o) {
3
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4
- var m = o[Symbol.asyncIterator], i;
5
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
6
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
7
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
8
- };
9
2
  Object.defineProperty(exports, "__esModule", { value: true });
10
3
  exports.runDeviceAgentCloudInterface = exports.DeviceAgentCloudConnection = void 0;
11
4
  const awsIot = require('aws-iot-device-sdk');
12
5
  const urls_1 = require("../infrastructure/urls");
13
6
  const directories_1 = require("../util/directories");
7
+ const sleep_1 = require("../util/sleep");
14
8
  const status_1 = require("../application-control/status");
15
9
  const install_1 = require("../application-control/install");
16
- const device_control_1 = require("../device-control/device-control");
17
10
  const device_agent_schemas_1 = require("@alwaysai/device-agent-schemas");
18
11
  const get_device_id_1 = require("../util/get-device-id");
12
+ const util_1 = require("alwaysai/lib/util");
13
+ const device_control_1 = require("../device-control/device-control");
19
14
  const agent_config_1 = require("../infrastructure/agent-config");
15
+ const utils_1 = require("../application-control/utils");
16
+ const models_1 = require("../application-control/models");
17
+ const config_1 = require("../application-control/config");
20
18
  class DeviceAgentCloudConnection {
21
19
  constructor() {
22
20
  this.clientId = (0, get_device_id_1.getDeviceId)();
23
21
  this.host = (0, urls_1.getIoTCoreEndpointUrl)();
24
- this.publishable = false;
25
- this.publishInterval = 10000;
26
- this.installationStatusInterval = 5000;
22
+ this.liveUpdatesAlive = {
23
+ [device_agent_schemas_1.keyMirrors.agentMessageType.device_stats]: false,
24
+ [device_agent_schemas_1.keyMirrors.agentMessageType.app_state]: false,
25
+ [device_agent_schemas_1.keyMirrors.agentMessageType.app_logs]: false,
26
+ };
27
+ this.liveUpdatesSleepIntervals = {
28
+ [device_agent_schemas_1.keyMirrors.agentMessageType.device_stats]: 5000,
29
+ [device_agent_schemas_1.keyMirrors.agentMessageType.app_state]: 5000,
30
+ [device_agent_schemas_1.keyMirrors.agentMessageType.app_logs]: 5000,
31
+ [device_agent_schemas_1.keyMirrors.agentMessageType.app_install_status]: 5000,
32
+ };
33
+ this.appLogStreams = new Set();
34
+ this.deviceType = 'aai-device';
35
+ this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
36
+ this.shadowTopics = {
37
+ projects: {
38
+ updateDelta: `${this.shadowPrefix}projects/update/delta`,
39
+ getAccepted: `${this.shadowPrefix}projects/get/accepted`,
40
+ },
41
+ };
42
+ this.toCloudTopic = `topic/to_cloud/${this.deviceType}/${this.clientId}`;
43
+ this.toClientTopic = `topic/to_client/${this.deviceType}/${this.clientId}`;
44
+ this.toDeviceTopic = `topic/to_device/${this.deviceType}/${this.clientId}`;
45
+ // must be arrow function due to this context when function is passed as param
46
+ this.getAppInstallStatusMessage = async () => {
47
+ const appInstallStatus = this.getAppInstallStatus();
48
+ const appInstallStatusMessage = {
49
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.app_install_status,
50
+ appInstallStatus,
51
+ };
52
+ return appInstallStatusMessage;
53
+ };
54
+ this.handleDeviceCommand = async (packet) => {
55
+ // TODO
56
+ };
57
+ // Public Methods
27
58
  this.device = awsIot.device;
28
- this.agentTopicPrefix = `destination/agent/device/${this.clientId}/topic/`;
29
- this.cloudTopicPrefix = `destination/cloud/device/${this.clientId}/topic/`;
30
59
  this.device = awsIot.device({
31
60
  keyPath: (0, directories_1.getPrivateKeyFilePath)(),
32
61
  certPath: (0, directories_1.getCertificateFilePath)(),
@@ -34,118 +63,117 @@ class DeviceAgentCloudConnection {
34
63
  clientId: this.clientId,
35
64
  host: this.host,
36
65
  });
37
- this.device.subscribe(`${this.agentTopicPrefix}command`);
38
- this.device.subscribe(`${this.agentTopicPrefix}response`);
66
+ this.device.subscribe(this.toDeviceTopic);
67
+ this.device.subscribe(this.shadowTopics.projects.getAccepted);
68
+ this.device.subscribe(this.shadowTopics.projects.updateDelta);
39
69
  }
40
- setInstallationStatus(installationStatus) {
41
- this.installationStatus = installationStatus;
70
+ // device shadow utils
71
+ getShadowPrefix() {
72
+ return this.shadowPrefix;
42
73
  }
43
- getInstallationStatus() {
44
- return this.installationStatus;
45
- }
46
- getPublishable() {
47
- return this.publishable;
48
- }
49
- setPublishable(enabled) {
50
- this.publishable = enabled;
51
- }
52
- restartPublishableTimeout() {
53
- clearTimeout(this.publishableTimeout);
54
- this.publishableTimeout = setTimeout(() => {
55
- this.setPublishable(false);
56
- }, 600000); // 10 min
74
+ async handleNamedShadowUpdate({ payload }) {
75
+ const delta = JSON.parse(payload);
76
+ const deltaKeys = Object.keys(delta);
77
+ for (const projectId of deltaKeys) {
78
+ const projectShadow = delta[projectId];
79
+ if (projectShadow.appConfig) {
80
+ const appConfig = projectShadow.appConfig;
81
+ const appDir = (0, utils_1.getAppDir)(projectId);
82
+ await (0, config_1.updateAppConfig)(projectId, appConfig);
83
+ if (appConfig.models) {
84
+ this.publishCloudRequest({
85
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.signed_urls_request,
86
+ modelsOnlyUrlsRequest: {
87
+ projectId,
88
+ models: appConfig.models,
89
+ },
90
+ });
91
+ }
92
+ if (appConfig.scripts && !appConfig.models) {
93
+ const appState = await (0, status_1.getAppStatus)({ projectId });
94
+ await (0, utils_1.buildApp)({ appDir });
95
+ if (appState.services.length &&
96
+ appState.services[0].state !== device_agent_schemas_1.keyMirrors.appState.stopped) {
97
+ (0, status_1.restartApp)({ projectId });
98
+ }
99
+ await this.publishReportedState(projectId);
100
+ }
101
+ }
102
+ }
57
103
  }
58
- getPublishInterval() {
59
- return this.publishInterval;
104
+ async startAppLogStream(projectId) {
105
+ this.appLogStreams.add(projectId);
106
+ const readable = await (0, status_1.getAppLogs)({
107
+ projectId,
108
+ args: ['--tail', '100', '--no-log-prefix'],
109
+ });
110
+ readable.on('data', (chunk) => {
111
+ if (!this.appLogStreams.has(projectId)) {
112
+ // why doesn't typescript know about this function?
113
+ // @ts-ignore
114
+ readable.destroy();
115
+ util_1.logger.info(`App log stream terminated for project ${projectId}`);
116
+ return;
117
+ }
118
+ const logStr = chunk.toString();
119
+ const message = {
120
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.app_logs,
121
+ appLogs: {
122
+ projectId,
123
+ logChunk: logStr,
124
+ },
125
+ };
126
+ const packet = this.buildMessagePacket(this.getClientId(), this.toClientTopic, message);
127
+ this.publishMessage(this.toClientTopic, JSON.stringify(packet));
128
+ });
129
+ readable.on('error', (error) => {
130
+ util_1.logger.error(`App log stream terminated for project ${projectId}: ${error}`);
131
+ });
132
+ readable.on('finished', () => {
133
+ util_1.logger.info(`App logs finished piping for project ${projectId}`);
134
+ });
60
135
  }
61
- getInstallationStatusInterval() {
62
- return this.installationStatusInterval;
136
+ // must contain app release hash
137
+ initAppInstallStatus(installationStatus) {
138
+ this.appInstallStatus = installationStatus;
63
139
  }
64
- getClientId() {
65
- return this.clientId;
140
+ updateAppInstallStatus(installationStatus) {
141
+ this.appInstallStatus.status = installationStatus.status;
142
+ this.appInstallStatus.message = installationStatus.message;
66
143
  }
67
- publishMessage(topic, message) {
68
- this.device.publish(topic, message);
144
+ getAppInstallStatus() {
145
+ return this.appInstallStatus;
69
146
  }
70
- }
71
- exports.DeviceAgentCloudConnection = DeviceAgentCloudConnection;
72
- function runDeviceAgentCloudInterface() {
73
- const deviceAgent = new DeviceAgentCloudConnection();
74
- async function buildMessagePacket(topic, payload) {
147
+ // Message Builders
148
+ buildMessagePacket(deviceId, topic, payload) {
75
149
  const packet = {
76
150
  timestamp: new Date().toUTCString(),
77
- deviceId: deviceAgent.getClientId(),
151
+ deviceId,
78
152
  topic,
79
153
  payload,
80
154
  };
81
155
  return packet;
82
156
  }
83
- async function getAppLogsMessage() {
84
- var e_1, _a;
85
- const appLogsList = [];
86
- const apps = await (0, agent_config_1.AgentConfigFile)().getReadyApps();
87
- for (const app of apps) {
88
- const projectId = app.projectId;
89
- const readable = await (0, status_1.getAppLogs)({ projectId });
90
- const logs = [];
91
- readable.setEncoding('utf8');
92
- try {
93
- for (var readable_1 = (e_1 = void 0, __asyncValues(readable)), readable_1_1; readable_1_1 = await readable_1.next(), !readable_1_1.done;) {
94
- const chunk = readable_1_1.value;
95
- logs.push(chunk);
96
- }
97
- }
98
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
99
- finally {
100
- try {
101
- if (readable_1_1 && !readable_1_1.done && (_a = readable_1.return)) await _a.call(readable_1);
102
- }
103
- finally { if (e_1) throw e_1.error; }
104
- }
105
- const appLogs = {
106
- projectId,
107
- logs,
108
- };
109
- appLogsList.push(appLogs);
110
- }
111
- const applicationStatePackage = {
112
- applicationLogs: appLogsList,
113
- };
114
- return applicationStatePackage;
115
- }
116
- // * This was causing massive CPU overhead
117
- // const publishAppLogs = setInterval(async function () {
118
- // const appLogsMessage = await getAppLogsMessage();
119
- // const topic = `device/${deviceAgent.getClientId()}/topic/application-management`;
120
- // const appLogsPacket = await buildMessagePacket(topic, appLogsMessage);
121
- // deviceAgent.publishMessage(topic, JSON.stringify({ appLogsPacket }));
122
- // }, deviceAgent.getPublishInterval());
123
- async function getAppStateMessage() {
157
+ async getAppStateMessage() {
124
158
  const appStateMessage = [];
125
- const apps = await (0, agent_config_1.AgentConfigFile)().getApps();
159
+ const apps = await (0, agent_config_1.AgentConfigFile)().getReadyApps();
126
160
  for (const app of apps) {
127
161
  const projectId = app.projectId;
128
162
  const status = await (0, status_1.getAppStatus)({ projectId });
129
163
  appStateMessage.push(status);
130
164
  }
131
- const applicationStatePackage = {
132
- applicationState: appStateMessage,
165
+ const appStatePackage = {
166
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.app_state,
167
+ appState: appStateMessage,
133
168
  };
134
- return applicationStatePackage;
169
+ return appStatePackage;
135
170
  }
136
- const publishAppState = setInterval(async function () {
137
- if (deviceAgent.getPublishable()) {
138
- const topic = `${deviceAgent.cloudTopicPrefix}application-management`;
139
- const appStateMessage = await getAppStateMessage();
140
- const appStatePacket = await buildMessagePacket(topic, appStateMessage);
141
- deviceAgent.publishMessage(topic, JSON.stringify({ appStatePacket }));
142
- }
143
- }, deviceAgent.getPublishInterval());
144
- async function getDeviceStatsMessage() {
171
+ async getDeviceStatsMessage() {
145
172
  const cpuUsage = await (0, device_control_1.getCpuUtil)();
146
173
  const diskUtil = await (0, device_control_1.getDiskUtil)();
147
174
  const memUtil = await (0, device_control_1.getMemUtil)();
148
175
  const deviceStatsMessage = {
176
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.device_stats,
149
177
  deviceStats: {
150
178
  cpuUsage,
151
179
  diskUtil,
@@ -154,122 +182,256 @@ function runDeviceAgentCloudInterface() {
154
182
  };
155
183
  return deviceStatsMessage;
156
184
  }
157
- const publishDeviceStats = setInterval(async function () {
158
- if (deviceAgent.getPublishable()) {
159
- const topic = `${deviceAgent.cloudTopicPrefix}device-management`;
160
- const deviceStatsMessage = await getDeviceStatsMessage();
161
- const deviceStatsPacket = await buildMessagePacket(topic, deviceStatsMessage);
162
- deviceAgent.publishMessage(topic, JSON.stringify({ deviceStatsPacket }));
185
+ async startPublishingLiveUpdates(topic, messageType, getMessageData) {
186
+ while (true) {
187
+ try {
188
+ const message = await getMessageData();
189
+ const packet = this.buildMessagePacket(this.getClientId(), topic, message);
190
+ this.publishMessage(topic, JSON.stringify(packet));
191
+ }
192
+ catch (e) {
193
+ util_1.logger.error(`Error publishing live updates for ${messageType}: ${e.message}`);
194
+ break;
195
+ }
196
+ if (!this.continuePublishing(messageType)) {
197
+ util_1.logger.info(`Turned off live updates for ${messageType}`);
198
+ break;
199
+ }
200
+ await (0, sleep_1.default)(this.getLiveUpdatesInterval(messageType));
163
201
  }
164
- }, deviceAgent.getPublishInterval());
165
- const publishDeviceRequest = async ({ projectId, releaseHash }) => {
166
- const topic = `${deviceAgent.cloudTopicPrefix}request`;
167
- const deviceRequestPackage = {
168
- timestamp: new Date().toUTCString(),
169
- deviceId: deviceAgent.getClientId(),
170
- projectId,
171
- releaseHash,
172
- topic,
173
- };
174
- deviceAgent.publishMessage(topic, JSON.stringify({ device_request: deviceRequestPackage }));
175
- };
176
- const publishInstallationStatus = async (interval) => {
177
- const topic = `${deviceAgent.cloudTopicPrefix}installation-status`;
178
- const installationStatus = deviceAgent.getInstallationStatus();
179
- const installationStatusPacket = await buildMessagePacket(topic, {
180
- installationStatus,
202
+ }
203
+ continuePublishing(flag) {
204
+ switch (flag) {
205
+ case device_agent_schemas_1.keyMirrors.agentMessageType.device_stats:
206
+ case device_agent_schemas_1.keyMirrors.agentMessageType.app_state:
207
+ return this.liveUpdatesAlive[flag];
208
+ case device_agent_schemas_1.keyMirrors.agentMessageType.app_install_status:
209
+ return this.appInstallStatus.status === device_agent_schemas_1.keyMirrors.appInstallStatus.in_progress;
210
+ default:
211
+ util_1.logger.error(`Unrecognized publishable flag ${flag}`);
212
+ return false;
213
+ }
214
+ }
215
+ getLiveUpdatesInterval(flag) {
216
+ const exists = this.liveUpdatesSleepIntervals[flag];
217
+ if (exists) {
218
+ return exists;
219
+ }
220
+ util_1.logger.error(`Unrecognized live updates flag ${flag}`);
221
+ return -1;
222
+ }
223
+ setLiveUpdates(toggles) {
224
+ if (toggles.deviceStats) {
225
+ this.liveUpdatesAlive.device_stats = toggles.deviceStats;
226
+ }
227
+ if (toggles.appState) {
228
+ this.liveUpdatesAlive.app_state = toggles.appState;
229
+ }
230
+ }
231
+ handleAppStateControl(payload) {
232
+ const { baseCommand, projectId } = payload;
233
+ switch (baseCommand) {
234
+ case device_agent_schemas_1.keyMirrors.appStateControl.start:
235
+ (0, status_1.startApp)({ projectId });
236
+ break;
237
+ case device_agent_schemas_1.keyMirrors.appStateControl.stop:
238
+ (0, status_1.stopApp)({ projectId });
239
+ break;
240
+ case device_agent_schemas_1.keyMirrors.appStateControl.restart:
241
+ (0, status_1.restartApp)({ projectId });
242
+ break;
243
+ }
244
+ }
245
+ handleAppVersionControl(payload) {
246
+ const { projectId, appReleaseHash } = payload;
247
+ const signedUrlsRequest = { projectId, appReleaseHash };
248
+ this.publishCloudRequest({
249
+ messageType: device_agent_schemas_1.keyMirrors.agentMessageType.signed_urls_request,
250
+ signedUrlsRequest,
181
251
  });
182
- deviceAgent.device.publish(topic, JSON.stringify({ installationStatusPacket }));
183
- if (installationStatus.status !== device_agent_schemas_1.InstallationStatusEnum.IN_PROGRESS) {
184
- clearInterval(interval);
252
+ }
253
+ handleAgentCommand(message) {
254
+ switch (message.messageType) {
255
+ case device_agent_schemas_1.keyMirrors.clientMessageType.live_state_updates:
256
+ this.liveUpdatesBroker(message.liveUpdatesToggles);
257
+ break;
258
+ default:
259
+ util_1.logger.error(`Invalid agent action message type from message '${message}'`);
185
260
  }
186
- };
187
- const handleMessageTopic = async ({ topic, payload }) => {
188
- const action = payload['action'];
189
- const actionPayload = payload[action];
190
- const type = topic.split('/').slice(-1)[0];
191
- if (!type) {
192
- return false;
261
+ }
262
+ restartLiveUpdatesTimeout() {
263
+ clearTimeout(this.liveUpdatesTimeout);
264
+ this.liveUpdatesTimeout = setTimeout(() => {
265
+ this.setLiveUpdates({
266
+ deviceStats: false,
267
+ appState: false,
268
+ });
269
+ this.appLogStreams.clear();
270
+ // TODO: Make constant, not hard coded
271
+ }, 600000); // 10 min
272
+ }
273
+ async liveUpdatesBroker({ deviceStats, appState, appLogs, }) {
274
+ this.restartLiveUpdatesTimeout();
275
+ if (deviceStats !== undefined) {
276
+ this.liveUpdatesAlive.device_stats = deviceStats;
277
+ if (deviceStats) {
278
+ this.startPublishingLiveUpdates(this.toClientTopic, device_agent_schemas_1.keyMirrors.agentMessageType.device_stats, this.getDeviceStatsMessage);
279
+ }
193
280
  }
194
- switch (action) {
195
- /**
196
- * Install app package based on the given project ID
197
- */
198
- case 'install':
199
- /**
200
- {
201
- "action": "install",
202
- "install": {
203
- "releaseHash": "7fb2a812f9e7aa193208dac353521965da50d755085162066c125592f1ed760b",
204
- "projectId": "786e4686-a681-4cff-9e17-1e7d385c0fdb"
205
- }
206
- }
207
- */
208
- if (type === 'response') {
209
- deviceAgent.setInstallationStatus({
210
- status: device_agent_schemas_1.InstallationStatusEnum.IN_PROGRESS,
211
- applicationReleaseHash: actionPayload.releaseHash,
212
- });
213
- const installationStatusPing = setInterval(() => publishInstallationStatus(installationStatusPing), deviceAgent.getInstallationStatusInterval());
214
- // Install the app with the given url
215
- await (async () => {
216
- try {
217
- await (0, install_1.installApp)(actionPayload);
218
- deviceAgent.setInstallationStatus({
219
- status: device_agent_schemas_1.InstallationStatusEnum.SUCCESS,
220
- applicationReleaseHash: actionPayload.releaseHash,
221
- });
222
- }
223
- catch (e) {
224
- const reason = e.message;
225
- deviceAgent.setInstallationStatus({
226
- status: device_agent_schemas_1.InstallationStatusEnum.FAILURE,
227
- reason,
228
- applicationReleaseHash: actionPayload.releaseHash,
229
- });
230
- }
231
- })();
232
- break;
233
- }
234
- publishDeviceRequest(actionPayload);
281
+ if (appState !== undefined) {
282
+ this.liveUpdatesAlive.app_state = appState;
283
+ if (appState) {
284
+ this.startPublishingLiveUpdates(this.toClientTopic, device_agent_schemas_1.keyMirrors.agentMessageType.app_state, this.getAppStateMessage);
285
+ }
286
+ }
287
+ if (appLogs !== undefined) {
288
+ if (appLogs.toggle) {
289
+ this.startAppLogStream(appLogs.projectId);
290
+ }
291
+ else {
292
+ this.appLogStreams.delete(appLogs.projectId);
293
+ }
294
+ }
295
+ }
296
+ async publishReportedState(projectId) {
297
+ const newAppCfg = await (0, utils_1.getAppConfig)(projectId);
298
+ const packet = {
299
+ state: {
300
+ reported: {
301
+ [projectId]: { appConfig: newAppCfg },
302
+ },
303
+ },
304
+ };
305
+ this.publishMessage(`${this.shadowPrefix}projects/update`, JSON.stringify(packet));
306
+ }
307
+ async publishCloudRequest(payload) {
308
+ const topic = this.toCloudTopic;
309
+ const deviceRequestPacket = this.buildMessagePacket(this.getClientId(), topic, payload);
310
+ this.publishMessage(topic, JSON.stringify({ deviceRequestPacket }));
311
+ }
312
+ getClientId() {
313
+ return this.clientId;
314
+ }
315
+ publishMessage(topic, message) {
316
+ // TODO: topic validation
317
+ this.device.publish(topic, message);
318
+ }
319
+ async handleClientMessage({ topic, message, }) {
320
+ const payload = message.payload;
321
+ switch (payload.messageType) {
322
+ case device_agent_schemas_1.keyMirrors.clientMessageType.app_state_control: {
323
+ this.handleAppStateControl(payload.appStateControl);
235
324
  break;
236
- case 'start':
237
- (0, status_1.startApp)({ projectId: actionPayload.projectId });
325
+ }
326
+ case device_agent_schemas_1.keyMirrors.clientMessageType.app_version_control: {
327
+ this.handleAppVersionControl(payload.appVersionControl);
238
328
  break;
239
- case 'stop':
240
- (0, status_1.stopApp)({ projectId: actionPayload.projectId });
329
+ }
330
+ case device_agent_schemas_1.keyMirrors.clientMessageType.live_state_updates: {
331
+ this.handleAgentCommand(payload);
241
332
  break;
242
- case 'restart':
243
- (0, status_1.restartApp)({ projectId: actionPayload.projectId });
333
+ }
334
+ case device_agent_schemas_1.keyMirrors.clientMessageType.app_install_cloud_response: {
335
+ const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } = payload.appInstallCloudResponse;
336
+ this.initAppInstallStatus({
337
+ status: device_agent_schemas_1.keyMirrors.appInstallStatus.in_progress,
338
+ appReleaseHash,
339
+ });
340
+ this.startPublishingLiveUpdates(this.toClientTopic, device_agent_schemas_1.keyMirrors.agentMessageType.app_install_status, this.getAppInstallStatusMessage);
341
+ // Install the app and models
342
+ try {
343
+ const signedUrlsPayload = {
344
+ appInstallPayload,
345
+ modelsInstallPayload,
346
+ };
347
+ await (0, install_1.installApp)({
348
+ projectId,
349
+ appReleaseHash,
350
+ signedUrlsPayload,
351
+ });
352
+ this.updateAppInstallStatus({
353
+ status: device_agent_schemas_1.keyMirrors.appInstallStatus.success,
354
+ });
355
+ // update app config shadow for project
356
+ await this.publishReportedState(projectId);
357
+ }
358
+ catch (e) {
359
+ console.error(e);
360
+ const message = e.message;
361
+ // uninstall the failed app to put system back in good state
362
+ await (0, install_1.uninstallApp)({ projectId });
363
+ this.updateAppInstallStatus({
364
+ status: device_agent_schemas_1.keyMirrors.appInstallStatus.failure,
365
+ message,
366
+ });
367
+ // delete shadow for project
368
+ this.publishMessage(`${this.shadowPrefix}${projectId}/delete`, '');
369
+ }
244
370
  break;
245
- /**
246
- * Allow/disallow publishing on this device. By default, (publishable = false)
247
- */
248
- case 'publishable':
249
- /**
250
- {
251
- "action": "publishable",
252
- "publishable": true | false
253
- }
254
- */
255
- deviceAgent.setPublishable(actionPayload);
256
- deviceAgent.restartPublishableTimeout();
371
+ }
372
+ case device_agent_schemas_1.keyMirrors.clientMessageType.models_install_cloud_response: {
373
+ const { projectId, newModels } = payload.modelsInstallCloudResponse;
374
+ try {
375
+ await (0, models_1.updateModelsWithPresignedUrls)(projectId, newModels);
376
+ await this.publishReportedState(projectId);
377
+ }
378
+ catch (e) {
379
+ console.error(e);
380
+ }
257
381
  break;
382
+ }
258
383
  default:
259
- break;
384
+ util_1.logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
385
+ }
386
+ }
387
+ async handleShadowTopic({ topic, payload }) {
388
+ const shadowName = topic.split('/')[5];
389
+ const message = JSON.parse(payload);
390
+ if (topic === this.shadowTopics.projects.updateDelta) {
391
+ this.handleNamedShadowUpdate({ payload });
260
392
  }
261
- };
393
+ else if (topic === this.shadowTopics.projects.getAccepted) {
394
+ if (message.delta) {
395
+ this.handleNamedShadowUpdate({
396
+ payload: JSON.stringify(message.delta),
397
+ });
398
+ }
399
+ else {
400
+ console.log(`No delta updates in shadow ${shadowName}`);
401
+ }
402
+ }
403
+ }
404
+ }
405
+ exports.DeviceAgentCloudConnection = DeviceAgentCloudConnection;
406
+ function runDeviceAgentCloudInterface() {
407
+ const deviceAgent = new DeviceAgentCloudConnection();
262
408
  deviceAgent.device.on('connect', function () {
263
409
  deviceAgent.publishMessage('connection', deviceAgent.getClientId());
264
410
  console.log('Device Agent has connected to the cloud');
411
+ // Get shadow updates
412
+ deviceAgent.publishMessage(`${deviceAgent.getShadowPrefix()}projects/get`, '');
265
413
  });
266
414
  deviceAgent.device.on('disconnect', function () {
267
415
  console.log('Device Agent has been disconnected from the cloud');
268
416
  });
269
417
  deviceAgent.device.on('message', function (topic, payload) {
270
- // ToDo: insert valdiation here for incoming messages. Maybe we will use a JSON schema for the message structure.
271
418
  try {
272
- handleMessageTopic({ topic, payload: JSON.parse(payload) });
419
+ const jsonPacket = JSON.parse(payload);
420
+ if (jsonPacket.hasOwnProperty('state')) {
421
+ deviceAgent.handleShadowTopic({
422
+ topic,
423
+ payload: JSON.stringify(jsonPacket.state),
424
+ });
425
+ }
426
+ else {
427
+ const valid = (0, device_agent_schemas_1.validateClientMessage)(jsonPacket);
428
+ if (!valid) {
429
+ console.error(JSON.stringify(device_agent_schemas_1.validateClientMessage.errors));
430
+ }
431
+ else {
432
+ deviceAgent.handleClientMessage({ topic, message: jsonPacket });
433
+ }
434
+ }
273
435
  }
274
436
  catch (error) {
275
437
  console.error(error);