@alwaysai/device-agent 0.0.12 → 0.0.14

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 (227) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +11 -5
  3. package/lib/application-control/backup.js.map +1 -1
  4. package/lib/application-control/config.d.ts +12 -4
  5. package/lib/application-control/config.d.ts.map +1 -1
  6. package/lib/application-control/config.js +59 -16
  7. package/lib/application-control/config.js.map +1 -1
  8. package/lib/application-control/environment-variables.d.ts.map +1 -1
  9. package/lib/application-control/environment-variables.js.map +1 -1
  10. package/lib/application-control/index.d.ts +5 -5
  11. package/lib/application-control/index.d.ts.map +1 -1
  12. package/lib/application-control/index.js +4 -6
  13. package/lib/application-control/index.js.map +1 -1
  14. package/lib/application-control/install.d.ts +1 -1
  15. package/lib/application-control/install.d.ts.map +1 -1
  16. package/lib/application-control/install.js +58 -57
  17. package/lib/application-control/install.js.map +1 -1
  18. package/lib/application-control/models.d.ts +7 -5
  19. package/lib/application-control/models.d.ts.map +1 -1
  20. package/lib/application-control/models.js +78 -57
  21. package/lib/application-control/models.js.map +1 -1
  22. package/lib/application-control/status.d.ts +0 -6
  23. package/lib/application-control/status.d.ts.map +1 -1
  24. package/lib/application-control/status.js +21 -33
  25. package/lib/application-control/status.js.map +1 -1
  26. package/lib/application-control/utils.d.ts +3 -2
  27. package/lib/application-control/utils.d.ts.map +1 -1
  28. package/lib/application-control/utils.js +54 -34
  29. package/lib/application-control/utils.js.map +1 -1
  30. package/lib/cloud-connection/app-install-status.d.ts +16 -0
  31. package/lib/cloud-connection/app-install-status.d.ts.map +1 -0
  32. package/lib/cloud-connection/app-install-status.js +53 -0
  33. package/lib/cloud-connection/app-install-status.js.map +1 -0
  34. package/lib/cloud-connection/bootstrap-provision.d.ts +2 -0
  35. package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -0
  36. package/lib/cloud-connection/bootstrap-provision.js +34 -0
  37. package/lib/cloud-connection/bootstrap-provision.js.map +1 -0
  38. package/lib/cloud-connection/cmd-status.d.ts +16 -0
  39. package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
  40. package/lib/cloud-connection/cmd-status.js +49 -0
  41. package/lib/cloud-connection/cmd-status.js.map +1 -0
  42. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +21 -34
  43. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  44. package/lib/cloud-connection/device-agent-cloud-connection.js +211 -387
  45. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  46. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  47. package/lib/cloud-connection/device-agent.js +22 -26
  48. package/lib/cloud-connection/device-agent.js.map +1 -1
  49. package/lib/cloud-connection/live-updates-handler.d.ts +34 -0
  50. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -0
  51. package/lib/cloud-connection/live-updates-handler.js +167 -0
  52. package/lib/cloud-connection/live-updates-handler.js.map +1 -0
  53. package/lib/cloud-connection/messages.d.ts +14 -0
  54. package/lib/cloud-connection/messages.d.ts.map +1 -0
  55. package/lib/cloud-connection/messages.js +38 -0
  56. package/lib/cloud-connection/messages.js.map +1 -0
  57. package/lib/cloud-connection/passthrough-handler.d.ts +11 -0
  58. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
  59. package/lib/cloud-connection/passthrough-handler.js +59 -0
  60. package/lib/cloud-connection/passthrough-handler.js.map +1 -0
  61. package/lib/cloud-connection/publisher.d.ts +15 -0
  62. package/lib/cloud-connection/publisher.d.ts.map +1 -0
  63. package/lib/cloud-connection/publisher.js +58 -0
  64. package/lib/cloud-connection/publisher.js.map +1 -0
  65. package/lib/cloud-connection/shadow-handler.d.ts +33 -0
  66. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
  67. package/lib/cloud-connection/shadow-handler.js +108 -0
  68. package/lib/cloud-connection/shadow-handler.js.map +1 -0
  69. package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
  70. package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
  71. package/lib/cloud-connection/shadow-handler.test.js +321 -0
  72. package/lib/cloud-connection/shadow-handler.test.js.map +1 -0
  73. package/lib/cloud-connection/shadow.d.ts +16 -0
  74. package/lib/cloud-connection/shadow.d.ts.map +1 -0
  75. package/lib/cloud-connection/shadow.js +36 -0
  76. package/lib/cloud-connection/shadow.js.map +1 -0
  77. package/lib/device-control/device-control.d.ts.map +1 -1
  78. package/lib/device-control/device-control.js +1 -0
  79. package/lib/device-control/device-control.js.map +1 -1
  80. package/lib/docker/docker-cmd.js +1 -1
  81. package/lib/docker/docker-compose-cmd.d.ts.map +1 -1
  82. package/lib/docker/docker-compose-cmd.js +1 -1
  83. package/lib/docker/docker-compose-cmd.js.map +1 -1
  84. package/lib/endpoints.js +10 -10
  85. package/lib/endpoints.js.map +1 -1
  86. package/lib/environment.d.ts +1 -0
  87. package/lib/environment.d.ts.map +1 -1
  88. package/lib/environment.js +2 -1
  89. package/lib/environment.js.map +1 -1
  90. package/lib/infrastructure/agent-config.d.ts +15 -58
  91. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  92. package/lib/infrastructure/agent-config.js +22 -15
  93. package/lib/infrastructure/agent-config.js.map +1 -1
  94. package/lib/infrastructure/agent-config.test.js +25 -23
  95. package/lib/infrastructure/agent-config.test.js.map +1 -1
  96. package/lib/infrastructure/system-id.d.ts +2 -0
  97. package/lib/infrastructure/system-id.d.ts.map +1 -0
  98. package/lib/infrastructure/system-id.js +21 -0
  99. package/lib/infrastructure/system-id.js.map +1 -0
  100. package/lib/infrastructure/tokens-and-device-cfg.d.ts +4 -0
  101. package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -0
  102. package/lib/infrastructure/tokens-and-device-cfg.js +27 -0
  103. package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -0
  104. package/lib/infrastructure/urls.d.ts.map +1 -1
  105. package/lib/infrastructure/urls.js +3 -3
  106. package/lib/infrastructure/urls.js.map +1 -1
  107. package/lib/local-connection/rabbitmq-connection.d.ts +4 -0
  108. package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
  109. package/lib/local-connection/rabbitmq-connection.js +58 -0
  110. package/lib/local-connection/rabbitmq-connection.js.map +1 -0
  111. package/lib/root.d.ts.map +1 -1
  112. package/lib/root.js +2 -7
  113. package/lib/root.js.map +1 -1
  114. package/lib/subcommands/app/app.d.ts +2 -1
  115. package/lib/subcommands/app/app.d.ts.map +1 -1
  116. package/lib/subcommands/app/app.js +112 -77
  117. package/lib/subcommands/app/app.js.map +1 -1
  118. package/lib/subcommands/app/index.js +2 -2
  119. package/lib/subcommands/device/clean.d.ts +2 -0
  120. package/lib/subcommands/device/clean.d.ts.map +1 -0
  121. package/lib/subcommands/device/clean.js +29 -0
  122. package/lib/subcommands/device/clean.js.map +1 -0
  123. package/lib/subcommands/device/device.d.ts +1 -1
  124. package/lib/subcommands/device/device.d.ts.map +1 -1
  125. package/lib/subcommands/device/device.js +44 -33
  126. package/lib/subcommands/device/device.js.map +1 -1
  127. package/lib/subcommands/device/index.d.ts.map +1 -1
  128. package/lib/subcommands/device/index.js +2 -1
  129. package/lib/subcommands/device/index.js.map +1 -1
  130. package/lib/subcommands/get-model-package.js +5 -5
  131. package/lib/subcommands/index.d.ts +0 -1
  132. package/lib/subcommands/index.d.ts.map +1 -1
  133. package/lib/subcommands/index.js +1 -1
  134. package/lib/subcommands/login.d.ts +0 -1
  135. package/lib/subcommands/login.d.ts.map +1 -1
  136. package/lib/subcommands/login.js +6 -14
  137. package/lib/subcommands/login.js.map +1 -1
  138. package/lib/util/clean-certs.d.ts +2 -0
  139. package/lib/util/clean-certs.d.ts.map +1 -0
  140. package/lib/util/clean-certs.js +16 -0
  141. package/lib/util/clean-certs.js.map +1 -0
  142. package/lib/util/directories.d.ts +16 -15
  143. package/lib/util/directories.d.ts.map +1 -1
  144. package/lib/util/directories.js +45 -26
  145. package/lib/util/directories.js.map +1 -1
  146. package/lib/util/fetch-with-timeout.d.ts +4 -0
  147. package/lib/util/fetch-with-timeout.d.ts.map +1 -0
  148. package/lib/util/fetch-with-timeout.js +15 -0
  149. package/lib/util/fetch-with-timeout.js.map +1 -0
  150. package/lib/util/get-device-id.d.ts +1 -1
  151. package/lib/util/get-device-id.d.ts.map +1 -1
  152. package/lib/util/get-device-id.js +14 -19
  153. package/lib/util/get-device-id.js.map +1 -1
  154. package/lib/util/http-client.d.ts +1 -1
  155. package/lib/util/http-client.d.ts.map +1 -1
  156. package/lib/util/http-client.js +10 -8
  157. package/lib/util/http-client.js.map +1 -1
  158. package/lib/util/logger.d.ts.map +1 -1
  159. package/lib/util/logger.js +4 -5
  160. package/lib/util/logger.js.map +1 -1
  161. package/lib/util/require-logged-in-and-paid-plan.d.ts +2 -0
  162. package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
  163. package/lib/util/require-logged-in-and-paid-plan.js +18 -0
  164. package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
  165. package/lib/util/run-in-dir.d.ts.map +1 -1
  166. package/lib/util/run-in-dir.js +1 -0
  167. package/lib/util/run-in-dir.js.map +1 -1
  168. package/lib/util/timer.d.ts +2 -0
  169. package/lib/util/timer.d.ts.map +1 -0
  170. package/lib/util/timer.js +6 -0
  171. package/lib/util/timer.js.map +1 -0
  172. package/package.json +32 -35
  173. package/readme.md +100 -89
  174. package/src/application-control/backup.ts +11 -6
  175. package/src/application-control/config.ts +75 -13
  176. package/src/application-control/environment-variables.ts +3 -3
  177. package/src/application-control/index.ts +18 -11
  178. package/src/application-control/install.ts +82 -78
  179. package/src/application-control/models.ts +104 -72
  180. package/src/application-control/status.ts +29 -40
  181. package/src/application-control/utils.ts +66 -38
  182. package/src/cloud-connection/app-install-status.ts +62 -0
  183. package/src/cloud-connection/bootstrap-provision.ts +40 -0
  184. package/src/cloud-connection/cmd-status.ts +52 -0
  185. package/src/cloud-connection/device-agent-cloud-connection.ts +302 -526
  186. package/src/cloud-connection/device-agent.ts +31 -38
  187. package/src/cloud-connection/live-updates-handler.ts +226 -0
  188. package/src/cloud-connection/messages.ts +39 -0
  189. package/src/cloud-connection/passthrough-handler.ts +67 -0
  190. package/src/cloud-connection/publisher.ts +86 -0
  191. package/src/cloud-connection/shadow-handler.test.ts +361 -0
  192. package/src/cloud-connection/shadow-handler.ts +175 -0
  193. package/src/cloud-connection/shadow.ts +50 -0
  194. package/src/device-control/device-control.ts +1 -0
  195. package/src/docker/docker-cmd.ts +1 -1
  196. package/src/docker/docker-compose-cmd.ts +5 -2
  197. package/src/endpoints.ts +9 -9
  198. package/src/environment.ts +11 -3
  199. package/src/infrastructure/agent-config.test.ts +33 -29
  200. package/src/infrastructure/agent-config.ts +57 -22
  201. package/src/infrastructure/system-id.ts +18 -0
  202. package/src/infrastructure/tokens-and-device-cfg.ts +34 -0
  203. package/src/infrastructure/urls.ts +4 -2
  204. package/src/local-connection/rabbitmq-connection.ts +53 -0
  205. package/src/root.ts +2 -8
  206. package/src/subcommands/app/app.ts +119 -83
  207. package/src/subcommands/app/index.ts +3 -3
  208. package/src/subcommands/device/clean.ts +26 -0
  209. package/src/subcommands/device/device.ts +67 -54
  210. package/src/subcommands/device/index.ts +2 -1
  211. package/src/subcommands/get-model-package.ts +5 -5
  212. package/src/subcommands/index.ts +1 -1
  213. package/src/subcommands/login.ts +6 -14
  214. package/src/util/clean-certs.ts +12 -0
  215. package/src/util/directories.ts +68 -52
  216. package/src/util/fetch-with-timeout.ts +18 -0
  217. package/src/util/get-device-id.ts +16 -18
  218. package/src/util/http-client.ts +18 -13
  219. package/src/util/logger.ts +6 -6
  220. package/src/util/require-logged-in-and-paid-plan.ts +16 -0
  221. package/src/util/run-in-dir.ts +2 -1
  222. package/src/util/timer.ts +1 -0
  223. package/lib/infrastructure/certificates-and-tokens.d.ts +0 -6
  224. package/lib/infrastructure/certificates-and-tokens.d.ts.map +0 -1
  225. package/lib/infrastructure/certificates-and-tokens.js +0 -43
  226. package/lib/infrastructure/certificates-and-tokens.js.map +0 -1
  227. package/src/infrastructure/certificates-and-tokens.ts +0 -53
