@alwaysai/device-agent 1.3.1 → 1.5.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 (183) hide show
  1. package/lib/application-control/config.js +2 -2
  2. package/lib/application-control/config.js.map +1 -1
  3. package/lib/application-control/environment-variables.d.ts.map +1 -1
  4. package/lib/application-control/environment-variables.js +9 -4
  5. package/lib/application-control/environment-variables.js.map +1 -1
  6. package/lib/application-control/environment-variables.test.js +1 -1
  7. package/lib/application-control/environment-variables.test.js.map +1 -1
  8. package/lib/application-control/install.d.ts.map +1 -1
  9. package/lib/application-control/install.js +7 -2
  10. package/lib/application-control/install.js.map +1 -1
  11. package/lib/application-control/models.d.ts +5 -0
  12. package/lib/application-control/models.d.ts.map +1 -1
  13. package/lib/application-control/models.js +28 -14
  14. package/lib/application-control/models.js.map +1 -1
  15. package/lib/application-control/status.d.ts.map +1 -1
  16. package/lib/application-control/status.js +14 -17
  17. package/lib/application-control/status.js.map +1 -1
  18. package/lib/application-control/utils.js +2 -2
  19. package/lib/application-control/utils.js.map +1 -1
  20. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
  21. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  22. package/lib/cloud-connection/device-agent-cloud-connection.js +140 -105
  23. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  24. package/lib/cloud-connection/live-updates-handler.d.ts +4 -2
  25. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  26. package/lib/cloud-connection/live-updates-handler.js +46 -25
  27. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  28. package/lib/cloud-connection/live-updates-handler.test.js +132 -16
  29. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  30. package/lib/cloud-connection/messages.d.ts.map +1 -1
  31. package/lib/cloud-connection/messages.js +3 -4
  32. package/lib/cloud-connection/messages.js.map +1 -1
  33. package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
  34. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  35. package/lib/cloud-connection/passthrough-handler.js +76 -62
  36. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  37. package/lib/cloud-connection/shadow-handler.d.ts +16 -21
  38. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  39. package/lib/cloud-connection/shadow-handler.js +162 -108
  40. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  41. package/lib/cloud-connection/shadow-handler.test.js +100 -83
  42. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  43. package/lib/cloud-connection/transaction-manager.d.ts +3 -0
  44. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  45. package/lib/cloud-connection/transaction-manager.js +11 -0
  46. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  47. package/lib/cloud-connection/transaction-manager.test.js +102 -0
  48. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  49. package/lib/device-control/device-control.d.ts +16 -15
  50. package/lib/device-control/device-control.d.ts.map +1 -1
  51. package/lib/device-control/device-control.js +117 -18
  52. package/lib/device-control/device-control.js.map +1 -1
  53. package/lib/docker/docker-compose.d.ts +14 -0
  54. package/lib/docker/docker-compose.d.ts.map +1 -0
  55. package/lib/docker/docker-compose.js +56 -0
  56. package/lib/docker/docker-compose.js.map +1 -0
  57. package/lib/index.js +2 -5
  58. package/lib/index.js.map +1 -1
  59. package/lib/infrastructure/agent-config.d.ts +45 -14
  60. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  61. package/lib/infrastructure/agent-config.js +30 -15
  62. package/lib/infrastructure/agent-config.js.map +1 -1
  63. package/lib/infrastructure/agent-config.test.js +3 -0
  64. package/lib/infrastructure/agent-config.test.js.map +1 -1
  65. package/lib/local-connection/rabbitmq-connection.js +11 -11
  66. package/lib/local-connection/rabbitmq-connection.js.map +1 -1
  67. package/lib/secure-tunneling/secure-tunneling.d.ts +97 -0
  68. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  69. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  70. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  71. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  72. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  73. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  74. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  75. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  76. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  77. package/lib/secure-tunneling/spawner-detached.js +107 -0
  78. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  79. package/lib/subcommands/app/analytics.d.ts.map +1 -1
  80. package/lib/subcommands/app/analytics.js +9 -13
  81. package/lib/subcommands/app/analytics.js.map +1 -1
  82. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  83. package/lib/subcommands/app/env-vars.js +11 -16
  84. package/lib/subcommands/app/env-vars.js.map +1 -1
  85. package/lib/subcommands/app/models.d.ts.map +1 -1
  86. package/lib/subcommands/app/models.js +12 -16
  87. package/lib/subcommands/app/models.js.map +1 -1
  88. package/lib/subcommands/device/clean.d.ts.map +1 -1
  89. package/lib/subcommands/device/clean.js +8 -6
  90. package/lib/subcommands/device/clean.js.map +1 -1
  91. package/lib/subcommands/device/get-info.d.ts +2 -0
  92. package/lib/subcommands/device/get-info.d.ts.map +1 -0
  93. package/lib/subcommands/device/get-info.js +36 -0
  94. package/lib/subcommands/device/get-info.js.map +1 -0
  95. package/lib/subcommands/device/index.d.ts.map +1 -1
  96. package/lib/subcommands/device/index.js +11 -2
  97. package/lib/subcommands/device/index.js.map +1 -1
  98. package/lib/subcommands/device/init.d.ts +5 -0
  99. package/lib/subcommands/device/init.d.ts.map +1 -0
  100. package/lib/subcommands/device/{device.js → init.js} +5 -36
  101. package/lib/subcommands/device/init.js.map +1 -0
  102. package/lib/subcommands/device/refresh.d.ts +2 -0
  103. package/lib/subcommands/device/refresh.d.ts.map +1 -0
  104. package/lib/subcommands/device/refresh.js +24 -0
  105. package/lib/subcommands/device/refresh.js.map +1 -0
  106. package/lib/subcommands/device/restart.d.ts +2 -0
  107. package/lib/subcommands/device/restart.d.ts.map +1 -0
  108. package/lib/subcommands/device/restart.js +14 -0
  109. package/lib/subcommands/device/restart.js.map +1 -0
  110. package/lib/util/check-for-updates.d.ts +3 -0
  111. package/lib/util/check-for-updates.d.ts.map +1 -0
  112. package/lib/util/check-for-updates.js +69 -0
  113. package/lib/util/check-for-updates.js.map +1 -0
  114. package/lib/util/cloud-mode-ready.d.ts +1 -0
  115. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  116. package/lib/util/cloud-mode-ready.js +36 -1
  117. package/lib/util/cloud-mode-ready.js.map +1 -1
  118. package/lib/util/file.d.ts +7 -0
  119. package/lib/util/file.d.ts.map +1 -0
  120. package/lib/util/file.js +66 -0
  121. package/lib/util/file.js.map +1 -0
  122. package/lib/util/file.test.d.ts +2 -0
  123. package/lib/util/file.test.d.ts.map +1 -0
  124. package/lib/util/file.test.js +87 -0
  125. package/lib/util/file.test.js.map +1 -0
  126. package/package.json +8 -7
  127. package/readme.md +3 -3
  128. package/src/application-control/config.ts +1 -1
  129. package/src/application-control/environment-variables.test.ts +1 -1
  130. package/src/application-control/environment-variables.ts +9 -6
  131. package/src/application-control/install.ts +8 -3
  132. package/src/application-control/models.ts +47 -19
  133. package/src/application-control/status.ts +16 -14
  134. package/src/application-control/utils.ts +1 -1
  135. package/src/cloud-connection/device-agent-cloud-connection.ts +202 -148
  136. package/src/cloud-connection/live-updates-handler.test.ts +161 -20
  137. package/src/cloud-connection/live-updates-handler.ts +63 -31
  138. package/src/cloud-connection/messages.ts +3 -4
  139. package/src/cloud-connection/passthrough-handler.ts +98 -76
  140. package/src/cloud-connection/shadow-handler.test.ts +101 -84
  141. package/src/cloud-connection/shadow-handler.ts +287 -133
  142. package/src/cloud-connection/transaction-manager.test.ts +124 -0
  143. package/src/cloud-connection/transaction-manager.ts +15 -0
  144. package/src/device-control/device-control.ts +125 -23
  145. package/src/docker/docker-compose.ts +60 -0
  146. package/src/index.ts +2 -6
  147. package/src/infrastructure/agent-config.test.ts +3 -0
  148. package/src/infrastructure/agent-config.ts +38 -40
  149. package/src/local-connection/rabbitmq-connection.ts +8 -8
  150. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  151. package/src/secure-tunneling/secure-tunneling.ts +599 -0
  152. package/src/secure-tunneling/spawner-detached.ts +123 -0
  153. package/src/subcommands/app/analytics.ts +16 -13
  154. package/src/subcommands/app/env-vars.ts +18 -16
  155. package/src/subcommands/app/models.ts +20 -16
  156. package/src/subcommands/device/clean.ts +5 -2
  157. package/src/subcommands/device/get-info.ts +49 -0
  158. package/src/subcommands/device/index.ts +11 -2
  159. package/src/subcommands/device/{device.ts → init.ts} +5 -47
  160. package/src/subcommands/device/refresh.ts +22 -0
  161. package/src/subcommands/device/restart.ts +11 -0
  162. package/src/util/check-for-updates.ts +69 -0
  163. package/src/util/cloud-mode-ready.ts +36 -0
  164. package/src/util/file.test.ts +90 -0
  165. package/src/util/file.ts +76 -0
  166. package/lib/docker/docker-compose-cmd.d.ts +0 -5
  167. package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
  168. package/lib/docker/docker-compose-cmd.js +0 -16
  169. package/lib/docker/docker-compose-cmd.js.map +0 -1
  170. package/lib/secure-tunneling/index.d.ts +0 -5
  171. package/lib/secure-tunneling/index.d.ts.map +0 -1
  172. package/lib/secure-tunneling/index.js +0 -64
  173. package/lib/secure-tunneling/index.js.map +0 -1
  174. package/lib/subcommands/device/device.d.ts +0 -7
  175. package/lib/subcommands/device/device.d.ts.map +0 -1
  176. package/lib/subcommands/device/device.js.map +0 -1
  177. package/lib/util/safe-rimraf.d.ts +0 -2
  178. package/lib/util/safe-rimraf.d.ts.map +0 -1
  179. package/lib/util/safe-rimraf.js +0 -16
  180. package/lib/util/safe-rimraf.js.map +0 -1
  181. package/src/docker/docker-compose-cmd.ts +0 -15
  182. package/src/secure-tunneling/index.ts +0 -74
  183. package/src/util/safe-rimraf.ts +0 -14
