@alwaysai/device-agent 1.3.0 → 1.3.1-2

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 (123) hide show
  1. package/lib/application-control/environment-variables.d.ts +1 -0
  2. package/lib/application-control/environment-variables.d.ts.map +1 -1
  3. package/lib/application-control/environment-variables.js +22 -20
  4. package/lib/application-control/environment-variables.js.map +1 -1
  5. package/lib/application-control/environment-variables.test.js +37 -2
  6. package/lib/application-control/environment-variables.test.js.map +1 -1
  7. package/lib/application-control/install.js +1 -1
  8. package/lib/application-control/install.js.map +1 -1
  9. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
  10. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  11. package/lib/cloud-connection/device-agent-cloud-connection.js +203 -178
  12. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  13. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  14. package/lib/cloud-connection/live-updates-handler.js +30 -25
  15. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  16. package/lib/cloud-connection/live-updates-handler.test.js +15 -0
  17. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  18. package/lib/cloud-connection/messages.d.ts +1 -3
  19. package/lib/cloud-connection/messages.d.ts.map +1 -1
  20. package/lib/cloud-connection/messages.js +1 -9
  21. package/lib/cloud-connection/messages.js.map +1 -1
  22. package/lib/cloud-connection/publisher.d.ts +1 -0
  23. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  24. package/lib/cloud-connection/publisher.js +3 -0
  25. package/lib/cloud-connection/publisher.js.map +1 -1
  26. package/lib/cloud-connection/shadow-handler.d.ts +10 -21
  27. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  28. package/lib/cloud-connection/shadow-handler.js +154 -100
  29. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  30. package/lib/cloud-connection/shadow-handler.test.js +140 -72
  31. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  32. package/lib/cloud-connection/transaction-manager.d.ts +26 -6
  33. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  34. package/lib/cloud-connection/transaction-manager.js +103 -22
  35. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  36. package/lib/cloud-connection/transaction-manager.test.js +179 -13
  37. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  38. package/lib/device-control/device-control.d.ts +2 -2
  39. package/lib/device-control/device-control.d.ts.map +1 -1
  40. package/lib/device-control/device-control.js.map +1 -1
  41. package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
  42. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  43. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  44. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  45. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  46. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  47. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  48. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  49. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  50. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  51. package/lib/secure-tunneling/spawner-detached.js +107 -0
  52. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  53. package/lib/subcommands/app/analytics.d.ts +10 -0
  54. package/lib/subcommands/app/analytics.d.ts.map +1 -0
  55. package/lib/subcommands/app/analytics.js +79 -0
  56. package/lib/subcommands/app/analytics.js.map +1 -0
  57. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  58. package/lib/subcommands/app/env-vars.js +11 -16
  59. package/lib/subcommands/app/env-vars.js.map +1 -1
  60. package/lib/subcommands/app/index.d.ts.map +1 -1
  61. package/lib/subcommands/app/index.js +3 -1
  62. package/lib/subcommands/app/index.js.map +1 -1
  63. package/lib/subcommands/app/models.d.ts +0 -5
  64. package/lib/subcommands/app/models.d.ts.map +1 -1
  65. package/lib/subcommands/app/models.js +16 -56
  66. package/lib/subcommands/app/models.js.map +1 -1
  67. package/lib/subcommands/app/status.d.ts +1 -0
  68. package/lib/subcommands/app/status.d.ts.map +1 -1
  69. package/lib/subcommands/app/status.js +14 -3
  70. package/lib/subcommands/app/status.js.map +1 -1
  71. package/lib/subcommands/app/version.d.ts +2 -1
  72. package/lib/subcommands/app/version.d.ts.map +1 -1
  73. package/lib/subcommands/app/version.js +16 -3
  74. package/lib/subcommands/app/version.js.map +1 -1
  75. package/lib/util/cloud-mode-ready.d.ts +1 -0
  76. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  77. package/lib/util/cloud-mode-ready.js +36 -1
  78. package/lib/util/cloud-mode-ready.js.map +1 -1
  79. package/lib/util/parsing.d.ts +2 -0
  80. package/lib/util/parsing.d.ts.map +1 -0
  81. package/lib/util/parsing.js +17 -0
  82. package/lib/util/parsing.js.map +1 -0
  83. package/package.json +4 -6
  84. package/readme.md +146 -92
  85. package/src/application-control/environment-variables.test.ts +43 -3
  86. package/src/application-control/environment-variables.ts +29 -19
  87. package/src/application-control/install.ts +1 -1
  88. package/src/cloud-connection/device-agent-cloud-connection.ts +272 -247
  89. package/src/cloud-connection/live-updates-handler.test.ts +20 -0
  90. package/src/cloud-connection/live-updates-handler.ts +45 -52
  91. package/src/cloud-connection/messages.ts +1 -14
  92. package/src/cloud-connection/publisher.ts +4 -0
  93. package/src/cloud-connection/shadow-handler.test.ts +150 -73
  94. package/src/cloud-connection/shadow-handler.ts +247 -126
  95. package/src/cloud-connection/transaction-manager.test.ts +193 -18
  96. package/src/cloud-connection/transaction-manager.ts +174 -26
  97. package/src/device-control/device-control.ts +3 -3
  98. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  99. package/src/secure-tunneling/secure-tunneling.ts +606 -0
  100. package/src/secure-tunneling/spawner-detached.ts +123 -0
  101. package/src/subcommands/app/analytics.ts +102 -0
  102. package/src/subcommands/app/env-vars.ts +18 -16
  103. package/src/subcommands/app/index.ts +4 -3
  104. package/src/subcommands/app/models.ts +25 -57
  105. package/src/subcommands/app/status.ts +20 -3
  106. package/src/subcommands/app/version.ts +19 -4
  107. package/src/util/cloud-mode-ready.ts +36 -0
  108. package/src/util/parsing.ts +11 -0
  109. package/lib/cloud-connection/cmd-status.d.ts +0 -8
  110. package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
  111. package/lib/cloud-connection/cmd-status.js +0 -62
  112. package/lib/cloud-connection/cmd-status.js.map +0 -1
  113. package/lib/cloud-connection/message-builder.d.ts +0 -7
  114. package/lib/cloud-connection/message-builder.d.ts.map +0 -1
  115. package/lib/cloud-connection/message-builder.js +0 -63
  116. package/lib/cloud-connection/message-builder.js.map +0 -1
  117. package/lib/secure-tunneling/index.d.ts +0 -5
  118. package/lib/secure-tunneling/index.d.ts.map +0 -1
  119. package/lib/secure-tunneling/index.js +0 -64
  120. package/lib/secure-tunneling/index.js.map +0 -1
  121. package/src/cloud-connection/cmd-status.ts +0 -71
  122. package/src/cloud-connection/message-builder.ts +0 -117
  123. package/src/secure-tunneling/index.ts +0 -74
