@alwaysai/device-agent 0.0.15 → 0.0.17

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 (76) hide show
  1. package/lib/application-control/config.d.ts.map +1 -1
  2. package/lib/application-control/config.js +4 -4
  3. package/lib/application-control/config.js.map +1 -1
  4. package/lib/application-control/install.d.ts.map +1 -1
  5. package/lib/application-control/install.js +2 -1
  6. package/lib/application-control/install.js.map +1 -1
  7. package/lib/application-control/models.js +1 -1
  8. package/lib/application-control/status.d.ts.map +1 -1
  9. package/lib/application-control/status.js +8 -13
  10. package/lib/application-control/status.js.map +1 -1
  11. package/lib/application-control/utils.d.ts.map +1 -1
  12. package/lib/application-control/utils.js +3 -3
  13. package/lib/application-control/utils.js.map +1 -1
  14. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  15. package/lib/cloud-connection/device-agent-cloud-connection.js +28 -15
  16. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  17. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  18. package/lib/cloud-connection/live-updates-handler.js +9 -8
  19. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  20. package/lib/cloud-connection/shadow-handler.d.ts +9 -5
  21. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  22. package/lib/cloud-connection/shadow-handler.js +25 -10
  23. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  24. package/lib/cloud-connection/shadow-handler.test.js +24 -14
  25. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  26. package/lib/cloud-connection/shadow.js +1 -1
  27. package/lib/cloud-connection/shadow.js.map +1 -1
  28. package/lib/index.js +3 -1
  29. package/lib/index.js.map +1 -1
  30. package/lib/subcommands/app/env-vars.d.ts +8 -0
  31. package/lib/subcommands/app/env-vars.d.ts.map +1 -0
  32. package/lib/subcommands/app/env-vars.js +43 -0
  33. package/lib/subcommands/app/env-vars.js.map +1 -0
  34. package/lib/subcommands/app/index.d.ts.map +1 -1
  35. package/lib/subcommands/app/index.js +20 -19
  36. package/lib/subcommands/app/index.js.map +1 -1
  37. package/lib/subcommands/app/models.d.ts +20 -0
  38. package/lib/subcommands/app/models.d.ts.map +1 -0
  39. package/lib/subcommands/app/models.js +114 -0
  40. package/lib/subcommands/app/models.js.map +1 -0
  41. package/lib/subcommands/app/status.d.ts +17 -0
  42. package/lib/subcommands/app/status.d.ts.map +1 -0
  43. package/lib/subcommands/app/status.js +103 -0
  44. package/lib/subcommands/app/status.js.map +1 -0
  45. package/lib/subcommands/app/version.d.ts +12 -0
  46. package/lib/subcommands/app/version.d.ts.map +1 -0
  47. package/lib/subcommands/app/version.js +103 -0
  48. package/lib/subcommands/app/version.js.map +1 -0
  49. package/lib/util/get-device-id.js +1 -1
  50. package/lib/util/get-device-id.js.map +1 -1
  51. package/lib/util/http-client.js +1 -1
  52. package/lib/util/http-client.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/application-control/config.ts +8 -4
  55. package/src/application-control/install.ts +6 -2
  56. package/src/application-control/models.ts +1 -1
  57. package/src/application-control/status.ts +10 -16
  58. package/src/application-control/utils.ts +3 -4
  59. package/src/cloud-connection/device-agent-cloud-connection.ts +33 -21
  60. package/src/cloud-connection/live-updates-handler.ts +14 -13
  61. package/src/cloud-connection/shadow-handler.test.ts +24 -14
  62. package/src/cloud-connection/shadow-handler.ts +36 -19
  63. package/src/cloud-connection/shadow.ts +1 -1
  64. package/src/index.ts +4 -1
  65. package/src/subcommands/app/env-vars.ts +46 -0
  66. package/src/subcommands/app/index.ts +16 -17
  67. package/src/subcommands/app/models.ts +129 -0
  68. package/src/subcommands/app/status.ts +92 -0
  69. package/src/subcommands/app/version.ts +103 -0
  70. package/src/util/get-device-id.ts +1 -1
  71. package/src/util/http-client.ts +1 -1
  72. package/lib/subcommands/app/app.d.ts +0 -60
  73. package/lib/subcommands/app/app.d.ts.map +0 -1
  74. package/lib/subcommands/app/app.js +0 -370
  75. package/lib/subcommands/app/app.js.map +0 -1
  76. package/src/subcommands/app/app.ts +0 -390
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rollbackAppCliLeaf = exports.uninstallAppCliLeaf = exports.installAppCliLeaf = exports.listAppsCliLeaf = void 0;
4
+ const alwayscli_1 = require("@alwaysai/alwayscli");
5
+ const device_agent_schemas_1 = require("@alwaysai/device-agent-schemas");
6
+ const application_control_1 = require("../../application-control");
7
+ const device_agent_cloud_connection_1 = require("../../cloud-connection/device-agent-cloud-connection");
8
+ const agent_config_1 = require("../../infrastructure/agent-config");
9
+ const sleep_1 = require("../../util/sleep");
10
+ exports.listAppsCliLeaf = (0, alwayscli_1.CliLeaf)({
11
+ name: 'list',
12
+ description: 'List all installed apps',
13
+ namedInputs: {},
14
+ async action(_, opts) {
15
+ const apps = await (0, agent_config_1.AgentConfigFile)().getApps();
16
+ console.table(apps);
17
+ }
18
+ });
19
+ exports.installAppCliLeaf = (0, alwayscli_1.CliLeaf)({
20
+ name: 'install',
21
+ description: 'Install an alwaysAI app from a project',
22
+ namedInputs: {
23
+ project: (0, alwayscli_1.CliStringInput)({
24
+ description: 'Project ID',
25
+ required: true
26
+ }),
27
+ releaseHash: (0, alwayscli_1.CliStringInput)({
28
+ description: 'Release Hash',
29
+ required: true
30
+ })
31
+ },
32
+ async action(_, opts) {
33
+ const { project, releaseHash } = opts;
34
+ const deviceAgent = new device_agent_cloud_connection_1.DeviceAgentCloudConnection();
35
+ await deviceAgent.setupHandlers();
36
+ const topic = deviceAgent.getToDeviceTopic();
37
+ const message = {
38
+ timestamp: '',
39
+ topic,
40
+ payload: {
41
+ messageType: device_agent_schemas_1.keyMirrors.clientMessageType.app_version_control,
42
+ appVersionControl: {
43
+ baseCommand: device_agent_schemas_1.keyMirrors.appVersionControl.install,
44
+ projectId: project,
45
+ appReleaseHash: releaseHash
46
+ }
47
+ }
48
+ };
49
+ await deviceAgent.handleMessage(topic, message);
50
+ while (deviceAgent.isCmdInProgress(project)) {
51
+ await (0, sleep_1.default)(1000);
52
+ }
53
+ deviceAgent.stop();
54
+ }
55
+ });
56
+ exports.uninstallAppCliLeaf = (0, alwayscli_1.CliLeaf)({
57
+ name: 'uninstall',
58
+ description: 'Remove an alwaysAI app',
59
+ namedInputs: {
60
+ project: (0, alwayscli_1.CliStringInput)({
61
+ description: 'Project ID',
62
+ required: true
63
+ })
64
+ },
65
+ async action(_, opts) {
66
+ const { project } = opts;
67
+ const deviceAgent = new device_agent_cloud_connection_1.DeviceAgentCloudConnection();
68
+ await deviceAgent.setupHandlers();
69
+ const topic = deviceAgent.getToDeviceTopic();
70
+ const message = {
71
+ timestamp: '',
72
+ topic,
73
+ payload: {
74
+ messageType: device_agent_schemas_1.keyMirrors.clientMessageType.app_version_control,
75
+ appVersionControl: {
76
+ baseCommand: device_agent_schemas_1.keyMirrors.appVersionControl.uninstall,
77
+ projectId: project
78
+ }
79
+ }
80
+ };
81
+ await deviceAgent.handleMessage(topic, message);
82
+ while (deviceAgent.isCmdInProgress(project)) {
83
+ await (0, sleep_1.default)(1000);
84
+ }
85
+ deviceAgent.stop();
86
+ }
87
+ });
88
+ exports.rollbackAppCliLeaf = (0, alwayscli_1.CliLeaf)({
89
+ name: 'rollback',
90
+ description: 'Rollback an alwaysAI app to the previous version',
91
+ namedInputs: {
92
+ project: (0, alwayscli_1.CliStringInput)({
93
+ description: 'Project ID',
94
+ required: true
95
+ })
96
+ },
97
+ hidden: true,
98
+ async action(_, opts) {
99
+ const { project } = opts;
100
+ await (0, application_control_1.rollbackApp)({ projectId: project });
101
+ }
102
+ });
103
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/subcommands/app/version.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ function getDeviceUuid() {
11
11
  const cfg = deviceCfgFile.read();
12
12
  return cfg.deviceUuid;
13
13
  }
