@alwaysai/device-agent 2.0.0 → 2.0.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/application-control/environment-variables.d.ts +4 -0
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/application-control/environment-variables.js +17 -13
  4. package/lib/application-control/environment-variables.js.map +1 -1
  5. package/lib/application-control/install.d.ts +4 -1
  6. package/lib/application-control/install.d.ts.map +1 -1
  7. package/lib/application-control/install.js +16 -1
  8. package/lib/application-control/install.js.map +1 -1
  9. package/lib/application-control/utils.d.ts.map +1 -1
  10. package/lib/application-control/utils.js +13 -0
  11. package/lib/application-control/utils.js.map +1 -1
  12. package/lib/cloud-connection/base-message-handler.d.ts +27 -0
  13. package/lib/cloud-connection/base-message-handler.d.ts.map +1 -0
  14. package/lib/cloud-connection/base-message-handler.js +72 -0
  15. package/lib/cloud-connection/base-message-handler.js.map +1 -0
  16. package/lib/cloud-connection/connection-manager.d.ts +20 -0
  17. package/lib/cloud-connection/connection-manager.d.ts.map +1 -0
  18. package/lib/cloud-connection/connection-manager.js +164 -0
  19. package/lib/cloud-connection/connection-manager.js.map +1 -0
  20. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +7 -23
  21. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  22. package/lib/cloud-connection/device-agent-cloud-connection.js +49 -517
  23. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  24. package/lib/cloud-connection/device-agent-message-handler.d.ts +22 -0
  25. package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -0
  26. package/lib/cloud-connection/device-agent-message-handler.js +357 -0
  27. package/lib/cloud-connection/device-agent-message-handler.js.map +1 -0
  28. package/lib/cloud-connection/live-updates-handler.d.ts +1 -0
  29. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  30. package/lib/cloud-connection/live-updates-handler.js +13 -10
  31. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  32. package/lib/cloud-connection/message-dispatcher.d.ts +10 -0
  33. package/lib/cloud-connection/message-dispatcher.d.ts.map +1 -0
  34. package/lib/cloud-connection/message-dispatcher.js +27 -0
  35. package/lib/cloud-connection/message-dispatcher.js.map +1 -0
  36. package/lib/cloud-connection/publisher.d.ts +3 -2
  37. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  38. package/lib/cloud-connection/publisher.js +8 -4
  39. package/lib/cloud-connection/publisher.js.map +1 -1
  40. package/lib/cloud-connection/shadow-handler.d.ts +7 -0
  41. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  42. package/lib/cloud-connection/shadow-handler.js +77 -1
  43. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  44. package/lib/cloud-connection/shadow-handler.test.js +5 -2
  45. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  46. package/lib/cloud-connection/transaction-manager.d.ts +9 -4
  47. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  48. package/lib/cloud-connection/transaction-manager.js +22 -11
  49. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  50. package/lib/cloud-connection/transaction-manager.test.js +43 -14
  51. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  52. package/lib/jobs/job-handler.d.ts +23 -0
  53. package/lib/jobs/job-handler.d.ts.map +1 -0
  54. package/lib/jobs/job-handler.js +131 -0
  55. package/lib/jobs/job-handler.js.map +1 -0
  56. package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts +8 -0
  57. package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts.map +1 -0
  58. package/lib/secure-tunneling/secure-tunnel-message-handler.js +42 -0
  59. package/lib/secure-tunneling/secure-tunnel-message-handler.js.map +1 -0
  60. package/lib/subcommands/app/version.d.ts +2 -0
  61. package/lib/subcommands/app/version.d.ts.map +1 -1
  62. package/lib/subcommands/app/version.js +14 -2
  63. package/lib/subcommands/app/version.js.map +1 -1
  64. package/package.json +2 -2
  65. package/src/application-control/environment-variables.ts +31 -21
  66. package/src/application-control/install.ts +24 -3
  67. package/src/application-control/utils.ts +13 -0
  68. package/src/cloud-connection/base-message-handler.ts +118 -0
  69. package/src/cloud-connection/connection-manager.ts +196 -0
  70. package/src/cloud-connection/device-agent-cloud-connection.ts +104 -817
  71. package/src/cloud-connection/device-agent-message-handler.ts +642 -0
  72. package/src/cloud-connection/live-updates-handler.ts +26 -18
  73. package/src/cloud-connection/message-dispatcher.ts +33 -0
  74. package/src/cloud-connection/publisher.ts +28 -23
  75. package/src/cloud-connection/shadow-handler.test.ts +6 -2
  76. package/src/cloud-connection/shadow-handler.ts +129 -1
  77. package/src/cloud-connection/transaction-manager.test.ts +55 -24
  78. package/src/cloud-connection/transaction-manager.ts +42 -31
  79. package/src/jobs/job-handler.ts +146 -0
  80. package/src/secure-tunneling/secure-tunnel-message-handler.ts +56 -0
  81. package/src/subcommands/app/version.ts +20 -2