@@ -3,6 +3,11 @@ import { Publisher } from './publisher';
3
3
 
4
4
  global.setTimeout = jest.fn() as unknown as typeof setTimeout;
5
5
 
6
+ // https://github.com/facebook/react-native/issues/35701
7
+ Object.defineProperty(global, 'performance', {
8
+ writable: true
9
+ });
10
+
6
11
  const testTrueToggles = {
7
12
  deviceStats: true,
8
13
  appState: true
@@ -66,4 +71,19 @@ describe('Test Live Updates Handler', () => {
66
71
  expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
67
72
  expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
68
73
  });
74
+
75
+ test('timeout turns off live updates', async () => {
76
+ jest.useFakeTimers({ legacyFakeTimers: true });
77
+
78
+ void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
79
+ expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
80
+ expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
81
+ expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
82
+
83
+ jest.runAllTimers();
84
+
85
+ expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
86
+ expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
87
+ expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
88
+ });
69
89
  });
@@ -1,27 +1,21 @@
1
1
  import {
2
2
  AppLogsPayload,
3
- ToClientMessagePayload,
4
3
  keyMirrors,
5
4
  LiveStateUpdatesTogglePayload,
6
- ToClientMessage
5
+ ToClientMessage,
6
+ buildAppLogsMessage,
7
+ buildAppStateMessage,
8
+ buildDeviceStatsMessage,
9
+ StatusResponsePayload,
10
+ buildToClientStatusResponseMessage
7
11
  } from '@alwaysai/device-agent-schemas';