@@ -1,318 +1,59 @@
1
- const awsIot = require("aws-iot-device-sdk");
2
- import { getIoTCoreEndpointUrl } from "../infrastructure/urls";
3
- import { existsSync, createReadStream } from "fs";
1
+ // eslint-disable-next-line
2
+ const awsIot = require('aws-iot-device-sdk');
3
+ import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
4
+ import { existsSync } from 'fs';
4
5
  import {
5
6
  getPrivateKeyFilePath,
6
7
  getCertificateFilePath,
7
- getRootCertificateFilePath,
8
- BOOTSTRAP_CLAIM_ID_FILE_PATH,
9
- DEVICE_CLAIM_ID_FILE_PATH,
10
- BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
11
8
  BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
12
9
  AWS_ROOT_CERTIFICATE_FILE_PATH,
13
- } from "../util/directories";
14
-
15
- import { BootstrapAgent } from "./device-agent";
16
-
17
- import sleep from "../util/sleep";
18
- import {
19
- startApp,
20
- stopApp,
21
- restartApp,
22
- getAppLogs,
23
- getAppStatus,
24
- } from "../application-control/status";
25
- import {
26
- getInstalledApps,
27
- installApp,
28
- uninstallApp,
29
- } from "../application-control/install";
10
+ BOOTSTRAP_CERTIFICATES_DIR_PATH
11
+ } from '../util/directories';
30
12
  import {
31
13
  keyMirrors,
32
14
  validateClientMessage,
33
- DeviceAgentMessagePayload,
34
- AppInstallStatusPacket,
35
15
  AppStateControlPacket,
36
16
  AppVersionControlPacket,
37
- LiveUpdatesToggles,
38
- AppLogsPacket,
39
- AppStatePacket,
40
17
  LiveUpdatesToggleMessage,
41
- DeviceStatsMessage,
42
- AppStateMessage,
43
- AppLogsMessage,
44
- AppInstallStatusMessage,
45
18
  SignedUrlsRequestMessage,
46
- DeviceAgentMessage,
47
19
  ClientMessage,
48
- AppDetailsPacket,
49
- getClientTopic,
50
- getCloudTopic,
51
- getDeviceTopic,
52
- } from "@alwaysai/device-agent-schemas";
53
- import { getDeviceId } from "../util/get-device-id";
54
- import { JsSpawner, logger } from "alwaysai/lib/util";
55
- import {
56
- getCpuUtil,
57
- getDiskUtil,
58
- getMemUtil,
59
- } from "../device-control/device-control";
60
- import { AgentConfigFile } from "../infrastructure/agent-config";
20
+ getDeviceTopic
21
+ } from '@alwaysai/device-agent-schemas';
22
+ import { getDeviceUuid } from '../util/get-device-id';
23
+ import { logger } from '../util/logger';
24
+ import { AgentConfigFile } from '../infrastructure/agent-config';
61
25
  import {
62
- buildApp,
63
- getAppConfig,
64
- getAppDir,
65
- } from "../application-control/utils";
66
- import { updateModelsWithPresignedUrls } from "../application-control/models";
67
- import { updateAppConfig } from "../application-control/config";
26
+ startApp,
27
+ stopApp,
28
+ restartApp,
29
+ updateModelsWithPresignedUrls,
30
+ installApp,
31
+ uninstallApp,
32
+ updateAppCfg
33
+ } from '../application-control';
34
+ import { AppConfigUpdate, ShadowHandler, ShadowTopics } from './shadow-handler';
35
+ import { Publisher } from './publisher';
36
+ import { LiveUpdatesHandler } from './live-updates-handler';
37
+ import { bootstrapProvision } from './bootstrap-provision';
38
+ import { AppInstallStatusManager } from './app-install-status';
39
+ import { AppConfig } from '@alwaysai/app-configuration-schemas';
40
+ import { CmdStatusManager, CmdStatusType } from './cmd-status';
41
+ import { PassthroughHandler, runChannel } from './passthrough-handler';
42
+ import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
68
43
 