@@ -0,0 +1,642 @@
1
+ import {
2
+ keyMirrors,
3
+ AppStateControlMessage,
4
+ buildToClientStatusResponseMessage,
5
+ AppStateControlPayload,
6
+ AppVersionControlMessage,
7
+ AppVersionControlInstallPayload,
8
+ AppVersionControlUninstallPayload,
9
+ SignedUrlsRequestPayload,
10
+ buildSignedUrlsRequestMessage,
11
+ ToCloudMessage,
12
+ LiveStateUpdatesToggleMessage,
13
+ buildDeviceStatsMessage,
14
+ buildAppStateMessage,
15
+ buildAppLogsMessage,
16
+ AppInstallResponsePayload,
17
+ ModelsInstallResponsePayload,
18
+ ToDeviceStatusResponseMessage,
19
+ DeviceActionPayload,
20
+ validateToDeviceAgentMessage,
21
+ ToDeviceAgentMessage,
22
+ EnvVars,
23
+ validateEnvVarSchemaShadowUpdate
24
+ } from '@alwaysai/device-agent-schemas';
25
+ import {
26
+ AppInstallResponseMessage,
27
+ ModelsInstallResponseMessage
28
+ } from '@alwaysai/device-agent-schemas/lib/app-action-schema';
29
+ import { DeviceActionMessage } from '@alwaysai/device-agent-schemas/lib/device-action-schema';
30
+ import { logger, stringifyError } from 'alwaysai/lib/util';
31
+ import {
32
+ startApp,
33
+ stopApp,
34
+ restartApp,
35
+ getAppLogs,
36
+ installApp,
37
+ updateModelsWithPresignedUrls
38
+ } from '../application-control';
39
+ import { reboot } from '../device-control/device-control';
40
+ import { MessageDispatcher, MessageHandler } from './message-dispatcher';
41
+ import { getDeviceStatsPayload, getAppStatePayload } from './messages';
42
+ import { BaseHandler, HandlerContext } from './base-message-handler';
43
+ import { ErrorFunction, SuccessFunction } from './transaction-manager';
44
+ import {
45
+ AppConfig,
46
+ validateAppConfig
47
+ } from '@alwaysai/app-configuration-schemas';
48
+
49
+ export type AppContent = {
50
+ projectId: string;
51
+ appCfg?: AppConfig;
52
+ envVars?: EnvVars;
53
+ };
54
+
55
+ export type AppContentUpdate = {
56
+ appCfg?: string;
57
+ envVars?: string;
58
+ };
59
+ export class DeviceAgentMessageHandler
60
+ extends MessageDispatcher<ToDeviceAgentMessage>
61
+ implements MessageHandler<any>
62
+ {
63
+ private readonly handlerContext: HandlerContext;
64
+ private readonly errorFn?: ErrorFunction;
65
+ private readonly successFn?: SuccessFunction;
66
+
67
+ constructor(
68
+ handlerContext: HandlerContext,
69
+ errorFn?: ErrorFunction,
70
+ successFn?: SuccessFunction
71
+ ) {
72
+ super();
73
+ this.handlerContext = handlerContext;
74
+ this.errorFn = errorFn;
75
+ this.successFn = successFn;
76
+
77
+ const handlerMapping: {
78
+ [key: string]: new (
79
+ context: HandlerContext,
80
+ errorFn?: ErrorFunction,
81
+ successFn?: SuccessFunction
82
+ ) => MessageHandler<ToDeviceAgentMessage>;
83
+ } = {
84
+ [keyMirrors.toDeviceAgentMessageType.app_state_control]:
85
+ AppStateMessageHandler,
86
+ [keyMirrors.toDeviceAgentMessageType.live_state_updates]:
87
+ LiveStateUpdatesMessageHandler,
88
+ [keyMirrors.toDeviceAgentMessageType.app_version_control]:
89
+ AppVersionControlMessageHandler,
90
+ [keyMirrors.toDeviceAgentMessageType.app_install_response]:
91
+ AppInstallResponseMessageHandler,
92
+ [keyMirrors.toDeviceAgentMessageType.models_install_response]:
93
+ ModelsInstallResponseMessageHandler,
94
+ [keyMirrors.toDeviceAgentMessageType.status_response]:
95
+ StatusResponseMessageHandler,
96
+ [keyMirrors.toDeviceAgentMessageType.device_action]:
97
+ DeviceActionMessageHandler
98
+ };
99
+
100
+ Object.entries(handlerMapping).forEach(([keyMirror, HandlerClass]) => {
101
+ this.registerHandler(
102
+ keyMirror,
103
+ new HandlerClass(this.handlerContext, this.errorFn, this.successFn)
104
+ );
105
+ });
106
+ }
107
+
108
+ public handle(message: any, topic?: string): void {
109
+ const valid = validateToDeviceAgentMessage(message);
110
+ if (!valid) {
111
+ logger.error(
112
+ `Error validating message: ${JSON.stringify(
113
+ { topic, message, errors: validateToDeviceAgentMessage.errors },
114
+ null,
115
+ 2
116
+ )}`
117
+ );
118
+ if (this.errorFn) {
119
+ this.errorFn(
120
+ '',
121
+ `Error validating message: ${JSON.stringify(
122
+ { topic, message, errors: validateToDeviceAgentMessage.errors },
123
+ null,
124
+ 2
125
+ )}`
126
+ );
127
+ }
128
+ return;
129
+ }
130
+ this.dispatch(message.messageType, message);
131
+ }
132
+ }
133
+
134
+ class AppStateMessageHandler
135
+ extends BaseHandler
136
+ implements MessageHandler<AppStateControlMessage>
137
+ {
138
+ public async handle(message: AppStateControlMessage): Promise<void> {
139
+ const payload = message.payload;
140
+ const projectId = payload.projectId;
141
+ const txId = message.txId;
142
+ try {
143
+ await this.txnMgr.runTransactionStep({
144
+ func: () => this.handleAppStateControl(message.payload),
145
+ projectId,
146
+ txId,
147
+ start: true,
148
+ liveUpdatesPublishFn: async () =>
149
+ this.publisher.publishToClient(
150
+ buildToClientStatusResponseMessage(
151
+ this.clientId,
152
+ { status: keyMirrors.statusResponse.in_progress },
153
+ txId
154
+ ),
155
+ logger.silly
156
+ ),
157
+ stepName: payload.baseCommand,
158
+ errorFn: this.errorFn,
159
+ successFn: this.successFn
160
+ });
161
+ } catch (e) {
162
+ logger.error(
163
+ `Error processing application state control request for ${projectId}!\n${stringifyError(
164
+ e
165
+ )}`
166
+ );
167
+ }
168
+ }
169
+
170
+ private handleAppStateControl = async (
171
+ payload: AppStateControlPayload
172
+ ): Promise<boolean> => {
173
+ const { baseCommand, projectId } = payload;
174
+ switch (baseCommand) {
175
+ case keyMirrors.appStateControl.start:
176
+ await startApp({ projectId });
177
+ break;
178
+ case keyMirrors.appStateControl.stop:
179
+ await stopApp({ projectId });
180
+ break;
181
+ case keyMirrors.appStateControl.restart:
182
+ await restartApp({ projectId });
183
+ break;
184
+ }
185
+ return true;
186
+ };
187
+ }
188
+
189
+ class AppVersionControlMessageHandler
190
+ extends BaseHandler
191
+ implements MessageHandler<AppVersionControlMessage>
192
+ {
193
+ public async handle(message: AppVersionControlMessage): Promise<void> {
194
+ const payload = message.payload;
195
+ const projectId = payload.projectId;
196
+ const txId = message.txId;
197
+ try {
198
+ await this.txnMgr.runTransactionStep({
199
+ func: () => this.handleAppVersionControl(payload, txId),
200
+ projectId,
201
+ txId,
202
+ start: true,
203
+ liveUpdatesPublishFn: async () =>
204
+ this.publisher.publishToClient(
205
+ buildToClientStatusResponseMessage(
206
+ this.clientId,
207
+ { status: keyMirrors.statusResponse.in_progress },
208
+ txId
209
+ ),
210
+ logger.silly
211
+ ),
212
+ stepName: payload.baseCommand,
213
+ errorFn: this.errorFn,
214
+ successFn: this.successFn
215
+ });
216
+ } catch (e) {
217
+ logger.error(
218
+ `Error processing application install request for ${projectId}!\n${stringifyError(
219
+ e
220
+ )}`
221
+ );
222
+ }
223
+ }
224
+
225
+ private handleAppVersionControl = async (
226
+ payload:
227
+ | AppVersionControlInstallPayload
228
+ | AppVersionControlUninstallPayload,
229
+ txId: string
230
+ ): Promise<boolean> => {
231
+ switch (payload.baseCommand) {
232
+ case keyMirrors.appVersionControl.install: {
233
+ const { projectId, appReleaseHash, appCfg, envVars } = payload;
234
+ const appContentUpdate: AppContentUpdate = {
235
+ appCfg,
236
+ envVars
237
+ };
238
+ const appContent = await this.processAppContentUpdate(
239
+ projectId,
240
+ appContentUpdate
241
+ );
242
+ if (appContent) {
243
+ this.txnMgr.setAppContentToTx(txId, appContent);
244
+ }
245
+
246
+ const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
247
+ signedUrlsRequest: {
248
+ projectId,
249
+ appReleaseHash
250
+ }
251
+ };
252
+ const message = buildSignedUrlsRequestMessage(
253
+ this.clientId,
254
+ signedUrlsRequestPayload,
255
+ txId
256
+ );
257
+ await this.publishCloudRequest(message);
258
+ return false;
259
+ }
260
+ case keyMirrors.appVersionControl.uninstall: {
261
+ const { projectId } = payload;
262
+ await this.atomicApplicationUninstall(projectId);
263
+ return true;
264
+ }
265
+ default:
266
+ logger.warn(
267
+ `Ignore App Version Control packet: ${JSON.stringify(
268
+ payload,
269
+ null,
270
+ 2
271
+ )}`
272
+ );
273
+ return true;
274
+ }
275
+ };
276
+
277
+ private async publishCloudRequest(message: ToCloudMessage) {
278
+ this.publisher.publishToCloud(message);
279
+ }
280
+
281
+ private async processAppContentUpdate(
282
+ projectId: string,
283
+ appContentUpdate: AppContentUpdate
284
+ ): Promise<AppContent | null> {
285
+ const appContent: AppContent = { projectId };
286
+ // appCfg
287
+ if (!appContentUpdate.appCfg) {
288
+ logger.info(`No appCfgUpdate for ${projectId}`);
289
+ } else {
290
+ // Handle errors and validation
291
+ try {
292
+ const appCfgUpdate = JSON.parse(appContentUpdate.appCfg);
293
+ if (!validateAppConfig(appCfgUpdate)) {
294
+ logger.error(
295
+ `Received invalid app config for ${projectId}!\n${JSON.stringify(
296
+ validateAppConfig.errors,
297
+ null,
298
+ 2
299
+ )}`
300
+ );
301
+ throw new Error(
302
+ `Received invalid app config for ${projectId}!\n${JSON.stringify(
303
+ validateAppConfig.errors,
304
+ null,
305
+ 2
306
+ )}`
307
+ );
308
+ } else {
309
+ appContent.appCfg = appCfgUpdate;
310
+ }
311
+ } catch (e) {
312
+ logger.error(
313
+ `Could not parse the appConfig for transaction!\n${stringifyError(e)}`
314
+ );
315
+ }
316
+ }
317
+
318
+ // envVars
319
+ if (!appContentUpdate.envVars) {
320
+ logger.info(`No envVars for ${projectId}`);
321
+ } else {
322
+ try {
323
+ const envvarsUpdate = JSON.parse(appContentUpdate.envVars);
324
+ if (!validateEnvVarSchemaShadowUpdate(envvarsUpdate)) {
325
+ logger.error(
326
+ `Received invalid environment variables update for ${projectId}!\n${JSON.stringify(
327
+ validateEnvVarSchemaShadowUpdate.errors,
328
+ null,
329
+ 2
330
+ )}`
331
+ );
332
+ throw new Error(
333
+ `Received invalid environment variables update for${projectId}!\n${JSON.stringify(
334
+ validateEnvVarSchemaShadowUpdate.errors,
335
+ null,
336
+ 2
337
+ )}`
338
+ );
339
+ } else {
340
+ appContent.envVars = envvarsUpdate;
341
+ }
342
+ } catch (e) {
343
+ // throw here
344
+ logger.error(
345
+ `Could not parse the environment variables for transaction!\n${stringifyError(
346
+ e
347
+ )}`
348
+ );
349
+ }
350
+ }
351
+
352
+ return appContent.appCfg || appContent.envVars ? appContent : null;
353
+ }
354
+ }
355
+
356
+ class LiveStateUpdatesMessageHandler
357
+ extends BaseHandler
358
+ implements MessageHandler<LiveStateUpdatesToggleMessage>
359
+ {
360
+ public async handle(message: LiveStateUpdatesToggleMessage): Promise<void> {
361
+ const { deviceStats, appState, appLogs } = message.payload;
362
+ const txId = message.txId;
363
+
364
+ if (deviceStats !== undefined) {
365
+ if (deviceStats) {
366
+ await this.liveUpdatesHandler.enable(
367
+ keyMirrors.toClientMessageType.device_stats,
368
+ async () =>
369
+ this.publisher.publishToClient(
370
+ buildDeviceStatsMessage(
371
+ this.clientId,
372
+ await getDeviceStatsPayload(),
373
+ txId
374
+ ),
375
+ logger.silly
376
+ )
377
+ );
378
+ } else {
379
+ this.liveUpdatesHandler.disable(
380
+ keyMirrors.toClientMessageType.device_stats
381
+ );
382
+ }
383
+ }
384
+
385
+ if (appState !== undefined) {
386
+ if (appState) {
387
+ await this.liveUpdatesHandler.enable(
388
+ keyMirrors.toClientMessageType.app_state,
389
+ async () =>
390
+ this.publisher.publishToClient(
391
+ buildAppStateMessage(
392
+ this.clientId,
393
+ await getAppStatePayload(),
394
+ txId
395
+ ),
396
+ logger.silly
397
+ )
398
+ );
399
+ } else {
400
+ this.liveUpdatesHandler.disable(
401
+ keyMirrors.toClientMessageType.app_state
402
+ );
403
+ }
404
+ }
405
+
406
+ if (appLogs !== undefined) {
407
+ if (appLogs.toggle) {
408
+ await this.liveUpdatesHandler.startStream(
409
+ appLogs.projectId,
410
+ async () =>
411
+ await getAppLogs({
412
+ projectId: appLogs.projectId,
413
+ args: ['--tail', '100', '--no-log-prefix']
414
+ }),
415
+ async (logChunk: string) =>
416
+ this.publisher.publishToClient(
417
+ buildAppLogsMessage(
418
+ this.clientId,
419
+ {
420
+ projectId: appLogs.projectId,
421
+ logChunk
422
+ },
423
+ txId
424
+ ),
425
+ logger.silly
426
+ )
427
+ );
428
+ } else {
429
+ this.liveUpdatesHandler.stopStream(appLogs.projectId);
430
+ }
431
+ }
432
+ }
433
+ }
434
+
435
+ class AppInstallResponseMessageHandler
436
+ extends BaseHandler
437
+ implements MessageHandler<AppInstallResponseMessage>
438
+ {
439
+ public async handle(message: AppInstallResponseMessage): Promise<void> {
440
+ const payload = message.payload;
441
+ const txId = message.txId;
442
+ const { projectId } = payload.appInstallResponse;
443
+ const start = !this.txnMgr.isOngoingTransactionForProjectID(projectId);
444
+
445
+ if (!start && txId !== this.txnMgr.getTransactionFromProject(projectId)) {
446
+ throw new Error(
447
+ `App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
448
+ projectId
449
+ )})!`
450
+ );
451
+ }
452
+ const appContent = this.txnMgr.getAppContentFromTxId(txId);
453
+ await this.txnMgr.runTransactionStep({
454
+ func: () =>
455
+ this.handleAppInstallCloudResponsePayload(payload, appContent),
456
+ projectId,
457
+ txId,
458
+ start,
459
+ stepName: message.messageType,
460
+ errorFn: this.errorFn,
461
+ successFn: this.successFn
462
+ });
463
+ }
464
+
465
+ private handleAppInstallCloudResponsePayload = async (
466
+ payload: AppInstallResponsePayload,
467
+ appContent?: AppContent
468
+ ): Promise<boolean> => {
469
+ const {
470
+ projectId,
471
+ appReleaseHash,
472
+ appInstallPayload,
473
+ modelsInstallPayload
474
+ } = payload.appInstallResponse;
475
+ const signedUrlsPayload = {
476
+ appInstallPayload,
477
+ modelsInstallPayload
478
+ };
479
+ await this.atomicApplicationUpdate(async () => {
480
+ this.shadowHandler.clearProjectShadow(projectId);
481
+ await installApp({
482
+ projectId,
483
+ appReleaseHash,
484
+ signedUrlsPayload,
485
+ appCfg: appContent?.appCfg,
486
+ envVars: appContent?.envVars
487
+ });
488
+ }, projectId);
489
+ return true;
490
+ };
491
+ }
492
+
493
+ class ModelsInstallResponseMessageHandler
494
+ extends BaseHandler
495
+ implements MessageHandler<ModelsInstallResponseMessage>
496
+ {
497
+ public async handle(message: ModelsInstallResponseMessage): Promise<void> {
498
+ const payload = message.payload;
499
+ const txId = message.txId;
500
+ const { projectId } = payload.modelsInstallResponse;
501
+ if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
502
+ throw new Error(
503
+ `Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
504
+ projectId
505
+ )})!`
506
+ );
507
+ }
508
+ await this.txnMgr.runTransactionStep({
509
+ func: () => this.handleModelsInstallCloudResponsePayload(payload, txId),
510
+ projectId,
511
+ txId,
512
+ start: false,
513
+ stepName: message.messageType,
514
+ errorFn: this.errorFn,
515
+ successFn: this.successFn
516
+ });
517
+ }
518
+
519
+ private handleModelsInstallCloudResponsePayload = async (
520
+ payload: ModelsInstallResponsePayload,
521
+ txId: string
522
+ ): Promise<boolean> => {
523
+ const projectId = payload.modelsInstallResponse.projectId;
524
+
525
+ const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
526
+ if (update === undefined) {
527
+ throw new Error(
528
+ 'Unknown error while updating models via application config! No config present for model update.'
529
+ );
530
+ }
531
+ const { appCfgUpdate, envVarUpdate } = update;
532
+ if (appCfgUpdate) {
533
+ await this.atomicApplicationUpdate(
534
+ async () =>
535
+ await updateModelsWithPresignedUrls({
536
+ projectId,
537
+ modelInstallPayloads: payload.modelsInstallResponse.newModels,
538
+ newAppCfg: appCfgUpdate.newAppCfg
539
+ }),
540
+ projectId
541
+ );
542
+ }
543
+
544
+ if (envVarUpdate) {
545
+ await this.atomicApplicationUpdate(
546
+ async () =>
547
+ await this.shadowHandler.updateProjectEnvVars({
548
+ projectId,
549
+ envVars: envVarUpdate.envVars
550
+ }),
551
+ projectId,
552
+ true
553
+ );
554
+ }
555
+ return true;
556
+ };
557
+ }
558
+
559
+ class StatusResponseMessageHandler
560
+ extends BaseHandler
561
+ implements MessageHandler<ToDeviceStatusResponseMessage>
562
+ {
563
+ public async handle(message: ToDeviceStatusResponseMessage): Promise<void> {
564
+ const txId = message.txId;
565
+ const { failure } = keyMirrors.statusResponse;
566
+ if (message.payload.status === failure) {
567
+ this.txnMgr.completeTransaction(txId);
568
+ const msg = buildToClientStatusResponseMessage(
569
+ this.clientId,
570
+ {
571
+ status: keyMirrors.statusResponse.failure,
572
+ message: message.payload.message
573
+ },
574
+ txId
575
+ );
576
+ this.publisher.publishToClient(msg);
577
+ }
578
+ }
579
+ }
580
+
581
+ class DeviceActionMessageHandler
582
+ extends BaseHandler
583
+ implements MessageHandler<DeviceActionMessage>
584
+ {
585
+ public async handle(message: DeviceActionMessage): Promise<void> {
586
+ const txId = message.txId;
587
+ try {
588
+ this.publisher.publishToClient(
589
+ buildToClientStatusResponseMessage(
590
+ this.clientId,
591
+ {
592
+ status: keyMirrors.statusResponse.in_progress
593
+ },
594
+ txId
595
+ )
596
+ );
597
+
598
+ await this.handleDeviceAction(message.payload);
599
+
600
+ this.publisher.publishToClient(
601
+ buildToClientStatusResponseMessage(
602
+ this.clientId,
603
+ {
604
+ status: keyMirrors.statusResponse.success
605
+ },
606
+ txId
607
+ )
608
+ );
609
+ } catch (e) {
610
+ logger.error(
611
+ `There was a problem performing device action '${
612
+ message.payload.action
613
+ }'!\n${stringifyError(e)}`
614
+ );
615
+ this.publisher.publishToClient(
616
+ buildToClientStatusResponseMessage(
617
+ this.clientId,
618
+ {
619
+ status: keyMirrors.statusResponse.failure,
620
+ message: e.message
621
+ },
622
+ txId
623
+ )
624
+ );
625
+ }
626
+ }
627
+
628
+ private async handleDeviceAction(payload: DeviceActionPayload) {
629
+ const { system_restart } = keyMirrors.deviceAction;
630
+ switch (payload.action) {
631
+ case system_restart: {
632
+ await reboot();
633
+ break;
634
+ }
635
+ default: {
636
+ logger.info(
637
+ `Unrecognized device action requested: '${payload.action}'.`
638
+ );
639
+ }
640
+ }
641
+ }
642
+ }
@@ -35,25 +35,9 @@ export class LiveUpdatesHandler {
35
35
 
36
36
  const key = this.generateIntervalKey(intervalType, transactionId);
37
37
 
38
- clearInterval(this.livingIntervals[key]);
38
+ this.safeSetInterval(key, publishingFn, options);
39
39
 
40
- // initial publish
41
40
  await publishingFn();
42
-
43
- this.livingIntervals[key] = setInterval(
44
- async () => {
45
- try {
46
- await publishingFn();
47
- } catch (e) {
48
- logger.error(
49
- `Error getting live updates: ${JSON.stringify(
50
- e
51
- )}: intervalKey: ${key}. Continuing...`
52
- );
53
- }
54
- },
55
- options?.ms ? options.ms : DEFAULT_INTERVALS_MS
56
- );
57
41
  }