14
- catch (err) {
14
+ catch (e) {
15
15
  throw new Error(`Failed to read device configuration: ${JSON.stringify(deviceCfgFile.getErrors(), null, 2)}`);
16
16
  }
17
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"get-device-id.js","sourceRoot":"","sources":["../../src/util/get-device-id.ts"],"names":[],"mappings":";;;AAAA,qDAA4D;AAE5D,SAAgB,aAAa;IAC3B,MAAM,aAAa,GAAG,IAAA,yBAAgB,GAAE,CAAC;IACzC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE;QAC3B,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;KACpD;IACD,IAAI;QACF,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,UAAU,CAAC;KACvB;IAAC,OAAO,GAAG,EAAE;QACZ,MAAM,IAAI,KAAK,CACb,wCAAwC,IAAI,CAAC,SAAS,CACpD,aAAa,CAAC,SAAS,EAAE,EACzB,IAAI,EACJ,CAAC,CACF,EAAE,CACJ,CAAC;KACH;AACH,CAAC;AAjBD,sCAiBC"}
1
+ {"version":3,"file":"get-device-id.js","sourceRoot":"","sources":["../../src/util/get-device-id.ts"],"names":[],"mappings":";;;AAAA,qDAA4D;AAE5D,SAAgB,aAAa;IAC3B,MAAM,aAAa,GAAG,IAAA,yBAAgB,GAAE,CAAC;IACzC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE;QAC3B,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;KACpD;IACD,IAAI;QACF,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,UAAU,CAAC;KACvB;IAAC,OAAO,CAAC,EAAE;QACV,MAAM,IAAI,KAAK,CACb,wCAAwC,IAAI,CAAC,SAAS,CACpD,aAAa,CAAC,SAAS,EAAE,EACzB,IAAI,EACJ,CAAC,CACF,EAAE,CACJ,CAAC;KACH;AACH,CAAC;AAjBD,sCAiBC"}
@@ -18,7 +18,7 @@ async function httpClient(url, method, headers, data) {
18
18
  }
