@alwaysai/device-agent 0.0.11 → 0.0.13

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 (181) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +8 -2
  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 +4 -4
  11. package/lib/application-control/index.d.ts.map +1 -1
  12. package/lib/application-control/index.js +4 -3
  13. package/lib/application-control/index.js.map +1 -1
  14. package/lib/application-control/install.d.ts.map +1 -1
  15. package/lib/application-control/install.js +28 -14
  16. package/lib/application-control/install.js.map +1 -1
  17. package/lib/application-control/models.d.ts +7 -1
  18. package/lib/application-control/models.d.ts.map +1 -1
  19. package/lib/application-control/models.js +69 -39
  20. package/lib/application-control/models.js.map +1 -1
  21. package/lib/application-control/status.d.ts.map +1 -1
  22. package/lib/application-control/status.js +18 -14
  23. package/lib/application-control/status.js.map +1 -1
  24. package/lib/application-control/utils.d.ts +0 -2
  25. package/lib/application-control/utils.d.ts.map +1 -1
  26. package/lib/application-control/utils.js +7 -16
  27. package/lib/application-control/utils.js.map +1 -1
  28. package/lib/cloud-connection/app-install-status.d.ts +16 -0
  29. package/lib/cloud-connection/app-install-status.d.ts.map +1 -0
  30. package/lib/cloud-connection/app-install-status.js +53 -0
  31. package/lib/cloud-connection/app-install-status.js.map +1 -0
  32. package/lib/cloud-connection/bootstrap-provision.d.ts +2 -0
  33. package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -0
  34. package/lib/cloud-connection/bootstrap-provision.js +34 -0
  35. package/lib/cloud-connection/bootstrap-provision.js.map +1 -0
  36. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +12 -35
  37. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  38. package/lib/cloud-connection/device-agent-cloud-connection.js +170 -387
  39. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  40. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  41. package/lib/cloud-connection/device-agent.js +22 -26
  42. package/lib/cloud-connection/device-agent.js.map +1 -1
  43. package/lib/cloud-connection/live-updates-handler.d.ts +34 -0
  44. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -0
  45. package/lib/cloud-connection/live-updates-handler.js +167 -0
  46. package/lib/cloud-connection/live-updates-handler.js.map +1 -0
  47. package/lib/cloud-connection/messages.d.ts +14 -0
  48. package/lib/cloud-connection/messages.d.ts.map +1 -0
  49. package/lib/cloud-connection/messages.js +38 -0
  50. package/lib/cloud-connection/messages.js.map +1 -0
  51. package/lib/cloud-connection/publisher.d.ts +14 -0
  52. package/lib/cloud-connection/publisher.d.ts.map +1 -0
  53. package/lib/cloud-connection/publisher.js +44 -0
  54. package/lib/cloud-connection/publisher.js.map +1 -0
  55. package/lib/cloud-connection/shadow-handler.d.ts +34 -0
  56. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
  57. package/lib/cloud-connection/shadow-handler.js +94 -0
  58. package/lib/cloud-connection/shadow-handler.js.map +1 -0
  59. package/lib/cloud-connection/shadow.d.ts +16 -0
  60. package/lib/cloud-connection/shadow.d.ts.map +1 -0
  61. package/lib/cloud-connection/shadow.js +36 -0
  62. package/lib/cloud-connection/shadow.js.map +1 -0
  63. package/lib/device-control/device-control.d.ts.map +1 -1
  64. package/lib/device-control/device-control.js +1 -0
  65. package/lib/device-control/device-control.js.map +1 -1
  66. package/lib/docker/docker-cmd.js +1 -1
  67. package/lib/docker/docker-compose-cmd.d.ts.map +1 -1
  68. package/lib/docker/docker-compose-cmd.js +1 -1
  69. package/lib/docker/docker-compose-cmd.js.map +1 -1
  70. package/lib/endpoints.js +10 -10
  71. package/lib/endpoints.js.map +1 -1
  72. package/lib/environment.d.ts.map +1 -1
  73. package/lib/environment.js.map +1 -1
  74. package/lib/infrastructure/agent-config.d.ts +4 -14
  75. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  76. package/lib/infrastructure/agent-config.js +22 -15
  77. package/lib/infrastructure/agent-config.js.map +1 -1
  78. package/lib/infrastructure/agent-config.test.js +26 -18
  79. package/lib/infrastructure/agent-config.test.js.map +1 -1
  80. package/lib/infrastructure/system-id.d.ts +2 -0
  81. package/lib/infrastructure/system-id.d.ts.map +1 -0
  82. package/lib/infrastructure/system-id.js +21 -0
  83. package/lib/infrastructure/system-id.js.map +1 -0
  84. package/lib/infrastructure/tokens-and-device-cfg.d.ts +4 -0
  85. package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -0
  86. package/lib/infrastructure/tokens-and-device-cfg.js +31 -0
  87. package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -0
  88. package/lib/infrastructure/urls.d.ts.map +1 -1
  89. package/lib/infrastructure/urls.js +3 -3
  90. package/lib/infrastructure/urls.js.map +1 -1
  91. package/lib/root.d.ts.map +1 -1
  92. package/lib/root.js +2 -7
  93. package/lib/root.js.map +1 -1
  94. package/lib/subcommands/app/app.d.ts.map +1 -1
  95. package/lib/subcommands/app/app.js +62 -60
  96. package/lib/subcommands/app/app.js.map +1 -1
  97. package/lib/subcommands/app/index.js +2 -2
  98. package/lib/subcommands/device/clean.d.ts +2 -0
  99. package/lib/subcommands/device/clean.d.ts.map +1 -0
  100. package/lib/subcommands/device/clean.js +29 -0
  101. package/lib/subcommands/device/clean.js.map +1 -0
  102. package/lib/subcommands/device/device.d.ts.map +1 -1
  103. package/lib/subcommands/device/device.js +40 -27
  104. package/lib/subcommands/device/device.js.map +1 -1
  105. package/lib/subcommands/device/index.d.ts.map +1 -1
  106. package/lib/subcommands/device/index.js +2 -1
  107. package/lib/subcommands/device/index.js.map +1 -1
  108. package/lib/subcommands/get-model-package.js +5 -5
  109. package/lib/subcommands/index.js +1 -1
  110. package/lib/subcommands/login.js +8 -8
  111. package/lib/subcommands/login.js.map +1 -1
  112. package/lib/util/clean-certs.d.ts +2 -0
  113. package/lib/util/clean-certs.d.ts.map +1 -0
  114. package/lib/util/clean-certs.js +16 -0
  115. package/lib/util/clean-certs.js.map +1 -0
  116. package/lib/util/directories.d.ts +16 -15
  117. package/lib/util/directories.d.ts.map +1 -1
  118. package/lib/util/directories.js +45 -26
  119. package/lib/util/directories.js.map +1 -1
  120. package/lib/util/get-device-id.d.ts +1 -1
  121. package/lib/util/get-device-id.d.ts.map +1 -1
  122. package/lib/util/get-device-id.js +14 -19
  123. package/lib/util/get-device-id.js.map +1 -1
  124. package/lib/util/http-client.d.ts +1 -1
  125. package/lib/util/http-client.d.ts.map +1 -1
  126. package/lib/util/http-client.js +10 -8
  127. package/lib/util/http-client.js.map +1 -1
  128. package/lib/util/logger.d.ts.map +1 -1
  129. package/lib/util/logger.js +4 -5
  130. package/lib/util/logger.js.map +1 -1
  131. package/lib/util/run-in-dir.d.ts.map +1 -1
  132. package/lib/util/run-in-dir.js +1 -0
  133. package/lib/util/run-in-dir.js.map +1 -1
  134. package/package.json +18 -8
  135. package/src/application-control/backup.ts +8 -3
  136. package/src/application-control/config.ts +75 -13
  137. package/src/application-control/environment-variables.ts +3 -3
  138. package/src/application-control/index.ts +19 -6
  139. package/src/application-control/install.ts +52 -28
  140. package/src/application-control/models.ts +100 -56
  141. package/src/application-control/status.ts +26 -21
  142. package/src/application-control/utils.ts +9 -20
  143. package/src/cloud-connection/app-install-status.ts +62 -0
  144. package/src/cloud-connection/bootstrap-provision.ts +40 -0
  145. package/src/cloud-connection/device-agent-cloud-connection.ts +258 -527
  146. package/src/cloud-connection/device-agent.ts +31 -38
  147. package/src/cloud-connection/live-updates-handler.ts +226 -0
  148. package/src/cloud-connection/messages.ts +39 -0
  149. package/src/cloud-connection/publisher.ts +65 -0
  150. package/src/cloud-connection/shadow-handler.ts +154 -0
  151. package/src/cloud-connection/shadow.ts +50 -0
  152. package/src/device-control/device-control.ts +1 -0
  153. package/src/docker/docker-cmd.ts +1 -1
  154. package/src/docker/docker-compose-cmd.ts +5 -2
  155. package/src/endpoints.ts +9 -9
  156. package/src/environment.ts +8 -3
  157. package/src/infrastructure/agent-config.test.ts +34 -23
  158. package/src/infrastructure/agent-config.ts +33 -20
  159. package/src/infrastructure/system-id.ts +18 -0
  160. package/src/infrastructure/tokens-and-device-cfg.ts +39 -0
  161. package/src/infrastructure/urls.ts +4 -2
  162. package/src/root.ts +2 -8
  163. package/src/subcommands/app/app.ts +64 -62
  164. package/src/subcommands/app/index.ts +3 -3
  165. package/src/subcommands/device/clean.ts +26 -0
  166. package/src/subcommands/device/device.ts +66 -50
  167. package/src/subcommands/device/index.ts +2 -1
  168. package/src/subcommands/get-model-package.ts +5 -5
  169. package/src/subcommands/index.ts +1 -1
  170. package/src/subcommands/login.ts +8 -8
  171. package/src/util/clean-certs.ts +12 -0
  172. package/src/util/directories.ts +68 -52
  173. package/src/util/get-device-id.ts +16 -18
  174. package/src/util/http-client.ts +18 -13
  175. package/src/util/logger.ts +6 -6
  176. package/src/util/run-in-dir.ts +2 -1
  177. package/lib/infrastructure/certificates-and-tokens.d.ts +0 -6
  178. package/lib/infrastructure/certificates-and-tokens.d.ts.map +0 -1
  179. package/lib/infrastructure/certificates-and-tokens.js +0 -43
  180. package/lib/infrastructure/certificates-and-tokens.js.map +0 -1
  181. package/src/infrastructure/certificates-and-tokens.ts +0 -53