@@ -1,59 +1,60 @@
1
1
  // eslint-disable-next-line
2
- const awsIot = require("aws-iot-device-sdk");
3
- import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
4
- import { existsSync } from 'fs';
5
- import {
6
- BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
7
- AWS_ROOT_CERTIFICATE_FILE_PATH,
8
- BOOTSTRAP_CERTIFICATES_DIR_PATH,
9
- DEVICE_PRIVATE_KEY_FILE_PATH,
10
- DEVICE_CERTIFICATE_FILE_PATH
11
- } from '../util/directories';
2
+ const awsIot = require('aws-iot-device-sdk');
12
3
  import {
13
- keyMirrors,
14
- SignedUrlsRequestPayload,
15
- getToDeviceTopic,
16
4
  AppInstallResponsePayload,
17
- validateToDeviceAgentMessage,
18
- ToDeviceAgentMessage,
19
- ToCloudMessage,
20
5
  AppStateControlPayload,
21
6
  AppVersionControlInstallPayload,
22
7
  AppVersionControlUninstallPayload,
23
8
  DeviceActionPayload,
9
+ ModelsInstallResponsePayload,
10
+ SignedUrlsRequestPayload,
11
+ ToCloudMessage,
12
+ ToDeviceAgentMessage,
13
+ getToDeviceTopic,
24
14
  buildSignedUrlsRequestMessage,
25
15
  buildToClientStatusResponseMessage,
26
- StatusResponsePayload
16
+ StatusResponsePayload,
17
+ keyMirrors,
18
+ validateToDeviceAgentMessage,
19
+ validateSecureTunnelShadowUpdate
27
20
  } from '@alwaysai/device-agent-schemas';