19
19
  }
20
20
  catch (e) {
21
- logger_1.logger.error(`HTTP Client error for ${url}: ${e}`);
21
+ logger_1.logger.error(`HTTP Client error for ${url}: ${e.message}`);
22
22
  }
23
23
  }
24
24
  exports.httpClient = httpClient;
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/util/http-client.ts"],"names":[],"mappings":";;;AAAA,2CAA+B;AAC/B,4CAAsD;AACtD,gEAAsE;AACtE,2CAAwC;AAEjC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,MAAc,EACd,OAAY,EACZ,IAAa;IAEb,MAAM,OAAO,mBACX,MAAM,IACH,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAClD,CAAC;IACF,IAAI;QACF,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,WAAW,KAAK,kBAAkB,EAAE;YACtC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;SAC9B;QACD,IAAI,WAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;YACvC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;SAC9B;KACF;IAAC,OAAO,CAAC,EAAE;QACV,eAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;KACpD;AACH,CAAC;AAtBD,gCAsBC;AAEM,KAAK,UAAU,sBAAsB,CAC1C,OAAe,EACf,IAAY,EACZ,MAAc,EACd,IAAa;IAEb,MAAM,QAAQ,GAAG,IAAA,kCAAsB,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvD,MAAM,EAAE,wBAAwB,EAAE,GAAG,IAAA,wCAAuB,GAAE,CAAC;IAC/D,MAAM,mBAAmB,GAAG,MAAM,wBAAwB,EAAE,CAAC;IAC7D,OAAO,MAAM,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;AACvE,CAAC;AAVD,wDAUC"}
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/util/http-client.ts"],"names":[],"mappings":";;;AAAA,2CAA+B;AAC/B,4CAAsD;AACtD,gEAAsE;AACtE,2CAAwC;AAEjC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,MAAc,EACd,OAAY,EACZ,IAAa;IAEb,MAAM,OAAO,mBACX,MAAM,IACH,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAClD,CAAC;IACF,IAAI;QACF,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,WAAW,KAAK,kBAAkB,EAAE;YACtC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;SAC9B;QACD,IAAI,WAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;YACvC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;SAC9B;KACF;IAAC,OAAO,CAAC,EAAE;QACV,eAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;KAC5D;AACH,CAAC;AAtBD,gCAsBC;AAEM,KAAK,UAAU,sBAAsB,CAC1C,OAAe,EACf,IAAY,EACZ,MAAc,EACd,IAAa;IAEb,MAAM,QAAQ,GAAG,IAAA,kCAAsB,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvD,MAAM,EAAE,wBAAwB,EAAE,GAAG,IAAA,wCAAuB,GAAE,CAAC;IAC/D,MAAM,mBAAmB,GAAG,MAAM,wBAAwB,EAAE,CAAC;IAC7D,OAAO,MAAM,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;AACvE,CAAC;AAVD,wDAUC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alwaysai/device-agent",
3
3
  "description": "The alwaysAI Device Agent",