8
12
  import { getAppLogs } from '../application-control';
9
13
  import { logger } from '../util/logger';
10
14
  import sleep from '../util/sleep';
11
15
  import { Publisher } from './publisher';
12
- import {
13
- getStatusResponsePayload,
14
- getAppStatePayload,
15
- getDeviceStatsPayload
16
- } from './messages';
16
+ import { getAppStatePayload, getDeviceStatsPayload } from './messages';
17
17
  import { ToClientMessageTypeValue } from '@alwaysai/device-agent-schemas';
18
18
  import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
19
- import {
20
- buildAppLogsMessage,
21
- buildAppStateMessage,
22
- buildDeviceStatsMessage,
23
- buildStatusResponseMessage
24
- } from './message-builder';
25
19
 
26
20
  const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
27
21
  ? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
@@ -48,8 +42,9 @@ export class LiveUpdatesHandler {
48
42
  private appLogStreams = new Set<string>();
49
43
  private transactionStatuses = new Set<string>();
50
44
 
51
- private async startAppLogStream(projectId: string) {
45
+ private async startAppLogStream(projectId: string, txId: string) {
52
46
  logger.info(`Starting log stream for ${projectId}`);
47
+
53
48
  this.appLogStreams.add(projectId);
54
49
  const readable = await getAppLogs({
55
50
  projectId,
@@ -68,7 +63,7 @@ export class LiveUpdatesHandler {
68
63
  projectId,
69
64
  logChunk: logStr
70
65
  };
71
- const message = await buildAppLogsMessage(payload, this.clientId);
66
+ const message = buildAppLogsMessage(this.clientId, payload, txId);
72
67
  this.publisher.publishToClient(message, logger.silly);
73
68
  });
74
69
 
@@ -117,7 +112,7 @@ export class LiveUpdatesHandler {
117
112
  this.liveUpdatesAlive.device_stats = toggles.deviceStats;
118
113
  }
119
114
  if (toggles.appState !== undefined) {
120
- this.liveUpdatesAlive.app_logs = toggles.appState;
115
+ this.liveUpdatesAlive.app_state = toggles.appState;
121
116
  }
122
117
  }
123
118
 
@@ -132,36 +127,25 @@ export class LiveUpdatesHandler {
132
127
  }, LIVE_UPDATES_TIMEOUT);
133
128
  }
134
129
 
