@alwaysai/device-agent 1.2.0 → 1.3.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 (84) hide show
  1. package/lib/application-control/environment-variables.d.ts +1 -1
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -2
  4. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  5. package/lib/cloud-connection/device-agent-cloud-connection.js +80 -59
  6. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  7. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  8. package/lib/cloud-connection/live-updates-handler.js +2 -4
  9. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  10. package/lib/cloud-connection/live-updates-handler.test.js +1 -1
  11. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  12. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  13. package/lib/cloud-connection/passthrough-handler.js +3 -6
  14. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  15. package/lib/cloud-connection/publisher.d.ts +5 -4
  16. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  17. package/lib/cloud-connection/publisher.js +9 -8
  18. package/lib/cloud-connection/publisher.js.map +1 -1
  19. package/lib/cloud-connection/shadow-handler.d.ts +1 -0
  20. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  21. package/lib/cloud-connection/shadow-handler.js +5 -6
  22. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  23. package/lib/cloud-connection/shadow-handler.test.js +9 -0
  24. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  25. package/lib/cloud-connection/transaction-manager.d.ts +10 -0
  26. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -0
  27. package/lib/cloud-connection/transaction-manager.js +41 -0
  28. package/lib/cloud-connection/transaction-manager.js.map +1 -0
  29. package/lib/cloud-connection/transaction-manager.test.d.ts +2 -0
  30. package/lib/cloud-connection/transaction-manager.test.d.ts.map +1 -0
  31. package/lib/cloud-connection/transaction-manager.test.js +63 -0
  32. package/lib/cloud-connection/transaction-manager.test.js.map +1 -0
  33. package/lib/device-control/device-control.d.ts +35 -24
  34. package/lib/device-control/device-control.d.ts.map +1 -1
  35. package/lib/device-control/device-control.js +46 -11
  36. package/lib/device-control/device-control.js.map +1 -1
  37. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  38. package/lib/subcommands/app/env-vars.js +15 -9
  39. package/lib/subcommands/app/env-vars.js.map +1 -1
  40. package/lib/subcommands/device/clean.d.ts.map +1 -1
  41. package/lib/subcommands/device/clean.js +14 -12
  42. package/lib/subcommands/device/clean.js.map +1 -1
  43. package/lib/subcommands/device/device.d.ts +1 -0
  44. package/lib/subcommands/device/device.d.ts.map +1 -1
  45. package/lib/subcommands/device/device.js +23 -16
  46. package/lib/subcommands/device/device.js.map +1 -1
  47. package/lib/subcommands/device/index.js +1 -1
  48. package/lib/subcommands/device/index.js.map +1 -1
  49. package/lib/util/logger.d.ts.map +1 -1
  50. package/lib/util/logger.js +1 -1
  51. package/lib/util/logger.js.map +1 -1
  52. package/lib/util/safe-rimraf.d.ts +2 -0
  53. package/lib/util/safe-rimraf.d.ts.map +1 -0
  54. package/lib/util/safe-rimraf.js +16 -0
  55. package/lib/util/safe-rimraf.js.map +1 -0
  56. package/package.json +3 -2
  57. package/readme.md +1 -0
  58. package/src/application-control/environment-variables.ts +1 -1
  59. package/src/cloud-connection/device-agent-cloud-connection.ts +124 -80
  60. package/src/cloud-connection/live-updates-handler.test.ts +2 -2
  61. package/src/cloud-connection/live-updates-handler.ts +2 -8
  62. package/src/cloud-connection/passthrough-handler.ts +8 -7
  63. package/src/cloud-connection/publisher.ts +27 -10
  64. package/src/cloud-connection/shadow-handler.test.ts +9 -0
  65. package/src/cloud-connection/shadow-handler.ts +6 -14
  66. package/src/cloud-connection/transaction-manager.test.ts +73 -0
  67. package/src/cloud-connection/transaction-manager.ts +43 -0
  68. package/src/device-control/device-control.ts +50 -12
  69. package/src/subcommands/app/env-vars.ts +17 -10
  70. package/src/subcommands/device/clean.ts +22 -13
  71. package/src/subcommands/device/device.ts +31 -20
  72. package/src/subcommands/device/index.ts +2 -2
  73. package/src/util/logger.ts +15 -10
  74. package/src/util/safe-rimraf.ts +14 -0
  75. package/lib/cloud-connection/transaction-queue.d.ts +0 -12
  76. package/lib/cloud-connection/transaction-queue.d.ts.map +0 -1
  77. package/lib/cloud-connection/transaction-queue.js +0 -38
  78. package/lib/cloud-connection/transaction-queue.js.map +0 -1
  79. package/lib/cloud-connection/transaction-queue.test.d.ts +0 -2
  80. package/lib/cloud-connection/transaction-queue.test.d.ts.map +0 -1
  81. package/lib/cloud-connection/transaction-queue.test.js +0 -46
  82. package/lib/cloud-connection/transaction-queue.test.js.map +0 -1
  83. package/src/cloud-connection/transaction-queue.test.ts +0 -55
  84. package/src/cloud-connection/transaction-queue.ts +0 -40