58
42
 
59
43
  public disable(
@@ -133,6 +117,30 @@ export class LiveUpdatesHandler {
133
117
  return null;
134
118
  }
135
119
 
120
+ // do not await any functions in the setSafeInterval other than inside setInterval() as per EI-1694.
121
+ private safeSetInterval(
122
+ key: string,
123
+ publishingFn: () => Promise<void>,
124
+ options?: IntervalOptions
125
+ ) {
126
+ clearInterval(this.livingIntervals[key]);
127
+
128
+ this.livingIntervals[key] = setInterval(
129
+ async () => {
130
+ try {
131
+ await publishingFn();
132
+ } catch (e) {
133
+ logger.error(
134
+ `Error getting live updates: ${JSON.stringify(
135
+ e
136
+ )}: intervalKey: ${key}. Continuing...`
137
+ );
138
+ }
139
+ },
140
+ options?.ms ? options.ms : DEFAULT_INTERVALS_MS
141
+ );
142
+ }
143
+
136
144
  private restartKillAllTimeout() {
137
145
  clearTimeout(this.killAllTimeout);
138
146
  this.killAllTimeout = setTimeout(() => {
@@ -147,7 +155,7 @@ export class LiveUpdatesHandler {
147
155
  private generateIntervalKey(
148
156
  intervalType: ToClientMessageTypeValue,
149
157
  transactionId?: string
150
- ) {
158
+ ): string {
151
159
  return intervalType + `${transactionId ? ':' + transactionId : ''}`;
152
160
  }
153
161
  }