@alwaysai/device-agent 2.0.2 → 2.0.3

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 (121) hide show
  1. package/lib/application-control/config.js +1 -1
  2. package/lib/application-control/config.js.map +1 -1
  3. package/lib/application-control/install.d.ts.map +1 -1
  4. package/lib/application-control/install.js +2 -2
  5. package/lib/application-control/install.js.map +1 -1
  6. package/lib/cloud-connection/base-message-handler.d.ts.map +1 -1
  7. package/lib/cloud-connection/base-message-handler.js +5 -4
  8. package/lib/cloud-connection/base-message-handler.js.map +1 -1
  9. package/lib/cloud-connection/connection-manager.js +2 -2
  10. package/lib/cloud-connection/connection-manager.js.map +1 -1
  11. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  12. package/lib/cloud-connection/device-agent-cloud-connection.js +3 -8
  13. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  14. package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -1
  15. package/lib/cloud-connection/device-agent-message-handler.js +19 -18
  16. package/lib/cloud-connection/device-agent-message-handler.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 +11 -4
  19. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  20. package/lib/cloud-connection/passthrough-handler.d.ts +3 -3
  21. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  22. package/lib/cloud-connection/passthrough-handler.js +97 -74
  23. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  24. package/lib/cloud-connection/shadow-handler.js +3 -3
  25. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  26. package/lib/cloud-connection/shadow.d.ts.map +1 -1
  27. package/lib/cloud-connection/shadow.js +1 -1
  28. package/lib/cloud-connection/shadow.js.map +1 -1
  29. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  30. package/lib/cloud-connection/transaction-manager.js +17 -7
  31. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  32. package/lib/cloud-connection/transaction-manager.test.js +52 -44
  33. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  34. package/lib/device-control/device-control.d.ts.map +1 -1
  35. package/lib/device-control/device-control.js +13 -9
  36. package/lib/device-control/device-control.js.map +1 -1
  37. package/lib/docker/docker-compose.d.ts.map +1 -1
  38. package/lib/docker/docker-compose.js +1 -1
  39. package/lib/docker/docker-compose.js.map +1 -1
  40. package/lib/environment.d.ts.map +1 -1
  41. package/lib/environment.js +1 -1
  42. package/lib/environment.js.map +1 -1
  43. package/lib/index.js +12 -0
  44. package/lib/index.js.map +1 -1
  45. package/lib/infrastructure/config-check-utility.js +2 -2
  46. package/lib/infrastructure/config-check-utility.js.map +1 -1
  47. package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -1
  48. package/lib/infrastructure/legacy-migration/legacy-migration.js +6 -10
  49. package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -1
  50. package/lib/local-connection/rabbitmq-container.d.ts +6 -0
  51. package/lib/local-connection/rabbitmq-container.d.ts.map +1 -0
  52. package/lib/local-connection/rabbitmq-container.js +111 -0
  53. package/lib/local-connection/rabbitmq-container.js.map +1 -0
  54. package/lib/local-connection/rabbitmq-container.test.d.ts +2 -0
  55. package/lib/local-connection/rabbitmq-container.test.d.ts.map +1 -0
  56. package/lib/local-connection/rabbitmq-container.test.js +219 -0
  57. package/lib/local-connection/rabbitmq-container.test.js.map +1 -0
  58. package/lib/subcommands/device/clean.d.ts.map +1 -1
  59. package/lib/subcommands/device/clean.js +15 -17
  60. package/lib/subcommands/device/clean.js.map +1 -1
  61. package/lib/subcommands/device/index.d.ts.map +1 -1
  62. package/lib/subcommands/device/index.js +3 -1
  63. package/lib/subcommands/device/index.js.map +1 -1
  64. package/lib/subcommands/device/local-connection.d.ts +2 -0
  65. package/lib/subcommands/device/local-connection.d.ts.map +1 -0
  66. package/lib/subcommands/device/local-connection.js +17 -0
  67. package/lib/subcommands/device/local-connection.js.map +1 -0
  68. package/lib/subcommands/index.d.ts +4 -1
  69. package/lib/subcommands/index.d.ts.map +1 -1
  70. package/lib/subcommands/index.js +1 -3
  71. package/lib/subcommands/index.js.map +1 -1
  72. package/lib/util/check-for-updates.d.ts.map +1 -1
  73. package/lib/util/check-for-updates.js +2 -2
  74. package/lib/util/check-for-updates.js.map +1 -1
  75. package/lib/util/file.d.ts.map +1 -1
  76. package/lib/util/file.js +6 -1
  77. package/lib/util/file.js.map +1 -1
  78. package/lib/util/file.test.js +1 -1
  79. package/lib/util/file.test.js.map +1 -1
  80. package/lib/util/get-device-id.d.ts.map +1 -1
  81. package/lib/util/get-device-id.js +1 -1
  82. package/lib/util/get-device-id.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/application-control/config.ts +1 -1
  85. package/src/application-control/install.ts +3 -2
  86. package/src/cloud-connection/base-message-handler.ts +10 -5
  87. package/src/cloud-connection/connection-manager.ts +2 -2
  88. package/src/cloud-connection/device-agent-cloud-connection.ts +6 -10
  89. package/src/cloud-connection/device-agent-message-handler.ts +10 -7
  90. package/src/cloud-connection/live-updates-handler.ts +12 -5
  91. package/src/cloud-connection/passthrough-handler.ts +79 -38
  92. package/src/cloud-connection/shadow-handler.ts +3 -3
  93. package/src/cloud-connection/shadow.ts +3 -1
  94. package/src/cloud-connection/transaction-manager.test.ts +60 -41
  95. package/src/cloud-connection/transaction-manager.ts +26 -12
  96. package/src/device-control/device-control.ts +23 -13
  97. package/src/docker/docker-compose.ts +3 -1
  98. package/src/environment.ts +1 -2
  99. package/src/index.ts +19 -0
  100. package/src/infrastructure/config-check-utility.ts +2 -2
  101. package/src/infrastructure/legacy-migration/legacy-migration.ts +8 -13
  102. package/src/local-connection/rabbitmq-container.test.ts +255 -0
  103. package/src/local-connection/rabbitmq-container.ts +151 -0
  104. package/src/subcommands/device/clean.ts +20 -19
  105. package/src/subcommands/device/index.ts +3 -1
  106. package/src/subcommands/device/local-connection.ts +16 -0
  107. package/src/subcommands/index.ts +1 -3
  108. package/src/util/check-for-updates.ts +4 -2
  109. package/src/util/file.test.ts +1 -1
  110. package/src/util/file.ts +7 -1
  111. package/src/util/get-device-id.ts +3 -1
  112. package/lib/local-connection/rabbitmq-connection.d.ts +0 -7
  113. package/lib/local-connection/rabbitmq-connection.d.ts.map +0 -1
  114. package/lib/local-connection/rabbitmq-connection.js +0 -95
  115. package/lib/local-connection/rabbitmq-connection.js.map +0 -1
  116. package/lib/subcommands/rabbitmq-connection.d.ts +0 -2
  117. package/lib/subcommands/rabbitmq-connection.d.ts.map +0 -1
  118. package/lib/subcommands/rabbitmq-connection.js +0 -14
  119. package/lib/subcommands/rabbitmq-connection.js.map +0 -1
  120. package/src/local-connection/rabbitmq-connection.ts +0 -124
  121. package/src/subcommands/rabbitmq-connection.ts +0 -11