@@ -1,6 +1,8 @@
1
1
  import { LiveUpdatesHandler } from './live-updates-handler';
2
2
  import { Publisher } from './publisher';
3
3
 
4
+ global.setTimeout = jest.fn() as unknown as typeof setTimeout;
5
+
4
6
  const testTrueToggles = {
5
7
  deviceStats: true,
6
8
  appState: true
@@ -15,8 +17,6 @@ const mockClient = jest.fn();
15
17
  const clientId = 'test-client';
16
18
  const emptyTxId = '';
17
19
 
18
- jest.spyOn(global, 'setTimeout');
19
-
20
20
  // NOTE: this was the way I found to mock private class functions
21
21
  const mockStartPublishingLiveUpdates = jest.spyOn(
22
22
  LiveUpdatesHandler.prototype as any,
@@ -69,7 +69,7 @@ export class LiveUpdatesHandler {
69
69
  logChunk: logStr
70
70
  };
71
71
  const message = await buildAppLogsMessage(payload, this.clientId);
72
- this.publisher.publishToClient(message);
72
+ this.publisher.publishToClient(message, logger.silly);
73
73
  });
74
74
 
75
75
  readable.on('error', (error) => {
@@ -153,14 +153,8 @@ export class LiveUpdatesHandler {
153
153
  const payload: ToClientMessagePayload = await payloadBuilderFunction(
154
154
  ...args
155
155
  );
156
- logger.debug(
157
- `payload returned from builder: ${JSON.stringify(payload)}`
158
- );
159
156
  const message = await messageBuilderFunction(payload, txId);
160
- logger.debug(
161
- `message returned from builder: ${JSON.stringify(message)}`
162
- );
163
- this.publisher.publishToClient(message);
157
+ this.publisher.publishToClient(message, logger.silly);
164
158
  } catch (e) {
165
159
  logger.error(
166
160
  `Error publishing live updates for ${messageType}: ${e.message}`
@@ -92,22 +92,23 @@ function processPublish(passthroughHandler: PassthroughHandler) {
92
92
  );
93
93
  break;
94
94
  case 'heartbeat':
95
- logger.debug(`Recieved heartbeat packet: ${packet}`);
96
95
  passthroughHandler.channel.ack(msg);
97
- logger.debug(`Heartbeat package acknowledged`);
96
+ logger.debug(
97
+ `Heartbeat package received & acknowledged: ${packet}`
98
+ );
98
99
  break;
99
100
  default:
100
- logger.debug(`Recieved unknown 'action' packet: ${packet}`);
101
101
  passthroughHandler.channel.ack(msg);
102
- logger.debug(`Unknown 'action' package acknowledged`);
102
+ logger.debug(
103
+ `Unknown 'action' package received & acknowledged: ${packet}`
104
+ );
103
105
  break;
104
106
  }
105
107
  } else {
108
+ passthroughHandler.channel.ack(msg);
106
109
  logger.debug(
107
- `Received a RabbitMQ packet that did not have an 'action' field ${parsedPacket}`
110
+ `Received & acknowledged a RabbitMQ Package of unknown structure: ${parsedPacket}`
108
111
  );
109
- passthroughHandler.channel.ack(msg);
110
- logger.debug(`Package of unknown structure acknowledged`);
111
112
  }
112
113
  } catch (e) {
113
114
  logger.error(`There was a problem parsing RabbitMQ packet ${e}`);
@@ -5,6 +5,7 @@ import {
5
5
  getToClientTopic,
6
6
  getToCloudTopic
7
7
  } from '@alwaysai/device-agent-schemas';
8
+ import * as winston from 'winston';
8
9
 
9
10
  export class Publisher {
10
11
  private client: any;
@@ -19,8 +20,20 @@ export class Publisher {
19
20
  this.toCloudTopic = getToCloudTopic(this.clientId);
20
21
  }
21
22
 
22
- public publish(topic: string, payload: string) {
23
+ public publish(
24
+ topic: string,
25
+ payload: string,
26
+ customLogger: winston.LeveledLogMethod = logger.debug
27
+ ) {
23
28
  // TODO: topic validation
29
+ // By default, log the published message at debug level, unless otherwise specified
30
+ customLogger(
31
+ `Publishing message:\nTopic: ${topic}\nMessage: ${JSON.stringify(
32
+ JSON.parse(payload),
33
+ null,
34
+ 2
35
+ )}`
36
+ );
24
37
  this.client.publish(topic, payload, (err: any) => {
25
38
  if (err) {
26
39
  logger.error(
@@ -52,21 +65,25 @@ export class Publisher {
52
65
 
53
66
  public publishDeviceAgentMessage(
54
67
  topic: string,
55
- message: ToClientMessage | ToCloudMessage
68
+ message: ToClientMessage | ToCloudMessage,
69
+ logger?: winston.LeveledLogMethod
56
70
  ) {
57
71
  const messageStr = JSON.stringify(message);
58
- logger.debug(
59
- `Publishing message:\n${JSON.stringify({ topic, message }, null, 2)}`
60
- );
61
- this.publish(topic, messageStr);
72
+ this.publish(topic, messageStr, logger);
62
73
  }
63
74
 
64
- public publishToClient(message: ToClientMessage) {
65
- this.publishDeviceAgentMessage(this.toClientTopic, message);
75
+ public publishToClient(
76
+ message: ToClientMessage,
77
+ logger?: winston.LeveledLogMethod
78
+ ) {
79
+ this.publishDeviceAgentMessage(this.toClientTopic, message, logger);
66
80
  }
67
81
 
68
- public publishToCloud(message: ToCloudMessage) {
82
+ public publishToCloud(
83
+ message: ToCloudMessage,
84
+ logger?: winston.LeveledLogMethod
85
+ ) {
69
86
  // Can edit topic field in message here if we want
70
- this.publishDeviceAgentMessage(this.toCloudTopic, message);
87
+ this.publishDeviceAgentMessage(this.toCloudTopic, message, logger);
71
88
  }
72
89
  }
@@ -113,6 +113,7 @@ describe('Test Shadow Handler', () => {
113
113
  expect(updates.length).toBe(1);
114
114
  expect(updates[0]).toEqual({
115
115
  projectId: projectId1,
116
+ txId: expect.any(String),
116
117
  appCfgUpdate: {
117
118
  newAppCfg: appCfg1,
118
119
  updatedModels: {
@@ -157,6 +158,7 @@ describe('Test Shadow Handler', () => {
157
158
  expect(updates.length).toBe(1);
158
159
  expect(updates[0]).toEqual({
159
160
  projectId: projectId1,
161
+ txId: expect.any(String),
160
162
  appCfgUpdate: {
161
163
  newAppCfg: appCfg1,
162
164
  updatedModels: {
@@ -222,6 +224,7 @@ describe('Test Shadow Handler', () => {
222
224
  expect(updates.length).toBe(2);
223
225
  expect(updates[0]).toEqual({
224
226
  projectId: projectId1,
227
+ txId: expect.any(String),
225
228
  appCfgUpdate: {
226
229
  newAppCfg: appCfg1,
227
230
  updatedModels: {
@@ -232,6 +235,7 @@ describe('Test Shadow Handler', () => {
232
235
  });
233
236
  expect(updates[1]).toEqual({
234
237
  projectId: projectId2,
238
+ txId: expect.any(String),
235
239
  appCfgUpdate: {
236
240
  newAppCfg: appCfg2,
237
241
  updatedModels: {
@@ -274,6 +278,7 @@ describe('Test Shadow Handler', () => {
274
278
  expect(updates.length).toBe(1);
275
279
  expect(updates[0]).toEqual({
276
280
  projectId: projectId1,
281
+ txId: expect.any(String),
277
282
  appCfgUpdate: {
278
283
  newAppCfg: appCfg1
279
284
  }
@@ -337,6 +342,7 @@ describe('Test Shadow Handler', () => {
337
342
  expect(updates.length).toBe(1);
338
343
  expect(updates[0]).toEqual({
339
344
  projectId: projectId1,
345
+ txId: expect.any(String),
340
346
  envVarUpdate: {
341
347
  envVars: envVars1
342
348
  }
@@ -361,6 +367,7 @@ describe('Test Shadow Handler', () => {
361
367
  expect(updates.length).toBe(1);
362
368
  expect(updates[0]).toEqual({
363
369
  projectId: projectId1,
370
+ txId: expect.any(String),
364
371
  envVarUpdate: {
365
372
  envVars: envVars1
366
373
  }
@@ -391,12 +398,14 @@ describe('Test Shadow Handler', () => {
391
398
  expect(updates.length).toBe(2);
392
399
  expect(updates[0]).toEqual({
393
400
  projectId: projectId1,
401
+ txId: expect.any(String),
394
402
  envVarUpdate: {
395
403
  envVars: envVars1
396
404
  }
397
405
  });
398
406
  expect(updates[1]).toEqual({
399
407
  projectId: projectId2,
408
+ txId: expect.any(String),
400
409
  envVarUpdate: {
401
410
  envVars: envVars2
402
411
  }
@@ -7,6 +7,7 @@ import { logger } from '../util/logger';
7
7
  import { Publisher } from './publisher';
8
8
  import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
9
9
  import { getSystemInformation } from '../device-control/device-control';
10
+ import { generateTxId } from '@alwaysai/device-agent-schemas';
10
11
 
11
12
  export interface ShadowTopics {
12
13
  projects: {
@@ -35,6 +36,7 @@ export type EnvVarUpdate = {
35
36
 
36
37
  export type ShadowUpdate = {
37
38
  projectId: string;
39
+ txId: string;
38
40
  appCfgUpdate?: AppConfigUpdate;
39
41
  envVarUpdate?: EnvVarUpdate;
40
42
  };
@@ -77,7 +79,9 @@ export class ShadowHandler {
77
79
 
78
80
  for (const projectId of deltaKeys) {
79
81
  const projectShadow = delta[projectId];
80
- const shadowUpdate: ShadowUpdate = { projectId };
82
+ // For incoming shadow updates, there will be no TxID, so it needs to be generated here.
83
+ const txId = generateTxId();
84
+ const shadowUpdate: ShadowUpdate = { projectId, txId };
81
85
 
82
86
  if (projectShadow.appConfig) {
83
87
  const newAppCfg = JSON.parse(projectShadow.appConfig);
@@ -140,7 +144,7 @@ export class ShadowHandler {
140
144
  case this.shadowTopics.projects.updateDelta:
141
145
  if (clientToken === this.clientId) {
142
146
  logger.debug(
143
- `Ignoring message sent from self: ${JSON.stringify(
147
+ `Ignoring delta caused by Device Agent: ${JSON.stringify(
144
148
  { topic, payload },
145
149
  null,
146
150
  2
@@ -179,9 +183,6 @@ export class ShadowHandler {
179
183
  clientToken: this.clientId
180
184
  };
181
185
  const topic = this.shadowTopics.systemInfo.update;
182
- logger.debug(
183
- `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
184
- );
185
186
  this.publisher.publish(topic, JSON.stringify(packet));
186
187
  }
187
188
 
@@ -200,9 +201,6 @@ export class ShadowHandler {
200
201
  clientToken: this.clientId
201
202
  };
202
203
  const topic = this.shadowTopics.projects.update;
203
- logger.debug(
204
- `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
205
- );
206
204
  this.publisher.publish(topic, JSON.stringify(packet));
207
205
  }
208
206
 
@@ -211,9 +209,6 @@ export class ShadowHandler {
211
209
  const packet = {
212
210
  clientToken: this.clientId
213
211
  };
214
- logger.debug(
215
- `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
216
- );
217
212
  this.publisher.publish(topic, JSON.stringify(packet));
218
213
  }
219
214
 
@@ -232,9 +227,6 @@ export class ShadowHandler {
232
227
  },
233
228
  clientToken: this.clientId
234
229
  };
235
- logger.debug(
236
- `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
237
- );
238
230
  this.publisher.publish(topic, JSON.stringify(packet));
239
231
  }
240
232
  }
@@ -0,0 +1,73 @@
1
+ import { generateTxId } from '@alwaysai/device-agent-schemas';
2
+ import { TransactionManager } from './transaction-manager';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ describe('Test Transaction Manager', () => {
6
+ let txnMgr: TransactionManager;
7
+
8
+ beforeEach(() => {
9
+ txnMgr = new TransactionManager();
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ function generateRandomProjectId() {
14
+ return uuidv4();
15
+ }
16
+
17
+ test('Add a new transaction', async () => {
18
+ const txId = generateTxId();
19
+ const projectId = generateRandomProjectId();
20
+ txnMgr.addTransaction(txId, projectId);
21
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
22
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
23
+ });
24
+
25
+ test('Add multiple transactions to different projects', async () => {
26
+ const numTransactions = 3;
27
+ const projectTxIdList: any = [];
28
+ for (let i = 0; i < numTransactions; i++) {
29
+ const txId = generateTxId();
30
+ const projectId = generateRandomProjectId();
31
+ txnMgr.addTransaction(txId, projectId);
32
+ projectTxIdList.push({ projectId, txId });
33
+ }
34
+ projectTxIdList.forEach(({ txId, projectId }) => {
35
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
36
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
37
+ });
38
+ });
39
+
40
+ test('Attempt to add existing transaction to a project, results in failure', async () => {
41
+ const txId = generateTxId();
42
+ const projectId = generateRandomProjectId();
43
+ txnMgr.addTransaction(txId, projectId);
44
+ expect(() => txnMgr.addTransaction(txId, projectId)).toThrow(
45
+ `Transaction ID ${txId} already exists for this projectId ${projectId}!`
46
+ );
47
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
48
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
49
+ });
50
+
51
+ test('Attempt to add a transaction to a project with an ongoing transaction, results in failure', async () => {
52
+ const txId = generateTxId();
53
+ const txId2 = generateTxId();
54
+ const projectId = generateRandomProjectId();
55
+ txnMgr.addTransaction(txId, projectId);
56
+ expect(() => txnMgr.addTransaction(txId2, projectId)).toThrow(
57
+ `This Project ID ${projectId} already has an ongoing transaction!`
58
+ );
59
+ expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
60
+ expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
61
+ expect(txnMgr.getProjectFromTransaction(txId2)).toBeUndefined();
62
+ });
63
+
64
+ test('Test remove transaction from queue', async () => {
65
+ const txId = generateTxId();
66
+ const projectId = generateRandomProjectId();
67
+ txnMgr.addTransaction(txId, projectId);
68
+ txnMgr.completeTransaction(txId);
69
+
70
+ expect(txnMgr.getTransactionFromProject(projectId)).toBeUndefined();
71
+ expect(txnMgr.getProjectFromTransaction(txId)).toBeUndefined();
72
+ });
73
+ });
@@ -0,0 +1,43 @@
1
+ export class TransactionManager {
2
+ private txToProject: Record<string, string> = {};
3
+ private projectToTx: Record<string, string> = {};
4
+
5
+ constructor() {
6
+ this.txToProject = {};
7
+ this.projectToTx = {};
8
+ }
9
+
10
+ public addTransaction(txId: string, projectId: string) {
11
+ // Check if the Transaction already exists
12
+ if (this.txToProject[txId]) {
13
+ throw new Error(
14
+ `Transaction ID ${txId} already exists for this projectId ${projectId}!`
15
+ );
16
+ } else {
17
+ // Check if there is any ongoing Transactions for Project ID
18
+ if (this.projectToTx[projectId]) {
19
+ throw new Error(
20
+ `This Project ID ${projectId} already has an ongoing transaction!`
21
+ );
22
+ } else {
23
+ // Map the Transaction ID with Project ID
24
+ this.txToProject[txId] = projectId;
25
+ this.projectToTx[projectId] = txId;
26
+ }
27
+ }
28
+ }
29
+
30
+ public getTransactionFromProject(projectId: string) {
31
+ return this.projectToTx[projectId];
32
+ }
33
+
34
+ public getProjectFromTransaction(txId: string) {
35
+ return this.txToProject[txId];
36
+ }
37
+
38
+ public completeTransaction(txId: string) {
39
+ const projectId = this.txToProject[txId];
40
+ delete this.txToProject[txId];
41
+ delete this.projectToTx[projectId];
42
+ }
43
+ }
@@ -1,9 +1,11 @@
1
1
  import { SystemInformationPayload } from '@alwaysai/device-agent-schemas';
2
2
  import { logger } from '../util/logger';
3
- // eslint-disable-next-line
4
- const osu = require('node-os-utils');
5
- // eslint-disable-next-line
6
- const si = require('systeminformation');
3
+ import * as osu from 'node-os-utils';
4
+ import * as si from 'systeminformation';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const exec_promise = promisify(exec);
7
9
 
8
10
  // Device Stats
9
11
  export async function getCpuDetails() {
@@ -16,6 +18,8 @@ export async function getCpuDetails() {
16
18
  }
17
19
 
18
20
  export async function getDiskDetails() {
21
+ // Types incorrectly specify diskname as required instead of optional
22
+ // @ts-expect-error
19
23
  const driveInfo = await osu.drive.info();
20
24
  return {
21
25
  usedGb: parseFloat(driveInfo.usedGb),
@@ -26,8 +30,8 @@ export async function getDiskDetails() {
26
30
  export async function getMemDetails() {
27
31
  const memInfo = await osu.mem.info();
28
32
  return {
29
- usedMb: parseFloat(memInfo.usedMemMb),
30
- freeMb: parseFloat(memInfo.freeMemMb)
33
+ usedMb: memInfo.usedMemMb,
34
+ freeMb: memInfo.freeMemMb
31
35
  };
32
36
  }
33
37
 
@@ -52,7 +56,7 @@ export async function getCpuInfo() {
52
56
  vendor: cpuInfo.vendor,
53
57
  model: cpuInfo.model,
54
58
  cores: cpuInfo.cores,
55
- physicalCores: cpuInfo.phsyicalCores,
59
+ physicalCores: cpuInfo.physicalCores,
56
60
  efficiencyCores: cpuInfo.efficiencyCores,
57
61
  processors: cpuInfo.processors
58
62
  };
@@ -78,16 +82,19 @@ export async function getDeviceInfo() {
78
82
  model: deviceInfo.model,
79
83
  version: deviceInfo.version,
80
84
  serial:
81
- deviceInfo.serial && deviceInfo.serial !== '-' ? deviceInfo.serial : null,
85
+ deviceInfo.serial && deviceInfo.serial !== '-'
86
+ ? deviceInfo.serial
87
+ : undefined,
82
88
  virtual: deviceInfo.virtual
83
89
  };
84
90
  }
85
91
 
86
92
  export async function getNetworkInfo() {
87
93
  const networkInterfaces = await si.networkInterfaces();
88
- const defaultNetworkInterface = networkInterfaces.filter(
89
- (iface: any) => iface.ip4 !== '127.0.0.1'
90
- )[0];
94
+
95
+ const defaultNetworkInterface = Array.isArray(networkInterfaces)
96
+ ? networkInterfaces.filter((iface: any) => iface.ip4 !== '127.0.0.1')[0]
97
+ : networkInterfaces;
91
98
  return {
92
99
  ipv4Address: defaultNetworkInterface.ip4,
93
100
  ipv6Address: defaultNetworkInterface.ip6,
@@ -95,6 +102,17 @@ export async function getNetworkInfo() {
95
102
  };
96
103
  }
97
104
 
105
+ export async function getPackageVersions() {
106
+ // eslint-disable-next-line
107
+ const agentJson = require('../../package.json');
108
+ // eslint-disable-next-line
109
+ const deviceAgentSchemasJson = require('../../node_modules/@alwaysai/device-agent-schemas/package.json');
110
+ return {
111
+ agent: agentJson.version,
112
+ deviceAgentSchemas: deviceAgentSchemasJson.version
113
+ };
114
+ }
115
+
98
116
  export async function getSystemInformation(): Promise<SystemInformationPayload> {
99
117
  try {
100
118
  const systemInfo: SystemInformationPayload = {
@@ -102,7 +120,8 @@ export async function getSystemInformation(): Promise<SystemInformationPayload>
102
120
  cpu: await getCpuInfo(),
103
121
  disk: await getDiskInfo(),
104
122
  device: await getDeviceInfo(),
105
- network: await getNetworkInfo()
123
+ network: await getNetworkInfo(),
124
+ versions: await getPackageVersions()
106
125
  };
107
126
  return systemInfo;
108
127
  } catch (e) {
@@ -110,3 +129,22 @@ export async function getSystemInformation(): Promise<SystemInformationPayload>
110
129
  }
111
130
  return {};
112
131
  }
132
+
133
+ export async function reboot() {
134
+ try {
135
+ // FIXME: This command must always be run with sudo. The user must enable access
136
+ // in the sudoers file for it to work.
137
+ // If passwordless access is not set, will prompt user for password. Use
138
+ // timeout to break out of the prompt
139
+ const result = await exec_promise('sudo /sbin/shutdown -r now', {
140
+ timeout: 5000
141
+ });
142
+ logger.info(result.stdout.trim());
143
+ } catch (err) {
144
+ throw new Error(
145
+ `Could not reboot device. You may need to add passwordless access to '/sbin/shutdown'. ${JSON.stringify(
146
+ err
147
+ )}`
148
+ );
149
+ }
150
+ }
@@ -7,6 +7,7 @@ import {
7
7
  import { EnvVars, getAllEnvs } from '../../application-control';
8
8
  import { DeviceAgentCloudConnection } from '../../cloud-connection/device-agent-cloud-connection';
9
9
  import sleep from '../../util/sleep';
10
+ import { logger } from '../../util/logger';
10
11
 
11
12
  export const getAllEnvsCliLeaf = CliLeaf({
12
13
  name: 'get-all-envs',
@@ -49,25 +50,31 @@ export const setEnvCliLeaf = CliLeaf({
49
50
  if (nameVal.length !== 2) {
50
51
  throw new CliTerseError(`Invalid argument: ${arg}`);
51
52
  }
52
- envVars[service][nameVal[0]] = nameVal[1];
53
+ const value = nameVal[1] === '' ? null : nameVal[1];
54
+ envVars[service][nameVal[0]] = value;
53
55
  });
54
56
  const deviceAgent = new DeviceAgentCloudConnection();
55
57
  await deviceAgent.setupHandlers();
56
58
 
57
- const topic = deviceAgent.getShadowTopics().projects.updateDelta;
58
-
59
- const message = {
60
- version: 3,
61
- timestamp: 0,
59
+ // Update the shadow as a client
60
+ const topic = deviceAgent.getShadowTopics().projects.update;
61
+ const packet = {
62
62
  state: {
63
- [project]: {
64
- envVars
63
+ desired: {
64
+ [project]: {
65
+ envVars
66
+ }
65
67
  }
66
68
  },
67
- clientToken: 'not-self'
69
+ clientToken: 'client'
68
70
  };
71
+ logger.debug(
72
+ `Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
73
+ );
74
+ deviceAgent.publisher.publish(topic, JSON.stringify(packet));
75
+ // Sleep for extra time to ensure time for shadow response
76
+ await sleep(10000);
69
77
 
70
- await deviceAgent.handleMessage(topic, message);
71
78
  while (deviceAgent.isCmdInProgress(project)) {
72
79
  await sleep(1000);
73
80
  }
@@ -1,12 +1,12 @@
1
1
  import { CliLeaf } from '@alwaysai/alwayscli';
2
- import { rimraf } from 'rimraf';
3
2
  import { logger } from '../../util/logger';
4
3
  import { AgentConfigFile } from '../../infrastructure/agent-config';
5
4
  import { DeviceConfigFile } from 'alwaysai/lib/core/device';
6
5
  import {
7
6
  APP_ROOT,
8
7
  CREDENTIALS_FILE_PATH,
9
- DEVICE_AGENT_CFG_PATH
8
+ DEVICE_AGENT_CFG_PATH,
9
+ DEVICE_AGENT_DOCKER_COMPOSE_PATH
10
10
  } from '../../util/directories';
11
11
  import {
12
12
  checkRabbitMQContainerRunning,
@@ -14,33 +14,42 @@ import {
14
14
  } from '../../local-connection/rabbitmq-connection';
15
15
  import { stopApp } from '../../application-control';
16
16
  import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/paths';
17
+ import safeRimraf from '../../util/safe-rimraf';
17
18
 
18
19
  export const cleanCliLeaf = CliLeaf({
19
20
  name: 'clean',
20
21
  description: 'Remove all provisioning files',
21
22
  async action(_, opts) {
22
- if (await checkRabbitMQContainerRunning()) {
23
- await stopRabbitMQContainer();
23
+ logger.info('Removing provisioning files.');
24
+ try {
25
+ if (await checkRabbitMQContainerRunning()) {
26
+ await stopRabbitMQContainer();
27
+ }
28
+ } catch (e) {
29
+ logger.error(
30
+ `You may need to manually stop the container by running docker-compose down in the following directory: ${DEVICE_AGENT_DOCKER_COMPOSE_PATH}`
31
+ );
32
+ logger.debug(`Error in checking / stopping RabbitMQ container: ${e}`);
24
33
  }
25
- logger.debug(`Removing ${DEVICE_AGENT_CFG_PATH}`);
26
- await rimraf(DEVICE_AGENT_CFG_PATH);
34
+
35
+ await safeRimraf(DEVICE_AGENT_CFG_PATH);
36
+
27
37
  logger.debug('Checking for alwaysAI applications still running');
28
38
  const apps = await AgentConfigFile().getApps();
29
39
  for (const app of apps) {
30
40
  const { projectId } = app;
31
41
  await stopApp({ projectId });
32
42
  }
33
- logger.info('Cleaning device configuration');
34
- logger.debug(`Removing ${LOCAL_CERT_AND_KEY_DIR}`);
35
- await rimraf(LOCAL_CERT_AND_KEY_DIR);
43
+ await safeRimraf(LOCAL_CERT_AND_KEY_DIR);
44
+
36
45
  logger.debug(`Removing ${AgentConfigFile().path}`);
37
46
  AgentConfigFile().remove();
38
47
  logger.debug(`Removing ${DeviceConfigFile().path}`);
39
48
  DeviceConfigFile().remove();
40
- logger.debug(`Removing ${CREDENTIALS_FILE_PATH}`);
41
- await rimraf(CREDENTIALS_FILE_PATH);
42
- logger.debug(`Removing ${APP_ROOT}`);
43
- await rimraf(APP_ROOT);
49
+
50
+ await safeRimraf(CREDENTIALS_FILE_PATH);
51
+ await safeRimraf(APP_ROOT);
52
+
44
53
  logger.info('Device configuration cleaned');
45
54
  }
46
55
  });