69
44
  export class DeviceAgentCloudConnection {
70
- private clientId = getDeviceId();
45
+ private shadowHandler: ShadowHandler;
46
+ public publisher: Publisher;
47
+ private cmdStatusMgr: CmdStatusManager;
48
+ private liveUpdatesHandler: LiveUpdatesHandler;
49
+ private appInstallStatusMgr: AppInstallStatusManager;
50
+ private device = awsIot.device;
51
+
52
+ private clientId = getDeviceUuid();
71
53
  private host = getIoTCoreEndpointUrl();
72
- private appInstallStatus: AppInstallStatusPacket;
73
- private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
74
- private liveUpdatesAlive = {
75
- [keyMirrors.agentMessageType.device_stats]: false,
76
- [keyMirrors.agentMessageType.app_state]: false,
77
- [keyMirrors.agentMessageType.app_logs]: false,
78
- };
79
- private liveUpdatesSleepIntervals = {
80
- [keyMirrors.agentMessageType.device_stats]: 5000,
81
- [keyMirrors.agentMessageType.app_state]: 5000,
82
- [keyMirrors.agentMessageType.app_logs]: 5000,
83
- [keyMirrors.agentMessageType.app_install_status]: 5000,
84
- };
85
- private appLogStreams = new Set<string>();
86
- private readonly shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
87
- private readonly shadowTopics = {
88
- projects: {
89
- updateDelta: `${this.shadowPrefix}projects/update/delta`,
90
- getAccepted: `${this.shadowPrefix}projects/get/accepted`,
91
- },
92
- };
93
- private readonly toCloudTopic = getCloudTopic(this.clientId);
94
- private readonly toClientTopic = getClientTopic(this.clientId);
95
54
  private readonly toDeviceTopic = getDeviceTopic(this.clientId);
96
-
97
- // device shadow utils
98
-
99
- public getShadowPrefix() {
100
- return this.shadowPrefix;
101
- }
102
-
103
- private async handleNamedShadowUpdate({ payload }: { payload: string }) {
104
- const delta = JSON.parse(payload);
105
- const deltaKeys = Object.keys(delta);
106
-
107
- for (const projectId of deltaKeys) {
108
- const projectShadow = delta[projectId];
109
- if (projectShadow.appConfig) {
110
- const appConfig = projectShadow.appConfig;
111
- const appDir = getAppDir(projectId);
112
- await updateAppConfig(projectId, appConfig);
113
-
114
- if (appConfig.models) {
115
- this.publishCloudRequest({
116
- messageType: keyMirrors.agentMessageType.signed_urls_request,
117
- modelsOnlyUrlsRequest: {
118
- projectId,
119
- models: appConfig.models,
120
- },
121
- });
122
- }
123
-
124
- if (appConfig.scripts && !appConfig.models) {
125
- const appState = await getAppStatus({ projectId });
126
-
127
- await buildApp({ appDir });
128
-
129
- if (
130
- appState.services.length &&
131
- appState.services[0].state !== keyMirrors.appState.stopped
132
- ) {
133
- restartApp({ projectId });
134
- }
135
- await this.publishReportedState(projectId);
136
- }
137
- }
138
- }
139
- }
140
-
141
- private async startAppLogStream(projectId: string) {
142
- this.appLogStreams.add(projectId);
143
- const readable = await getAppLogs({
144
- projectId,
145
- args: ["--tail", "100", "--no-log-prefix"],
146
- });
147
- readable.on("data", (chunk: Buffer) => {
148
- if (!this.appLogStreams.has(projectId)) {
149
- // why doesn't typescript know about this function?
150
- // @ts-ignore
151
- readable.destroy();
152
- logger.info(`App log stream terminated for project ${projectId}`);
153
- return;
154
- }
155
- const logStr = chunk.toString();
156
- const message = {
157
- messageType: keyMirrors.agentMessageType.app_logs,
158
- appLogs: {
159
- projectId,
160
- logChunk: logStr,
161
- },
162
- };
163
- const packet = this.buildMessagePacket(
164
- this.getClientId(),
165
- this.toClientTopic,
166
- message
167
- );
168
- this.publishMessage(this.toClientTopic, JSON.stringify(packet));
169
- });
170
-
171
- readable.on("error", (error) => {
172
- logger.error(
173
- `App log stream terminated for project ${projectId}: ${error}`
174
- );
175
- });
176
-
177
- readable.on("finished", () => {
178
- logger.info(`App logs finished piping for project ${projectId}`);
179
- });
180
- }
181
-
182
- // must contain app release hash
183
- private initAppInstallStatus(installationStatus: AppInstallStatusPacket) {
184
- this.appInstallStatus = installationStatus;
185
- }
186
-
187
- private updateAppInstallStatus(
188
- installationStatus: Omit<AppInstallStatusPacket, "appReleaseHash">
189
- ) {
190
- this.appInstallStatus.status = installationStatus.status;
191
- this.appInstallStatus.message = installationStatus.message;
192
- }
193
-
194
- private getAppInstallStatus(): AppInstallStatusPacket {
195
- return this.appInstallStatus;
196
- }
197
-
198
- // Message Builders
199
- private buildMessagePacket(
200
- deviceId: string,
201
- topic: string,
202
- payload: DeviceAgentMessagePayload
203
- ): DeviceAgentMessage {
204
- const packet = {
205
- timestamp: new Date().toUTCString(),
206
- deviceId,
207
- topic,
208
- payload,
209
- };
210
- return packet;
211
- }
212
-
213
- private async getAppStateMessage(): Promise<AppStateMessage> {
214
- const appStateMessage: AppStatePacket[] = [];
215
- const apps = await AgentConfigFile().getReadyApps();
216
- for (const app of apps) {
217
- const projectId = app.projectId;
218
- const status = await getAppStatus({ projectId });
219
- appStateMessage.push(status);
220
- }
221
- const appStatePackage = {
222
- messageType: keyMirrors.agentMessageType.app_state,
223
- appState: appStateMessage,
224
- };
225
- return appStatePackage;
226
- }
227
-
228
- private async getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
229
- const cpuUsage = await getCpuUtil();
230
- const diskUtil = await getDiskUtil();
231
- const memUtil = await getMemUtil();
232
-
233
- const deviceStatsMessage = {
234
- messageType: keyMirrors.agentMessageType.device_stats,
235
- deviceStats: {
236
- cpuUsage,
237
- diskUtil,
238
- usedMemoryPercentage: memUtil,
239
- },
240
- };
241
- return deviceStatsMessage;
242
- }
243
-
244
- // must be arrow function due to this context when function is passed as param
245
- private getAppInstallStatusMessage =
246
- async (): Promise<AppInstallStatusMessage> => {
247
- const appInstallStatus = this.getAppInstallStatus();
248
- const appInstallStatusMessage = {
249
- messageType: keyMirrors.agentMessageType.app_install_status,
250
- appInstallStatus,
251
- };
252
- return appInstallStatusMessage;
253
- };
254
-
255
- private async startPublishingLiveUpdates(
256
- topic: string,
257
- messageType: string,
258
- getMessageData: () => Promise<DeviceAgentMessagePayload>
259
- ) {
260
- while (true) {
261
- try {
262
- const message = await getMessageData();
263
- const packet = this.buildMessagePacket(
264
- this.getClientId(),
265
- topic,
266
- message
267
- );
268
- this.publishMessage(topic, JSON.stringify(packet));
269
- } catch (e) {
270
- logger.error(
271
- `Error publishing live updates for ${messageType}: ${e.message}`
272
- );
273
- break;
274
- }
275
- if (!this.continuePublishing(messageType)) {
276
- logger.info(`Turned off live updates for ${messageType}`);
277
- break;
278
- }
279
- await sleep(this.getLiveUpdatesInterval(messageType));
280
- }
281
- }
282
-
283
- private continuePublishing(flag: string): boolean {
284
- switch (flag) {
285
- case keyMirrors.agentMessageType.device_stats:
286
- case keyMirrors.agentMessageType.app_state:
287
- return this.liveUpdatesAlive[flag];
288
- case keyMirrors.agentMessageType.app_install_status:
289
- return (
290
- this.appInstallStatus.status ===
291
- keyMirrors.appInstallStatus.in_progress
292
- );
293
- default:
294
- logger.error(`Unrecognized publishable flag ${flag}`);
295
- return false;
296
- }
297
- }
298
-
299
- private getLiveUpdatesInterval(flag: string): number {
300
- const exists = this.liveUpdatesSleepIntervals[flag];
301
- if (exists) {
302
- return exists;
303
- }
304
- logger.error(`Unrecognized live updates flag ${flag}`);
305
- return -1;
306
- }
307
-
308
- private setLiveUpdates(toggles: LiveUpdatesToggles) {
309
- if (toggles.deviceStats) {
310
- this.liveUpdatesAlive.device_stats = toggles.deviceStats;
311
- }
312
- if (toggles.appState) {
313
- this.liveUpdatesAlive.app_state = toggles.appState;
314
- }
315
- }
55
+ // FIXME: Add support for multiple simultaneous project updates
56
+ private newAppCfgQueue: AppConfig[] = [];
316
57
 
