@alwaysai/device-agent 0.0.14 → 0.0.16

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 (85) hide show
  1. package/lib/application-control/install.d.ts.map +1 -1
  2. package/lib/application-control/install.js +1 -0
  3. package/lib/application-control/install.js.map +1 -1
  4. package/lib/application-control/status.d.ts.map +1 -1
  5. package/lib/application-control/status.js +8 -13
  6. package/lib/application-control/status.js.map +1 -1
  7. package/lib/cloud-connection/bootstrap-provision.d.ts +1 -1
  8. package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -1
  9. package/lib/cloud-connection/bootstrap-provision.js +9 -9
  10. package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
  11. package/lib/cloud-connection/cmd-status.d.ts +3 -11
  12. package/lib/cloud-connection/cmd-status.d.ts.map +1 -1
  13. package/lib/cloud-connection/cmd-status.js +24 -11
  14. package/lib/cloud-connection/cmd-status.js.map +1 -1
  15. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -3
  16. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  17. package/lib/cloud-connection/device-agent-cloud-connection.js +93 -43
  18. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  19. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  20. package/lib/cloud-connection/device-agent.js +4 -3
  21. package/lib/cloud-connection/device-agent.js.map +1 -1
  22. package/lib/cloud-connection/live-updates-handler.d.ts +10 -18
  23. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  24. package/lib/cloud-connection/live-updates-handler.js +50 -50
  25. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  26. package/lib/cloud-connection/messages.d.ts +3 -1
  27. package/lib/cloud-connection/messages.d.ts.map +1 -1
  28. package/lib/cloud-connection/messages.js +13 -1
  29. package/lib/cloud-connection/messages.js.map +1 -1
  30. package/lib/cloud-connection/passthrough-handler.js +1 -1
  31. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  32. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  33. package/lib/cloud-connection/shadow-handler.js +11 -4
  34. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  35. package/lib/cloud-connection/shadow-handler.test.js +3 -3
  36. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  37. package/lib/environment.d.ts.map +1 -1
  38. package/lib/environment.js +1 -1
  39. package/lib/environment.js.map +1 -1
  40. package/lib/index.js +5 -3
  41. package/lib/index.js.map +1 -1
  42. package/lib/subcommands/app/app.d.ts +2 -2
  43. package/lib/subcommands/app/app.d.ts.map +1 -1
  44. package/lib/subcommands/app/app.js +28 -10
  45. package/lib/subcommands/app/app.js.map +1 -1
  46. package/lib/subcommands/app/index.js +2 -2
  47. package/lib/subcommands/device/device.d.ts.map +1 -1
  48. package/lib/subcommands/device/device.js +2 -1
  49. package/lib/subcommands/device/device.js.map +1 -1
  50. package/lib/util/directories.d.ts +11 -12
  51. package/lib/util/directories.d.ts.map +1 -1
  52. package/lib/util/directories.js +24 -29
  53. package/lib/util/directories.js.map +1 -1
  54. package/lib/util/logger.js +1 -0
  55. package/lib/util/logger.js.map +1 -1
  56. package/package.json +1 -1
  57. package/readme.md +3 -3
  58. package/src/application-control/install.ts +3 -1
  59. package/src/application-control/status.ts +10 -16
  60. package/src/cloud-connection/bootstrap-provision.ts +13 -10
  61. package/src/cloud-connection/cmd-status.ts +30 -11
  62. package/src/cloud-connection/device-agent-cloud-connection.ts +128 -66
  63. package/src/cloud-connection/device-agent.ts +7 -4
  64. package/src/cloud-connection/live-updates-handler.ts +79 -86
  65. package/src/cloud-connection/messages.ts +22 -1
  66. package/src/cloud-connection/passthrough-handler.ts +1 -1
  67. package/src/cloud-connection/shadow-handler.test.ts +3 -3
  68. package/src/cloud-connection/shadow-handler.ts +15 -11
  69. package/src/environment.ts +1 -1
  70. package/src/index.ts +6 -3
  71. package/src/subcommands/app/app.ts +27 -10
  72. package/src/subcommands/app/index.ts +4 -4
  73. package/src/subcommands/device/device.ts +5 -2
  74. package/src/util/directories.ts +31 -29
  75. package/src/util/logger.ts +2 -0
  76. package/lib/cloud-connection/app-install-status.d.ts +0 -16
  77. package/lib/cloud-connection/app-install-status.d.ts.map +0 -1
  78. package/lib/cloud-connection/app-install-status.js +0 -53
  79. package/lib/cloud-connection/app-install-status.js.map +0 -1
  80. package/lib/util/timer.d.ts +0 -2
  81. package/lib/util/timer.d.ts.map +0 -1
  82. package/lib/util/timer.js +0 -6
  83. package/lib/util/timer.js.map +0 -1
  84. package/src/cloud-connection/app-install-status.ts +0 -62
  85. package/src/util/timer.ts +0 -1
