@alwaysai/device-agent 0.2.0 → 1.0.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.
- package/LICENSE +12 -0
- package/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +6 -1
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/index.d.ts +2 -2
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +2 -2
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +2 -2
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +10 -0
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/status.d.ts +3 -3
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +4 -4
- package/lib/application-control/status.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +7 -7
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +158 -100
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +9 -9
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +45 -42
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +6 -5
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/message-builder.d.ts +7 -0
- package/lib/cloud-connection/message-builder.d.ts.map +1 -0
- package/lib/cloud-connection/message-builder.js +63 -0
- package/lib/cloud-connection/message-builder.js.map +1 -0
- package/lib/cloud-connection/messages.d.ts +5 -15
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +22 -31
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +4 -5
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +12 -21
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/transaction-queue.d.ts +12 -0
- package/lib/cloud-connection/transaction-queue.d.ts.map +1 -0
- package/lib/cloud-connection/transaction-queue.js +38 -0
- package/lib/cloud-connection/transaction-queue.js.map +1 -0
- package/lib/cloud-connection/transaction-queue.test.d.ts +2 -0
- package/lib/cloud-connection/transaction-queue.test.d.ts.map +1 -0
- package/lib/cloud-connection/transaction-queue.test.js +46 -0
- package/lib/cloud-connection/transaction-queue.test.js.map +1 -0
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +5 -1
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +1 -0
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +5 -0
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +42 -1
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.js +1 -1
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +9 -11
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +3 -1
- package/lib/util/logger.js.map +1 -1
- package/package.json +5 -4
- package/readme.md +30 -1
- package/src/application-control/config.ts +5 -1
- package/src/application-control/index.ts +2 -2
- package/src/application-control/install.ts +17 -4
- package/src/application-control/status.ts +9 -8
- package/src/cloud-connection/device-agent-cloud-connection.ts +225 -132
- package/src/cloud-connection/live-updates-handler.test.ts +6 -5
- package/src/cloud-connection/live-updates-handler.ts +90 -64
- package/src/cloud-connection/message-builder.ts +117 -0
- package/src/cloud-connection/messages.ts +27 -35
- package/src/cloud-connection/publisher.ts +17 -30
- package/src/cloud-connection/transaction-queue.test.ts +55 -0
- package/src/cloud-connection/transaction-queue.ts +40 -0
- package/src/local-connection/rabbitmq-connection.ts +5 -1
- package/src/subcommands/app/index.ts +3 -1
- package/src/subcommands/app/models.ts +44 -0
- package/src/subcommands/app/status.ts +2 -2
- package/src/subcommands/app/version.ts +16 -14
- package/src/util/logger.ts +5 -1
|
@@ -11,14 +11,17 @@ import {
|
|
|
11
11
|
} from '../util/directories';
|
|
12
12
|
import {
|
|
13
13
|
keyMirrors,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
validateToClientMessage,
|
|
15
|
+
SignedUrlsRequestPayload,
|
|
16
|
+
getToDeviceTopic,
|
|
17
|
+
AppInstallResponsePayload,
|
|
18
|
+
validateToDeviceAgentMessage,
|
|
19
|
+
ToDeviceAgentMessage,
|
|
20
|
+
ToCloudMessage,
|
|
21
|
+
AppStateControlPayload,
|
|
22
|
+
AppVersionControlInstallPayload,
|
|
23
|
+
AppVersionControlUninstallPayload,
|
|
24
|
+
ToClientMessage
|
|
22
25
|
} from '@alwaysai/device-agent-schemas';
|
|
23
26
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
24
27
|
import { logger } from '../util/logger';
|
|
@@ -42,26 +45,34 @@ import { bootstrapProvision } from './bootstrap-provision';
|
|
|
42
45
|
import { CmdStatusManager } from './cmd-status';
|
|
43
46
|
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
44
47
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
48
|
+
import { getStatusResponsePayload } from './messages';
|
|
49
|
+
import { ModelsInstallResponsePayload } from '@alwaysai/device-agent-schemas';
|
|
47
50
|
import sleep from '../util/sleep';
|
|
51
|
+
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
52
|
+
import { TransactionQueue } from './transaction-queue';
|
|
53
|
+
import {
|
|
54
|
+
buildSignedUrlsRequestMessage,
|
|
55
|
+
buildStatusResponseMessage
|
|
56
|
+
} from './message-builder';
|
|
57
|
+
import { generateTxId } from '@alwaysai/device-agent-schemas';
|
|
48
58
|
|
|
49
59
|
export class DeviceAgentCloudConnection {
|
|
50
60
|
private shadowHandler: ShadowHandler;
|
|
51
61
|
public publisher: Publisher;
|
|
52
62
|
private cmdStatusMgr: CmdStatusManager;
|
|
53
63
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
64
|
+
private txnQueue: TransactionQueue;
|
|
54
65
|
private device = awsIot.device;
|
|
55
66
|
|
|
56
67
|
private clientId = getDeviceUuid();
|
|
57
68
|
private host = getIoTCoreEndpointUrl();
|
|
58
|
-
private readonly toDeviceTopic =
|
|
69
|
+
private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
|
|
59
70
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
60
71
|
// FIXME: Add support for multiple simultaneous project updates
|
|
61
72
|
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
62
73
|
|
|
63
74
|
private handleAppStateControl = async (
|
|
64
|
-
payload:
|
|
75
|
+
payload: AppStateControlPayload
|
|
65
76
|
): Promise<boolean> => {
|
|
66
77
|
const { baseCommand, projectId } = payload;
|
|
67
78
|
switch (baseCommand) {
|
|
@@ -79,17 +90,26 @@ export class DeviceAgentCloudConnection {
|
|
|
79
90
|
};
|
|
80
91
|
|
|
81
92
|
private handleAppVersionControl = async (
|
|
82
|
-
payload:
|
|
93
|
+
payload:
|
|
94
|
+
| AppVersionControlInstallPayload
|
|
95
|
+
| AppVersionControlUninstallPayload,
|
|
96
|
+
txId: string
|
|
83
97
|
): Promise<boolean> => {
|
|
84
98
|
switch (payload.baseCommand) {
|
|
85
99
|
case keyMirrors.appVersionControl.install: {
|
|
86
100
|
const { projectId, appReleaseHash } = payload;
|
|
87
101
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
103
|
+
signedUrlsRequest: {
|
|
104
|
+
projectId,
|
|
105
|
+
appReleaseHash
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const message = await buildSignedUrlsRequestMessage(
|
|
109
|
+
signedUrlsRequestPayload,
|
|
110
|
+
txId
|
|
111
|
+
);
|
|
112
|
+
await this.publishCloudRequest(message);
|
|
93
113
|
return false;
|
|
94
114
|
}
|
|
95
115
|
case keyMirrors.appVersionControl.uninstall: {
|
|
@@ -109,29 +129,15 @@ export class DeviceAgentCloudConnection {
|
|
|
109
129
|
}
|
|
110
130
|
};
|
|
111
131
|
|
|
112
|
-
private
|
|
113
|
-
|
|
114
|
-
): Promise<void> => {
|
|
115
|
-
switch (message.messageType) {
|
|
116
|
-
case keyMirrors.clientMessageType.live_state_updates:
|
|
117
|
-
await this.liveUpdatesHandler.handleToggles(message.liveUpdatesToggles);
|
|
118
|
-
break;
|
|
119
|
-
default:
|
|
120
|
-
logger.error(
|
|
121
|
-
`Invalid agent action message type from message '${message}'`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
private handleAppInstallCloudResponse = async (
|
|
127
|
-
payload: AppInstallCloudResponseMessage
|
|
132
|
+
private handleAppInstallCloudResponsePayload = async (
|
|
133
|
+
payload: AppInstallResponsePayload
|
|
128
134
|
): Promise<boolean> => {
|
|
129
135
|
const {
|
|
130
136
|
projectId,
|
|
131
137
|
appReleaseHash,
|
|
132
138
|
appInstallPayload,
|
|
133
139
|
modelsInstallPayload
|
|
134
|
-
} = payload.
|
|
140
|
+
} = payload.appInstallResponse;
|
|
135
141
|
const signedUrlsPayload = {
|
|
136
142
|
appInstallPayload,
|
|
137
143
|
modelsInstallPayload
|
|
@@ -144,8 +150,8 @@ export class DeviceAgentCloudConnection {
|
|
|
144
150
|
return true;
|
|
145
151
|
};
|
|
146
152
|
|
|
147
|
-
private
|
|
148
|
-
payload:
|
|
153
|
+
private handleModelsInstallCloudResponsePayload = async (
|
|
154
|
+
payload: ModelsInstallResponsePayload
|
|
149
155
|
): Promise<boolean> => {
|
|
150
156
|
const update = this.appCfgUpdateQueue.shift();
|
|
151
157
|
if (update === undefined) {
|
|
@@ -154,14 +160,14 @@ export class DeviceAgentCloudConnection {
|
|
|
154
160
|
);
|
|
155
161
|
}
|
|
156
162
|
const { appCfgUpdate, envVarUpdate } = update;
|
|
157
|
-
const projectId = payload.
|
|
163
|
+
const projectId = payload.modelsInstallResponse.projectId;
|
|
158
164
|
if (appCfgUpdate) {
|
|
159
165
|
await this.atomicApplicationUpdate(
|
|
160
166
|
updateModelsWithPresignedUrls,
|
|
161
167
|
[
|
|
162
168
|
{
|
|
163
169
|
projectId,
|
|
164
|
-
modelInstallPayloads: payload.
|
|
170
|
+
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
165
171
|
newAppCfg: appCfgUpdate.newAppCfg
|
|
166
172
|
}
|
|
167
173
|
],
|
|
@@ -179,8 +185,8 @@ export class DeviceAgentCloudConnection {
|
|
|
179
185
|
return true;
|
|
180
186
|
};
|
|
181
187
|
|
|
182
|
-
private async publishCloudRequest(
|
|
183
|
-
this.publisher.publishToCloud(
|
|
188
|
+
private async publishCloudRequest(message: ToCloudMessage) {
|
|
189
|
+
this.publisher.publishToCloud(message);
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
private subscribe(topic: string) {
|
|
@@ -204,21 +210,48 @@ export class DeviceAgentCloudConnection {
|
|
|
204
210
|
args: T,
|
|
205
211
|
projectId: string
|
|
206
212
|
) {
|
|
213
|
+
// First try to create a backup, so that there is one available if something goes wrong in the next try:catch.
|
|
214
|
+
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
215
|
+
try {
|
|
216
|
+
await createAppBackup({ projectId });
|
|
217
|
+
} catch (e) {
|
|
218
|
+
logger.error(
|
|
219
|
+
`Could not create a backup for the project: ${projectId}:\n${e.message}\n${e.stack}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
207
224
|
try {
|
|
208
225
|
const out: R = await func(...args);
|
|
209
226
|
await this.shadowHandler.updateProjectShadow(projectId);
|
|
210
227
|
return out;
|
|
211
|
-
} catch (
|
|
212
|
-
logger.error(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
228
|
+
} catch (errorAppUpdate) {
|
|
229
|
+
logger.error(
|
|
230
|
+
`Failed to update ${projectId}:\n${JSON.stringify(errorAppUpdate)}}`
|
|
231
|
+
);
|
|
232
|
+
// If something goes wrong, first try to rollback
|
|
216
233
|
try {
|
|
217
|
-
await
|
|
218
|
-
|
|
219
|
-
|
|
234
|
+
await rollbackApp({ projectId });
|
|
235
|
+
logger.error(
|
|
236
|
+
`Application update failed, rolled back to previous version: ${errorAppUpdate}`
|
|
237
|
+
);
|
|
238
|
+
} catch (errorRollbackApp) {
|
|
239
|
+
// and if that fails, uninstall the app as a last resort.
|
|
240
|
+
try {
|
|
241
|
+
await uninstallApp({ projectId });
|
|
242
|
+
} finally {
|
|
243
|
+
this.shadowHandler.clearAppConfig(projectId);
|
|
244
|
+
}
|
|
245
|
+
logger.error(
|
|
246
|
+
`Application update failed, rolled back to previous version: ${errorAppUpdate}`
|
|
247
|
+
);
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Application update and rollback failed, uninstalled the application: ${errorAppUpdate}`
|
|
250
|
+
);
|
|
220
251
|
}
|
|
221
|
-
throw
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Application update failed, rolled the application back: ${errorAppUpdate}`
|
|
254
|
+
);
|
|
222
255
|
}
|
|
223
256
|
}
|
|
224
257
|
|
|
@@ -227,40 +260,30 @@ export class DeviceAgentCloudConnection {
|
|
|
227
260
|
func: (...args: T) => Promise<boolean>;
|
|
228
261
|
args: T;
|
|
229
262
|
projectId: string;
|
|
230
|
-
|
|
231
|
-
}) {
|
|
232
|
-
const { func, args, projectId } = props;
|
|
233
|
-
let appReleaseHash = props.appReleaseHash;
|
|
234
|
-
if (appReleaseHash === undefined) {
|
|
235
|
-
try {
|
|
236
|
-
appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
237
|
-
projectId
|
|
238
|
-
});
|
|
239
|
-
} catch (e) {
|
|
240
|
-
logger.warn(`Unable to get app release hash for ${projectId}!`);
|
|
241
|
-
appReleaseHash = '';
|
|
242
|
-
}
|
|
243
|
-
}
|
|
263
|
+
txId: string;
|
|
264
|
+
}): Promise<boolean> {
|
|
265
|
+
const { func, args, projectId, txId } = props;
|
|
244
266
|
try {
|
|
245
267
|
await this.cmdStatusMgr.start(projectId);
|
|
246
|
-
await this.liveUpdatesHandler.
|
|
247
|
-
|
|
248
|
-
appReleaseHash
|
|
268
|
+
await this.liveUpdatesHandler.enableTransactionStatus({
|
|
269
|
+
txId
|
|
249
270
|
});
|
|
250
271
|
const completed = await func(...args);
|
|
251
272
|
if (completed) {
|
|
252
273
|
await this.cmdStatusMgr.stop(projectId);
|
|
253
|
-
await this.liveUpdatesHandler.
|
|
254
|
-
|
|
274
|
+
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
275
|
+
txId
|
|
255
276
|
});
|
|
277
|
+
const sucessStatusResponsePayload = await getStatusResponsePayload(
|
|
278
|
+
keyMirrors.statusResponse.success,
|
|
279
|
+
''
|
|
280
|
+
);
|
|
256
281
|
// Send final status message
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
'',
|
|
261
|
-
appReleaseHash
|
|
262
|
-
)
|
|
282
|
+
const message = await buildStatusResponseMessage(
|
|
283
|
+
sucessStatusResponsePayload,
|
|
284
|
+
txId
|
|
263
285
|
);
|
|
286
|
+
this.publisher.publishToClient(message);
|
|
264
287
|
}
|
|
265
288
|
return completed;
|
|
266
289
|
} catch (e) {
|
|
@@ -271,22 +294,26 @@ export class DeviceAgentCloudConnection {
|
|
|
271
294
|
|
|
272
295
|
// uninstall the failed app to put system back in good state
|
|
273
296
|
await this.cmdStatusMgr.stop(projectId);
|
|
274
|
-
await this.liveUpdatesHandler.
|
|
275
|
-
|
|
297
|
+
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
298
|
+
txId
|
|
276
299
|
});
|
|
300
|
+
const failureStatusResponsePayload = await getStatusResponsePayload(
|
|
301
|
+
keyMirrors.statusResponse.failure,
|
|
302
|
+
message
|
|
303
|
+
);
|
|
277
304
|
// Send final status message
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
message,
|
|
282
|
-
appReleaseHash
|
|
283
|
-
)
|
|
305
|
+
const failureStatusResponseMessage = await buildStatusResponseMessage(
|
|
306
|
+
failureStatusResponsePayload,
|
|
307
|
+
txId
|
|
284
308
|
);
|
|
309
|
+
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
310
|
+
return true;
|
|
285
311
|
}
|
|
286
312
|
}
|
|
287
313
|
|
|
288
314
|
private handleAppConfigUpdates = async (
|
|
289
|
-
updates: ShadowUpdate[]
|
|
315
|
+
updates: ShadowUpdate[],
|
|
316
|
+
txId: string
|
|
290
317
|
): Promise<boolean> => {
|
|
291
318
|
for (const update of updates) {
|
|
292
319
|
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
@@ -304,13 +331,17 @@ export class DeviceAgentCloudConnection {
|
|
|
304
331
|
updatedModels
|
|
305
332
|
)}`
|
|
306
333
|
);
|
|
307
|
-
|
|
308
|
-
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
334
|
+
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
309
335
|
modelsOnlyUrlsRequest: {
|
|
310
336
|
projectId,
|
|
311
337
|
models: updatedModels
|
|
312
338
|
}
|
|
313
|
-
}
|
|
339
|
+
};
|
|
340
|
+
const message = await buildSignedUrlsRequestMessage(
|
|
341
|
+
modelsOnlyUrlsRequestPayload,
|
|
342
|
+
txId
|
|
343
|
+
);
|
|
344
|
+
this.publisher.publishToCloud(message);
|
|
314
345
|
|
|
315
346
|
this.appCfgUpdateQueue.push(update);
|
|
316
347
|
return false;
|
|
@@ -357,7 +388,11 @@ export class DeviceAgentCloudConnection {
|
|
|
357
388
|
this.publisher = new Publisher(this.device, this.clientId);
|
|
358
389
|
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
359
390
|
this.cmdStatusMgr = new CmdStatusManager();
|
|
360
|
-
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
391
|
+
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
392
|
+
this.publisher,
|
|
393
|
+
this.clientId
|
|
394
|
+
);
|
|
395
|
+
this.txnQueue = new TransactionQueue();
|
|
361
396
|
|
|
362
397
|
this.subscribe(this.toDeviceTopic);
|
|
363
398
|
this.subscribe(this.secureTunnelNotifyTopic);
|
|
@@ -388,18 +423,18 @@ export class DeviceAgentCloudConnection {
|
|
|
388
423
|
await this.shadowHandler.updateProjectShadow(projectId);
|
|
389
424
|
}
|
|
390
425
|
|
|
391
|
-
public async
|
|
426
|
+
public async handleDeviceAgentMessage({
|
|
392
427
|
topic,
|
|
393
428
|
message
|
|
394
429
|
}: {
|
|
395
430
|
topic: string;
|
|
396
|
-
message:
|
|
431
|
+
message: ToDeviceAgentMessage;
|
|
397
432
|
}) {
|
|
398
|
-
const valid =
|
|
433
|
+
const valid = validateToDeviceAgentMessage(message);
|
|
399
434
|
if (!valid) {
|
|
400
435
|
logger.error(
|
|
401
436
|
`Error validating message: ${JSON.stringify(
|
|
402
|
-
{ topic, message, errors:
|
|
437
|
+
{ topic, message, errors: validateToDeviceAgentMessage.errors },
|
|
403
438
|
null,
|
|
404
439
|
2
|
|
405
440
|
)}`
|
|
@@ -407,64 +442,109 @@ export class DeviceAgentCloudConnection {
|
|
|
407
442
|
// TODO: Send generic error response
|
|
408
443
|
return;
|
|
409
444
|
}
|
|
410
|
-
const
|
|
411
|
-
switch (
|
|
412
|
-
case keyMirrors.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
445
|
+
const txId = message.txId;
|
|
446
|
+
switch (message.messageType) {
|
|
447
|
+
case keyMirrors.toDeviceAgentMessageType.app_state_control: {
|
|
448
|
+
// txId sent from cloud, just need to continue it
|
|
449
|
+
const payload = message.payload;
|
|
450
|
+
const projectId = payload.projectId;
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
this.txnQueue.addTxIdToQueue(txId, projectId);
|
|
454
|
+
const completed = await this.atomicCmd({
|
|
455
|
+
func: this.handleAppStateControl,
|
|
456
|
+
args: [message.payload],
|
|
457
|
+
projectId,
|
|
458
|
+
txId
|
|
459
|
+
});
|
|
460
|
+
if (completed) {
|
|
461
|
+
this.txnQueue.completeTxn(txId);
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
logger.error(
|
|
465
|
+
`Error processing application state control request: ${e}!`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
419
469
|
break;
|
|
420
470
|
}
|
|
421
|
-
case keyMirrors.
|
|
422
|
-
|
|
471
|
+
case keyMirrors.toDeviceAgentMessageType.app_version_control: {
|
|
472
|
+
// txId sent from cloud, just need to continue it
|
|
473
|
+
const payload = message.payload;
|
|
474
|
+
const projectId = payload.projectId;
|
|
423
475
|
const appReleaseHash =
|
|
424
|
-
payload.
|
|
425
|
-
|
|
426
|
-
? payload.appVersionControl.appReleaseHash
|
|
476
|
+
payload.baseCommand === keyMirrors.appVersionControl.install
|
|
477
|
+
? payload.appReleaseHash
|
|
427
478
|
: undefined;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
479
|
+
try {
|
|
480
|
+
this.txnQueue.addTxIdToQueue(txId, projectId);
|
|
481
|
+
const completed = await this.atomicCmd({
|
|
482
|
+
func: this.handleAppVersionControl,
|
|
483
|
+
args: [payload, txId],
|
|
484
|
+
projectId,
|
|
485
|
+
txId
|
|
486
|
+
});
|
|
487
|
+
if (completed) {
|
|
488
|
+
this.txnQueue.completeTxn(txId);
|
|
489
|
+
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
logger.error(`Error processing application install request: ${e}!`);
|
|
492
|
+
}
|
|
493
|
+
|
|
434
494
|
break;
|
|
435
495
|
}
|
|
436
|
-
case keyMirrors.
|
|
496
|
+
case keyMirrors.toDeviceAgentMessageType.live_state_updates: {
|
|
497
|
+
const payload = message.payload;
|
|
437
498
|
// TODO: Send response?
|
|
438
|
-
await this.
|
|
499
|
+
await this.liveUpdatesHandler.handleToggles(payload, txId);
|
|
439
500
|
break;
|
|
440
501
|
}
|
|
441
|
-
case keyMirrors.
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
502
|
+
case keyMirrors.toDeviceAgentMessageType.app_install_response: {
|
|
503
|
+
const payload = message.payload;
|
|
504
|
+
const { projectId, appReleaseHash } = payload.appInstallResponse;
|
|
505
|
+
if (txId !== this.txnQueue.getCurrentTxId()) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnQueue.getCurrentTxId()})!`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const completed = await this.atomicCmd({
|
|
511
|
+
func: this.handleAppInstallCloudResponsePayload,
|
|
445
512
|
args: [payload],
|
|
446
513
|
projectId,
|
|
447
|
-
|
|
514
|
+
txId
|
|
448
515
|
});
|
|
516
|
+
if (completed) {
|
|
517
|
+
this.txnQueue.completeTxn(txId);
|
|
518
|
+
}
|
|
449
519
|
|
|
450
520
|
break;
|
|
451
521
|
}
|
|
452
|
-
case keyMirrors.
|
|
522
|
+
case keyMirrors.toDeviceAgentMessageType.models_install_response: {
|
|
453
523
|
// This message doesn't have appReleaseHash in it's payload, but
|
|
454
524
|
// atomicCmd should be able to read it from the installed app
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
525
|
+
const payload = message.payload;
|
|
526
|
+
const { projectId } = payload.modelsInstallResponse;
|
|
527
|
+
if (txId !== this.txnQueue.getCurrentTxId()) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnQueue.getCurrentTxId()})!`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const completed = await this.atomicCmd({
|
|
533
|
+
func: this.handleModelsInstallCloudResponsePayload,
|
|
458
534
|
args: [payload],
|
|
459
|
-
projectId
|
|
535
|
+
projectId,
|
|
536
|
+
txId
|
|
460
537
|
});
|
|
538
|
+
if (completed) {
|
|
539
|
+
this.txnQueue.completeTxn(txId);
|
|
540
|
+
}
|
|
461
541
|
|
|
462
542
|
break;
|
|
463
543
|
}
|
|
464
544
|
default:
|
|
465
545
|
logger.error(
|
|
466
546
|
`Invalid client message: '${JSON.stringify(
|
|
467
|
-
{ topic, message },
|
|
547
|
+
{ topic, message, txId },
|
|
468
548
|
null,
|
|
469
549
|
2
|
|
470
550
|
)}'`
|
|
@@ -472,10 +552,14 @@ export class DeviceAgentCloudConnection {
|
|
|
472
552
|
}
|
|
473
553
|
}
|
|
474
554
|
|
|
475
|
-
public async handleMessage(
|
|
555
|
+
public async handleMessage(
|
|
556
|
+
topic: string,
|
|
557
|
+
message: ToDeviceAgentMessage | any
|
|
558
|
+
) {
|
|
476
559
|
logger.debug(
|
|
477
560
|
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
478
561
|
);
|
|
562
|
+
const txId = message.txId || generateTxId(); // txId should be passed in for DeviceAgentMessage messages but shadow updates won't have this
|
|
479
563
|
switch (topic) {
|
|
480
564
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
481
565
|
case this.shadowHandler.shadowTopics.projects.updateDelta: {
|
|
@@ -489,11 +573,20 @@ export class DeviceAgentCloudConnection {
|
|
|
489
573
|
// so this should be sufficient for now.
|
|
490
574
|
const projectId = shadowUpdates[0].projectId;
|
|
491
575
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
576
|
+
try {
|
|
577
|
+
this.txnQueue.addTxIdToQueue(txId, projectId);
|
|
578
|
+
const completed = await this.atomicCmd({
|
|
579
|
+
func: this.handleAppConfigUpdates,
|
|
580
|
+
args: [shadowUpdates, txId],
|
|
581
|
+
projectId,
|
|
582
|
+
txId
|
|
583
|
+
});
|
|
584
|
+
if (completed) {
|
|
585
|
+
this.txnQueue.completeTxn(txId);
|
|
586
|
+
}
|
|
587
|
+
} catch (e) {
|
|
588
|
+
logger.error(`Error processing model update request: ${e}!`);
|
|
589
|
+
}
|
|
497
590
|
} catch (e) {
|
|
498
591
|
logger.error(`Error handling shadow message: ${e.message}`);
|
|
499
592
|
}
|
|
@@ -506,7 +599,7 @@ export class DeviceAgentCloudConnection {
|
|
|
506
599
|
// Not handling these for now
|
|
507
600
|
break;
|
|
508
601
|
case this.toDeviceTopic:
|
|
509
|
-
await this.
|
|
602
|
+
await this.handleDeviceAgentMessage({
|
|
510
603
|
topic,
|
|
511
604
|
message
|
|
512
605
|
});
|
|
@@ -13,6 +13,7 @@ const testFalseToggles = {
|
|
|
13
13
|
|
|
14
14
|
const mockClient = jest.fn();
|
|
15
15
|
const clientId = 'test-client';
|
|
16
|
+
const emptyTxId = '';
|
|
16
17
|
|
|
17
18
|
jest.spyOn(global, 'setTimeout');
|
|
18
19
|
|
|
@@ -29,12 +30,12 @@ describe('Test Live Updates Handler', () => {
|
|
|
29
30
|
|
|
30
31
|
beforeEach(() => {
|
|
31
32
|
publisher = new Publisher(mockClient, clientId);
|
|
32
|
-
liveUpdatesHandler = new LiveUpdatesHandler(publisher);
|
|
33
|
+
liveUpdatesHandler = new LiveUpdatesHandler(publisher, clientId);
|
|
33
34
|
jest.clearAllMocks();
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
test('ignore subsequent enables', async () => {
|
|
37
|
-
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
38
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
38
39
|
// called twice, once for device stats, once for app state
|
|
39
40
|
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
|
|
40
41
|
// restartLiveUpdatesTimeout is always called once when handleToggles is called
|
|
@@ -42,14 +43,14 @@ describe('Test Live Updates Handler', () => {
|
|
|
42
43
|
|
|
43
44
|
// Second call -> should not call startPublishingLiveUpdates should not be called
|
|
44
45
|
jest.clearAllMocks();
|
|
45
|
-
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
46
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
46
47
|
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
|
|
47
48
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
test('test disable live updates', async () => {
|
|
51
52
|
// Test calling handleToggles one time, enabling it
|
|
52
|
-
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
53
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
53
54
|
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
|
|
54
55
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
55
56
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
@@ -58,7 +59,7 @@ describe('Test Live Updates Handler', () => {
|
|
|
58
59
|
|
|
59
60
|
// Check to see that attributes are properly set to false when disabled
|
|
60
61
|
jest.clearAllMocks();
|
|
61
|
-
void liveUpdatesHandler.handleToggles(testFalseToggles);
|
|
62
|
+
void liveUpdatesHandler.handleToggles(testFalseToggles, emptyTxId);
|
|
62
63
|
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
|
|
63
64
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
64
65
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|