317
58
  private handleAppStateControl(payload: AppStateControlPacket) {
318
59
  const { baseCommand, projectId } = payload;
@@ -330,12 +71,26 @@ export class DeviceAgentCloudConnection {
330
71
  }
331
72
 
332
73
  private handleAppVersionControl(payload: AppVersionControlPacket) {
333
- const { projectId, appReleaseHash } = payload;
334
- const signedUrlsRequest = { projectId, appReleaseHash };
335
- this.publishCloudRequest({
336
- messageType: keyMirrors.agentMessageType.signed_urls_request,
337
- signedUrlsRequest,
338
- });
74
+ switch (payload.baseCommand) {
75
+ case keyMirrors.appVersionControl.install: {
76
+ const { projectId, appReleaseHash } = payload;
77
+ this.cmdStatusMgr.update(projectId, 'in_progress');
78
+ const signedUrlsRequest = { projectId, appReleaseHash };
79
+ this.publishCloudRequest({
80
+ messageType: keyMirrors.agentMessageType.signed_urls_request,
81
+ signedUrlsRequest
82
+ });
83
+ break;
84
+ }
85
+ default:
86
+ logger.warn(
87
+ `Ignore App Version Control packet: ${JSON.stringify(
88
+ payload,
89
+ null,
90
+ 2
91
+ )}`
92
+ );
93
+ }
339
94
  }
340
95
 
341
96
  private handleDeviceCommand = async (packet: any) => {
@@ -345,7 +100,7 @@ export class DeviceAgentCloudConnection {
345
100
  private handleAgentCommand(message: LiveUpdatesToggleMessage) {
346
101
  switch (message.messageType) {
347
102
  case keyMirrors.clientMessageType.live_state_updates:
348
- this.liveUpdatesBroker(message.liveUpdatesToggles);
103
+ this.liveUpdatesHandler.update(message.liveUpdatesToggles);
349
104
  break;
350
105
  default:
351
106
  logger.error(
@@ -354,91 +109,112 @@ export class DeviceAgentCloudConnection {
354
109
  }
355
110
  }
356
111
 
357
- private restartLiveUpdatesTimeout() {
358
- clearTimeout(this.liveUpdatesTimeout);
359
- this.liveUpdatesTimeout = setTimeout(() => {
360
- this.setLiveUpdates({
361
- deviceStats: false,
362
- appState: false,
363
- });
364
- this.appLogStreams.clear();
365
- // TODO: Make constant, not hard coded
366
- }, 600000); // 10 min
112
+ private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
113
+ this.publisher.publishToCloud(payload);
367
114
  }
368
115
 
369
- private async liveUpdatesBroker({
370
- deviceStats,
371
- appState,
372
- appLogs,
373
- }: {
374
- deviceStats?: boolean;
375
- appState?: boolean;
376
- appLogs?: {
377
- projectId: string;
378
- toggle: boolean;
379
- };
380
- }) {
381
- this.restartLiveUpdatesTimeout();
382
- if (deviceStats !== undefined) {
383
- this.liveUpdatesAlive.device_stats = deviceStats;
384
- if (deviceStats) {
385
- this.startPublishingLiveUpdates(
386
- this.toClientTopic,
387
- keyMirrors.agentMessageType.device_stats,
388
- this.getDeviceStatsMessage
389
- );
390
- }
391
- }
116
+ private subscribe(topic: string) {
117
+ logger.info(`Subscribing to ${topic}`);
118
+ this.device.subscribe(topic);
119
+ }
392
120
 
393
- if (appState !== undefined) {
394
- this.liveUpdatesAlive.app_state = appState;
395
- if (appState) {
396
- this.startPublishingLiveUpdates(
397
- this.toClientTopic,
398
- keyMirrors.agentMessageType.app_state,
399
- this.getAppStateMessage
400
- );
401
- }
402
- }
121
+ // eslint-disable-next-line
122
+ private async atomicApplicationUpdate<T extends any[], R extends any>(
123
+ func: (...args: T) => R,
124
+ args: T,
125
+ projectId: string,
126
+ appReleaseHash: string
127
+ ) {
128
+ this.appInstallStatusMgr.update(
129
+ appReleaseHash,
130
+ keyMirrors.appInstallStatus.in_progress
131
+ );
403
132
 
404
- if (appLogs !== undefined) {
405
- if (appLogs.toggle) {
406
- this.startAppLogStream(appLogs.projectId);
407
- } else {
408
- this.appLogStreams.delete(appLogs.projectId);
409
- }
133
+ this.liveUpdatesHandler.update({
134
+ appInstallStatus: { toggle: true, appReleaseHash }
135
+ });
136
+
137
+ // Install the app and models
138
+ try {
139
+ const out: R = await func(...args);
140
+ this.appInstallStatusMgr.update(
141
+ appReleaseHash,
142
+ keyMirrors.appInstallStatus.success
143
+ );
144
+ this.liveUpdatesHandler.update({
145
+ appInstallStatus: { toggle: false, appReleaseHash }
146
+ });
147
+ this.cmdStatusMgr.update(projectId, 'idle');
148
+
149
+ // update app config shadow for project
150
+ await this.shadowHandler.publishAppState(projectId);
151
+ return out;
152
+ } catch (e) {
153
+ logger.error(e);
154
+ const message: string = e.message;
155
+
156
+ // uninstall the failed app to put system back in good state
157
+ await uninstallApp({ projectId });
158
+ this.appInstallStatusMgr.update(
159
+ appReleaseHash,
160
+ keyMirrors.appInstallStatus.failure,
161
+ message
162
+ );
163
+ this.liveUpdatesHandler.update({
164
+ appInstallStatus: { toggle: false, appReleaseHash }
165
+ });
166
+ this.cmdStatusMgr.update(projectId, 'idle');
167
+
168
+ // delete shadow for project
169
+ this.shadowHandler.deleteProjectShadow(projectId);
410
170
  }
411
171
  }
412
172
 
413
- private async publishReportedState(projectId) {
414
- const newAppCfg = await getAppConfig(projectId);
415
- const packet = {
416
- state: {
417
- reported: {
418
- [projectId]: { appConfig: newAppCfg },
419
- },
420
- },
421
- };
422
- this.publishMessage(
423
- `${this.shadowPrefix}projects/update`,
424
- JSON.stringify(packet)
425
- );
426
- }
173
+ private async handleAppConfigUpdates(appConfgUpdates: AppConfigUpdate[]) {
174
+ for (const appConfigUpdate of appConfgUpdates) {
175
+ const { projectId, newAppCfg, updatedModels } = appConfigUpdate;
176
+ this.cmdStatusMgr.update(projectId, 'in_progress');
177
+ if (updatedModels && Object.keys(updatedModels).length) {
178
+ // Publish request for model urls
179
+ this.newAppCfgQueue.push(newAppCfg);
180
+
181
+ logger.debug(
182
+ `Requesting presigned urls from cloud for model versions: ${JSON.stringify(
183
+ updatedModels
184
+ )}`
185
+ );
186
+ this.publisher.publishToCloud({
187
+ messageType: keyMirrors.agentMessageType.signed_urls_request,
188
+ modelsOnlyUrlsRequest: {
189
+ projectId,
190
+ models: updatedModels
191
+ }
192
+ });
193
+ } else {
194
+ // FIXME: do we need to send this up to the cloud?
195
+ // should it be something other than appReleaseHash?
196
+ const appReleaseHash = await AgentConfigFile().getAppVersion({
197
+ projectId
198
+ });
427
199
 
428
- private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
429
- const topic = this.toCloudTopic;
430
- const deviceRequestPacket = this.buildMessagePacket(
431
- this.getClientId(),
432
- topic,
433
- payload
434
- );
435
- this.publishMessage(topic, JSON.stringify({ deviceRequestPacket }));
200
+ await this.atomicApplicationUpdate(
201
+ updateAppCfg,
202
+ [
203
+ {
204
+ projectId,
205
+ appReleaseHash,
206
+ newAppCfg
207
+ }
208
+ ],
209
+ projectId,
210
+ appReleaseHash
211
+ );
212
+ }
213
+ }
436
214
  }
437
215
 
438
216
  // Public Methods
439
217
 
440
- public device = awsIot.device;
441
-
442
218
  constructor() {
443
219
  this.device = awsIot.device({
444
220
  keyPath: getPrivateKeyFilePath(),
@@ -447,29 +223,56 @@ export class DeviceAgentCloudConnection {
447
223
  clientId: this.clientId,
448
224
  host: this.host,
449
225
  port: 8883,
226
+ keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
450
227
  });
228
+ this.publisher = new Publisher(this.device, this.clientId);
229
+ this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
230
+ this.cmdStatusMgr = new CmdStatusManager();
231
+ this.appInstallStatusMgr = new AppInstallStatusManager();
232
+ this.liveUpdatesHandler = new LiveUpdatesHandler(
233
+ this.publisher,
234
+ this.appInstallStatusMgr
235
+ );
451
236
 
452
- this.device.subscribe(this.toDeviceTopic);
453
- this.device.subscribe(this.shadowTopics.projects.getAccepted);
454
- this.device.subscribe(this.shadowTopics.projects.updateDelta);
237
+ this.subscribe(this.toDeviceTopic);
238
+ this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
239
+ this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
455
240
  }
456
241
 
457
242
  public getClientId(): string {
458
243
  return this.clientId;
459
244
  }
460
245
 
461
- public publishMessage(topic: string, message: string) {
462
- // TODO: topic validation
463
- this.device.publish(topic, message, (err: any) => {});
246
+ public getToDeviceTopic() {
247
+ return this.toDeviceTopic;
248
+ }
249
+
250
+ public getShadowTopics(): ShadowTopics {
251
+ return this.shadowHandler.shadowTopics;
252
+ }
253
+
254
+ public getCmdStatus(projectId: string): CmdStatusType {
255
+ return this.cmdStatusMgr.getAppCmdStatus(projectId);
464
256
  }
465
257
 
466
258
  public async handleClientMessage({
467
259
  topic,
468
- message,
260
+ message
469
261
  }: {
470
262
  topic: string;
471
263
  message: ClientMessage;
472
264
  }) {
265
+ const valid = validateClientMessage(message);
266
+ if (!valid) {
267
+ logger.error(
268
+ `Error validating message: ${JSON.stringify(
269
+ { topic, message, errors: validateClientMessage.errors },
270
+ null,
271
+ 2
272
+ )}`
273
+ );
274
+ return;
275
+ }
473
276
  const payload = message.payload;
474
277
  switch (payload.messageType) {
475
278
  case keyMirrors.clientMessageType.app_state_control: {
@@ -489,175 +292,148 @@ export class DeviceAgentCloudConnection {
489
292
  projectId,
490
293
  appReleaseHash,
491
294
  appInstallPayload,
492
- modelsInstallPayload,
295
+ modelsInstallPayload
493
296
  } = payload.appInstallCloudResponse;
494
-
495
- this.initAppInstallStatus({
496
- status: keyMirrors.appInstallStatus.in_progress,
497
- appReleaseHash,
498
- });
499
-
500
- this.startPublishingLiveUpdates(
501
- this.toClientTopic,
502
- keyMirrors.agentMessageType.app_install_status,
503
- this.getAppInstallStatusMessage
297
+ const signedUrlsPayload = {
298
+ appInstallPayload,
299
+ modelsInstallPayload
300
+ };
301
+ await this.atomicApplicationUpdate(
302
+ installApp,
303
+ [{ projectId, appReleaseHash, signedUrlsPayload }],
304
+ projectId,
305
+ appReleaseHash
504
306
  );
505
307
 
506
- // Install the app and models
507
- try {
508
- const signedUrlsPayload = {
509
- appInstallPayload,
510
- modelsInstallPayload,
511
- };
512
- await installApp({
513
- projectId,
514
- appReleaseHash,
515
- signedUrlsPayload,
516
- });
517
- this.updateAppInstallStatus({
518
- status: keyMirrors.appInstallStatus.success,
519
- });
520
-
521
- // update app config shadow for project
522
- await this.publishReportedState(projectId);
523
- } catch (e) {
524
- logger.error(e);
525
- const message: string = e.message;
526
-
527
- // uninstall the failed app to put system back in good state
528
- await uninstallApp({ projectId });
529
- this.updateAppInstallStatus({
530
- status: keyMirrors.appInstallStatus.failure,
531
- message,
532
- });
533
-
534
- // delete shadow for project
535
- this.publishMessage(`${this.shadowPrefix}${projectId}/delete`, "");
536
- }
537
308
  break;
538
309
  }
539
310
  case keyMirrors.clientMessageType.models_install_cloud_response: {
540
311
  const { projectId, newModels } = payload.modelsInstallCloudResponse;
312
+ const appReleaseHash = await AgentConfigFile().getAppVersion({
313
+ projectId
314
+ });
541
315
 
542
- try {
543
- await updateModelsWithPresignedUrls(projectId, newModels);
544
-
545
- await this.publishReportedState(projectId);
546
- } catch (e) {
547
- logger.error(e);
316
+ const newAppCfg = this.newAppCfgQueue.shift();
317
+ if (newAppCfg === undefined) {
318
+ logger.error(
319
+ 'Unknown error while updating models via application config! No config present for model update.'
320
+ );
321
+ return;
548
322
  }
323
+
324
+ await this.atomicApplicationUpdate(
325
+ updateModelsWithPresignedUrls,
326
+ [
327
+ {
328
+ projectId,
329
+ modelInstallPayloads: newModels,
330
+ newAppCfg,
331
+ appReleaseHash
332
+ }
333
+ ],
334
+ projectId,
335
+ appReleaseHash
336
+ );
337
+
549
338
  break;
550
339
  }
551
340
  default:
552
- logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
341
+ logger.error(
342
+ `Invalid client message: '${JSON.stringify(
343
+ { topic, message },
344
+ null,
345
+ 2
346
+ )}'`
347
+ );
553
348
  }
554
349
  }
555
350
 
556
- public async handleShadowTopic({
557
- topic,
558
- payload,
559
- }: {
560
- topic: string;
561
- payload: string;
562
- }) {
563
- const shadowName = topic.split("/")[5];
564
- const message = JSON.parse(payload);
565
- if (topic === this.shadowTopics.projects.updateDelta) {
566
- this.handleNamedShadowUpdate({ payload });
567
- } else if (topic === this.shadowTopics.projects.getAccepted) {
568
- if (message.delta) {
569
- this.handleNamedShadowUpdate({
570
- payload: JSON.stringify(message.delta),
351
+ public async handleMessage(topic: string, message: ClientMessage | any) {
352
+ switch (topic) {
353
+ case this.shadowHandler.shadowTopics.projects.getAccepted:
354
+ case this.shadowHandler.shadowTopics.projects.updateDelta: {
355
+ const appConfigUpdates = await this.shadowHandler.handleShadowTopic({
356
+ topic,
357
+ payload: message.state
571
358
  });
572
- } else {
573
- logger.info(`No delta updates in shadow ${shadowName}`);
359
+ await this.handleAppConfigUpdates(appConfigUpdates);
360
+ break;
574
361
  }
362
+ case this.toDeviceTopic:
363
+ this.handleClientMessage({
364
+ topic,
365
+ message
366
+ });
367
+ break;
368
+ default:
369
+ logger.error(`Unexpected topic, ignoring! ${topic}`);
575
370
  }
576
371
  }
577
- }
578
372
 
579
- export async function runDeviceAgentCloudInterface() {
580
- switch (existsSync(getCertificateFilePath())) {
581
- case true:
582
- {
583
- const deviceAgent = new DeviceAgentCloudConnection();
584
-
585
- deviceAgent.device.on("connect", function (connack: any) {
586
- logger.info("Device Agent has connected to the cloud");
587
-
588
- // Get shadow updates
589
- deviceAgent.publishMessage(
590
- `${deviceAgent.getShadowPrefix()}projects/get`,
591
- ""
592
- );
593
- });
373
+ public async setupHandlers() {
374
+ this.device.on('connect', (connack: any) => {
375
+ logger.info('Device Agent has connected to the cloud');
594
376
 
595
- deviceAgent.device.on("disconnect", function () {
596
- logger.info("Device Agent has been disconnected from the cloud");
597
- });
377
+ this.shadowHandler.getShadowUpdates();
378
+ });
598
379
 
599
- deviceAgent.device.on(
600
- "message",
601
- function (topic: string, payload: string) {
602
- console.log(topic);
603
- console.log(JSON.parse(payload));
604
- try {
605
- const jsonPacket = JSON.parse(payload);
606
- if (jsonPacket.hasOwnProperty("state")) {
607
- deviceAgent.handleShadowTopic({
608
- topic,
609
- payload: JSON.stringify(jsonPacket.state),
610
- });
611
- } else {
612
- const valid = validateClientMessage(jsonPacket);
613
- if (!valid) {
614
- console.error(JSON.stringify(validateClientMessage.errors));
615
- } else {
616
- deviceAgent.handleClientMessage({
617
- topic,
618
- message: jsonPacket,
619
- });
620
- }
621
- }
622
- } catch (error) {
623
- logger.error(error);
624
- }
625
- }
626
- );
380
+ this.device.on('disconnect', () => {
381
+ logger.warn('Device Agent has been disconnected from the cloud');
382
+ });
627
383
 
628
- deviceAgent.device.on("packetsend", (packet: any) => {
629
- console.log({ packet: packet });
630
- });
384
+ this.device.on('reconnect', () => {
385
+ logger.info(
386
+ `Device Agent attempting to re-connect ${new Date().toLocaleString()}`
387
+ );
388
+ });
389
+
390
+ this.device.on('error', function (error) {
391
+ const errorString = error.message.toString();
392
+ logger.error(`${errorString}`);
393
+ });
394
+
395
+ this.device.on('message', async (topic: string, payload: string) => {
396
+ try {
397
+ const jsonPacket = JSON.parse(payload);
398
+ logger.debug(
399
+ `Received message: ${JSON.stringify({ topic, jsonPacket }, null, 2)}`
400
+ );
401
+ await this.handleMessage(topic, jsonPacket);
402
+ } catch (error) {
403
+ logger.error(`Error parsing message: ${error}`);
631
404
  }
405
+ });
632
406
 
633
- break;
634
- case false: {
635
- //set timer
636
- const clientId = getDeviceId();
637
- const bootstrapConfig = {
638
- keyPath: BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
639
- certPath: BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
640
- caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
641
- clientId,
642
- host: getIoTCoreEndpointUrl(),
643
- };
644
-
645
- const bootstrapAgent = new BootstrapAgent(bootstrapConfig);
646
- bootstrapAgent.subscribeToAllTopics();
647
-
648
- bootstrapAgent.publishMessage("$aws/certificates/create/json", "");
649
-
650
- bootstrapAgent.device.on("connect", () => {
651
- console.log("Your device is being provisioned");
652
- });
407
+ this.device.on('offline', () => {
408
+ logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
409
+ });
410
+ }
653
411
 
654
- bootstrapAgent.device.on("message", (topic: string, payload: string) => {
655
- bootstrapAgent.handleAwsCertificateTopics(topic, payload);
656
- });
412
+ public stop() {
413
+ this.device.end();
414
+ }
415
+ }
657
416
 
658
- bootstrapAgent.device.on("packetsend", (packet: any) => {
659
- console.log({ packet: packet.subscriptions });
660
- });
417
+ export async function runDeviceAgentCloudInterface() {
418
+ // FIXME: Check for KeyPath as well
419
+ if (existsSync(getCertificateFilePath())) {
420
+ const deviceAgent = new DeviceAgentCloudConnection();
421
+ await deviceAgent.setupHandlers();
422
+ if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
423
+ const publisher = deviceAgent.publisher;
424
+ const passthroughHandler = new PassthroughHandler(publisher);
425
+ await passthroughHandler.setup();
426
+ runChannel(passthroughHandler);
661
427
  }
428
+ } else if (existsSync(BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH())) {
429
+ bootstrapProvision();
430
+ } else if (existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH())) {
431
+ throw new Error(
432
+ "Device has not been created using 'aai-agent device init' or there has been an issue with device initialization"
433
+ );
434
+ } else {
435
+ throw new Error(
436
+ "Set device agent to local mode and retry the 'aai-agent device init' command"
437
+ );
662
438
  }
663
439
  }