28
- import { getDeviceUuid } from '../util/get-device-id';
29
- import { logger } from '../util/logger';
30
- import { cloudModeReady } from '../util/cloud-mode-ready';
31
- import { AgentConfigFile } from '../infrastructure/agent-config';
21
+ import { existsSync } from 'fs';
32
22
  import {
23
+ installApp,
24
+ restartApp,
33
25
  startApp,
34
26
  stopApp,
35
- restartApp,
36
- updateModelsWithPresignedUrls,
37
- installApp,
38
27
  uninstallApp,
39
28
  updateAppCfg,
40
- setEnv
29
+ updateModelsWithPresignedUrls
41
30
  } from '../application-control';
42
- import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
43
- import { secureTunnelNotifyHandler } from '../secure-tunneling/index';
44
- import { Publisher } from './publisher';
45
- import { bootstrapProvision } from './bootstrap-provision';
46
- import { PassthroughHandler, runChannel } from './passthrough-handler';
31
+ import { createAppBackup, rollbackApp } from '../application-control/backup';
32
+ import { reboot } from '../device-control/device-control';
47
33
  import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
48
- import { ModelsInstallResponsePayload } from '@alwaysai/device-agent-schemas';
34
+ import { AgentConfigFile } from '../infrastructure/agent-config';
35
+ import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
36
+ import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
37
+ import { cloudModeReady } from '../util/cloud-mode-ready';
38
+ import {
39
+ AWS_ROOT_CERTIFICATE_FILE_PATH,
40
+ BOOTSTRAP_CERTIFICATES_DIR_PATH,
41
+ BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
42
+ DEVICE_CERTIFICATE_FILE_PATH,
43
+ DEVICE_PRIVATE_KEY_FILE_PATH
44
+ } from '../util/directories';
45
+ import { getDeviceUuid } from '../util/get-device-id';
46
+ import { logger } from '../util/logger';
49
47
  import sleep from '../util/sleep';