135
- private async startPublishingLiveUpdates<T extends any[]>(
130
+ private async startPublishingLiveUpdates(
136
131
  messageType: ToClientMessageTypeValue,
137
- payloadBuilderFunction: (...args: T) => Promise<ToClientMessagePayload>,
138
- messageBuilderFunction: (
139
- payload: ToClientMessagePayload,
140
- txId: string
141
- ) => Promise<ToClientMessage>,
142
- args: T,
132
+ getMessage: () => Promise<ToClientMessage>,
143
133
  txId: string
144
134
  ) {
145
135
  logger.info(`Turned on live updates for ${messageType}`);
146
- // eslint-disable-next-line no-constant-condition
147
- while (true) {
148
- try {
149
- if (!this.continuePublishing(messageType, txId)) {
150
- logger.info(`Turned off live updates for ${messageType}`);
151
- break;
152
- }
153
- const payload: ToClientMessagePayload = await payloadBuilderFunction(
154
- ...args
155
- );
156
- const message = await messageBuilderFunction(payload, txId);
136
+ try {
137
+ while (this.continuePublishing(messageType, txId)) {
138
+ const message = await getMessage();
157
139
  this.publisher.publishToClient(message, logger.silly);
158
- } catch (e) {
159
- logger.error(
160
- `Error publishing live updates for ${messageType}: ${e.message}`
161
- );
140
+
141
+ await sleep(this.getLiveUpdatesInterval(messageType));
162
142
  }
163
- await sleep(this.getLiveUpdatesInterval(messageType));
143
+ } catch (e) {
144
+ logger.error(
145
+ `Error publishing live updates for ${messageType}: ${e.message}`
146
+ );
164
147
  }
148
+ logger.info(`Turned off live updates for ${messageType}`);
165
149
  }
166
150
 
167
151
  /*=================================================================
@@ -196,9 +180,16 @@ export class LiveUpdatesHandler {
196
180
  // Don't wait for this call to finish since it loops until disabled
197
181
  void this.startPublishingLiveUpdates(
198
182
  keyMirrors.toClientMessageType.status_response,
199
- getStatusResponsePayload,
200
- buildStatusResponseMessage,
201
- [keyMirrors.statusResponse.in_progress, ''],
183
+ async () => {
184
+ const payload: StatusResponsePayload = {
185
+ status: keyMirrors.statusResponse.in_progress
186
+ };
187
+ return buildToClientStatusResponseMessage(
188
+ this.clientId,
189
+ payload,
190
+ txId
191
+ );
192
+ },
202
193
  txId
203
194
  );
204
195
  }
@@ -220,41 +211,43 @@ export class LiveUpdatesHandler {
220
211
  const { deviceStats, appState, appLogs } = toggles;
221
212
  this.restartLiveUpdatesTimeout();
222
213
 
223
- const currentDeviceStats = this.getDeviceStatsLiveUpdates();
224
214
  if (deviceStats !== undefined) {
215
+ const currentDeviceStats = this.getDeviceStatsLiveUpdates();
225
216
  this.liveUpdatesAlive.device_stats = deviceStats;
226
217
  if (deviceStats && currentDeviceStats !== true) {
227
218
  // Don't wait for this call to finish since it loops until disabled
228
219
  void this.startPublishingLiveUpdates(
229
220
  keyMirrors.toClientMessageType.device_stats,
230
- getDeviceStatsPayload,
231
- buildDeviceStatsMessage,
232
- [],
221
+ async () => {
222
+ const payload = await getDeviceStatsPayload();
223
+ return buildDeviceStatsMessage(this.clientId, payload, txId);
224
+ },
233
225
  txId
234
226
  );
235
227
  }
236
228
  }
237
229
 
238
- const currentAppState = this.getAppStateLiveUpdates();
239
230
  if (appState !== undefined) {
231
+ const currentAppState = this.getAppStateLiveUpdates();
240
232
  this.liveUpdatesAlive.app_state = appState;
241
233
  if (appState && currentAppState !== true) {
242
234
  // Don't wait for this call to finish since it loops until disabled
243
235
  void this.startPublishingLiveUpdates(
244
236
  keyMirrors.toClientMessageType.app_state,
245
- getAppStatePayload,
246
- buildAppStateMessage,
247
- [],
237
+ async () => {
238
+ const payload = await getAppStatePayload();
239
+ return buildAppStateMessage(this.clientId, payload, txId);
240
+ },
248
241
  txId
249
242
  );
250
243
  }
251
244
  }
252
245
 
253
- const currentAppLogs = this.getAppLogsLiveUpdates();
254
246
  if (appLogs !== undefined) {
247
+ const currentAppLogs = this.getAppLogsLiveUpdates();
255
248
  if (appLogs.toggle && currentAppLogs !== true) {
256
249
  // Don't wait for this call to finish since it loops until disabled
257
- void this.startAppLogStream(appLogs.projectId);
250
+ void this.startAppLogStream(appLogs.projectId, txId);
258
251
  } else {
259
252
  this.appLogStreams.delete(appLogs.projectId);
260
253
  }
@@ -1,10 +1,8 @@
1
1
  import {
2
2
  AppState,
3
3
  AppStatePayload,
4
- DeviceStatsPayload,
5
- StatusResponsePayload
4
+ DeviceStatsPayload
6
5
  } from '@alwaysai/device-agent-schemas';
7
- import { StatusResponseValue } from '@alwaysai/device-agent-schemas/lib/constants';
8
6
  import { getAppState } from '../application-control';
9
7
  import {
10
8
  getCpuDetails,
@@ -27,17 +25,6 @@ export async function getAppStatePayload(): Promise<AppStatePayload> {
27
25
  return appStatePayload;
28
26
  }
29
27
 
30
- export async function getStatusResponsePayload(
31
- status: StatusResponseValue,
32
- message: string
33
- ): Promise<StatusResponsePayload> {
34
- const statusResponsePayload: StatusResponsePayload = {
35
- status,
36
- message
37
- };
38
- return statusResponsePayload;
39
- }
40
-
41
28
  export async function getDeviceStatsPayload(): Promise<DeviceStatsPayload> {
42
29
  const cpuDetails = await getCpuDetails();
43
30
  const diskDetails = await getDiskDetails();
@@ -86,4 +86,8 @@ export class Publisher {
86
86
  // Can edit topic field in message here if we want
87
87
  this.publishDeviceAgentMessage(this.toCloudTopic, message, logger);
88
88
  }
89
+
90
+ public getClientId(): string {
91
+ return this.clientId;
92
+ }
89
93
  }
@@ -2,6 +2,9 @@ import { AppConfig } from '@alwaysai/app-configuration-schemas';
2
2
  import { readAppCfgFile } from '../application-control';
3
3
  import { Publisher } from './publisher';
4
4
  import { ShadowHandler } from './shadow-handler';
5
+ import { Logger } from 'winston';
6
+ import { logger } from '../util/logger';
7
+ import { getShadowTopic } from '@alwaysai/device-agent-schemas';
5
8
 
6
9
  jest.mock('../application-control');
7
10
  jest.mock('./publisher');
@@ -22,8 +25,8 @@ describe('Test Shadow Handler', () => {
22
25
  test.skip('reject buffer payload', async () => {
23
26
  //FIXME: Invalid input is silently ignored, need input validation
24
27
  expect(async () => {
25
- await shadowHandler.handleShadowTopic({
26
- topic: shadowHandler.shadowTopics.projects.updateDelta,
28
+ await shadowHandler.handleProjectShadow({
29
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
27
30
  payload: Buffer.from('test-payload'),
28
31
  clientToken: ''
29
32
  });
@@ -55,8 +58,8 @@ describe('Test Shadow Handler', () => {
55
58
  }
56
59
  };
57
60
 
58
- const updates = await shadowHandler.handleShadowTopic({
59
- topic: shadowHandler.shadowTopics.projects.updateDelta,
61
+ const updates = await shadowHandler.handleProjectShadow({
62
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
60
63
  payload,
61
64
  clientToken: clientId
62
65
  });
@@ -65,11 +68,13 @@ describe('Test Shadow Handler', () => {
65
68
 
66
69
  test('handle project shadow empty delta', async () => {
67
70
  const payload = {
68
- [projectId1]: {}
71
+ desired: {
72
+ [projectId1]: {}
73
+ }
69
74
  };
70
75
 
71
- const updates = await shadowHandler.handleShadowTopic({
72
- topic: shadowHandler.shadowTopics.projects.updateDelta,
76
+ const updates = await shadowHandler.handleProjectShadow({
77
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
73
78
  payload,
74
79
  clientToken: ''
75
80
  });
@@ -98,15 +103,17 @@ describe('Test Shadow Handler', () => {
98
103
  };
99
104
 
100
105
  const payload = {
101
- delta: {
102
- [projectId1]: {
103
- appConfig: JSON.stringify(appCfg1)
106
+ state: {
107
+ desired: {
108
+ [projectId1]: {
109
+ appConfig: JSON.stringify(appCfg1)
110
+ }
104
111
  }
105
112
  }
106
113
  };
107
114
 
108
- const updates = await shadowHandler.handleShadowTopic({
109
- topic: shadowHandler.shadowTopics.projects.getAccepted,
115
+ const updates = await shadowHandler.handleProjectShadow({
116
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
110
117
  payload,
111
118
  clientToken: ''
112
119
  });
@@ -145,13 +152,17 @@ describe('Test Shadow Handler', () => {
145
152
  };
146
153
 
147
154
  const payload = {
148
- [projectId1]: {
149
- appConfig: JSON.stringify(appCfg1)
155
+ state: {
156
+ desired: {
157
+ [projectId1]: {
158
+ appConfig: JSON.stringify(appCfg1)
159
+ }
160
+ }
150
161
  }
151
162
  };
152
163
 
153
- const updates = await shadowHandler.handleShadowTopic({
154
- topic: shadowHandler.shadowTopics.projects.updateDelta,
164
+ const updates = await shadowHandler.handleProjectShadow({
165
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
155
166
  payload,
156
167
  clientToken: ''
157
168
  });
@@ -208,16 +219,20 @@ describe('Test Shadow Handler', () => {
208
219
  }
209
220
  };
210
221
  const payload = {
211
- [projectId1]: {
212
- appConfig: JSON.stringify(appCfg1)
213
- },
214
- [projectId2]: {
215
- appConfig: JSON.stringify(appCfg2)
222
+ state: {
223
+ desired: {
224
+ [projectId1]: {
225
+ appConfig: JSON.stringify(appCfg1)
226
+ },
227
+ [projectId2]: {
228
+ appConfig: JSON.stringify(appCfg2)
229
+ }
230
+ }
216
231
  }
217
232
  };
218
233
 
219
- const updates = await shadowHandler.handleShadowTopic({
220
- topic: shadowHandler.shadowTopics.projects.updateDelta,
234
+ const updates = await shadowHandler.handleProjectShadow({
235
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
221
236
  payload,
222
237
  clientToken: ''
223
238
  });
@@ -265,13 +280,17 @@ describe('Test Shadow Handler', () => {
265
280
  };
266
281
 
267
282
  const payload = {
268
- [projectId1]: {
269
- appConfig: JSON.stringify(appCfg1)
283
+ state: {
284
+ desired: {
285
+ [projectId1]: {
286
+ appConfig: JSON.stringify(appCfg1)
287
+ }
288
+ }
270
289
  }
271
290
  };
272
291
 
273
- const updates = await shadowHandler.handleShadowTopic({
274
- topic: shadowHandler.shadowTopics.projects.updateDelta,
292
+ const updates = await shadowHandler.handleProjectShadow({
293
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
275
294
  payload,
276
295
  clientToken: ''
277
296
  });
@@ -307,60 +326,110 @@ describe('Test Shadow Handler', () => {
307
326
  };
308
327
 
309
328
  const payload = {
310
- [projectId1]: {
311
- appConfig: JSON.stringify(appCfg1)
329
+ state: {
330
+ desired: {
331
+ [projectId1]: {
332
+ appConfig: JSON.stringify(appCfg1)
333
+ }
334
+ }
335
+ }
336
+ };
337
+
338
+ const updates = await shadowHandler.handleProjectShadow({
339
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
340
+ payload,
341
+ clientToken: ''
342
+ });
343
+ expect(updates.length).toBe(0);
344
+ });
345
+
346
+ test('handles an unparsable object in a project shadow appCfg delta', async () => {
347
+ const ogAppCfg1: AppConfig = {
348
+ scripts: {
349
+ start: 'python app.py'
350
+ },
351
+ models: {}
352
+ };
353
+ jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
354
+
355
+ // This appCfg is invalid on it's own (see values below)
356
+ const appCfg1 = {
357
+ scripts: {
358
+ start: 'python app.py'
359
+ },
360
+ models: {
361
+ 'alwaysai/mobilenet_ssd': '3', // string instead of int
362
+ 'alwaysai/yolo_v4': '5' // string instead of int
363
+ }
364
+ };
365
+
366
+ const payload = {
367
+ state: {
368
+ desired: {
369
+ [projectId1]: {
370
+ appConfig: appCfg1 // This is missing JSON.stringify() making this an unparsable object.
371
+ }
372
+ }
312
373
  }
313
374
  };
314
375
 
315
- const updates = await shadowHandler.handleShadowTopic({
316
- topic: shadowHandler.shadowTopics.projects.updateDelta,
376
+ const loggerSpy = jest
377
+ .spyOn(logger, 'error')
378
+ .mockReturnValue({} as unknown as Logger);
379
+
380
+ const updates = await shadowHandler.handleProjectShadow({
381
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
317
382
  payload,
318
383
  clientToken: ''
319
384
  });
385
+
320
386
  expect(updates.length).toBe(0);
387
+ expect(loggerSpy).toHaveBeenCalledWith(
388
+ expect.stringContaining('Error validating shadow update')
389
+ );
321
390
  });
322
391
  });
323
392
 
324
393
  describe('handle project shadow env vars', () => {
325
- test('handle project shadow env vars get response with delta', async () => {
394
+ test('handle a response from the getAccepted from the cloud', async () => {
326
395
  const envVars1 = {
327
396
  VAR0: 'value0'
328
397
  };
329
398
  const payload = {
330
- delta: {
331
- [projectId1]: {
332
- envVars: envVars1
399
+ state: {
400
+ reported: {
401
+ [projectId1]: {
402
+ envVars: { service: envVars1 }
403
+ }
333
404
  }
334
405
  }
335
406
  };
336
407
 
337
- const updates = await shadowHandler.handleShadowTopic({
338
- topic: shadowHandler.shadowTopics.projects.getAccepted,
408
+ const updates = await shadowHandler.handleProjectShadow({
409
+ topic: getShadowTopic(clientId, 'projects', 'get/accepted'),
339
410
  payload,
340
- clientToken: ''
341
- });
342
- expect(updates.length).toBe(1);
343
- expect(updates[0]).toEqual({
344
- projectId: projectId1,
345
- txId: expect.any(String),
346
- envVarUpdate: {
347
- envVars: envVars1
348
- }
411
+ clientToken: clientId
349
412
  });
413
+ expect(updates.length).toBe(0);
350
414
  });
351
-
352
415
  test('handle project shadow env vars update delta', async () => {
353
416
  const envVars1 = {
354
417
  VAR1: 'value1'
355
418
  };
356
419
  const payload = {
357
- [projectId1]: {
358
- envVars: envVars1
420
+ state: {
421
+ desired: {
422
+ [projectId1]: {
423
+ envVars: {
424
+ service: envVars1
425
+ }
426
+ }
427
+ }
359
428
  }
360
429
  };
361
430
 
362
- const updates = await shadowHandler.handleShadowTopic({
363
- topic: shadowHandler.shadowTopics.projects.updateDelta,
431
+ const updates = await shadowHandler.handleProjectShadow({
432
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
364
433
  payload,
365
434
  clientToken: ''
366
435
  });
@@ -369,29 +438,36 @@ describe('Test Shadow Handler', () => {
369
438
  projectId: projectId1,
370
439
  txId: expect.any(String),
371
440
  envVarUpdate: {
372
- envVars: envVars1
441
+ envVars: { service: envVars1 }
373
442
  }
374
443
  });
375
444
  });
376
445
 
377
446
  test('handle project shadow env vars update delta for two projects', async () => {
378
- const envVars1 = {
447
+ const service1 = {
379
448
  VAR1: 'value1'
380
449
  };
381
- const envVars2 = {
382
- VAR2: null
450
+ const service2 = {
451
+ VAR2: 'value2'
383
452
  };
453
+
384
454
  const payload = {
385
- [projectId1]: {
386
- envVars: envVars1
387
- },
388
- [projectId2]: {
389
- envVars: envVars2
455
+ state: {
456
+ desired: {
457
+ [projectId1]: {
458
+ envVars: {
459
+ service: service1
460
+ }
461
+ },
462
+ [projectId2]: {
463
+ envVars: { service: service2 }
464
+ }
465
+ }
390
466
  }
391
467
  };
392
468
 
393
- const updates = await shadowHandler.handleShadowTopic({
394
- topic: shadowHandler.shadowTopics.projects.updateDelta,
469
+ const updates = await shadowHandler.handleProjectShadow({
470
+ topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
395
471
  payload,
396
472
  clientToken: ''
397
473
  });
@@ -400,34 +476,35 @@ describe('Test Shadow Handler', () => {
400
476
  projectId: projectId1,
401
477
  txId: expect.any(String),
402
478
  envVarUpdate: {
403
- envVars: envVars1
479
+ envVars: { service: service1 }
404
480
  }
405
481
  });
406
482
  expect(updates[1]).toEqual({
407
483
  projectId: projectId2,
408
484
  txId: expect.any(String),
409
485
  envVarUpdate: {
410
- envVars: envVars2
486
+ envVars: { service: service2 }
411
487
  }
412
488
  });
413
489
  });
414
490
  });
415
491
 
416
492
  test('clear project shadow', async () => {
417
- shadowHandler.clearAppConfig(projectId1);
493
+ await shadowHandler.clearProjectShadow(projectId1);
494
+ // The order of reported and desired in the packet does not matter for the device agent, but the test is checking literal equality of expected output, and therefore the order matters here with reported being first.
418
495
  const packet = {
496
+ clientToken: clientId,
419
497
  state: {
420
- desired: {
498
+ reported: {
421
499
  [projectId1]: null
422
500
  },
423
- reported: {
501
+ desired: {
424
502
  [projectId1]: null
425
503
  }
426
- },
427
- clientToken: clientId
504
+ }
428
505
  };
429
506
  expect(jest.mocked(publisher.publish)).toBeCalledWith(
430
- shadowHandler.shadowTopics.projects.update,
507
+ getShadowTopic(clientId, 'projects', 'update'),
431
508
  JSON.stringify(packet)
432
509
  );
433
510
  });
@@ -453,15 +530,15 @@ describe('Test Shadow Handler', () => {
453
530
  clientToken: clientId
454
531
  };
455
532
  expect(jest.mocked(publisher.publish)).toBeCalledWith(
456
- shadowHandler.shadowTopics.projects.update,
533
+ getShadowTopic(clientId, 'projects', 'update'),
457
534
  JSON.stringify(packet)
458
535
  );
459
536
  });
460
537
 
461
538
  test('get shadow updates', async () => {
462
- shadowHandler.getShadowUpdates();
539
+ shadowHandler.getProjectShadowUpdates();
463
540
  expect(jest.mocked(publisher.publish)).toBeCalledWith(
464
- shadowHandler.shadowTopics.projects.get,
541
+ getShadowTopic(clientId, 'projects', 'get'),
465
542
  JSON.stringify({
466
543
  clientToken: clientId
467
544
  })