@@ -3,11 +3,11 @@ const awsIot = require('aws-iot-device-sdk');
3
3
  import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
4
4
  import { existsSync } from 'fs';
5
5
  import {
6
- getPrivateKeyFilePath,
7
- getCertificateFilePath,
8
- BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
6
+ BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
9
7
  AWS_ROOT_CERTIFICATE_FILE_PATH,
10
- BOOTSTRAP_CERTIFICATES_DIR_PATH
8
+ BOOTSTRAP_CERTIFICATES_DIR_PATH,
9
+ DEVICE_PRIVATE_KEY_FILE_PATH,
10
+ DEVICE_CERTIFICATE_FILE_PATH
11
11
  } from '../util/directories';
12
12
  import {
13
13
  keyMirrors,
@@ -35,18 +35,17 @@ import { AppConfigUpdate, ShadowHandler, ShadowTopics } from './shadow-handler';
35
35
  import { Publisher } from './publisher';
36
36
  import { LiveUpdatesHandler } from './live-updates-handler';
37
37
  import { bootstrapProvision } from './bootstrap-provision';
38
- import { AppInstallStatusManager } from './app-install-status';
39
38
  import { AppConfig } from '@alwaysai/app-configuration-schemas';
40
- import { CmdStatusManager, CmdStatusType } from './cmd-status';
39
+ import { CmdStatusManager } from './cmd-status';
41
40
  import { PassthroughHandler, runChannel } from './passthrough-handler';
42
41
  import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
42
+ import { getAppInstallStatusMessage } from './messages';
43
43
 
44
44
  export class DeviceAgentCloudConnection {
45
45
  private shadowHandler: ShadowHandler;
46
46
  public publisher: Publisher;
47
47
  private cmdStatusMgr: CmdStatusManager;
48
48
  private liveUpdatesHandler: LiveUpdatesHandler;
49
- private appInstallStatusMgr: AppInstallStatusManager;
50
49
  private device = awsIot.device;
51
50
 
52
51
  private clientId = getDeviceUuid();
@@ -55,33 +54,43 @@ export class DeviceAgentCloudConnection {
55
54
  // FIXME: Add support for multiple simultaneous project updates
56
55
  private newAppCfgQueue: AppConfig[] = [];
57
56
 
58
- private handleAppStateControl(payload: AppStateControlPacket) {
57
+ private async handleAppStateControl(payload: AppStateControlPacket) {
59
58
  const { baseCommand, projectId } = payload;
60
59
  switch (baseCommand) {
61
60
  case keyMirrors.appStateControl.start:
62
- startApp({ projectId });
61
+ await startApp({ projectId });
63
62
  break;
64
63
  case keyMirrors.appStateControl.stop:
65
- stopApp({ projectId });
64
+ await stopApp({ projectId });
66
65
  break;
67
66
  case keyMirrors.appStateControl.restart:
68
- restartApp({ projectId });
67
+ await restartApp({ projectId });
69
68
  break;
70
69
  }
71
70
  }
72
71
 
73
- private handleAppVersionControl(payload: AppVersionControlPacket) {
72
+ private async handleAppVersionControl(payload: AppVersionControlPacket) {
74
73
  switch (payload.baseCommand) {
75
74
  case keyMirrors.appVersionControl.install: {
76
75
  const { projectId, appReleaseHash } = payload;
77
- this.cmdStatusMgr.update(projectId, 'in_progress');
76
+ await this.cmdStatusMgr.start(projectId);
77
+ await this.liveUpdatesHandler.enableAppInstallStatus({
78
+ projectId,
79
+ appReleaseHash
80
+ });
81
+
78
82
  const signedUrlsRequest = { projectId, appReleaseHash };
79
- this.publishCloudRequest({
83
+ await this.publishCloudRequest({
80
84
  messageType: keyMirrors.agentMessageType.signed_urls_request,
81
85
  signedUrlsRequest
82
86
  });
83
87
  break;
84
88
  }
89
+ case keyMirrors.appVersionControl.uninstall: {
90
+ const { projectId } = payload;
91
+ await this.atomicApplicationUninstall(projectId);
92
+ break;
93
+ }
85
94
  default:
86
95
  logger.warn(
87
96
  `Ignore App Version Control packet: ${JSON.stringify(
@@ -97,10 +106,10 @@ export class DeviceAgentCloudConnection {
97
106
  // TODO
98
107
  };
99
108
 
100
- private handleAgentCommand(message: LiveUpdatesToggleMessage) {
109
+ private async handleAgentCommand(message: LiveUpdatesToggleMessage) {
101
110
  switch (message.messageType) {
102
111
  case keyMirrors.clientMessageType.live_state_updates:
103
- this.liveUpdatesHandler.update(message.liveUpdatesToggles);
112
+ await this.liveUpdatesHandler.handleToggles(message.liveUpdatesToggles);
104
113
  break;
105
114
  default:
106
115
  logger.error(
@@ -118,6 +127,49 @@ export class DeviceAgentCloudConnection {
118
127
  this.device.subscribe(topic);
119
128
  }
120
129
 
130
+ private async atomicApplicationUninstall(projectId: string) {
131
+ const appReleaseHash = await AgentConfigFile().getAppVersion({
132
+ projectId
133
+ });
134
+ await this.cmdStatusMgr.start(projectId);
135
+ await this.liveUpdatesHandler.enableAppInstallStatus({
136
+ projectId,
137
+ appReleaseHash
138
+ });
139
+ try {
140
+ await uninstallApp({ projectId });
141
+ this.shadowHandler.deleteProjectShadow(projectId);
142
+
143
+ await this.cmdStatusMgr.stop(projectId);
144
+ await this.liveUpdatesHandler.disableAppInstallStatus({
145
+ projectId
146
+ });
147
+ // Send final status message
148
+ this.publisher.publishToClient(
149
+ await getAppInstallStatusMessage(
150
+ keyMirrors.appInstallStatus.success,
151
+ '',
152
+ appReleaseHash
153
+ )
154
+ );
155
+ } catch (e) {
156
+ logger.error(`Failed to uninstall ${projectId}: ${e}`);
157
+ const message: string = e.message;
158
+ await this.cmdStatusMgr.stop(projectId);
159
+ await this.liveUpdatesHandler.disableAppInstallStatus({
160
+ projectId
161
+ });
162
+ // Send final status message
163
+ this.publisher.publishToClient(
164
+ await getAppInstallStatusMessage(
165
+ keyMirrors.appInstallStatus.failure,
166
+ message,
167
+ appReleaseHash
168
+ )
169
+ );
170
+ }
171
+ }
172
+
121
173
  // eslint-disable-next-line
122
174
  private async atomicApplicationUpdate<T extends any[], R extends any>(
123
175
  func: (...args: T) => R,
@@ -125,55 +177,67 @@ export class DeviceAgentCloudConnection {
125
177
  projectId: string,
126
178
  appReleaseHash: string
127
179
  ) {
128
- this.appInstallStatusMgr.update(
129
- appReleaseHash,
130
- keyMirrors.appInstallStatus.in_progress
131
- );
132
-
133
- this.liveUpdatesHandler.update({
134
- appInstallStatus: { toggle: true, appReleaseHash }
180
+ await this.cmdStatusMgr.start(projectId);
181
+ await this.liveUpdatesHandler.enableAppInstallStatus({
182
+ projectId,
183
+ appReleaseHash
135
184
  });
136
185
 
137
186
  // Install the app and models
138
187
  try {
139
188
  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 }
189
+ await this.cmdStatusMgr.stop(projectId);
190
+ await this.liveUpdatesHandler.disableAppInstallStatus({
191
+ projectId
146
192
  });
147
- this.cmdStatusMgr.update(projectId, 'idle');
193
+ // Send final status message
194
+ this.publisher.publishToClient(
195
+ await getAppInstallStatusMessage(
196
+ keyMirrors.appInstallStatus.success,
197
+ '',
198
+ appReleaseHash
199
+ )
200
+ );
148
201
 
149
202
  // update app config shadow for project
150
203
  await this.shadowHandler.publishAppState(projectId);
151
204
  return out;
152
205
  } catch (e) {
153
- logger.error(e);
206
+ logger.error(`Failed to install ${projectId}: ${e}`);
154
207
  const message: string = e.message;
155
208
 
156
209
  // uninstall the failed app to put system back in good state
210
+ // TODO: Replace this with rollback
157
211
  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
212
  this.shadowHandler.deleteProjectShadow(projectId);
213
+
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
+ );
170
226
  }
171
227
  }
172
228
 
173
- private async handleAppConfigUpdates(appConfgUpdates: AppConfigUpdate[]) {
174
- for (const appConfigUpdate of appConfgUpdates) {
229
+ private async handleAppConfigUpdates(appCfgUpdates: AppConfigUpdate[]) {
230
+ for (const appConfigUpdate of appCfgUpdates) {
175
231
  const { projectId, newAppCfg, updatedModels } = appConfigUpdate;
176
- this.cmdStatusMgr.update(projectId, 'in_progress');
232
+ const appReleaseHash = await AgentConfigFile().getAppVersion({
233
+ projectId
234
+ });
235
+ await this.cmdStatusMgr.start(projectId);
236
+ await this.liveUpdatesHandler.enableAppInstallStatus({
237
+ projectId,
238
+ appReleaseHash
239
+ });
240
+
177
241
  if (updatedModels && Object.keys(updatedModels).length) {
178
242
  // Publish request for model urls
179
243
  this.newAppCfgQueue.push(newAppCfg);
@@ -213,12 +277,14 @@ export class DeviceAgentCloudConnection {
213
277
  }
214
278
  }
215
279
 
216
- // Public Methods
280
+ /*=================================================================
281
+ Public interface
282
+ =================================================================*/
217
283
 
218
284
  constructor() {
219
285
  this.device = awsIot.device({
220
- keyPath: getPrivateKeyFilePath(),
221
- certPath: getCertificateFilePath(),
286
+ keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
287
+ certPath: DEVICE_CERTIFICATE_FILE_PATH,
222
288
  caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
223
289
  clientId: this.clientId,
224
290
  host: this.host,
@@ -228,11 +294,7 @@ export class DeviceAgentCloudConnection {
228
294
  this.publisher = new Publisher(this.device, this.clientId);
229
295
  this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
230
296
  this.cmdStatusMgr = new CmdStatusManager();
231
- this.appInstallStatusMgr = new AppInstallStatusManager();
232
- this.liveUpdatesHandler = new LiveUpdatesHandler(
233
- this.publisher,
234
- this.appInstallStatusMgr
235
- );
297
+ this.liveUpdatesHandler = new LiveUpdatesHandler(this.publisher);
236
298
 
237
299
  this.subscribe(this.toDeviceTopic);
238
300
  this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
@@ -251,8 +313,8 @@ export class DeviceAgentCloudConnection {
251
313
  return this.shadowHandler.shadowTopics;
252
314
  }
253
315
 
254
- public getCmdStatus(projectId: string): CmdStatusType {
255
- return this.cmdStatusMgr.getAppCmdStatus(projectId);
316
+ public isCmdInProgress(projectId: string): boolean {
317
+ return this.cmdStatusMgr.isCmdInProgress(projectId);
256
318
  }
257
319
 
258
320
  public async handleClientMessage({
@@ -276,15 +338,15 @@ export class DeviceAgentCloudConnection {
276
338
  const payload = message.payload;
277
339
  switch (payload.messageType) {
278
340
  case keyMirrors.clientMessageType.app_state_control: {
279
- this.handleAppStateControl(payload.appStateControl);
341
+ await this.handleAppStateControl(payload.appStateControl);
280
342
  break;
281
343
  }
282
344
  case keyMirrors.clientMessageType.app_version_control: {
283
- this.handleAppVersionControl(payload.appVersionControl);
345
+ await this.handleAppVersionControl(payload.appVersionControl);
284
346
  break;
285
347
  }
286
348
  case keyMirrors.clientMessageType.live_state_updates: {
287
- this.handleAgentCommand(payload);
349
+ await this.handleAgentCommand(payload);
288
350
  break;
289
351
  }
290
352
  case keyMirrors.clientMessageType.app_install_cloud_response: {
@@ -349,6 +411,9 @@ export class DeviceAgentCloudConnection {
349
411
  }
350
412
 
351
413
  public async handleMessage(topic: string, message: ClientMessage | any) {
414
+ logger.debug(
415
+ `Received message: ${JSON.stringify({ topic, message }, null, 2)}`
416
+ );
352
417
  switch (topic) {
353
418
  case this.shadowHandler.shadowTopics.projects.getAccepted:
354
419
  case this.shadowHandler.shadowTopics.projects.updateDelta: {
@@ -360,7 +425,7 @@ export class DeviceAgentCloudConnection {
360
425
  break;
361
426
  }
362
427
  case this.toDeviceTopic:
363
- this.handleClientMessage({
428
+ await this.handleClientMessage({
364
429
  topic,
365
430
  message
366
431
  });
@@ -395,9 +460,6 @@ export class DeviceAgentCloudConnection {
395
460
  this.device.on('message', async (topic: string, payload: string) => {
396
461
  try {
397
462
  const jsonPacket = JSON.parse(payload);
398
- logger.debug(
399
- `Received message: ${JSON.stringify({ topic, jsonPacket }, null, 2)}`
400
- );
401
463
  await this.handleMessage(topic, jsonPacket);
402
464
  } catch (error) {
403
465
  logger.error(`Error parsing message: ${error}`);
@@ -416,17 +478,17 @@ export class DeviceAgentCloudConnection {
416
478
 
417
479
  export async function runDeviceAgentCloudInterface() {
418
480
  // FIXME: Check for KeyPath as well
419
- if (existsSync(getCertificateFilePath())) {
481
+ if (existsSync(DEVICE_CERTIFICATE_FILE_PATH)) {
420
482
  const deviceAgent = new DeviceAgentCloudConnection();
421
483
  await deviceAgent.setupHandlers();
422
484
  if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
423
485
  const publisher = deviceAgent.publisher;
424
486
  const passthroughHandler = new PassthroughHandler(publisher);
425
487
  await passthroughHandler.setup();
426
- runChannel(passthroughHandler);
488
+ await runChannel(passthroughHandler);
427
489
  }
428
- } else if (existsSync(BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH())) {
429
- bootstrapProvision();
490
+ } else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
491
+ await bootstrapProvision();
430
492
  } else if (existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH())) {
431
493
  throw new Error(
432
494
  "Device has not been created using 'aai-agent device init' or there has been an issue with device initialization"
@@ -3,7 +3,7 @@ const awsIot = require('aws-iot-device-sdk');
3
3
  import {
4
4
  DEVICE_PRIVATE_KEY_FILE_NAME,
5
5
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
6
- DEVICE_CLAIM_ID_FILE_NAME,
6
+ DEVICE_ID_FILE_NAME,
7
7
  DEVICE_CERTIFICATE_FILE_NAME
8
8
  } from '../util/directories';
9
9
  import { getDeviceUuid } from '../util/get-device-id';
@@ -66,6 +66,9 @@ export class BootstrapAgent extends DeviceAgent {
66
66
  public async handleAwsCertificateTopics(topic: string, payload: any) {
67
67
  switch (topic) {
68
68
  case '$aws/certificates/create/json/accepted': {
69
+ logger.debug(
70
+ `Received Certs: ${JSON.stringify(JSON.parse(payload), null, 2)}`
71
+ );
69
72
  const {
70
73
  certificateId,
71
74
  certificatePem,
@@ -76,13 +79,13 @@ export class BootstrapAgent extends DeviceAgent {
76
79
  const certSpawner = JsSpawner({ path: LOCAL_CERT_AND_KEY_DIR });
77
80
 
78
81
  await certSpawner.writeFile(
79
- DEVICE_CERTIFICATE_FILE_NAME(),
82
+ DEVICE_CERTIFICATE_FILE_NAME,
80
83
  certificatePem
81
84
  );
82
85
 
83
- await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME(), privateKey);
86
+ await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME, privateKey);
84
87
 
85
- await certSpawner.writeFile(DEVICE_CLAIM_ID_FILE_NAME(), certificateId);
88
+ await certSpawner.writeFile(DEVICE_ID_FILE_NAME, certificateId);
86
89
 
87
90
  await certSpawner.writeFile(
88
91
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
@@ -8,13 +8,15 @@ import { getAppLogs } from '../application-control';
8
8
  import { logger } from '../util/logger';
9
9
  import sleep from '../util/sleep';
10
10
  import { Publisher } from './publisher';
11
- import { getAppStateMessage, getDeviceStatsMessage } from './messages';
11
+ import {
12
+ getAppInstallStatusMessage,
13
+ getAppStateMessage,
14
+ getDeviceStatsMessage
15
+ } from './messages';
12
16
  import { AgentMessageTypeValue } from '@alwaysai/device-agent-schemas/lib/constants';
13
- import { AppInstallStatusManager } from './app-install-status';
14
17
 
15
18
  export class LiveUpdatesHandler {
16
19
  private publisher: Publisher;
17
- private appInstallStatusMgr: AppInstallStatusManager;
18
20
 
19
21
  private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
20
22
  private liveUpdatesAlive = {
@@ -32,14 +34,6 @@ export class LiveUpdatesHandler {
32
34
  private appLogStreams = new Set<string>();
33
35
  private appInstallStatuses = new Set<string>();
34
36
 
35
- constructor(
36
- publisher: Publisher,
37
- appInstallStatusMgr: AppInstallStatusManager
38
- ) {
39
- this.publisher = publisher;
40
- this.appInstallStatusMgr = appInstallStatusMgr;
41
- }
42
-
43
37
  private async startAppLogStream(projectId: string) {
44
38
  logger.info(`Starting log stream for ${projectId}`);
45
39
  this.appLogStreams.add(projectId);
@@ -77,50 +71,19 @@ export class LiveUpdatesHandler {
77
71
  });
78
72
  }
79
73
 
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 {
74
+ private continuePublishing(
75
+ flag: AgentMessageTypeValue,
76
+ projectId?: string
77
+ ): boolean {
117
78
  switch (flag) {
118
79
  case keyMirrors.agentMessageType.device_stats:
119
80
  case keyMirrors.agentMessageType.app_state:
120
81
  return this.liveUpdatesAlive[flag];
121
82
  case keyMirrors.agentMessageType.app_install_status: {
122
- const appReleaseHash: string = data;
123
- return this.appInstallStatuses.has(appReleaseHash);
83
+ if (!projectId) {
84
+ throw new Error(`Project ID not provided to continuePublishing!`);
85
+ }
86
+ return this.appInstallStatuses.has(projectId);
124
87
  }
125
88
  default:
126
89
  logger.error(`Unrecognized publishable flag ${flag}`);
@@ -158,28 +121,73 @@ export class LiveUpdatesHandler {
158
121
  }, 600000); // 10 min
159
122
  }
160
123
 
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
- };
124
+ private async startPublishingLiveUpdates<T extends any[]>(
125
+ messageType: AgentMessageTypeValue,
126
+ getMessageData: (...args: T) => Promise<DeviceAgentMessagePayload>,
127
+ args: T,
128
+ projectId?: string
129
+ ) {
130
+ logger.info(`Turned on live updates for ${messageType}`);
131
+ // eslint-disable-next-line no-constant-condition
132
+ while (true) {
133
+ try {
134
+ const message = await getMessageData(...args);
135
+ this.publisher.publishToClient(message);
136
+ } catch (e) {
137
+ logger.error(
138
+ `Error publishing live updates for ${messageType}: ${e.message}`
139
+ );
140
+ break;
141
+ }
142
+ if (!this.continuePublishing(messageType, projectId)) {
143
+ logger.info(`Turned off live updates for ${messageType}`);
144
+ break;
145
+ }
146
+ await sleep(this.getLiveUpdatesInterval(messageType));
147
+ }
148
+ }
149
+
150
+ /*=================================================================
151
+ Public interface
152
+ =================================================================*/
153
+
154
+ constructor(publisher: Publisher) {
155
+ this.publisher = publisher;
156
+ }
157
+
158
+ public async enableAppInstallStatus(props: {
159
+ projectId: string;
160
+ appReleaseHash: string;
177
161
  }) {
162
+ const { projectId, appReleaseHash } = props;
163
+ 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
+ );
172
+ }
173
+
174
+ public async disableAppInstallStatus(props: { projectId: string }) {
175
+ const { projectId } = props;
176
+ this.appInstallStatuses.delete(projectId);
177
+
178
+ if (this.appInstallStatuses.size === 0) {
179
+ this.liveUpdatesAlive.app_install_status = false;
180
+ }
181
+ }
182
+
183
+ public async handleToggles(toggles: LiveUpdatesToggles) {
184
+ const { deviceStats, appState, appLogs } = toggles;
178
185
  this.restartLiveUpdatesTimeout();
179
186
  if (deviceStats !== undefined) {
180
187
  this.liveUpdatesAlive.device_stats = deviceStats;
181
188
  if (deviceStats) {
182
- this.startPublishingLiveUpdates(
189
+ // Don't wait for this call to finish since it loops until disabled
190
+ void this.startPublishingLiveUpdates(
183
191
  keyMirrors.agentMessageType.device_stats,
184
192
  getDeviceStatsMessage,
185
193
  []
@@ -190,7 +198,8 @@ export class LiveUpdatesHandler {
190
198
  if (appState !== undefined) {
191
199
  this.liveUpdatesAlive.app_state = appState;
192
200
  if (appState) {
193
- this.startPublishingLiveUpdates(
201
+ // Don't wait for this call to finish since it loops until disabled
202
+ void this.startPublishingLiveUpdates(
194
203
  keyMirrors.agentMessageType.app_state,
195
204
  getAppStateMessage,
196
205
  []
@@ -200,27 +209,11 @@ export class LiveUpdatesHandler {
200
209
 
201
210
  if (appLogs !== undefined) {
202
211
  if (appLogs.toggle) {
203
- this.startAppLogStream(appLogs.projectId);
212
+ // Don't wait for this call to finish since it loops until disabled
213
+ void this.startAppLogStream(appLogs.projectId);
204
214
  } else {
205
215
  this.appLogStreams.delete(appLogs.projectId);
206
216
  }
207
217
  }
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
218
  }
226
219
  }
@@ -1,4 +1,9 @@
1
- import { AppStatePacket, keyMirrors } from '@alwaysai/device-agent-schemas';
1
+ import {
2
+ AppInstallStatusMessage,
3
+ AppStatePacket,
4
+ keyMirrors
5
+ } from '@alwaysai/device-agent-schemas';
6
+ import { AppInstallStatusValue } from '@alwaysai/device-agent-schemas/lib/constants';
2
7
  import { getAppStatus } from '../application-control';
3
8
  import {
4
9
  getCpuUtil,
@@ -22,6 +27,22 @@ export async function getAppStateMessage() {
22
27
  return appStatePackage;
23
28
  }
24
29
 
30
+ export async function getAppInstallStatusMessage(
31
+ status: AppInstallStatusValue,
32
+ message: string,
33
+ appReleaseHash: string
34
+ ): Promise<AppInstallStatusMessage> {
35
+ const appInstallStatusMessage: AppInstallStatusMessage = {
36
+ messageType: keyMirrors.agentMessageType.app_install_status,
37
+ appInstallStatus: {
38
+ status,
39
+ message,
40
+ appReleaseHash
41
+ }
42
+ };
43
+ return appInstallStatusMessage;
44
+ }
45
+
25
46
  export async function getDeviceStatsMessage() {
26
47
  const cpuUsage = await getCpuUtil();
27
48
  const diskUtil = await getDiskUtil();
@@ -29,7 +29,7 @@ export class PassthroughHandler {
29
29
  }
30
30
  }
31
31
 
32
- async function processPublish(passthroughHandler: PassthroughHandler) {
32
+ function processPublish(passthroughHandler: PassthroughHandler) {
33
33
  while (messageQueue.length > 0) {
34
34
  const entry = messageQueue.shift();
35
35
  const { packet, msg } = entry;
@@ -21,8 +21,8 @@ describe('Test Shadow Handler', () => {
21
21
 
22
22
  test.skip('reject buffer payload', async () => {
23
23
  //FIXME: Invalid input is silently ignored, need input validation
24
- expect(() => {
25
- shadowHandler.handleShadowTopic({
24
+ expect(async () => {
25
+ await shadowHandler.handleShadowTopic({
26
26
  topic: shadowHandler.shadowTopics.projects.updateDelta,
27
27
  payload: Buffer.from('test-payload')
28
28
  });
@@ -317,7 +317,7 @@ describe('Test Shadow Handler', () => {
317
317
  };
318
318
  jest.mocked(readAppCfgFile).mockResolvedValue(testAppCfg);
319
319
 
320
- shadowHandler.publishAppState(projectId1);
320
+ await shadowHandler.publishAppState(projectId1);
321
321
  expect(jest.mocked(readAppCfgFile)).toBeCalledWith({ projectId1 });
322
322
  const packet = {
323
323
  state: {