50
- import { createAppBackup, rollbackApp } from '../application-control/backup';
48
+ import { bootstrapProvision } from './bootstrap-provision';
49
+ import { LiveUpdatesHandler } from './live-updates-handler';
50
+ import { PassthroughHandler } from './passthrough-handler';
51
+ import { Publisher } from './publisher';
52
+ import { ShadowHandler, ShadowUpdate } from './shadow-handler';
51
53
  import { TransactionManager } from './transaction-manager';
52
- import { reboot } from '../device-control/device-control';
53
-
54
54
  import { exec } from 'child_process';
55
55
  import { promisify } from 'util';
56
- import { LiveUpdatesHandler } from './live-updates-handler';
56
+ import { pruneModels } from '../application-control/models';
57
+ import { getDeviceAgentVersion } from '../util/check-for-updates';
57
58
 
58
59
  const exec_promise = promisify(exec);
59
60
 
@@ -69,8 +70,8 @@ export class DeviceAgentCloudConnection {
69
70
  private port = 8883;
70
71
  private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
71
72
  private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
72
- // FIXME: Add support for multiple simultaneous project updates
73
- private appCfgUpdateQueue: ShadowUpdate[] = [];
73
+ private readonly secureTunnelHandler =
74
+ SecureTunnelHandlerSingleton.getInstance();
74
75
 
75
76
  private handleAppStateControl = async (
76
77
  payload: AppStateControlPayload
@@ -144,46 +145,50 @@ export class DeviceAgentCloudConnection {
144
145
  appInstallPayload,
145
146
  modelsInstallPayload
146
147
  };
147
- await this.atomicApplicationUpdate(
148
- installApp,
149
- [{ projectId, appReleaseHash, signedUrlsPayload }],
150
- projectId
151
- );
148
+ await this.atomicApplicationUpdate(async () => {
149
+ this.shadowHandler.clearProjectShadow(projectId);
150
+ await installApp({ projectId, appReleaseHash, signedUrlsPayload });
151
+ }, projectId);
152
152
  return true;
153
153
  };
154
154
 
155
155
  private handleModelsInstallCloudResponsePayload = async (
156
- payload: ModelsInstallResponsePayload
156
+ payload: ModelsInstallResponsePayload,
157
+ txId: string
157
158
  ): Promise<boolean> => {
158
- const update = this.appCfgUpdateQueue.shift();
159
+ const projectId = payload.modelsInstallResponse.projectId;
160
+
161
+ const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
159
162
  if (update === undefined) {
160
163
  throw new Error(
161
164
  'Unknown error while updating models via application config! No config present for model update.'
162
165
  );
163
166
  }
164
167
  const { appCfgUpdate, envVarUpdate } = update;
165
- const projectId = payload.modelsInstallResponse.projectId;
166
168
  if (appCfgUpdate) {
167
169
  await this.atomicApplicationUpdate(
168
- updateModelsWithPresignedUrls,
169
- [
170
- {
170
+ async () =>
171
+ await updateModelsWithPresignedUrls({
171
172
  projectId,
172
173
  modelInstallPayloads: payload.modelsInstallResponse.newModels,
173
174
  newAppCfg: appCfgUpdate.newAppCfg
174
- }
175
- ],
175
+ }),
176
176
  projectId
177
177
  );
178
178
  }
179
179
 
180
180
  if (envVarUpdate) {
181
181
  await this.atomicApplicationUpdate(
182
- setEnv,
183
- [{ projectId, envVars: envVarUpdate.envVars }],
184
- projectId
182
+ async () =>
183
+ await this.shadowHandler.updateProjectEnvVars({
184
+ projectId,
185
+ envVars: envVarUpdate.envVars
186
+ }),
187
+ projectId,
188
+ true
185
189
  );
186
190
  }
191
+
187
192
  return true;
188
193
  };
189
194
 
@@ -214,7 +219,7 @@ export class DeviceAgentCloudConnection {
214
219
  private async atomicApplicationUninstall(projectId: string) {
215
220
  try {
216
221
  await uninstallApp({ projectId });
217
- this.shadowHandler.clearAppConfig(projectId);
222
+ this.shadowHandler.clearProjectShadow(projectId);
218
223
  } catch (e) {
219
224
  logger.error(`Failed to uninstall ${projectId}: ${e.message}`);
220
225
  throw e;
@@ -222,11 +227,11 @@ export class DeviceAgentCloudConnection {
222
227
  }
223
228
 
224
229
  // eslint-disable-next-line
225
- private async atomicApplicationUpdate<T extends any[], R extends any>(
226
- func: (...args: T) => R,
227
- args: T,
228
- projectId: string
229
- ) {
230
+ private async atomicApplicationUpdate <F extends () => any>(
231
+ func: F,
232
+ projectId: string,
233
+ skipUpdateShadow?: boolean
234
+ ): Promise<ReturnType<F>> {
230
235
  // First try to create a backup, so that there is one available if something goes wrong in the next try:catch.
231
236
  if (await AgentConfigFile().isAppPresent({ projectId })) {
232
237
  try {
@@ -239,9 +244,9 @@ export class DeviceAgentCloudConnection {
239
244
  }
240
245
 
241
246
  try {
242
- const out: R = await func(...args);
243
- this.shadowHandler.clearAppConfig(projectId);
244
- await this.shadowHandler.updateProjectShadow(projectId);
247
+ const out: ReturnType<F> = await func();
248
+ if (!skipUpdateShadow)
249
+ await this.shadowHandler.updateProjectShadow(projectId);
245
250
  return out;
246
251
  } catch (errorAppUpdate) {
247
252
  logger.error(
@@ -256,9 +261,9 @@ export class DeviceAgentCloudConnection {
256
261
  } catch (errorRollbackApp) {
257
262
  // and if that fails, uninstall the app as a last resort.
258
263
  try {
259
- await uninstallApp({ projectId });
260
- } finally {
261
- this.shadowHandler.clearAppConfig(projectId);
264
+ await this.atomicApplicationUninstall(projectId);
265
+ } catch {
266
+ // atomicApplicationUninstall handles failing, so there's nothing to handle here.
262
267
  }
263
268
  logger.error(
264
269
  `Application update failed, rolled back to previous version: ${errorAppUpdate}`
@@ -273,15 +278,14 @@ export class DeviceAgentCloudConnection {
273
278
  }
274
279
  }
275
280
 
276
- private handleAppConfigUpdate = async (
281
+ private handleProjectShadowConfigUpdate = async (
277
282
  update: ShadowUpdate,
278
283
  txId: string
279
284
  ): Promise<boolean> => {
280
285
  const { projectId, appCfgUpdate, envVarUpdate } = update;
281
286
 
282
287
  if (
283
- appCfgUpdate &&
284
- appCfgUpdate.updatedModels &&
288
+ appCfgUpdate?.updatedModels &&
285
289
  Object.keys(appCfgUpdate.updatedModels).length
286
290
  ) {
287
291
  // When there are model updates request signed URLs and wait to apply config changes
@@ -305,33 +309,97 @@ export class DeviceAgentCloudConnection {
305
309
  );
306
310
  this.publisher.publishToCloud(message);
307
311
 
308
- this.appCfgUpdateQueue.push(update);
312
+ this.txnMgr.setAppCfgUpdateToTx(txId, update);
313
+
309
314
  return false;
310
315
  }
311
316
 
312
317
  if (appCfgUpdate) {
313
- await this.atomicApplicationUpdate(
314
- updateAppCfg,
315
- [
316
- {
317
- projectId,
318
- newAppCfg: appCfgUpdate.newAppCfg
319
- }
320
- ],
321
- projectId
322
- );
318
+ await this.atomicApplicationUpdate(async () => {
319
+ await pruneModels({
320
+ projectId,
321
+ appCfg: appCfgUpdate.newAppCfg
322
+ });
323
+ await updateAppCfg({
324
+ projectId,
325
+ newAppCfg: appCfgUpdate.newAppCfg
326
+ });
327
+ }, projectId);
323
328
  }
324
329
 
325
330
  if (envVarUpdate) {
326
331
  await this.atomicApplicationUpdate(
327
- setEnv,
328
- [{ projectId, envVars: envVarUpdate.envVars }],
329
- projectId
332
+ async () =>
333
+ await this.shadowHandler.updateProjectEnvVars({
334
+ projectId,
335
+ envVars: envVarUpdate.envVars
336
+ }),
337
+ projectId,
338
+ true
330
339
  );
331
340
  }
332
341
  return true;
333
342
  };
334
343
 
344
+ private async handleProjectShadowMessage(topic: string, message: any) {
345
+ const shadowUpdates = await this.shadowHandler.handleProjectShadow({
346
+ topic,
347
+ payload: message,
348
+ clientToken: message.clientToken
349
+ });
350
+ if (shadowUpdates.length) {
351
+ const shadowUpdatePromises: Promise<void>[] = [];
352
+ for (const shadowUpdate of shadowUpdates) {
353
+ const projectId = shadowUpdate.projectId;
354
+ const txId = shadowUpdate.txId;
355
+ shadowUpdatePromises.push(
356
+ this.txnMgr
357
+ .runTransactionStep({
358
+ func: () =>
359
+ this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
360
+ projectId,
361
+ txId,
362
+ start: true,
363
+ stepName: topic
364
+ })
365
+ .catch((e: Error) => {
366
+ logger.error(
367
+ `There was an issue updating project shadow config: ${JSON.stringify(
368
+ e
369
+ )}`
370
+ );
371
+ })
372
+ );
373
+ }
374
+
375
+ await Promise.all(shadowUpdatePromises);
376
+ }
377
+ }
378
+
379
+ public async handleSecureTunnelMessage(payload: any): Promise<void> {
380
+ logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
381
+ const state = payload.state;
382
+ if (!state) {
383
+ logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
384
+ return;
385
+ }
386
+ const valid = validateSecureTunnelShadowUpdate(state);
387
+ if (!valid) {
388
+ logger.error(
389
+ `Error validating message: ${JSON.stringify(
390
+ { payload, errors: validateSecureTunnelShadowUpdate.errors },
391
+ null,
392
+ 2
393
+ )}`
394
+ );
395
+ return;
396
+ }
397
+ const secureTunnelUpdate =
398
+ await this.secureTunnelHandler.syncShadowToDeviceState(payload);
399
+ await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
400
+ return;
401
+ }
402
+
335
403
  /*=================================================================
336
404
  Public interface
337
405
  =================================================================*/
@@ -359,11 +427,11 @@ export class DeviceAgentCloudConnection {
359
427
 
360
428
  this.subscribe(this.toDeviceTopic);
361
429
  this.subscribe(this.secureTunnelNotifyTopic);
362
- this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
363
- this.subscribe(this.shadowHandler.shadowTopics.projects.getRejected);
364
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
365
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateAccepted);
366
- this.subscribe(this.shadowHandler.shadowTopics.projects.updateRejected);
430
+ for (const topic of this.shadowHandler.projectShadowTopics) {
431
+ this.subscribe(topic);
432
+ }
433
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
434
+ this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
367
435
  }
368
436
 
369
437
  public getClientId(): string {
@@ -374,10 +442,6 @@ export class DeviceAgentCloudConnection {
374
442
  return this.toDeviceTopic;
375
443
  }
376
444
 
377
- public getShadowTopics(): ShadowTopics {
378
- return this.shadowHandler.shadowTopics;
379
- }
380
-
381
445
  public isCmdInProgress(projectId: string): boolean {
382
446
  return this.txnMgr.isOngoingTransactionForProjectID(projectId);
383
447
  }
@@ -412,7 +476,8 @@ export class DeviceAgentCloudConnection {
412
476
  live_state_updates,
413
477
  app_install_response,
414
478
  models_install_response,
415
- status_response
479
+ status_response,
480
+ device_action
416
481
  } = keyMirrors.toDeviceAgentMessageType;
417
482
  switch (message.messageType) {
418
483
  case app_state_control: {
@@ -492,7 +557,8 @@ export class DeviceAgentCloudConnection {
492
557
  );
493
558
  }
494
559
  await this.txnMgr.runTransactionStep({
495
- func: () => this.handleModelsInstallCloudResponsePayload(payload),
560
+ func: () =>
561
+ this.handleModelsInstallCloudResponsePayload(payload, txId),
496
562
  projectId,
497
563
  txId,
498
564
  start: false,
@@ -520,8 +586,20 @@ export class DeviceAgentCloudConnection {
520
586
  }
521
587
  break;
522
588
  }
523
- case keyMirrors.toDeviceAgentMessageType.device_action: {
589
+ case device_action: {
524
590
  try {
591
+ const statusResponsePayload: StatusResponsePayload = {
592
+ status: keyMirrors.statusResponse.in_progress
593
+ };
594
+ const statusResponseMessage = buildToClientStatusResponseMessage(
595
+ this.clientId,
596
+ statusResponsePayload,
597
+ txId
598
+ );
599
+ this.publisher.publishToClient(statusResponseMessage);
600
+
601
+ await this.handleDeviceAction(message.payload);
602
+
525
603
  const successStatusResponsePayload: StatusResponsePayload = {
526
604
  status: keyMirrors.statusResponse.success
527
605
  };
@@ -532,8 +610,6 @@ export class DeviceAgentCloudConnection {
532
610
  txId
533
611
  );
534
612
  this.publisher.publishToClient(successStatusResponseMessage);
535
-
536
- await this.handleDeviceAction(message.payload);
537
613
  } catch (e) {
538
614
  logger.error(
539
615
  `There was a problem performing device action '${message.payload.action}': ${e.message}`
@@ -563,57 +639,33 @@ export class DeviceAgentCloudConnection {
563
639
  }
564
640
  }
565
641
 
566
- public async handleMessage(
567
- topic: string,
568
- message: ToDeviceAgentMessage | any
569
- ) {
642
+ public async handleMessage(topic: string, message: any) {
570
643
  logger.debug(
571
644
  `Received message: ${JSON.stringify({ topic, message }, null, 2)}`
572
645
  );
573
- switch (topic) {
574
- case this.shadowHandler.shadowTopics.projects.getAccepted:
575
- case this.shadowHandler.shadowTopics.projects.updateAccepted: {
576
- const shadowUpdates = await this.shadowHandler.handleShadowTopic({
577
- topic,
578
- payload: message.state,
579
- clientToken: message.clientToken
580
- });
581
- if (shadowUpdates.length) {
582
- for (const shadowUpdate of shadowUpdates) {
583
- const projectId = shadowUpdate.projectId;
584
- const txId = shadowUpdate.txId;
585
- try {
586
- await this.txnMgr.runTransactionStep({
587
- func: () => this.handleAppConfigUpdate(shadowUpdate, txId),
588
- projectId,
589
- txId,
590
- start: true,
591
- stepName: topic
592
- });
593
- } catch (e) {
594
- logger.error(`Error handling shadow message: ${e.message}`);
595
- }
596
- }
597
- }
598
- break;
599
- }
600
- case this.shadowHandler.shadowTopics.projects.getRejected:
601
- case this.shadowHandler.shadowTopics.projects.updateDelta:
602
- case this.shadowHandler.shadowTopics.projects.updateRejected:
603
- // Not handling these for now
604
- break;
605
- case this.toDeviceTopic:
606
- await this.handleDeviceAgentMessage({
607
- topic,
608
- message
609
- });
610
- break;
611
-
612
- case this.secureTunnelNotifyTopic:
613
- await secureTunnelNotifyHandler(message);
614
- break;
615
- default:
616
- logger.error(`Unexpected topic, ignoring! ${topic}`);
646
+ // ProjectShadow messages
647
+ if (this.shadowHandler.projectShadowTopics.includes(topic)) {
648
+ await this.handleProjectShadowMessage(topic, message);
649
+ } else if (topic === this.toDeviceTopic) {
650
+ await this.handleDeviceAgentMessage({
651
+ topic,
652
+ message
653
+ });
654
+ // SecureTunnelNotify messages
655
+ } else if (topic === this.secureTunnelNotifyTopic) {
656
+ await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
657
+ // SecureTunnel messages
658
+ } else if (
659
+ topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
660
+ ) {
661
+ await this.handleSecureTunnelMessage(message);
662
+ } else if (
663
+ topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
664
+ ) {
665
+ logger.info(`Received secure tunnel deleteAccepted: ${message}`);
666
+ await this.secureTunnelHandler.destroy();
667
+ } else {
668
+ logger.error(`Unexpected topic, ignoring! ${topic}`);
617
669
  }
618
670
  }
619
671
 
@@ -710,15 +762,18 @@ export class DeviceAgentCloudConnection {
710
762
  }
711
763
 
712
764
  public async stop() {
713
- // FIXME: This method is currently only used by the CLI, and shadow messages
714
- // can be lost since we aren't waiting for responses so sleep for a short
715
- // time to receive them
765
+ // This method is currently only used by the CLI, and shadow messages can be
766
+ // lost since we aren't waiting for responses so sleep for a short time to
767
+ // receive them
716
768
  await sleep(1000);
717
769
  this.device.end();
718
770
  }
719
771
  }
720
772
 
721
773
  export async function runDeviceAgentCloudInterface() {
774
+ logger.info(
775
+ `Starting alwaysAI Device Agent v${await getDeviceAgentVersion()}`
776
+ );
722
777
  if (cloudModeReady()) {
723
778
  const deviceAgent = new DeviceAgentCloudConnection();
724
779
  await deviceAgent.setupHandlers();
@@ -726,7 +781,6 @@ export async function runDeviceAgentCloudInterface() {
726
781
  const publisher = deviceAgent.publisher;
727
782
  const passthroughHandler = new PassthroughHandler(publisher);
728
783
  await passthroughHandler.setup();
729
- await runChannel(passthroughHandler);
730
784
  }
731
785
  } else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
732
786
  await bootstrapProvision();