@@ -1,4 +1,4 @@
1
- import { logger, stringifyError } from 'alwaysai/lib/util';
1
+ import { stringifyError } from 'alwaysai/lib/util';
2
2
  import { uninstallApp, rollbackApp } from '../application-control';
3
3
  import { createAppBackup } from '../application-control/backup';
4
4
  import { AgentConfigFile } from '../infrastructure/agent-config';
@@ -12,6 +12,7 @@ import {
12
12
  ErrorFunction,
13
13
  SuccessFunction
14
14
  } from './transaction-manager';
15
+ import { logger } from '../util/logger';
15
16
 
16
17
  export interface HandlerContext {
17
18
  clientId: string;
@@ -52,7 +53,9 @@ export abstract class BaseHandler {
52
53
  await uninstallApp({ projectId });
53
54
  this.shadowHandler.clearProjectShadow(projectId);
54
55
  } catch (e) {
55
- logger.error(`Failed to uninstall ${projectId}!\n${stringifyError(e)}`);
56
+ logger.error(
57
+ `Failed to uninstall ${projectId}! Error:\n${stringifyError(e)}`
58
+ );
56
59
  throw e;
57
60
  }
58
61
  }
@@ -73,7 +76,7 @@ export abstract class BaseHandler {
73
76
  await createAppBackup({ projectId });
74
77
  } catch (e) {
75
78
  logger.error(
76
- `Could not create a backup for the project: ${projectId}!\n${stringifyError(
79
+ `Could not create a backup for the project: ${projectId}! Error:\n${stringifyError(
77
80
  e
78
81
  )}`
79
82
  );
@@ -87,14 +90,16 @@ export abstract class BaseHandler {
87
90
  return out;
88
91
  } catch (errorAppUpdate) {
89
92
  logger.error(
90
- `Failed to update ${projectId}!\n${stringifyError(errorAppUpdate)}`
93
+ `Failed to update ${projectId}! Error:\n${stringifyError(
94
+ errorAppUpdate
95
+ )}`
91
96
  );
92
97
  // If something goes wrong, first try to rollback
93
98
  try {
94
99
  await rollbackApp({ projectId });
95
100
  } catch (errorRollbackApp) {
96
101
  logger.error(
97
- `Application rollback failed for ${projectId}!\n${stringifyError(
102
+ `Application rollback failed for ${projectId}! Error:\n${stringifyError(
98
103
  errorRollbackApp
99
104
  )}`
100
105
  );
@@ -90,7 +90,7 @@ export class ConnectionManager extends MessageDispatcher<any> {
90
90
 
91
91
  this.device.on('error', function (e) {
92
92
  logger.error(
93
- `Error connecting to the AWS IoT Core!\n${stringifyError(e)}`
93
+ `Error connecting to the AWS IoT Core! Error:\n${stringifyError(e)}`
94
94
  );
95
95
  this.connected = false;
96
96
  });
@@ -114,7 +114,7 @@ export class ConnectionManager extends MessageDispatcher<any> {
114
114
  const jsonPacket = JSON.parse(payload);
115
115
  this.dispatch(topic, jsonPacket);
116
116
  } catch (e) {
117
- logger.error(`Error parsing message!\n${stringifyError(e)}`);
117
+ logger.error(`Error parsing message! Error:\n${stringifyError(e)}`);
118
118
  }
119
119
  });
120
120
  }
@@ -191,16 +191,12 @@ export async function runDeviceAgentCloudInterface() {
191
191
  }
192
192
 
193
193
  if (await requiredConfigFilesPresentAndValid()) {
194
- const deviceAgent = new DeviceAgentCloudConnection();
195
- if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
196
- const shadowHandler = deviceAgent.shadowHandler;
197
- const publisher = deviceAgent.publisher;
198
- const passthroughHandler = new PassthroughHandler(
199
- publisher,
200
- shadowHandler
201
- );
202
- await passthroughHandler.setup();
203
- }
194
+ const cloudConnection = new DeviceAgentCloudConnection();
195
+ const passthroughHandler = new PassthroughHandler(
196
+ cloudConnection.publisher,
197
+ cloudConnection.shadowHandler
198
+ );
199
+ await passthroughHandler.run();
204
200
  } else {
205
201
  throw new Error(
206
202
  "Set device agent to local mode and retry the 'aai-agent device init' command"
@@ -27,7 +27,7 @@ import {
27
27
  ModelsInstallResponseMessage
28
28
  } from '@alwaysai/device-agent-schemas/lib/app-action-schema';
29
29
  import { DeviceActionMessage } from '@alwaysai/device-agent-schemas/lib/device-action-schema';
30
- import { logger, stringifyError } from 'alwaysai/lib/util';
30
+ import { stringifyError } from 'alwaysai/lib/util';
31
31
  import {
32
32
  startApp,
33
33
  stopApp,
@@ -45,6 +45,7 @@ import {
45
45
  AppConfig,
46
46
  validateAppConfig
47
47
  } from '@alwaysai/app-configuration-schemas';
48
+ import { logger } from '../util/logger';
48
49
 
49
50
  export type AppContent = {
50
51
  projectId: string;
@@ -160,7 +161,7 @@ class AppStateMessageHandler
160
161
  });
161
162
  } catch (e) {
162
163
  logger.error(
163
- `Error processing application state control request for ${projectId}!\n${stringifyError(
164
+ `Error processing application state control request for ${projectId}! Error:\n${stringifyError(
164
165
  e
165
166
  )}`
166
167
  );
@@ -215,7 +216,7 @@ class AppVersionControlMessageHandler
215
216
  });
216
217
  } catch (e) {
217
218
  logger.error(
218
- `Error processing application install request for ${projectId}!\n${stringifyError(
219
+ `Error processing application install request for ${projectId}! Error:\n${stringifyError(
219
220
  e
220
221
  )}`
221
222
  );
@@ -310,7 +311,9 @@ class AppVersionControlMessageHandler
310
311
  }
311
312
  } catch (e) {
312
313
  logger.error(
313
- `Could not parse the appConfig for transaction!\n${stringifyError(e)}`
314
+ `Could not parse the appConfig for transaction! Error:\n${stringifyError(
315
+ e
316
+ )}`
314
317
  );
315
318
  }
316
319
  }
@@ -340,9 +343,9 @@ class AppVersionControlMessageHandler
340
343
  appContent.envVars = envvarsUpdate;
341
344
  }