4
- "version": "0.0.15",
4
+ "version": "0.0.17",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "publishConfig": {
@@ -23,9 +23,11 @@ export async function readAppCfgFile(props: {
23
23
  }
24
24
  try {
25
25
  return appJson.read();
26
- } catch (err) {
26
+ } catch (e) {
27
27
  throw new Error(
28
- `Error reading app config for ${projectId}:\n${err}\n${appJson.getErrors()}`
28
+ `Error reading app config for ${projectId}:\n${
29
+ e.message
30
+ }\n${appJson.getErrors()}`
29
31
  );
30
32
  }
31
33
  }
@@ -42,9 +44,11 @@ export async function writeAppCfgFile(props: {
42
44
  const appJson = AppJsonFile(appDir);
43
45
  try {
44
46
  appJson.write(appCfg);
45
- } catch (err) {
47
+ } catch (e) {
46
48
  throw new Error(
47
- `Error writing app config for ${projectId}:\n${err}\n${appJson.getErrors()}`
49
+ `Error writing app config for ${projectId}:\n${
50
+ e.message
51
+ }\n${appJson.getErrors()}`
48
52
  );
49
53
  }
50
54
  }
@@ -6,7 +6,7 @@ import { JsSpawner, Spawner } from 'alwaysai/lib/util';
6
6
  import { getAppDir, downloadPackageUsingPresignedUrl, buildApp } from './utils';
7
7
  import { AppDetailsPacket } from '@alwaysai/device-agent-schemas';
8
8
  import { BACKUP_EXT } from './backup';
9
- import { stopApp } from './status';
9
+ import { startApp, stopApp } from './status';
10
10
  import { AgentConfigFile } from '../infrastructure/agent-config';
11
11
  import { ProjectJsonFile } from 'alwaysai/lib/core/project';
12
12
  import {
@@ -107,6 +107,8 @@ export async function installApp(props: {
107
107
  version: appReleaseHash
108
108
  });
109
109
 
110
+ await startApp({ projectId });
111
+
110
112
  logger.info(`Completed installing ${projectId}:${appReleaseHash}`);
111
113
  }
112
114
 
@@ -199,7 +201,9 @@ export async function uninstallApp(props: {
199
201
  try {
200
202
  await stopApp({ projectId });
201
203
  } catch (e) {
202
- logger.warn(`Failed to stop ${projectId}, may be left running...\n${e}`);
204
+ logger.warn(
205
+ `Failed to stop ${projectId}, may be left running...\n${e.message}`
206
+ );
203
207
  }
204
208
  await AgentConfigFile().setAppUninstalled({ projectId });
205
209
  // Delete application directory and backup
@@ -213,7 +213,7 @@ export async function updateModelsWithPresignedUrls(props: {
213
213
  } catch (e) {
214
214
  logger.error(
215
215
  'Error updating app models from presigned URL, restoring models.',
216
- e
216
+ e.message
217
217
  );
218
218
  await spawner.rimraf(ogDir);
219
219
  await copyDir({ srcPath: restoreDir, destPath: ogDir });
@@ -135,8 +135,7 @@ export async function startApp(props: {
135
135
  logger.debug(`docker login: ${result}`);
136
136
  }
137
137
 
138
- // TODO: Check if app is running
139
- // Start app
138
+ // Always call `docker-compose up` regardless of application state
140
139
  const upOut = await compose.upAll({ cwd: appDir });
141
140
  logger.debug(`docker-compose up: ${JSON.stringify(upOut, null, 2)}`);
142
141
  if (upOut.exitCode !== 0) {
@@ -152,21 +151,16 @@ export async function stopApp(props: { projectId: string }): Promise<void> {
152
151
  await requireAppInstalled({ projectId });
153
152
 
154
153
  const appDir = getAppDir(projectId);
155
- const appStatus = await getAppStatus({ projectId });
156
- const appIsRunning =
157
- appStatus.services.length &&
158
- appStatus.services[0].state !== keyMirrors.appState.stopped;
159
- if (appIsRunning) {
160
- // Stop app
161
- const output = await compose.down({ cwd: appDir });
162
- logger.debug(`docker-compose down: ${JSON.stringify(output, null, 2)}`);
163
- if (output.exitCode !== 0) {
164
- throw new Error(
165
- `Failed to stop application! stdout=${output.out} stderr=${output.err}`
166
- );
167
- }
168
- logger.info(`Stopped ${projectId}`);
154
+ // Always call `docker-compose down` regardless of application state to ensure
155
+ // containers are cleaned up
156
+ const output = await compose.down({ cwd: appDir });
157
+ logger.debug(`docker-compose down: ${JSON.stringify(output, null, 2)}`);
158
+ if (output.exitCode !== 0) {
159
+ throw new Error(
160
+ `Failed to stop application! stdout=${output.out} stderr=${output.err}`
161
+ );
169
162
  }
163
+ logger.info(`Stopped ${projectId}`);
170
164
  }
171
165
 
172
166
  export async function restartApp(props: { projectId: string }): Promise<void> {
@@ -104,11 +104,10 @@ export async function downloadPackageUsingPresignedUrl(props: {
104
104
  let response: any;
105
105
  try {
106
106
  response = await fetchWithTimeout(presignedUrl);
107
- } catch (error) {
108
- const errorBody =
109
- error.type === 'aborted' ? error : await error.response.text();
107
+ } catch (e) {
108
+ const errorBody = e.type === 'aborted' ? e : await e.response.text();
110
109
  throw new Error(
111
- `downloadPackageUsingPresignedUrl: Error=${error}\n${errorBody}`
110
+ `downloadPackageUsingPresignedUrl: Error=${e}\n${errorBody}`
112
111
  );
113
112
  }
114
113
 
@@ -138,7 +138,7 @@ export class DeviceAgentCloudConnection {
138
138
  });
139
139
  try {
140
140
  await uninstallApp({ projectId });
141
- this.shadowHandler.deleteProjectShadow(projectId);
141
+ this.shadowHandler.clearAppConfig(projectId);
142
142
 
143
143
  await this.cmdStatusMgr.stop(projectId);
144
144
  await this.liveUpdatesHandler.disableAppInstallStatus({
@@ -153,7 +153,7 @@ export class DeviceAgentCloudConnection {
153
153
  )
154
154
  );
155
155
  } catch (e) {
156
- logger.error(`Failed to uninstall ${projectId}: ${e}`);
156
+ logger.error(`Failed to uninstall ${projectId}: ${e.message}`);
157
157
  const message: string = e.message;
158
158
  await this.cmdStatusMgr.stop(projectId);
159
159
  await this.liveUpdatesHandler.disableAppInstallStatus({
@@ -200,29 +200,32 @@ export class DeviceAgentCloudConnection {
200
200
  );
201
201
 
202
202
  // update app config shadow for project
203
- await this.shadowHandler.publishAppState(projectId);
203
+ await this.shadowHandler.publishAppConfig(projectId);
204
204
  return out;
205
205
  } catch (e) {
206
- logger.error(`Failed to install ${projectId}: ${e}`);
206
+ logger.error(`Failed to install ${projectId}: ${e.message}`);
207
207
  const message: string = e.message;
208
208
 
209
209
  // uninstall the failed app to put system back in good state
210
210
  // TODO: Replace this with rollback
211
- await uninstallApp({ projectId });
212
- this.shadowHandler.deleteProjectShadow(projectId);
211
+ try {
212
+ await uninstallApp({ projectId });
213
+ } finally {
214
+ this.shadowHandler.clearAppConfig(projectId);
213
215
 
214
- await this.cmdStatusMgr.stop(projectId);
215
- await this.liveUpdatesHandler.disableAppInstallStatus({
216
- projectId
217
- });
218
- // Send final status message
219
- this.publisher.publishToClient(
220
- await getAppInstallStatusMessage(
221
- keyMirrors.appInstallStatus.failure,
222
- message,
223
- appReleaseHash
224
- )
225
- );
216
+ await this.cmdStatusMgr.stop(projectId);
217
+ await this.liveUpdatesHandler.disableAppInstallStatus({
218
+ projectId
219
+ });
220
+ // Send final status message
221
+ this.publisher.publishToClient(
222
+ await getAppInstallStatusMessage(
223
+ keyMirrors.appInstallStatus.failure,
224
+ message,
225
+ appReleaseHash
226
+ )
227
+ );
228
+ }
226
229
  }
227
230
  }
228
231
 
@@ -298,7 +301,10 @@ export class DeviceAgentCloudConnection {
298
301
 
299
302
  this.subscribe(this.toDeviceTopic);
300
303
  this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
304
+ this.subscribe(this.shadowHandler.shadowTopics.projects.getRejected);
301
305
  this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
306
+ this.subscribe(this.shadowHandler.shadowTopics.projects.updateAccepted);
307
+ this.subscribe(this.shadowHandler.shadowTopics.projects.updateRejected);
302
308
  }
303
309
 
304
310
  public getClientId(): string {
@@ -419,11 +425,17 @@ export class DeviceAgentCloudConnection {
419
425
  case this.shadowHandler.shadowTopics.projects.updateDelta: {
420
426
  const appConfigUpdates = await this.shadowHandler.handleShadowTopic({
421
427
  topic,
422
- payload: message.state
428
+ payload: message.state,
429
+ clientToken: message.clientToken
423
430
  });
424
431
  await this.handleAppConfigUpdates(appConfigUpdates);
425
432
  break;
426
433
  }
434
+ case this.shadowHandler.shadowTopics.projects.getRejected:
435
+ case this.shadowHandler.shadowTopics.projects.updateAccepted:
436
+ case this.shadowHandler.shadowTopics.projects.updateRejected:
437
+ // Not handling these for now
438
+ break;
427
439
  case this.toDeviceTopic:
428
440
  await this.handleClientMessage({
429
441
  topic,
@@ -461,8 +473,8 @@ export class DeviceAgentCloudConnection {
461
473
  try {
462
474
  const jsonPacket = JSON.parse(payload);
463
475
  await this.handleMessage(topic, jsonPacket);
464
- } catch (error) {
465
- logger.error(`Error parsing message: ${error}`);
476
+ } catch (e) {
477
+ logger.error(`Error parsing message: ${e.message}`);
466
478
  }
467
479
  });
468
480
 
@@ -132,16 +132,15 @@ export class LiveUpdatesHandler {
132
132
  while (true) {
133
133
  try {
134
134
  const message = await getMessageData(...args);
135
+ if (!this.continuePublishing(messageType, projectId)) {
136
+ logger.info(`Turned off live updates for ${messageType}`);
137
+ break;
138
+ }
135
139
  this.publisher.publishToClient(message);
136
140
  } catch (e) {
137
141
  logger.error(
138
142
  `Error publishing live updates for ${messageType}: ${e.message}`
139
143
  );
140
- break;
141
- }
142
- if (!this.continuePublishing(messageType, projectId)) {
143
- logger.info(`Turned off live updates for ${messageType}`);
144
- break;
145
144
  }
146
145
  await sleep(this.getLiveUpdatesInterval(messageType));
147
146
  }
@@ -161,14 +160,16 @@ export class LiveUpdatesHandler {
161
160
  }) {
162
161
  const { projectId, appReleaseHash } = props;
163
162
  this.liveUpdatesAlive.app_install_status = true;
164
- this.appInstallStatuses.add(projectId);
165
- // Don't wait for this call to finish since it loops until disabled
166
- void this.startPublishingLiveUpdates(
167
- keyMirrors.agentMessageType.app_install_status,
168
- getAppInstallStatusMessage,
169
- [keyMirrors.appInstallStatus.in_progress, '', appReleaseHash],
170
- projectId
171
- );
163
+ if (!this.appInstallStatuses.has(projectId)) {
164
+ this.appInstallStatuses.add(projectId);
165
+ // Don't wait for this call to finish since it loops until disabled
166
+ void this.startPublishingLiveUpdates(
167
+ keyMirrors.agentMessageType.app_install_status,
168
+ getAppInstallStatusMessage,
169
+ [keyMirrors.appInstallStatus.in_progress, '', appReleaseHash],
170
+ projectId
171
+ );
172
+ }
172
173
  }
173
174
 
174
175
  public async disableAppInstallStatus(props: { projectId: string }) {
@@ -24,7 +24,8 @@ describe('Test Shadow Handler', () => {
24
24
  expect(async () => {
25
25
  await shadowHandler.handleShadowTopic({
26
26
  topic: shadowHandler.shadowTopics.projects.updateDelta,
27
- payload: Buffer.from('test-payload')
27
+ payload: Buffer.from('test-payload'),
28
+ clientToken: ''
28
29
  });
29
30
  }).toThrow(Error);
30
31
  });
@@ -51,13 +52,13 @@ describe('Test Shadow Handler', () => {
51
52
  const payload = {
52
53
  [projectId1]: {
53
54
  appConfig: JSON.stringify(appCfg1)
54
- },
55
- clientToken: clientId
55
+ }
56
56
  };
57
57
 
58
58
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
59
59
  topic: shadowHandler.shadowTopics.projects.updateDelta,
60
- payload
60
+ payload,
61
+ clientToken: clientId
61
62
  });
62
63
  expect(appCfgUpdates.length).toBe(0);
63
64
  });
@@ -92,7 +93,8 @@ describe('Test Shadow Handler', () => {
92
93
 
93
94
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
94
95
  topic: shadowHandler.shadowTopics.projects.getAccepted,
95
- payload
96
+ payload,
97
+ clientToken: ''
96
98
  });
97
99
  expect(appCfgUpdates.length).toBe(1);
98
100
  expect(appCfgUpdates[0]).toEqual({
@@ -121,7 +123,8 @@ describe('Test Shadow Handler', () => {
121
123
 
122
124
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
123
125
  topic: shadowHandler.shadowTopics.projects.updateDelta,
124
- payload
126
+ payload,
127
+ clientToken: ''
125
128
  });
126
129
  expect(appCfgUpdates.length).toBe(0);
127
130
  });
@@ -155,7 +158,8 @@ describe('Test Shadow Handler', () => {
155
158
 
156
159
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
157
160
  topic: shadowHandler.shadowTopics.projects.updateDelta,
158
- payload
161
+ payload,
162
+ clientToken: ''
159
163
  });
160
164
  expect(appCfgUpdates.length).toBe(1);
161
165
  expect(appCfgUpdates[0]).toEqual({
@@ -217,7 +221,8 @@ describe('Test Shadow Handler', () => {
217
221
 
218
222
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
219
223
  topic: shadowHandler.shadowTopics.projects.updateDelta,
220
- payload
224
+ payload,
225
+ clientToken: ''
221
226
  });
222
227
  expect(appCfgUpdates.length).toBe(2);
223
228
  expect(appCfgUpdates[0]).toEqual({
@@ -264,7 +269,8 @@ describe('Test Shadow Handler', () => {
264
269
 
265
270
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
266
271
  topic: shadowHandler.shadowTopics.projects.updateDelta,
267
- payload
272
+ payload,
273
+ clientToken: ''
268
274
  });
269
275
  expect(appCfgUpdates.length).toBe(1);
270
276
  expect(appCfgUpdates[0]).toEqual({
@@ -302,12 +308,13 @@ describe('Test Shadow Handler', () => {
302
308
 
303
309
  const appCfgUpdates = await shadowHandler.handleShadowTopic({
304
310
  topic: shadowHandler.shadowTopics.projects.updateDelta,
305
- payload
311
+ payload,
312
+ clientToken: ''
306
313
  });
307
314
  expect(appCfgUpdates.length).toBe(0);
308
315
  });
309
316
 
310
- test.skip('publish app state', async () => {
317
+ test.skip('publish app config', async () => {
311
318
  // FIXME: For some reason publisher is not being called...
312
319
  const testAppCfg: AppConfig = {
313
320
  scripts: {
@@ -317,7 +324,7 @@ describe('Test Shadow Handler', () => {
317
324
  };
318
325
  jest.mocked(readAppCfgFile).mockResolvedValue(testAppCfg);
319
326
 
320
- await shadowHandler.publishAppState(projectId1);
327
+ await shadowHandler.publishAppConfig(projectId1);
321
328
  expect(jest.mocked(readAppCfgFile)).toBeCalledWith({ projectId1 });
322
329
  const packet = {
323
330
  state: {
@@ -343,10 +350,13 @@ describe('Test Shadow Handler', () => {
343
350
  );
344
351
  });
345
352
 
346
- test('delete project shadow', async () => {
347
- shadowHandler.deleteProjectShadow(projectId1);
353
+ test('clear project shadow', async () => {
354
+ shadowHandler.clearAppConfig(projectId1);
348
355
  const packet = {
349
356
  state: {
357
+ desired: {
358
+ [projectId1]: null
359
+ },
350
360
  reported: {
351
361
  [projectId1]: null
352
362
  }
@@ -9,10 +9,13 @@ import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
9
9
 
10
10
  export interface ShadowTopics {
11
11
  projects: {
12
- update: string;
13
12
  get: string;
14
- updateDelta: string;
15
13
  getAccepted: string;
14
+ getRejected: string;
15
+ update: string;
16
+ updateDelta: string;
17
+ updateAccepted: string;
18
+ updateRejected: string;
16
19
  delete: string;
17
20
  };
18
21
  }
@@ -35,10 +38,13 @@ export class ShadowHandler {
35
38
  this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
36
39
  this.shadowTopics = {
37
40
  projects: {
38
- update: `${this.shadowPrefix}projects/update`,
39
41
  get: `${this.shadowPrefix}projects/get`,
40
- updateDelta: `${this.shadowPrefix}projects/update/delta`,
41
42
  getAccepted: `${this.shadowPrefix}projects/get/accepted`,
43
+ getRejected: `${this.shadowPrefix}projects/get/rejected`,
44
+ update: `${this.shadowPrefix}projects/update`,
45
+ updateDelta: `${this.shadowPrefix}projects/update/delta`,
46
+ updateAccepted: `${this.shadowPrefix}projects/update/accepted`,
47
+ updateRejected: `${this.shadowPrefix}projects/update/rejected`,
42
48
  delete: `${this.shadowPrefix}projects/delete`
43
49
  }
44
50
  };
@@ -92,16 +98,18 @@ export class ShadowHandler {
92
98
 
93
99
  public async handleShadowTopic({
94
100
  topic,
95
- payload
101
+ payload,
102
+ clientToken
96
103
  }: {
97
104
  topic: string;
98
105
  payload: any;
106
+ clientToken: string;
99
107
  }): Promise<AppConfigUpdate[]> {
100
108
  // TODO: make use a function like the other topic getters
101
109
  const shadowName = topic.split('/')[5];
102
110
  switch (topic) {
103
111
  case this.shadowTopics.projects.updateDelta:
104
- if (payload.clientToken === this.clientId) {
112
+ if (clientToken === this.clientId) {
105
113
  logger.debug(
106
114
  `Ignoring message sent from self: ${JSON.stringify(
107
115
  { topic, payload },
@@ -133,7 +141,7 @@ export class ShadowHandler {
133
141
  return [];
134
142
  }
135
143
 
136
- public async publishAppState(projectId: string) {
144
+ public async publishAppConfig(projectId: string) {
137
145
  const appCfg = await readAppCfgFile({ projectId });
138
146
  const packet = {
139
147
  state: {
@@ -143,33 +151,42 @@ export class ShadowHandler {
143
151
  },
144
152
  clientToken: this.clientId
145
153
  };
146
- this.publisher.publish(
147
- this.shadowTopics.projects.update,
148
- JSON.stringify(packet)
154
+ const topic = this.shadowTopics.projects.update;
155
+ logger.debug(
156
+ `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
149
157
  );
158
+ this.publisher.publish(topic, JSON.stringify(packet));
150
159
  }
151
160
 
152
161
  public getShadowUpdates() {
153
- this.publisher.publish(
154
- this.shadowTopics.projects.get,
155
- JSON.stringify({
156
- clientToken: this.clientId
157
- })
162
+ const topic = this.shadowTopics.projects.get;
163
+ const packet = {
164
+ clientToken: this.clientId
165
+ };
166
+ logger.debug(
167
+ `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
158
168
  );
169
+ this.publisher.publish(topic, JSON.stringify(packet));
159
170
  }
160
171
 
161
- public deleteProjectShadow(projectId: string) {
172
+ public clearAppConfig(projectId: string) {
173
+ const topic = this.shadowTopics.projects.update;
174
+ // TODO: We should actually send only desired and handle the delta
175
+ // to update reported
162
176
  const packet = {
163
177
  state: {
178
+ desired: {
179
+ [projectId]: null
180
+ },
164
181
  reported: {
165
182
  [projectId]: null
166
183
  }
167
184
  },
168
185
  clientToken: this.clientId
169
186
  };
170
- this.publisher.publish(
171
- this.shadowTopics.projects.update,
172
- JSON.stringify(packet)
187
+ logger.debug(
188
+ `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
173
189
  );
190
+ this.publisher.publish(topic, JSON.stringify(packet));
174
191
  }
175
192
  }