@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,33 @@
1
+ import { logger } from '../util/logger';
2
+
3
+ export interface MessageHandler<T = any> {
4
+ handle(payload: T, topic?: string): void;
5
+ }
6
+
7
+ export class MessageDispatcher<T = any> {
8
+ private handlers: Map<string, MessageHandler<T>> = new Map();
9
+
10
+ public registerHandler(topic: string, handler: MessageHandler): void {
11
+ this.handlers.set(topic, handler);
12
+ }
13
+
14
+ public dispatch(topic: string, payload: T): void {
15
+ const handler = this.handlers.get(topic);
16
+ if (handler) {
17
+ handler.handle(payload, topic);
18
+ } else {
19
+ this.handleUnknownMessage(topic, payload);
20
+ }
21
+ }
22
+
23
+ // Handle unknown or unregistered message topic
24
+ private handleUnknownMessage(topic: string, payload: T): void {
25
+ logger.error(
26
+ `No handler found for topic/type: ${topic} for message ${JSON.stringify(
27
+ payload,
28
+ null,
29
+ 2
30
+ )}`
31
+ );
32
+ }
33
+ }
@@ -6,15 +6,16 @@ import {
6
6
  getToCloudTopic
7
7
  } from '@alwaysai/device-agent-schemas';
8
8
  import * as winston from 'winston';
9
+ import { ConnectionManager } from './connection-manager';
9
10
 