342
345
  } catch (e) {
343
- // throw here
346
+ // TODO: throw here
344
347
  logger.error(
345
- `Could not parse the environment variables for transaction!\n${stringifyError(
348
+ `Could not parse the environment variables for transaction! Error:\n${stringifyError(
346
349
  e
347
350
  )}`
348
351
  );
@@ -610,7 +613,7 @@ class DeviceActionMessageHandler
610
613
  logger.error(
611
614
  `There was a problem performing device action '${
612
615
  message.payload.action
613
- }'!\n${stringifyError(e)}`
616
+ }'! Error: \n${stringifyError(e)}`
614
617
  );
615
618
  this.publisher.publishToClient(
616
619
  buildToClientStatusResponseMessage(
@@ -14,6 +14,9 @@ interface IntervalOptions {
14
14
  ms?: number;
15
15
  }
16
16
 
17
+ /*
18
+ Responsible for managing the lifecycle of periodic updates and streams.
19
+ */
17
20
  export class LiveUpdatesHandler {
18
21
  private killAllTimeout: ReturnType<typeof setTimeout>;
19
22
  private livingIntervals: Record<string, ReturnType<typeof setInterval>> = {};
@@ -31,12 +34,14 @@ export class LiveUpdatesHandler {
31
34
  transactionId?: string,
32
35
  options?: IntervalOptions
33
36
  ) {
37
+ logger.silly(`LiveUpdatesHandler: Enabling ${intervalType}`);
34
38
  this.restartKillAllTimeout();
35
39
 
36
40
  const key = this.generateIntervalKey(intervalType, transactionId);
37
41
 
38
42
  this.safeSetInterval(key, publishingFn, options);
39
43
 
44
+ // Call publishing function right away for immediate results
40
45
  await publishingFn();
41
46
  }
42
47
 
@@ -44,6 +49,7 @@ export class LiveUpdatesHandler {
44
49
  intervalType: ToClientMessageTypeValue,
45
50
  transactionId?: string
46
51
  ) {
52
+ logger.silly(`LiveUpdatesHandler: Disabling ${intervalType}`);
47
53
  const key = this.generateIntervalKey(intervalType, transactionId);
48
54
  clearInterval(this.livingIntervals[key]);
49
55
  delete this.livingIntervals[key];
@@ -54,7 +60,7 @@ export class LiveUpdatesHandler {
54
60
  streamGetter: () => Promise<NodeJS.ReadableStream | null>,
55
61
  publishingFn: (logChunk: string) => void
56
62
  ) {
57
- logger.info(`Starting log stream for ${projectId}`);
63
+ logger.info(`LiveUpdatesHandler: Starting log stream for ${projectId}`);
58
64
 
59
65
  this.livingStreams.add(projectId);
60
66
 
@@ -84,7 +90,7 @@ export class LiveUpdatesHandler {
84
90
  });
85
91
 
86
92
  readable.on('finished', () => {
87
- logger.info(`Strean complete. ProjectId: ${projectId}`);
93
+ logger.info(`Stream complete. ProjectId: ${projectId}`);
88
94
  });
89
95
  }
90
96
 
@@ -105,8 +111,8 @@ export class LiveUpdatesHandler {
105
111
  try {
106
112
  return await streamGetter();
107
113
  } catch (e) {
108
- logger.info(
109
- `Failed to start app logs, retrying in 1 second.\n${stringifyError(
114
+ logger.error(
115
+ `Failed to start app logs, retrying in 1 second. Error:\n${stringifyError(
110
116
  e
111
117
  )}`
112
118
  );
@@ -117,7 +123,8 @@ export class LiveUpdatesHandler {
117
123
  return null;
118
124
  }
119
125
 
120
- // do not await any functions in the setSafeInterval other than inside setInterval() as per EI-1694.
126
+ // Do not await any functions in setSafeInterval other than inside
127
+ // setInterval() as per EI-1694.
121
128
  private safeSetInterval(
122
129
  key: string,
123
130
  publishingFn: () => Promise<void>,
@@ -11,9 +11,9 @@ import {
11
11
  LOCAL_CONNECTION_ROUTING_KEY
12
12
  } from '../local-connection/constants';
13
13
  import {
14
- setupRabbitMQContainer,
14
+ runRabbitMQContainer,
15
15
  stopRabbitMQContainer
16
- } from '../local-connection/rabbitmq-connection';
16
+ } from '../local-connection/rabbitmq-container';
17
17
  import { logger } from '../util/logger';
18
18
  import sleep from '../util/sleep';
19
19
  import { Publisher } from './publisher';
@@ -23,6 +23,11 @@ const messageQueue: any[] = [];
23
23
  const ackQueue: any[] = [];
24
24
  const MAX_LOCAL_CONNECTION_ATTEMPTS = 10;
25
25
 
26
+ /*
27
+ Responsible for managing the lifecycle of the Local Connection container
28
+ (RabbitMQ), the connection to it, and the passthrough of messages received
29
+ from the local connection to endpoints of provided `Publisher`.
30
+ */
26
31
  export class PassthroughHandler {
27
32
  public publisher: Publisher;
28
33
  public shadowHandler: ShadowHandler;
@@ -35,8 +40,8 @@ export class PassthroughHandler {
35
40
  this.shadowHandler = shadowHandler;
36
41
  }
37
42
 
38
- runChannel = async () => {
39
- logger.debug('Beginning to consume packets');
43
+ private async runLocalConnectionChannel() {
44
+ logger.debug('Beginning to consume packets from Local Connection');
40
45
  await this.channel.consume(
41
46
  this.packetQueue,
42
47
  (msg) => {
@@ -84,12 +89,12 @@ export class PassthroughHandler {
84
89
  } else {
85
90
  this.channel.ack(msg);
86
91
  logger.debug(
87
- `Received & acknowledged a RabbitMQ Package of unknown structure: ${parsedPacket}`
92
+ `Received & acknowledged a RabbitMQ packet of unknown structure: ${parsedPacket}`
88
93
  );
89
94
  }
90
95
  } catch (e) {
91
96
  logger.error(
92
- `There was a problem parsing RabbitMQ packet!\n${stringifyError(
97
+ `There was a problem parsing RabbitMQ packet! Error:\n${stringifyError(
93
98
  e
94
99
  )}`
95
100
  );
@@ -103,12 +108,12 @@ export class PassthroughHandler {
103
108
  noAck: false // When true, RabbitMQ deletes message as soon as it is consumed
104
109
  }
105
110
  );
106
- };
111
+ }
107
112
 
108
- async establishLocalConnection(): Promise<void> {
113
+ private async establishLocalConnection(): Promise<void> {
109
114
  let connectAttempts = 0;
110
115
  let connected = false;
111
- logger.debug(`Establishing local connection...`);
116
+ logger.debug(`Establishing Local Connection...`);
112
117
  while (
113
118
  connectAttempts <= MAX_LOCAL_CONNECTION_ATTEMPTS &&
114
119
  this.connection === undefined
@@ -117,14 +122,40 @@ export class PassthroughHandler {
117
122
  this.connection = await amqp.connect(
118
123
  `amqp://${LOCAL_CONNECTION_HOST}:${LOCAL_CONNECTION_PORT}`
119
124
  );
120
- this.channel = await this.connection.createChannel();
121
- this.connection.on('error', async () => {
122
- logger.error(`Local connection failed. Attempting to reconnect...`);
123
- await stopRabbitMQContainer();
125
+
126
+ this.connection.on('error', async (e) => {
127
+ logger.error(
128
+ `Local Connection failed due to ${stringifyError(
129
+ e
130
+ )}. Reconnect will be handled by "close"`
131
+ );
132
+ });
133
+
134
+ this.connection.on('close', async () => {
135
+ // Close should be called for every connection loss, error or not.
136
+ logger.warn(
137
+ `Local Connection closed by server. Attempting to reconnect...`
138
+ );
139
+ const stopped = await stopRabbitMQContainer();
140
+ if (stopped === false) {
141
+ logger.warn(
142
+ 'Failed to stop Local Connection container. Restart may fail...'
143
+ );
144
+ }
124
145
  this.connection = undefined;
125
- await this.setup();
146
+ await this.run();
126
147
  });
127
148
 
149
+ this.connection.on('blocked', async () => {
150
+ logger.warn(`Local Connection blocked.`);
151
+ });
152
+
153
+ this.connection.on('unblocked', async () => {
154
+ logger.warn(`Local Connection unblocked.`);
155
+ });
156
+
157
+ this.channel = await this.connection.createChannel();
158
+
128
159
  connected = true;
129
160
  } catch (e) {
130
161
  const timeTillNextAttemptMs = 1000 + 1000 * connectAttempts;
@@ -143,7 +174,7 @@ export class PassthroughHandler {
143
174
  await this.channel.assertQueue(this.packetQueue, {
144
175
  durable: true
145
176
  });
146
- logger.info(`Local connection established.`);
177
+ logger.info(`Local Connection established.`);
147
178
  } else {
148
179
  throw new Error(
149
180
  'Unable to establish connection to alwaysAI Local Connection, please try restarting Device Agent.'
@@ -166,30 +197,40 @@ export class PassthroughHandler {
166
197
  );
167
198
  }
168
199
 
169
- async setup() {
170
- if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
171
- logger.debug(
172
- `Setting up alwaysAI Local Connection on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
173
- );
174
- await this.publishPassthroughStatusUpdate('starting');
175
- await setupRabbitMQContainer();
176
- try {
177
- await this.establishLocalConnection();
178
- await this.runChannel();
179
- await this.publishPassthroughStatusUpdate(
180
- 'running',
181
- `Passthrough running on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
182
- );
183
- } catch (e) {
184
- logger.error(
185
- `There was a problem maintaining RabbitMQ connection!\n${stringifyError(
186
- e
187
- )}`
188
- );
189
- await this.publishPassthroughStatusUpdate('error', stringifyError(e));
190
- }
191
- } else {
200
+ async run() {
201
+ if (ALWAYSAI_ANALYTICS_PASSTHROUGH === false) {
202
+ logger.debug(`alwaysAI Local Connection disabled`);
192
203
  await this.publishPassthroughStatusUpdate('disabled');
204
+ return;
193
205
  }
206
+
207
+ logger.debug(
208
+ `Setting up alwaysAI Local Connection on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
209
+ );
210
+ await this.publishPassthroughStatusUpdate('starting');
211
+ const containerUp = await runRabbitMQContainer();
212
+ if (!containerUp) {
213
+ await this.publishPassthroughStatusUpdate(
214
+ 'error',
215
+ 'Local Connection container failed to start!'
216
+ );
217
+ return;
218
+ }
219
+ try {
220
+ await this.establishLocalConnection();
221
+ await this.runLocalConnectionChannel();
222
+ } catch (e) {
223
+ logger.error(
224
+ `There was a problem maintaining RabbitMQ connection! Error:\n${stringifyError(
225
+ e
226
+ )}`
227
+ );
228
+ await this.publishPassthroughStatusUpdate('error', stringifyError(e));
229
+ return;
230
+ }
231
+ await this.publishPassthroughStatusUpdate(
232
+ 'running',
233
+ `Passthrough running on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
234
+ );
194
235
  }
195
236
  }
@@ -125,7 +125,7 @@ export class ShadowHandler {
125
125
  newAppCfg = JSON.parse(appConfig);
126
126
  } catch (e) {
127
127
  logger.error(
128
- `Could not parse the appConfig for transaction ${txId}!\n${stringifyError(
128
+ `Could not parse the appConfig for transaction ${txId}! Error:\n${stringifyError(
129
129
  e
130
130
  )}`
131
131
  );
@@ -172,7 +172,7 @@ export class ShadowHandler {
172
172
  newEnvVars = JSON.parse(envVars);
173
173
  } catch (e) {
174
174
  logger.error(
175
- `Could not parse the environment variables for transaction ${txId}!\n${stringifyError(
175
+ `Could not parse the environment variables for transaction ${txId}! Error:\n${stringifyError(
176
176
  e
177
177
  )}`
178
178
  );
@@ -493,7 +493,7 @@ export class ProjectShadowMessageHandler
493
493
  })
494
494
  .catch((e) => {
495
495
  logger.error(
496
- `There was an issue updating project shadow config for ${projectId}!\n${stringifyError(
496
+ `There was an issue updating project shadow config for ${projectId}! Error:\n${stringifyError(
497
497
  e
498
498
  )}`
499
499
  );
@@ -45,7 +45,9 @@ export const getAppCfgModelsDiff = async ({
45
45
  });
46
46
  } catch (e) {
47
47
  logger.error(
48
- `Error parsing app config update for ${projectId}!\n${stringifyError(e)}`
48
+ `Error parsing app config update for ${projectId}! Error:\n${stringifyError(
49
+ e
50
+ )}`
49
51
  );
50
52
  }
51
53
 
@@ -115,78 +115,97 @@ describe('Test Transaction Manager', () => {
115
115
  test('Attempt to start an ongoing transaction, results in failure', async () => {
116
116
  const txId = generateTxId();
117
117
  const projectId = generateRandomProjectId();
118
+ let error;
119
+ const errorFn = jest.fn().mockImplementation((txid, message) => {
120
+ error = message;
121
+ });
118
122
  await txnMgr.runTransactionStep({
119
123
  func: func_incomplete,
120
124
  projectId,
121
125
  txId,
122
126
  start: true,
123
- stepName: 'step1'
127
+ stepName: 'step1',
128
+ errorFn
129
+ });
130
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
131
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
132
+ expect(errorFn).toBeCalledTimes(0);
133
+
134
+ await txnMgr.runTransactionStep({
135
+ func: func_incomplete,
136
+ projectId,
137
+ txId,
138
+ start: true,
139
+ stepName: 'step2',
140
+ errorFn
124
141
  });
125
- try {
126
- await txnMgr.runTransactionStep({
127
- func: func_incomplete,
128
- projectId,
129
- txId,
130
- start: true,
131
- stepName: 'step2'
132
- });
133
- throw new Error('Expected starting transaction to fail!');
134
- } catch (e) {
135
- console.log(e);
136
- expect(e.code).toBe(txnMgr.Errors.TRANSACTION_ONGOING);
137
- }
138
142
  expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
139
143
  expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
144
+ expect(errorFn).toBeCalledTimes(1);
145
+ expect(error.includes(`Transaction ${txId} already ongoing`)).toBeTruthy();
140
146
  });
141
147
 
142
148
  test('Attempt to continue a transaction that is not ongoing', async () => {
143
149
  const txId = generateTxId();
144
150
  const projectId = generateRandomProjectId();
145
- try {
146
- await txnMgr.runTransactionStep({
147
- func: func_incomplete,
148
- projectId,
149
- txId,
150
- start: false,
151
- stepName: 'step1'
152
- });
153
- throw new Error('Expected continue transaction to fail!');
154
- } catch (e) {
155
- console.log(e);
156
- expect(e.code).toBe(txnMgr.Errors.TRANSACTION_NOT_ONGOING);
157
- }
151
+ let error;
152
+ const errorFn = jest.fn().mockImplementation((txid, message) => {
153
+ error = message;
154
+ });
155
+ await txnMgr.runTransactionStep({
156
+ func: func_incomplete,
157
+ projectId,
158
+ txId,
159
+ start: false,
160
+ stepName: 'step1',
161
+ errorFn
162
+ });
158
163
  expect(txnMgr.isOngoingTransaction(txId)).toBe(false);
159
164
  expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(false);
160
165
  expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
166
+ expect(errorFn).toBeCalledTimes(1);
167
+ expect(
168
+ error.includes(
169
+ `Cannot update transaction ${txId} since it doesn't exist!`
170
+ )
171
+ ).toBeTruthy();
161
172
  });
162
173
 
163
174
  test('Attempt to start a transaction for a project with an ongoing transaction, results in failure', async () => {
164
175
  const txId = generateTxId();
165
176
  const txId2 = generateTxId();
166
177
  const projectId = generateRandomProjectId();
178
+ let error;
179
+ const errorFn = jest.fn().mockImplementation((txid, message) => {
180
+ error = message;
181
+ });
167
182
  await txnMgr.runTransactionStep({
168
183
  func: func_incomplete,
169
184
  projectId,
170
185
  txId,
171
186
  start: true,
172
- stepName: 'step1'
187
+ stepName: 'step1',
188
+ errorFn
189
+ });
190
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
191
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
192
+ expect(errorFn).toBeCalledTimes(0);
193
+
194
+ await txnMgr.runTransactionStep({
195
+ func: func_incomplete,
196
+ projectId,
197
+ txId: txId2,
198
+ start: true,
199
+ stepName: 'step2',
200
+ errorFn
173
201
  });
174
- try {
175
- await txnMgr.runTransactionStep({
176
- func: func_incomplete,
177
- projectId,
178
- txId: txId2,
179
- start: true,
180
- stepName: 'step2'
181
- });
182
- throw new Error('Expected start transaction to fail!');
183
- } catch (e) {
184
- console.log(e);
185
- expect(e.code).toBe(txnMgr.Errors.PROJECT_TRANSACTION_ONGOING);
186
- }
187
202
  expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
188
203
  expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
189
204
  expect(txnMgr.getProjectFromTransaction(txId2)).toBeUndefined();
205
+ expect(errorFn).toBeCalledTimes(1);
206
+ expect(
207
+ error.includes(`Project ${projectId} already has an ongoing transaction`)
208
+ ).toBeTruthy();
190
209
  });
191
210
 
192
211
  test('Handle error in step function', async () => {
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  ToClientStatusResponseMessage,
3
- buildToClientStatusResponseMessage,
4
3
  keyMirrors
5
4
  } from '@alwaysai/device-agent-schemas';
6
5
  import { LiveUpdatesHandler } from './live-updates-handler';
@@ -39,7 +38,7 @@ export class TransactionManager {
39
38
  if (this.detailsByTx[txId]) {
40
39
  const txnDetails = this.detailsByTx[txId];
41
40
  throw new CodedError(
42
- `Transaction ${txId} already ongoing!\n${JSON.stringify(
41
+ `Transaction ${txId} already ongoing:\n${JSON.stringify(
43
42
  txnDetails,
44
43
  null,
45
44
  2
@@ -52,7 +51,7 @@ export class TransactionManager {
52
51
  if (this.detailsByProject[projectId]) {
53
52
  const txnDetails = this.detailsByProject[projectId];
54
53
  throw new CodedError(
55
- `Project ${projectId} already has an ongoing transaction!\n${JSON.stringify(
54
+ `Project ${projectId} already has an ongoing transaction:\n${JSON.stringify(
56
55
  txnDetails,
57
56
  null,
58
57
  2
@@ -126,16 +125,30 @@ export class TransactionManager {
126
125
  errorFn,
127
126
  successFn
128
127
  } = props;
129
- if (start) {
130
- await this.startTransaction(
131
- txId,
132
- projectId,
133
- liveUpdatesPublishFn,
134
- stepName
128
+ try {
129
+ if (start) {
130
+ await this.startTransaction(
131
+ txId,
132
+ projectId,
133
+ liveUpdatesPublishFn,
134
+ stepName
135
+ );
136
+ } else {
137
+ this.updateTransaction(txId, stepName);
138
+ }
139
+ } catch (e) {
140
+ logger.error(
141
+ `Failed to start or update cmd for ${projectId}! Error:\n${stringifyError(
142
+ e
143
+ )}`
135
144
  );
136
- } else {
137
- this.updateTransaction(txId, stepName);
145
+
146
+ if (errorFn) {
147
+ errorFn(txId, e.message);
148
+ }
149
+ return;
138
150
  }
151
+
139
152
  try {
140
153
  const completed = await func();
141
154
  if (completed) {
@@ -146,13 +159,14 @@ export class TransactionManager {
146
159
  }
147
160
  } catch (e) {
148
161
  logger.error(
149
- `Failed to execute cmd for ${projectId}!\n${stringifyError(e)}`
162
+ `Failed to execute cmd for ${projectId}! Error:\n${stringifyError(e)}`
150
163
  );
151
164
 
152
165
  this.completeTransaction(txId);
153
166
  if (errorFn) {
154
167
  errorFn(txId, e.message);
155
168
  }
169
+ return;
156
170
  }
157
171
  }
158
172