@@ -1,19 +1,18 @@
1
- const awsIot = require("aws-iot-device-sdk");
1
+ // eslint-disable-next-line
2
+ const awsIot = require('aws-iot-device-sdk');
2
3
  import {
3
- BOOTSTRAP_CLAIM_ID_FILE_NAME,
4
- BOOTSTRAP_CLAIM_ID_FILE_PATH,
5
4
  DEVICE_PRIVATE_KEY_FILE_NAME,
6
5
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
7
6
  DEVICE_CLAIM_ID_FILE_NAME,
8
- DEVICE_CERTIFICATE_FILE_NAME,
9
- } from "../util/directories";
10
- import { getTargetHardwareUuid } from "../infrastructure/certificates-and-tokens";
11
- import { getDeviceId } from "../util/get-device-id";
12
- import { LOCAL_CERT_AND_KEY_DIR } from "alwaysai/lib/constants";
13
- import { JsSpawner } from "alwaysai/lib/util";
14
- import { logger } from "alwaysai/lib/util/logger";
15
- import { DeviceAgentCloudConnection } from "./device-agent-cloud-connection";
16
- const process = require("process");
7
+ DEVICE_CERTIFICATE_FILE_NAME
8
+ } from '../util/directories';
9
+ import { getDeviceUuid } from '../util/get-device-id';
10
+ import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/constants';
11
+ import { JsSpawner } from 'alwaysai/lib/util';
12
+ import { logger } from '../util/logger';
13
+ import { getTargetHardwareUuid } from 'alwaysai/lib/core/app';
14
+ // eslint-disable-next-line
15
+ const process = require('process');
17
16
 