10
11
  export class Publisher {
11
- private client: any;
12
+ private connectionManager: ConnectionManager;
12
13
  private clientId: string;
13
14
  private readonly toClientTopic: string;
14
15
  private readonly toCloudTopic: string;
15
16
 
16
- constructor(client: any, clientId: string) {
17
- this.client = client;
17
+ constructor(connectionManager: ConnectionManager, clientId: string) {
18
+ this.connectionManager = connectionManager;
18
19
  this.clientId = clientId;
19
20
  this.toClientTopic = getToClientTopic(this.clientId);
20
21
  this.toCloudTopic = getToCloudTopic(this.clientId);
@@ -34,13 +35,15 @@ export class Publisher {
34
35
  2
35
36
  )}`
36
37
  );
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
- });
38
+ this.connectionManager
39
+ .getIoTDevice()
40
+ .publish(topic, payload, (err: any) => {
41
+ if (err) {
42
+ logger.error(
43
+ `Error publishing message: \nTopic: ${topic}\nMessage: ${payload}\nError: ${err}`
44
+ );
45
+ }
46
+ });
44
47
  }
45
48
 
46
49
  public publishToCloudWithAck(
@@ -48,19 +51,21 @@ export class Publisher {
48
51
  ackNackCallback: CallableFunction
49
52
  ) {
50
53
  const topic = this.toCloudTopic;
51
- this.client.publish(topic, payload, { qos: 1 }, (err: any, resp: any) => {
52
- if (err) {
53
- logger.error(
54
- `Error publishing message: \nTopic: ${topic}\nMessage: ${payload}\nError: ${err}`
55
- );
56
- ackNackCallback(false);
57
- } else if (resp) {
58
- logger.debug(
59
- `Successfully published message: \nTopic: ${topic}\nMessage: ${payload}`
60
- );
61
- ackNackCallback(true);
62
- }
63
- });
54
+ this.connectionManager
55
+ .getIoTDevice()
56
+ .publish(topic, payload, { qos: 1 }, (err: any, resp: any) => {
57
+ if (err) {
58
+ logger.error(
59
+ `Error publishing message: \nTopic: ${topic}\nMessage: ${payload}\nError: ${err}`
60
+ );
61
+ ackNackCallback(false);
62
+ } else if (resp) {
63
+ logger.debug(
64
+ `Successfully published message: \nTopic: ${topic}\nMessage: ${payload}`
65
+ );
66
+ ackNackCallback(true);
67
+ }
68
+ });
64
69
  }
65
70
 
66
71
  public publishDeviceAgentMessage(
@@ -5,10 +5,14 @@ import { ShadowHandler } from './shadow-handler';
5
5
  import { Logger } from 'winston';
6
6
  import { logger } from '../util/logger';
7
7
  import { getShadowTopic } from '@alwaysai/device-agent-schemas';
8
+ import { ConnectionManager } from './connection-manager';
8
9
 
9
10
  jest.mock('../application-control');
10
11
  jest.mock('./publisher');
11
- const mockClient = jest.fn();
12
+ jest.mock('./connection-manager');
13
+ const mockConnectionManager = {
14
+ getIotDevice: jest.fn()
15
+ } as any as ConnectionManager;
12
16
  const clientId = 'test-client';
13
17
  const projectId1 = 'test-project';
14
18
  const projectId2 = 'test-project-2';
@@ -18,7 +22,7 @@ describe('Test Shadow Handler', () => {
18
22
  let shadowHandler: ShadowHandler;
19
23
 
20
24
  beforeEach(() => {
21
- publisher = new Publisher(mockClient, clientId);
25
+ publisher = new Publisher(mockConnectionManager, clientId);
22
26
  shadowHandler = new ShadowHandler(clientId, publisher);
23
27
  });
24
28
 
@@ -3,13 +3,17 @@ import {
3
3
  validateAppConfig
4
4
  } from '@alwaysai/app-configuration-schemas';
5
5
  import {
6
+ buildSignedUrlsRequestMessage,
7
+ buildToClientStatusResponseMessage,
6
8
  buildUpdateShadowMessage,
7
9
  EnvVars,
8
10
  generateTxId,
9
11
  getShadowTopic,
10
12
  getUpdateDeltaStateFromMessage,
13
+ keyMirrors,
11
14
  ProjectShadowUpdate,
12
15
  SecureTunnelShadowUpdate,
16
+ SignedUrlsRequestPayload,
13
17
  validateEnvVarSchemaShadowUpdate,
14
18
  validateProjectShadowUpdate
15
19
  } from '@alwaysai/device-agent-schemas';
@@ -19,11 +23,19 @@ import {
19
23
  ShadowProjectsUpdateAll
20
24
  } from '@alwaysai/device-agent-schemas/lib/shadow-schema';
21
25
  import { stringifyError } from 'alwaysai/lib/util';
22
- import { getAllEnvs, readAppCfgFile, setEnv } from '../application-control';
26
+ import {
27
+ getAllEnvs,
28
+ readAppCfgFile,
29
+ setEnv,
30
+ updateAppCfg
31
+ } from '../application-control';
23
32
  import { getSystemInformation } from '../device-control/device-control';
24
33
  import { logger } from '../util/logger';
25
34
  import { Publisher } from './publisher';
26
35
  import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
36
+ import { pruneModels } from '../application-control/models';
37
+ import { MessageHandler } from './message-dispatcher';
38
+ import { BaseHandler } from './base-message-handler';
27
39
 
28
40
  export type AppConfigUpdate = {
29
41
  newAppCfg: AppConfig;
@@ -334,6 +346,10 @@ export class ShadowHandler {
334
346
  return [];
335
347
  }
336
348
 
349
+ public async initShadows() {
350
+ await this.updateSystemInfoShadow();
351
+ }
352
+
337
353
  public async updateSystemInfoShadow() {
338
354
  const systemInfo = await getSystemInformation();
339
355
  this.publisher.publish(
@@ -440,3 +456,115 @@ export class ShadowHandler {
440
456
  );
441
457
  }
442
458
  }
459
+
460
+ export class ProjectShadowMessageHandler
461
+ extends BaseHandler
462
+ implements MessageHandler<any>
463
+ {
464
+ public async handle(message: any, topic: string): Promise<void> {
465
+ const shadowUpdates = await this.shadowHandler.handleProjectShadow({
466
+ topic,
467
+ payload: message,
468
+ clientToken: message.clientToken
469
+ });
470
+ if (shadowUpdates.length) {
471
+ const shadowUpdatePromises: Promise<void>[] = [];
472
+ for (const shadowUpdate of shadowUpdates) {
473
+ const projectId = shadowUpdate.projectId;
474
+ const txId = shadowUpdate.txId;
475
+ shadowUpdatePromises.push(
476
+ this.txnMgr
477
+ .runTransactionStep({
478
+ func: () =>
479
+ this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
480
+ projectId,
481
+ txId,
482
+ start: true,
483
+ liveUpdatesPublishFn: async () =>
484
+ this.publisher.publishToClient(
485
+ buildToClientStatusResponseMessage(
486
+ this.clientId,
487
+ { status: keyMirrors.statusResponse.in_progress },
488
+ txId
489
+ ),
490
+ logger.silly
491
+ ),
492
+ stepName: topic
493
+ })
494
+ .catch((e) => {
495
+ logger.error(
496
+ `There was an issue updating project shadow config for ${projectId}!\n${stringifyError(
497
+ e
498
+ )}`
499
+ );
500
+ })
501
+ );
502
+ }
503
+
504
+ await Promise.all(shadowUpdatePromises);
505
+ }
506
+ }
507
+
508
+ private handleProjectShadowConfigUpdate = async (
509
+ update: ShadowUpdate,
510
+ txId: string
511
+ ): Promise<boolean> => {
512
+ const { projectId, appCfgUpdate, envVarUpdate } = update;
513
+
514
+ if (
515
+ appCfgUpdate?.updatedModels &&
516
+ Object.keys(appCfgUpdate.updatedModels).length
517
+ ) {
518
+ // When there are model updates request signed URLs and wait to apply config changes
519
+ const { updatedModels } = appCfgUpdate;
520
+
521
+ logger.debug(
522
+ `Requesting presigned urls from cloud for model versions: ${JSON.stringify(
523
+ updatedModels
524
+ )}`
525
+ );
526
+ const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
527
+ modelsOnlyUrlsRequest: {
528
+ projectId,
529
+ models: updatedModels
530
+ }
531
+ };
532
+ const message = buildSignedUrlsRequestMessage(
533
+ this.clientId,
534
+ modelsOnlyUrlsRequestPayload,
535
+ txId
536
+ );
537
+ this.publisher.publishToCloud(message);
538
+
539
+ this.txnMgr.setAppCfgUpdateToTx(txId, update);
540
+
541
+ return false;
542
+ }
543
+
544
+ if (appCfgUpdate) {
545
+ await this.atomicApplicationUpdate(async () => {
546
+ await pruneModels({
547
+ projectId,
548
+ appCfg: appCfgUpdate.newAppCfg
549
+ });
550
+ await updateAppCfg({
551
+ projectId,
552
+ newAppCfg: appCfgUpdate.newAppCfg
553
+ });
554
+ }, projectId);
555
+ }
556
+
557
+ if (envVarUpdate) {
558
+ await this.atomicApplicationUpdate(
559
+ async () =>
560
+ await this.shadowHandler.updateProjectEnvVars({
561
+ projectId,
562
+ envVars: envVarUpdate.envVars
563
+ }),
564
+ projectId,
565
+ true
566
+ );
567
+ }
568
+ return true;
569
+ };
570
+ }
@@ -1,18 +1,10 @@
1
- import {
2
- ToClientStatusResponseMessage,
3
- generateTxId,
4
- keyMirrors
5
- } from '@alwaysai/device-agent-schemas';
1
+ import { EnvVars, generateTxId } from '@alwaysai/device-agent-schemas';
6
2
  import { TransactionManager } from './transaction-manager';
7
3
  import { v4 as uuidv4 } from 'uuid';
8
- import { Publisher } from './publisher';
9
4
  import { LiveUpdatesHandler } from './live-updates-handler';
10
5
  import { AppConfigUpdate, ShadowUpdate } from './shadow-handler';
11
-
12
- const mockClient = {
13
- publish: jest.fn()
14
- };
15
- const clientId = 'test-client';
6
+ import { AppContent } from './device-agent-message-handler';
7
+ import { AppConfig } from '@alwaysai/app-configuration-schemas';
16
8
 
17
9
  const mockLiveUpdatesHandler = {
18
10
  enable: jest.fn(),
@@ -21,7 +13,6 @@ const mockLiveUpdatesHandler = {
21
13
 
22
14
  describe('Test Transaction Manager', () => {
23
15
  let txnMgr: TransactionManager;
24
- let publisher: Publisher;
25
16
  const func_complete: () => Promise<boolean> = jest
26
17
  .fn()
27
18
  .mockResolvedValue(true);
@@ -30,8 +21,7 @@ describe('Test Transaction Manager', () => {
30
21
  .mockResolvedValue(false);
31
22
 
32
23
  beforeEach(() => {
33
- publisher = new Publisher(mockClient, clientId);
34
- txnMgr = new TransactionManager(publisher, mockLiveUpdatesHandler);
24
+ txnMgr = new TransactionManager(mockLiveUpdatesHandler);
35
25
  jest.clearAllMocks();
36
26
  });
37
27
 
@@ -58,19 +48,23 @@ describe('Test Transaction Manager', () => {
58
48
  test('Start a new transaction which completes in one step', async () => {
59
49
  const txId = generateTxId();
60
50
  const projectId = generateRandomProjectId();
51
+
52
+ const successFn = jest.fn();
53
+ const errorFn = jest.fn();
54
+
61
55
  await txnMgr.runTransactionStep({
62
56
  func: func_complete,
63
57
  projectId,
64
58
  txId,
65
- start: true
59
+ start: true,
60
+ successFn,
61
+ errorFn
66
62
  });
67
63
  expect(txnMgr.isOngoingTransaction(txId)).toBe(false);
68
64
  expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(false);
69
65
  expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
70
- const msg: ToClientStatusResponseMessage = JSON.parse(
71
- mockClient.publish.mock.calls[0][1]
72
- );
73
- expect(msg.payload.status).toBe(keyMirrors.statusResponse.success);
66
+ expect(successFn).toBeCalledTimes(1);
67
+ expect(errorFn).toBeCalledTimes(0);
74
68
  });
75
69
 
76
70
  test('Start a new transaction and complete in second step', async () => {
@@ -198,6 +192,10 @@ describe('Test Transaction Manager', () => {
198
192
  test('Handle error in step function', async () => {
199
193
  const txId = generateTxId();
200
194
  const projectId = generateRandomProjectId();
195
+
196
+ const successFn = jest.fn();
197
+ const errorFn = jest.fn();
198
+
201
199
  await txnMgr.runTransactionStep({
202
200
  func: jest.fn().mockImplementation(() => {
203
201
  throw new Error('Test error!');
@@ -205,15 +203,15 @@ describe('Test Transaction Manager', () => {
205
203
  projectId,
206
204
  txId,
207
205
  start: true,
208
- stepName: 'step1'
206
+ stepName: 'step1',
207
+ successFn,
208
+ errorFn
209
209
  });
210
210
  expect(txnMgr.isOngoingTransaction(txId)).toBe(false);
211
211
  expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(false);
212
212
  expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
213
- const msg: ToClientStatusResponseMessage = JSON.parse(
214
- mockClient.publish.mock.calls[0][1]
215
- );
216
- expect(msg.payload.status).toBe(keyMirrors.statusResponse.failure);
213
+ expect(successFn).toBeCalledTimes(0);
214
+ expect(errorFn).toBeCalledTimes(1);
217
215
  });
218
216
 
219
217
  test('Complete ongoing transaction', async () => {
@@ -369,4 +367,37 @@ describe('Test Transaction Manager', () => {
369
367
  expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
370
368
  expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(undefined);
371
369
  });
370
+
371
+ test('store appContent', async () => {
372
+ const txId = generateTxId();
373
+ const projectId = generateRandomProjectId();
374
+ const appCfg: AppConfig = {
375
+ models: {},
376
+ scripts: { start: 'python app.py' }
377
+ };
378
+ const envVars: EnvVars = { alwaysai: { TEST: '1' } };
379
+ const cfgUpdate: AppContent = {
380
+ projectId: projectId,
381
+ appCfg: appCfg,
382
+ envVars: envVars
383
+ };
384
+
385
+ await txnMgr.runTransactionStep({
386
+ func: func_incomplete,
387
+ projectId,
388
+ txId,
389
+ start: true
390
+ });
391
+
392
+ expect(txnMgr.getAppContentFromTxId(txId)).toEqual(undefined);
393
+
394
+ txnMgr.setAppContentToTx(txId, cfgUpdate);
395
+
396
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
397
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
398
+ expect(txnMgr.isOngoingTransaction(txId)).toBe(true);
399
+ expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(true);
400
+ expect(txnMgr.isAnyOngoingTransaction()).toBe(true);
401
+ expect(txnMgr.getAppContentFromTxId(txId)).toEqual(cfgUpdate);
402
+ });
372
403
  });
@@ -4,11 +4,11 @@ import {
4
4
  keyMirrors
5
5
  } from '@alwaysai/device-agent-schemas';
6
6
  import { LiveUpdatesHandler } from './live-updates-handler';
7
- import { Publisher } from './publisher';
8
7
  import { logger } from '../util/logger';
9
8
  import { keyMirror, stringifyError } from 'alwaysai/lib/util';
10
9
  import { CodedError } from '@carnesen/coded-error';
11
10
  import { ShadowUpdate } from './shadow-handler';
11
+ import { AppContent } from './device-agent-message-handler';
12
12
 
13
13
  interface TransactionDetails {
14
14
  txId: string;
@@ -18,14 +18,17 @@ interface TransactionDetails {
18
18
  update?: string;
19
19
  stop?: string;
20
20
  appCfgUpdate?: ShadowUpdate;
21
+ appContent?: AppContent;
21
22
  }
22
23
 
24
+ export type ErrorFunction = (txid: string, message: string) => void;
25
+
26
+ export type SuccessFunction = (txId: string) => void;
27
+
23
28
  export class TransactionManager {
24
29
  private detailsByTx: Record<string, TransactionDetails> = {};
25
30
  private detailsByProject: Record<string, TransactionDetails> = {};
26
- private liveUpdatesHandler: LiveUpdatesHandler;
27
- private publisher: Publisher;
28
-
31
+ private readonly liveUpdatesHandler: LiveUpdatesHandler;
29
32
  private async startTransaction(
30
33
  txId: string,
31
34
  projectId: string,
@@ -91,10 +94,9 @@ export class TransactionManager {
91
94
  logger.info(`Updated transaction:\n${JSON.stringify(txDetails, null, 2)}`);
92
95
  }
93
96
 
94
- constructor(publisher: Publisher, liveUpdatesHandler: LiveUpdatesHandler) {
97
+ constructor(liveUpdatesHandler: LiveUpdatesHandler) {
95
98
  this.detailsByTx = {};
96
99
  this.detailsByProject = {};
97
- this.publisher = publisher;
98
100
  this.liveUpdatesHandler = liveUpdatesHandler;
99
101
  }
100
102
 
@@ -111,9 +113,19 @@ export class TransactionManager {
111
113
  start: boolean;
112
114
  liveUpdatesPublishFn?: () => Promise<void>;
113
115
  stepName?: string;
116
+ errorFn?: ErrorFunction;
117
+ successFn?: SuccessFunction;
114
118
  }) {
115
- const { func, projectId, txId, start, liveUpdatesPublishFn, stepName } =
116
- props;
119
+ const {
120
+ func,
121
+ projectId,
122
+ txId,
123
+ start,
124
+ liveUpdatesPublishFn,
125
+ stepName,
126
+ errorFn,
127
+ successFn
128
+ } = props;
117
129
  if (start) {
118
130
  await this.startTransaction(
119
131
  txId,
@@ -127,31 +139,20 @@ export class TransactionManager {
127
139
  try {
128
140
  const completed = await func();
129
141
  if (completed) {
130
- this.completeTransaction(
131
- txId,
132
- buildToClientStatusResponseMessage(
133
- this.publisher.getClientId(),
134
- { status: keyMirrors.statusResponse.success },
135
- txId
136
- )
137
- );
142
+ this.completeTransaction(txId);
143
+ if (successFn) {
144
+ successFn(txId);
145
+ }
138
146
  }
139
147
  } catch (e) {
140
148
  logger.error(
141
149
  `Failed to execute cmd for ${projectId}!\n${stringifyError(e)}`
142
150
  );
143
151
 
144
- this.completeTransaction(
145
- txId,
146
- buildToClientStatusResponseMessage(
147
- this.publisher.getClientId(),
148
- {
149
- status: keyMirrors.statusResponse.failure,
150
- message: e.message
151
- },
152
- txId
153
- )
154
- );
152
+ this.completeTransaction(txId);
153
+ if (errorFn) {
154
+ errorFn(txId, e.message);
155
+ }
155
156
  }
156
157
  }
157
158
 
@@ -181,6 +182,10 @@ export class TransactionManager {
181
182
  return this.detailsByTx[txId]?.appCfgUpdate;
182
183
  }
183
184
 
185
+ public getAppContentFromTxId(txId: string): AppContent | undefined {
186
+ return this.detailsByTx[txId]?.appContent;
187
+ }
188
+
184
189
  public setAppCfgUpdateToTx(txId: string, appCfgUpdate: ShadowUpdate) {
185
190
  if (this.isOngoingTransaction(txId)) {
186
191
  this.detailsByTx[txId].appCfgUpdate = appCfgUpdate;
@@ -190,6 +195,16 @@ export class TransactionManager {
190
195
  );
191
196
  }
192
197
 
198
+ public setAppContentToTx(txId: string, appContent: AppContent) {
199
+ if (this.isOngoingTransaction(txId)) {
200
+ logger.debug(`${txId}: Setting AppContent:${JSON.stringify(appContent)}`);
201
+ this.detailsByTx[txId].appContent = appContent;
202
+ } else
203
+ throw new Error(
204
+ `Could not set appCfgUpdate, the transaction ${txId} does not exist.`
205
+ );
206
+ }
207
+
193
208
  public completeTransaction(
194
209
  txId: string,
195
210
  messageToPublish?: ToClientStatusResponseMessage
@@ -212,9 +227,5 @@ export class TransactionManager {
212
227
  keyMirrors.toClientMessageType.status_response,
213
228
  txId
214
229
  );
215
-
216
- if (messageToPublish) {
217
- this.publisher.publishToClient(messageToPublish);
218
- }
219
230
  }
220
231
  }