18
17
  interface DeviceAgentConfigType {
19
18
  keyPath: string;
@@ -24,12 +23,12 @@ interface DeviceAgentConfigType {
24
23
  }
25
24
 
26
25
  interface FleetProvisionTemplateMessageType {
27
- certificateOwnershipToken: String;
26
+ certificateOwnershipToken: string;
28
27
  parameters: {
29
- hardwareId: String;
30
- deviceUuid: String;
31
- certificateId: String;
32
- deviceType: String;
28
+ hardwareId: string;
29
+ deviceUuid: string;
30
+ certificateId: string;
31
+ deviceType: string;
33
32
  };
34
33
  }
35
34
 
@@ -38,10 +37,10 @@ export class DeviceAgent {
38
37
  this.device = awsIot.device(config);
39
38
  }
40
39
 
41
- public deviceType = "aai-device";
40
+ public deviceType = 'aai-device';
42
41
  public device = awsIot.device;
43
42
  public hardwareId = async () => await getTargetHardwareUuid(JsSpawner());
44
- public deviceId = getDeviceId();
43
+ public deviceId = getDeviceUuid();
45
44
 
46
45
  public publishMessage(topic: string, message: string) {
47
46
  this.device.publish(topic, message);
@@ -51,40 +50,39 @@ export class DeviceAgent {
51
50
  export class BootstrapAgent extends DeviceAgent {
52
51
  public async subscribeToAllTopics() {
53
52
  const AWS_CERTIFICATE_ACCEPT_TOPIC =
54
- "$aws/certificates/create/json/accepted";
53
+ '$aws/certificates/create/json/accepted';
55
54
  const PROVISIONING_ACCEPTED_TOPIC =
56
- "$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted";
55
+ '$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted';
57
56
 
58
57
  const topics = [AWS_CERTIFICATE_ACCEPT_TOPIC, PROVISIONING_ACCEPTED_TOPIC];
59
58
  const resp = this.device.subscribe(
60
59
  topics,
61
60
  function (err: any, granted: { topic: string; qos: number }[]) {
62
- logger.debug(granted);
61
+ logger.debug(`Bootstrap Agent: ${JSON.stringify(granted, null, 2)}`);
63
62
  }
64
63
  );
65
64
  }
66
65
 
67
66
  public async handleAwsCertificateTopics(topic: string, payload: any) {
68
67
  switch (topic) {
69
- case "$aws/certificates/create/json/accepted": {
68
+ case '$aws/certificates/create/json/accepted': {
70
69
  const {
71
70
  certificateId,
72
71
  certificatePem,
73
72
  privateKey,
74
- certificateOwnershipToken,
73
+ certificateOwnershipToken
75
74
  } = JSON.parse(payload);
76
- console.log(certificateId);
77
75
 
78
76
  const certSpawner = JsSpawner({ path: LOCAL_CERT_AND_KEY_DIR });
79
77
 
80
78
  await certSpawner.writeFile(
81
- DEVICE_CERTIFICATE_FILE_NAME,
79
+ DEVICE_CERTIFICATE_FILE_NAME(),
82
80
  certificatePem
83
81
  );
84
82
 
85
- await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME, privateKey);
83
+ await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME(), privateKey);
86
84
 
87
- await certSpawner.writeFile(DEVICE_CLAIM_ID_FILE_NAME, certificateId);
85
+ await certSpawner.writeFile(DEVICE_CLAIM_ID_FILE_NAME(), certificateId);
88
86
 
89
87
  await certSpawner.writeFile(
90
88
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
@@ -97,24 +95,19 @@ export class BootstrapAgent extends DeviceAgent {
97
95
  hardwareId: await this.hardwareId(),
98
96
  deviceUuid: this.deviceId,
99
97
  certificateId,
100
- deviceType: this.deviceType,
101
- },
98
+ deviceType: this.deviceType
99
+ }
102
100
  };
103
- console.log(content);
104
101
 
105
102
  this.publishMessage(
106
- "$aws/provisioning-templates/FleetProvisionTemplate/provision/json",
103
+ '$aws/provisioning-templates/FleetProvisionTemplate/provision/json',
107
104
  JSON.stringify(content)
108
105
  );
109
106
 
110
107
  break;
111
108
  }
112
- case "$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted": {
113
- console.log("success");
114
- // Resolve the `child_process` module, and `spawn`
115
- // a new process.
116
- // The `child_process` module lets us
117
- // access OS functionalities by running any bash command.`.
109
+ case '$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted': {
110
+ logger.info('Device agent provisioning: success');
118
111
  process.exit();
119
112
  }
120
113
  }
@@ -0,0 +1,226 @@
1
+ import {
2
+ AppLogsMessage,
3
+ DeviceAgentMessagePayload,
4
+ keyMirrors,
5
+ LiveUpdatesToggles
6
+ } from '@alwaysai/device-agent-schemas';
7
+ import { getAppLogs } from '../application-control';
8
+ import { logger } from '../util/logger';
9
+ import sleep from '../util/sleep';
10
+ import { Publisher } from './publisher';
11
+ import { getAppStateMessage, getDeviceStatsMessage } from './messages';
12
+ import { AgentMessageTypeValue } from '@alwaysai/device-agent-schemas/lib/constants';
13
+ import { AppInstallStatusManager } from './app-install-status';
14
+
15
+ export class LiveUpdatesHandler {
16
+ private publisher: Publisher;
17
+ private appInstallStatusMgr: AppInstallStatusManager;
18
+
19
+ private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
20
+ private liveUpdatesAlive = {
21
+ [keyMirrors.agentMessageType.device_stats]: false,
22
+ [keyMirrors.agentMessageType.app_state]: false,
23
+ [keyMirrors.agentMessageType.app_logs]: false,
24
+ [keyMirrors.agentMessageType.app_install_status]: false
25
+ };
26
+ private liveUpdatesSleepIntervals = {
27
+ [keyMirrors.agentMessageType.device_stats]: 5000,
28
+ [keyMirrors.agentMessageType.app_state]: 5000,
29
+ [keyMirrors.agentMessageType.app_logs]: 5000,
30
+ [keyMirrors.agentMessageType.app_install_status]: 5000
31
+ };
32
+ private appLogStreams = new Set<string>();
33
+ private appInstallStatuses = new Set<string>();
34
+
35
+ constructor(
36
+ publisher: Publisher,
37
+ appInstallStatusMgr: AppInstallStatusManager
38
+ ) {
39
+ this.publisher = publisher;
40
+ this.appInstallStatusMgr = appInstallStatusMgr;
41
+ }
42
+
43
+ private async startAppLogStream(projectId: string) {
44
+ logger.info(`Starting log stream for ${projectId}`);
45
+ this.appLogStreams.add(projectId);
46
+ const readable = await getAppLogs({
47
+ projectId,
48
+ args: ['--tail', '100', '--no-log-prefix']
49
+ });
50
+ readable.on('data', (chunk: Buffer) => {
51
+ if (!this.appLogStreams.has(projectId)) {
52
+ // why doesn't typescript know about this function?
53
+ // @ts-ignore
54
+ readable.destroy();
55
+ logger.info(`App log stream terminated for project ${projectId}`);
56
+ return;
57
+ }
58
+ const logStr = chunk.toString();
59
+ const message: AppLogsMessage = {
60
+ messageType: keyMirrors.agentMessageType.app_logs,
61
+ appLogs: {
62
+ projectId,
63
+ logChunk: logStr
64
+ }
65
+ };
66
+ this.publisher.publishToClient(message);
67
+ });
68
+
69
+ readable.on('error', (error) => {
70
+ logger.error(
71
+ `App log stream terminated for project ${projectId}: ${error}`
72
+ );
73
+ });
74
+
75
+ readable.on('finished', () => {
76
+ logger.info(`App logs finished piping for project ${projectId}`);
77
+ });
78
+ }
79
+
80
+ // Use arrow function to allow `this` to be accessed
81
+ private getAppInstallStatusMessage = async (appReleaseHash: string) => {
82
+ const appInstallStatus =
83
+ this.appInstallStatusMgr.getStatusPacket(appReleaseHash);
84
+ const appInstallStatusMessage = {
85
+ messageType: keyMirrors.agentMessageType.app_install_status,
86
+ appInstallStatus
87
+ };
88
+ return appInstallStatusMessage;
89
+ };
90
+
91
+ public async startPublishingLiveUpdates<T extends any[]>(
92
+ messageType: AgentMessageTypeValue,
93
+ getMessageData: (...args: T) => Promise<DeviceAgentMessagePayload>,
94
+ args: T
95
+ ) {
96
+ logger.info(`Turned on live updates for ${messageType}`);
97
+ // eslint-disable-next-line no-constant-condition
98
+ while (true) {
99
+ try {
100
+ const message = await getMessageData(...args);
101
+ this.publisher.publishToClient(message);
102
+ } catch (e) {
103
+ logger.error(
104
+ `Error publishing live updates for ${messageType}: ${e.message}`
105
+ );
106
+ break;
107
+ }
108
+ if (!this.continuePublishing(messageType, ...args)) {
109
+ logger.info(`Turned off live updates for ${messageType}`);
110
+ break;
111
+ }
112
+ await sleep(this.getLiveUpdatesInterval(messageType));
113
+ }
114
+ }
115
+
116
+ private continuePublishing(flag: AgentMessageTypeValue, data?: any): boolean {
117
+ switch (flag) {
118
+ case keyMirrors.agentMessageType.device_stats:
119
+ case keyMirrors.agentMessageType.app_state:
120
+ return this.liveUpdatesAlive[flag];
121
+ case keyMirrors.agentMessageType.app_install_status: {
122
+ const appReleaseHash: string = data;
123
+ return this.appInstallStatuses.has(appReleaseHash);
124
+ }
125
+ default:
126
+ logger.error(`Unrecognized publishable flag ${flag}`);
127
+ return false;
128
+ }
129
+ }
130
+
131
+ private getLiveUpdatesInterval(flag: AgentMessageTypeValue): number {
132
+ const exists = this.liveUpdatesSleepIntervals[flag];
133
+ if (exists) {
134
+ return exists;
135
+ }
136
+ logger.error(`Unrecognized live updates flag ${flag}`);
137
+ return -1;
138
+ }
139
+
140
+ private setLiveUpdates(toggles: LiveUpdatesToggles) {
141
+ if (toggles.deviceStats) {
142
+ this.liveUpdatesAlive.device_stats = toggles.deviceStats;
143
+ }
144
+ if (toggles.appState) {
145
+ this.liveUpdatesAlive.app_state = toggles.appState;
146
+ }
147
+ }
148
+
149
+ private restartLiveUpdatesTimeout() {
150
+ clearTimeout(this.liveUpdatesTimeout);
151
+ this.liveUpdatesTimeout = setTimeout(() => {
152
+ this.setLiveUpdates({
153
+ deviceStats: false,
154
+ appState: false
155
+ });
156
+ this.appLogStreams.clear();
157
+ // TODO: Make constant, not hard coded
158
+ }, 600000); // 10 min
159
+ }
160
+
161
+ public async update({
162
+ deviceStats,
163
+ appState,
164
+ appLogs,
165
+ appInstallStatus
166
+ }: {
167
+ deviceStats?: boolean;
168
+ appState?: boolean;
169
+ appLogs?: {
170
+ projectId: string;
171
+ toggle: boolean;
172
+ };
173
+ appInstallStatus?: {
174
+ appReleaseHash: string;
175
+ toggle: boolean;
176
+ };
177
+ }) {
178
+ this.restartLiveUpdatesTimeout();
179
+ if (deviceStats !== undefined) {
180
+ this.liveUpdatesAlive.device_stats = deviceStats;
181
+ if (deviceStats) {
182
+ this.startPublishingLiveUpdates(
183
+ keyMirrors.agentMessageType.device_stats,
184
+ getDeviceStatsMessage,
185
+ []
186
+ );
187
+ }
188
+ }
189
+
190
+ if (appState !== undefined) {
191
+ this.liveUpdatesAlive.app_state = appState;
192
+ if (appState) {
193
+ this.startPublishingLiveUpdates(
194
+ keyMirrors.agentMessageType.app_state,
195
+ getAppStateMessage,
196
+ []
197
+ );
198
+ }
199
+ }
200
+
201
+ if (appLogs !== undefined) {
202
+ if (appLogs.toggle) {
203
+ this.startAppLogStream(appLogs.projectId);
204
+ } else {
205
+ this.appLogStreams.delete(appLogs.projectId);
206
+ }
207
+ }
208
+
209
+ if (appInstallStatus !== undefined) {
210
+ if (appInstallStatus.toggle) {
211
+ this.liveUpdatesAlive.app_install_status = appInstallStatus.toggle;
212
+ this.appInstallStatuses.add(appInstallStatus.appReleaseHash);
213
+ this.startPublishingLiveUpdates(
214
+ keyMirrors.agentMessageType.app_install_status,
215
+ this.getAppInstallStatusMessage,
216
+ [appInstallStatus.appReleaseHash]
217
+ );
218
+ } else {
219
+ this.appInstallStatuses.delete(appInstallStatus.appReleaseHash);
220
+ }
221
+ if (this.appInstallStatuses.size === 0) {
222
+ this.liveUpdatesAlive.app_install_status = false;
223
+ }
224
+ }
225
+ }
226
+ }
@@ -0,0 +1,39 @@
1
+ import { AppStatePacket, keyMirrors } from '@alwaysai/device-agent-schemas';
2
+ import { getAppStatus } from '../application-control';
3
+ import {
4
+ getCpuUtil,
5
+ getDiskUtil,
6
+ getMemUtil
7
+ } from '../device-control/device-control';
8
+ import { AgentConfigFile } from '../infrastructure/agent-config';
9
+
10
+ export async function getAppStateMessage() {
11
+ const appStateMessage: AppStatePacket[] = [];
12
+ const apps = await AgentConfigFile().getReadyApps();
13
+ for (const app of apps) {
14
+ const projectId = app.projectId;
15
+ const status = await getAppStatus({ projectId });
16
+ appStateMessage.push(status);
17
+ }
18
+ const appStatePackage = {
19
+ messageType: keyMirrors.agentMessageType.app_state,
20
+ appState: appStateMessage
21
+ };
22
+ return appStatePackage;
23
+ }
24
+
25
+ export async function getDeviceStatsMessage() {
26
+ const cpuUsage = await getCpuUtil();
27
+ const diskUtil = await getDiskUtil();
28
+ const memUtil = await getMemUtil();
29
+
30
+ const deviceStatsMessage = {
31
+ messageType: keyMirrors.agentMessageType.device_stats,
32
+ deviceStats: {
33
+ cpuUsage,
34
+ diskUtil,
35
+ usedMemoryPercentage: memUtil
36
+ }
37
+ };
38
+ return deviceStatsMessage;
39
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ DeviceAgentMessage,
3
+ DeviceAgentMessagePayload,
4
+ getClientTopic,
5
+ getCloudTopic
6
+ } from '@alwaysai/device-agent-schemas';
7
+ import { logger } from '../util/logger';
8
+
9
+ export class Publisher {
10
+ private client: any;
11
+ private clientId: string;
12
+ private readonly toClientTopic: string;
13
+ private readonly toCloudTopic: string;
14
+
15
+ constructor(client: any, clientId: string) {
16
+ this.client = client;
17
+ this.clientId = clientId;
18
+ this.toClientTopic = getClientTopic(this.clientId);
19
+ this.toCloudTopic = getCloudTopic(this.clientId);
20
+ }
21
+
22
+ private buildMessagePacket(
23
+ topic: string,
24
+ payload: DeviceAgentMessagePayload
25
+ ): DeviceAgentMessage {
26
+ const packet = {
27
+ timestamp: new Date().toUTCString(),
28
+ deviceId: this.clientId,
29
+ topic,
30
+ payload
31
+ };
32
+ return packet;
33
+ }
34
+
35
+ public publish(topic: string, payload: string) {
36
+ // TODO: topic validation
37
+ this.client.publish(topic, payload, (err: any) => {
38
+ if (err) {
39
+ logger.error(
40
+ `Error publishing message: \nTopic: ${topic}\nMessage: ${payload}\nError: ${err}`
41
+ );
42
+ }
43
+ });
44
+ }
45
+
46
+ public publishDeviceAgentPayload(
47
+ topic: string,
48
+ payload: DeviceAgentMessagePayload
49
+ ) {
50
+ const packet = this.buildMessagePacket(topic, payload);
51
+ const packetStr = JSON.stringify(packet);
52
+ logger.debug(
53
+ `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
54
+ );
55
+ this.publish(topic, packetStr);
56
+ }
57
+
58
+ public publishToClient(payload: DeviceAgentMessagePayload) {
59
+ this.publishDeviceAgentPayload(this.toClientTopic, payload);
60
+ }
61
+
62
+ public publishToCloud(payload: DeviceAgentMessagePayload) {
63
+ this.publishDeviceAgentPayload(this.toCloudTopic, payload);
64
+ }
65
+ }
@@ -0,0 +1,154 @@
1
+ import {
2
+ AppConfig,
3
+ validateAppConfig
4
+ } from '@alwaysai/app-configuration-schemas';
5
+ import { readAppCfgFile } from '../application-control';
6
+ import { logger } from '../util/logger';
7
+ import { Publisher } from './publisher';
8
+ import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
9
+
10
+ interface ShadowTopics {
11
+ projects: {
12
+ update: string;
13
+ get: string;
14
+ updateDelta: string;
15
+ getAccepted: string;
16
+ delete: string;
17
+ };
18
+ }
19
+
20
+ export type AppConfigUpdate = {
21
+ projectId: string;
22
+ newAppCfg: AppConfig;
23
+ updatedModels?: AppConfigModels;
24
+ };
25
+
26
+ export class ShadowHandler {
27
+ private clientId: string;
28
+ private publisher: Publisher;
29
+ public readonly shadowPrefix: string;
30
+ public readonly shadowTopics: ShadowTopics;
31
+
32
+ constructor(clientId: string, publisher: Publisher) {
33
+ this.clientId = clientId;
34
+ this.publisher = publisher;
35
+ this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
36
+ this.shadowTopics = {
37
+ projects: {
38
+ update: `${this.shadowPrefix}projects/update`,
39
+ get: `${this.shadowPrefix}projects/get`,
40
+ updateDelta: `${this.shadowPrefix}projects/update/delta`,
41
+ getAccepted: `${this.shadowPrefix}projects/get/accepted`,
42
+ delete: `${this.shadowPrefix}projects/delete`
43
+ }
44
+ };
45
+ }
46
+
47
+ private async handleNamedShadowUpdate({
48
+ delta
49
+ }: {
50
+ delta: any;
51
+ }): Promise<AppConfigUpdate[]> {
52
+ const appConfigUpdates: AppConfigUpdate[] = [];
53
+
54
+ const deltaKeys = Object.keys(delta);
55
+
56
+ for (const projectId of deltaKeys) {
57
+ const projectShadow = delta[projectId];
58
+
59
+ if (projectShadow.appConfig) {
60
+ const newAppCfg = JSON.parse(projectShadow.appConfig);
61
+ if (!validateAppConfig(newAppCfg)) {
62
+ logger.error(
63
+ `Received invalid app config for ${projectId}!/n${JSON.stringify(
64
+ validateAppConfig.errors,
65
+ null,
66
+ 2
67
+ )}`
68
+ );
69
+ continue;
70
+ }
71
+ const { updatedModels } = await getAppCfgModelsDiff({
72
+ newAppCfg,
73
+ projectId
74
+ });
75
+
76
+ if (updatedModels && Object.keys(updatedModels).length) {
77
+ appConfigUpdates.push({ projectId, newAppCfg, updatedModels });
78
+ } else {
79
+ appConfigUpdates.push({ projectId, newAppCfg });
80
+ }
81
+ }
82
+ }
83
+ return appConfigUpdates;
84
+ }
85
+
86
+ // Public interface
87
+
88
+ public async handleShadowTopic({
89
+ topic,
90
+ payload
91
+ }: {
92
+ topic: string;
93
+ payload: any;
94
+ }): Promise<AppConfigUpdate[]> {
95
+ // TODO: make use a function like the other topic getters
96
+ const shadowName = topic.split('/')[5];
97
+ switch (topic) {
98
+ case this.shadowTopics.projects.updateDelta:
99
+ return await this.handleNamedShadowUpdate({ delta: payload });
100
+ case this.shadowTopics.projects.getAccepted:
101
+ if (payload['delta']) {
102
+ return await this.handleNamedShadowUpdate({
103
+ delta: payload['delta']
104
+ });
105
+ } else {
106
+ logger.info(`No delta updates in named shadow '${shadowName}'`);
107
+ }
108
+ break;
109
+ default:
110
+ logger.info(
111
+ `Ignoring shadow message: ${JSON.stringify(
112
+ { topic, payload },
113
+ null,
114
+ 2
115
+ )}`
116
+ );
117
+ }
118
+ return [];
119
+ }
120
+
121
+ public async publishAppState(projectId: string) {
122
+ const appCfg = await readAppCfgFile({ projectId });
123
+ const packet = {
124
+ state: {
125
+ reported: {
126
+ [projectId]: { appConfig: JSON.stringify(appCfg) }
127
+ }
128
+ },
129
+ clientToken: this.clientId
130
+ };
131
+ this.publisher.publish(
132
+ this.shadowTopics.projects.update,
133
+ JSON.stringify(packet)
134
+ );
135
+ }
136
+
137
+ public getShadowUpdates() {
138
+ this.publisher.publish(
139
+ this.shadowTopics.projects.get,
140
+ JSON.stringify({
141
+ clientToken: this.clientId
142
+ })
143
+ );
144
+ }
145
+
146
+ public deleteProjectShadow() {
147
+ this.publisher.publish(
148
+ `${this.shadowTopics.projects.delete}`,
149
+ JSON.stringify({
150
+ clientToken: this.clientId
151
+ })
152
+ );
153
+ }
154
+ }
@@ -0,0 +1,50 @@
1
+ import { logger } from '../util/logger';
2
+ import { readAppCfgFile } from '../application-control';
3
+ import { AppConfig } from '@alwaysai/app-configuration-schemas';
4
+
5
+ export type AppConfigModels = {
6
+ [modelId: string]: number;
7
+ };
8
+
9
+ export type AppConfigScripts = {
10
+ start: string;
11
+ };
12
+
13
+ export const getAppCfgModelsDiff = async ({
14
+ newAppCfg,
15
+ projectId
16
+ }: {
17
+ newAppCfg: AppConfig;
18
+ projectId: string;
19
+ }) => {
20
+ const updatedModels: AppConfigModels = {};
21
+ const untouchedModels: AppConfigModels = {};
22
+ const newScripts: AppConfigScripts = { start: '' };
23
+
24
+ try {
25
+ const shadowScripts = newAppCfg.scripts;
26
+ const shadowModels = newAppCfg.models;
27
+
28
+ const localAppCfg = await readAppCfgFile({ projectId });
29
+ const localModels = localAppCfg.models;
30
+
31
+ Object.keys(shadowModels).forEach((modelId: string) => {
32
+ const localVersion = localModels[modelId];
33
+ const shadowVersion = shadowModels[modelId];
34
+ if (!localVersion || localVersion !== shadowVersion) {
35
+ updatedModels[modelId] = shadowVersion;
36
+ } else {
37
+ untouchedModels[modelId] = localVersion;
38
+ }
39
+ });
40
+
41
+ shadowScripts &&
42
+ Object.keys(shadowScripts).forEach((scriptName: string) => {
43
+ newScripts[scriptName] = shadowScripts[scriptName];
44
+ });
45
+ } catch (e) {
46
+ logger.error('Error parsing app config update: ', e);
47
+ }
48
+
49
+ return { scripts: newScripts, updatedModels, untouchedModels };
50
+ };
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line
1
2
  const osu = require('node-os-utils');
2
3
 
3
4
  export async function getCpuUtil(): Promise<number> {
@@ -6,7 +6,7 @@ export async function runDockerLogin(props: { token: string }) {
6
6
  const server = '994534263224.dkr.ecr.us-west-2.amazonaws.com';
7
7
  const output = await spawner.run({
8
8
  exe: 'docker',
9
- args: ['login', '--username', 'AWS', '--password', token, server],
9
+ args: ['login', '--username', 'AWS', '--password', token, server]
10
10
  });
11
11
  return output;
12
12
  }