@alwaysai/device-agent 1.3.0-1 → 1.3.1-1
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/lib/application-control/environment-variables.d.ts +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +22 -20
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +37 -2
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.js +1 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +4 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +149 -113
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +30 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +15 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +1 -3
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +1 -9
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +3 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +12 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +36 -22
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +84 -40
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +25 -9
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +97 -28
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +169 -22
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +90 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts +10 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -0
- package/lib/subcommands/app/analytics.js +83 -0
- package/lib/subcommands/app/analytics.js.map +1 -0
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +3 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -5
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +11 -47
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.d.ts +1 -0
- package/lib/subcommands/app/status.d.ts.map +1 -1
- package/lib/subcommands/app/status.js +14 -3
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts +2 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +16 -3
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/lib/util/parsing.d.ts +2 -0
- package/lib/util/parsing.d.ts.map +1 -0
- package/lib/util/parsing.js +17 -0
- package/lib/util/parsing.js.map +1 -0
- package/package.json +4 -6
- package/readme.md +146 -92
- package/src/application-control/environment-variables.test.ts +43 -3
- package/src/application-control/environment-variables.ts +29 -19
- package/src/application-control/install.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +216 -172
- package/src/cloud-connection/live-updates-handler.test.ts +20 -0
- package/src/cloud-connection/live-updates-handler.ts +45 -52
- package/src/cloud-connection/messages.ts +1 -14
- package/src/cloud-connection/publisher.ts +4 -0
- package/src/cloud-connection/shadow-handler.test.ts +93 -41
- package/src/cloud-connection/shadow-handler.ts +57 -21
- package/src/cloud-connection/transaction-manager.test.ts +183 -27
- package/src/cloud-connection/transaction-manager.ts +167 -36
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -0
- package/src/secure-tunneling/spawner-detached.ts +107 -0
- package/src/subcommands/app/analytics.ts +99 -0
- package/src/subcommands/app/index.ts +4 -3
- package/src/subcommands/app/models.ts +13 -49
- package/src/subcommands/app/status.ts +20 -3
- package/src/subcommands/app/version.ts +19 -4
- package/src/util/cloud-mode-ready.ts +36 -0
- package/src/util/parsing.ts +11 -0
- package/lib/cloud-connection/cmd-status.d.ts +0 -8
- package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
- package/lib/cloud-connection/cmd-status.js +0 -62
- package/lib/cloud-connection/cmd-status.js.map +0 -1
- package/lib/cloud-connection/message-builder.d.ts +0 -7
- package/lib/cloud-connection/message-builder.d.ts.map +0 -1
- package/lib/cloud-connection/message-builder.js +0 -63
- package/lib/cloud-connection/message-builder.js.map +0 -1
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/src/cloud-connection/cmd-status.ts +0 -71
- package/src/cloud-connection/message-builder.ts +0 -117
- package/src/secure-tunneling/index.ts +0 -74
|
@@ -1,74 +1,75 @@
|
|
|
1
1
|
// eslint-disable-next-line
|
|
2
2
|
const awsIot = require('aws-iot-device-sdk');
|
|
3
|
-
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
5
|
-
import {
|
|
6
|
-
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
7
|
-
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
8
|
-
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
9
|
-
DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
10
|
-
DEVICE_CERTIFICATE_FILE_PATH
|
|
11
|
-
} from '../util/directories';
|
|
12
3
|
import {
|
|
13
|
-
keyMirrors,
|
|
14
|
-
validateToClientMessage,
|
|
15
|
-
SignedUrlsRequestPayload,
|
|
16
|
-
getToDeviceTopic,
|
|
17
4
|
AppInstallResponsePayload,
|
|
18
|
-
validateToDeviceAgentMessage,
|
|
19
|
-
ToDeviceAgentMessage,
|
|
20
|
-
ToCloudMessage,
|
|
21
5
|
AppStateControlPayload,
|
|
22
6
|
AppVersionControlInstallPayload,
|
|
23
7
|
AppVersionControlUninstallPayload,
|
|
24
|
-
|
|
25
|
-
|
|
8
|
+
DeviceActionPayload,
|
|
9
|
+
ModelsInstallResponsePayload,
|
|
10
|
+
SignedUrlsRequestPayload,
|
|
11
|
+
ToCloudMessage,
|
|
12
|
+
ToDeviceAgentMessage,
|
|
13
|
+
getToDeviceTopic,
|
|
14
|
+
buildSignedUrlsRequestMessage,
|
|
15
|
+
buildToClientStatusResponseMessage,
|
|
16
|
+
StatusResponsePayload,
|
|
17
|
+
keyMirrors,
|
|
18
|
+
validateToDeviceAgentMessage
|
|
26
19
|
} from '@alwaysai/device-agent-schemas';
|
|
27
|
-
import {
|
|
28
|
-
import { logger } from '../util/logger';
|
|
29
|
-
import { cloudModeReady } from '../util/cloud-mode-ready';
|
|
30
|
-
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
20
|
+
import { existsSync } from 'fs';
|
|
31
21
|
import {
|
|
22
|
+
installApp,
|
|
23
|
+
restartApp,
|
|
24
|
+
setEnv,
|
|
32
25
|
startApp,
|
|
33
26
|
stopApp,
|
|
34
|
-
restartApp,
|
|
35
|
-
updateModelsWithPresignedUrls,
|
|
36
|
-
installApp,
|
|
37
27
|
uninstallApp,
|
|
38
28
|
updateAppCfg,
|
|
39
|
-
|
|
29
|
+
updateModelsWithPresignedUrls
|
|
40
30
|
} from '../application-control';
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import { Publisher } from './publisher';
|
|
44
|
-
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
45
|
-
import { bootstrapProvision } from './bootstrap-provision';
|
|
46
|
-
import { CmdStatusManager } from './cmd-status';
|
|
47
|
-
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
31
|
+
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
32
|
+
import { reboot } from '../device-control/device-control';
|
|
48
33
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
34
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
35
|
+
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
36
|
+
import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
|
|
37
|
+
import { cloudModeReady } from '../util/cloud-mode-ready';
|
|
38
|
+
import {
|
|
39
|
+
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
40
|
+
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
41
|
+
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
42
|
+
DEVICE_CERTIFICATE_FILE_PATH,
|
|
43
|
+
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
44
|
+
} from '../util/directories';
|
|
45
|
+
import { getDeviceUuid } from '../util/get-device-id';
|
|
46
|
+
import { logger } from '../util/logger';
|
|
51
47
|
import sleep from '../util/sleep';
|
|
52
|
-
import {
|
|
48
|
+
import { bootstrapProvision } from './bootstrap-provision';
|
|
49
|
+
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
50
|
+
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
51
|
+
import { Publisher } from './publisher';
|
|
52
|
+
import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
|
|
53
53
|
import { TransactionManager } from './transaction-manager';
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
import { reboot } from '../device-control/device-control';
|
|
54
|
+
import { exec } from 'child_process';
|
|
55
|
+
import { promisify } from 'util';
|
|
56
|
+
|
|
57
|
+
const exec_promise = promisify(exec);
|
|
59
58
|
|
|
60
59
|
export class DeviceAgentCloudConnection {
|
|
61
60
|
private shadowHandler: ShadowHandler;
|
|
62
61
|
public publisher: Publisher;
|
|
63
|
-
private cmdStatusMgr: CmdStatusManager;
|
|
64
62
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
65
63
|
private txnMgr: TransactionManager;
|
|
66
64
|
private device = awsIot.device;
|
|
67
65
|
|
|
68
66
|
private clientId = getDeviceUuid();
|
|
69
67
|
private host = getIoTCoreEndpointUrl();
|
|
68
|
+
private port = 8883;
|
|
70
69
|
private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
|
|
71
70
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
71
|
+
private readonly secureTunnelHandler =
|
|
72
|
+
SecureTunnelHandlerSingleton.getInstance();
|
|
72
73
|
// FIXME: Add support for multiple simultaneous project updates
|
|
73
74
|
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
74
75
|
|
|
@@ -106,7 +107,8 @@ export class DeviceAgentCloudConnection {
|
|
|
106
107
|
appReleaseHash
|
|
107
108
|
}
|
|
108
109
|
};
|
|
109
|
-
const message =
|
|
110
|
+
const message = buildSignedUrlsRequestMessage(
|
|
111
|
+
this.clientId,
|
|
110
112
|
signedUrlsRequestPayload,
|
|
111
113
|
txId
|
|
112
114
|
);
|
|
@@ -272,62 +274,6 @@ export class DeviceAgentCloudConnection {
|
|
|
272
274
|
}
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
// eslint-disable-next-line
|
|
276
|
-
private async atomicCmd<T extends any[]>(props: {
|
|
277
|
-
func: (...args: T) => Promise<boolean>;
|
|
278
|
-
args: T;
|
|
279
|
-
projectId: string;
|
|
280
|
-
txId: string;
|
|
281
|
-
}): Promise<boolean> {
|
|
282
|
-
const { func, args, projectId, txId } = props;
|
|
283
|
-
try {
|
|
284
|
-
await this.cmdStatusMgr.start(projectId);
|
|
285
|
-
await this.liveUpdatesHandler.enableTransactionStatus({
|
|
286
|
-
txId
|
|
287
|
-
});
|
|
288
|
-
const completed = await func(...args);
|
|
289
|
-
if (completed) {
|
|
290
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
291
|
-
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
292
|
-
txId
|
|
293
|
-
});
|
|
294
|
-
const successStatusResponsePayload = await getStatusResponsePayload(
|
|
295
|
-
keyMirrors.statusResponse.success,
|
|
296
|
-
''
|
|
297
|
-
);
|
|
298
|
-
// Send final status message
|
|
299
|
-
const message = await buildStatusResponseMessage(
|
|
300
|
-
successStatusResponsePayload,
|
|
301
|
-
txId
|
|
302
|
-
);
|
|
303
|
-
this.publisher.publishToClient(message);
|
|
304
|
-
}
|
|
305
|
-
return completed;
|
|
306
|
-
} catch (e) {
|
|
307
|
-
logger.error(
|
|
308
|
-
`Failed to execute cmd for ${projectId}:\n${e.message}\n${e.stack}`
|
|
309
|
-
);
|
|
310
|
-
const message: string = e.message;
|
|
311
|
-
|
|
312
|
-
// uninstall the failed app to put system back in good state
|
|
313
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
314
|
-
await this.liveUpdatesHandler.disableTransactionStatus({
|
|
315
|
-
txId
|
|
316
|
-
});
|
|
317
|
-
const failureStatusResponsePayload = await getStatusResponsePayload(
|
|
318
|
-
keyMirrors.statusResponse.failure,
|
|
319
|
-
message
|
|
320
|
-
);
|
|
321
|
-
// Send final status message
|
|
322
|
-
const failureStatusResponseMessage = await buildStatusResponseMessage(
|
|
323
|
-
failureStatusResponsePayload,
|
|
324
|
-
txId
|
|
325
|
-
);
|
|
326
|
-
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
277
|
private handleAppConfigUpdate = async (
|
|
332
278
|
update: ShadowUpdate,
|
|
333
279
|
txId: string
|
|
@@ -353,7 +299,8 @@ export class DeviceAgentCloudConnection {
|
|
|
353
299
|
models: updatedModels
|
|
354
300
|
}
|
|
355
301
|
};
|
|
356
|
-
const message =
|
|
302
|
+
const message = buildSignedUrlsRequestMessage(
|
|
303
|
+
this.clientId,
|
|
357
304
|
modelsOnlyUrlsRequestPayload,
|
|
358
305
|
txId
|
|
359
306
|
);
|
|
@@ -397,17 +344,19 @@ export class DeviceAgentCloudConnection {
|
|
|
397
344
|
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
398
345
|
clientId: this.clientId,
|
|
399
346
|
host: this.host,
|
|
400
|
-
port:
|
|
347
|
+
port: this.port,
|
|
401
348
|
keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
402
349
|
});
|
|
403
350
|
this.publisher = new Publisher(this.device, this.clientId);
|
|
404
351
|
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
405
|
-
this.cmdStatusMgr = new CmdStatusManager();
|
|
406
352
|
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
407
353
|
this.publisher,
|
|
408
354
|
this.clientId
|
|
409
355
|
);
|
|
410
|
-
this.txnMgr = new TransactionManager(
|
|
356
|
+
this.txnMgr = new TransactionManager(
|
|
357
|
+
this.publisher,
|
|
358
|
+
this.liveUpdatesHandler
|
|
359
|
+
);
|
|
411
360
|
|
|
412
361
|
this.subscribe(this.toDeviceTopic);
|
|
413
362
|
this.subscribe(this.secureTunnelNotifyTopic);
|
|
@@ -416,6 +365,8 @@ export class DeviceAgentCloudConnection {
|
|
|
416
365
|
this.subscribe(this.shadowHandler.shadowTopics.projects.updateDelta);
|
|
417
366
|
this.subscribe(this.shadowHandler.shadowTopics.projects.updateAccepted);
|
|
418
367
|
this.subscribe(this.shadowHandler.shadowTopics.projects.updateRejected);
|
|
368
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
369
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
419
370
|
}
|
|
420
371
|
|
|
421
372
|
public getClientId(): string {
|
|
@@ -431,7 +382,7 @@ export class DeviceAgentCloudConnection {
|
|
|
431
382
|
}
|
|
432
383
|
|
|
433
384
|
public isCmdInProgress(projectId: string): boolean {
|
|
434
|
-
return this.
|
|
385
|
+
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
435
386
|
}
|
|
436
387
|
|
|
437
388
|
public async updateProjectShadow(projectId: string) {
|
|
@@ -458,23 +409,29 @@ export class DeviceAgentCloudConnection {
|
|
|
458
409
|
return;
|
|
459
410
|
}
|
|
460
411
|
const txId = message.txId;
|
|
412
|
+
const {
|
|
413
|
+
app_state_control,
|
|
414
|
+
app_version_control,
|
|
415
|
+
live_state_updates,
|
|
416
|
+
app_install_response,
|
|
417
|
+
models_install_response,
|
|
418
|
+
status_response,
|
|
419
|
+
device_action
|
|
420
|
+
} = keyMirrors.toDeviceAgentMessageType;
|
|
461
421
|
switch (message.messageType) {
|
|
462
|
-
case
|
|
422
|
+
case app_state_control: {
|
|
463
423
|
// txId sent from cloud, just need to continue it
|
|
464
424
|
const payload = message.payload;
|
|
465
425
|
const projectId = payload.projectId;
|
|
466
426
|
|
|
467
427
|
try {
|
|
468
|
-
this.txnMgr.
|
|
469
|
-
|
|
470
|
-
func: this.handleAppStateControl,
|
|
471
|
-
args: [message.payload],
|
|
428
|
+
await this.txnMgr.runTransactionStep({
|
|
429
|
+
func: () => this.handleAppStateControl(message.payload),
|
|
472
430
|
projectId,
|
|
473
|
-
txId
|
|
431
|
+
txId,
|
|
432
|
+
start: true,
|
|
433
|
+
stepName: payload.baseCommand
|
|
474
434
|
});
|
|
475
|
-
if (completed) {
|
|
476
|
-
this.txnMgr.completeTransaction(txId);
|
|
477
|
-
}
|
|
478
435
|
} catch (e) {
|
|
479
436
|
logger.error(
|
|
480
437
|
`Error processing application state control request: ${e}!`
|
|
@@ -483,40 +440,33 @@ export class DeviceAgentCloudConnection {
|
|
|
483
440
|
|
|
484
441
|
break;
|
|
485
442
|
}
|
|
486
|
-
case
|
|
443
|
+
case app_version_control: {
|
|
487
444
|
// txId sent from cloud, just need to continue it
|
|
488
445
|
const payload = message.payload;
|
|
489
446
|
const projectId = payload.projectId;
|
|
490
|
-
const appReleaseHash =
|
|
491
|
-
payload.baseCommand === keyMirrors.appVersionControl.install
|
|
492
|
-
? payload.appReleaseHash
|
|
493
|
-
: undefined;
|
|
494
447
|
try {
|
|
495
|
-
this.txnMgr.
|
|
496
|
-
|
|
497
|
-
func: this.handleAppVersionControl,
|
|
498
|
-
args: [payload, txId],
|
|
448
|
+
await this.txnMgr.runTransactionStep({
|
|
449
|
+
func: () => this.handleAppVersionControl(payload, txId),
|
|
499
450
|
projectId,
|
|
500
|
-
txId
|
|
451
|
+
txId,
|
|
452
|
+
start: true,
|
|
453
|
+
stepName: payload.baseCommand
|
|
501
454
|
});
|
|
502
|
-
if (completed) {
|
|
503
|
-
this.txnMgr.completeTransaction(txId);
|
|
504
|
-
}
|
|
505
455
|
} catch (e) {
|
|
506
456
|
logger.error(`Error processing application install request: ${e}!`);
|
|
507
457
|
}
|
|
508
458
|
|
|
509
459
|
break;
|
|
510
460
|
}
|
|
511
|
-
case
|
|
461
|
+
case live_state_updates: {
|
|
512
462
|
const payload = message.payload;
|
|
513
463
|
// TODO: Send response?
|
|
514
|
-
|
|
464
|
+
void this.liveUpdatesHandler.handleToggles(payload, txId);
|
|
515
465
|
break;
|
|
516
466
|
}
|
|
517
|
-
case
|
|
467
|
+
case app_install_response: {
|
|
518
468
|
const payload = message.payload;
|
|
519
|
-
const { projectId
|
|
469
|
+
const { projectId } = payload.appInstallResponse;
|
|
520
470
|
if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
521
471
|
throw new Error(
|
|
522
472
|
`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
@@ -524,19 +474,16 @@ export class DeviceAgentCloudConnection {
|
|
|
524
474
|
)})!`
|
|
525
475
|
);
|
|
526
476
|
}
|
|
527
|
-
|
|
528
|
-
func: this.handleAppInstallCloudResponsePayload,
|
|
529
|
-
args: [payload],
|
|
477
|
+
await this.txnMgr.runTransactionStep({
|
|
478
|
+
func: () => this.handleAppInstallCloudResponsePayload(payload),
|
|
530
479
|
projectId,
|
|
531
|
-
txId
|
|
480
|
+
txId,
|
|
481
|
+
start: false,
|
|
482
|
+
stepName: message.messageType
|
|
532
483
|
});
|
|
533
|
-
if (completed) {
|
|
534
|
-
this.txnMgr.completeTransaction(txId);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
484
|
break;
|
|
538
485
|
}
|
|
539
|
-
case
|
|
486
|
+
case models_install_response: {
|
|
540
487
|
// This message doesn't have appReleaseHash in it's payload, but
|
|
541
488
|
// atomicCmd should be able to read it from the installed app
|
|
542
489
|
const payload = message.payload;
|
|
@@ -548,43 +495,73 @@ export class DeviceAgentCloudConnection {
|
|
|
548
495
|
)})!`
|
|
549
496
|
);
|
|
550
497
|
}
|
|
551
|
-
|
|
552
|
-
func: this.handleModelsInstallCloudResponsePayload,
|
|
553
|
-
args: [payload],
|
|
498
|
+
await this.txnMgr.runTransactionStep({
|
|
499
|
+
func: () => this.handleModelsInstallCloudResponsePayload(payload),
|
|
554
500
|
projectId,
|
|
555
|
-
txId
|
|
501
|
+
txId,
|
|
502
|
+
start: false,
|
|
503
|
+
stepName: message.messageType
|
|
556
504
|
});
|
|
557
|
-
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
case status_response: {
|
|
508
|
+
const { failure } = keyMirrors.statusResponse;
|
|
509
|
+
if (message.payload.status === failure) {
|
|
558
510
|
this.txnMgr.completeTransaction(txId);
|
|
559
|
-
}
|
|
560
511
|
|
|
512
|
+
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
513
|
+
status: keyMirrors.statusResponse.failure,
|
|
514
|
+
message: message.payload.message
|
|
515
|
+
};
|
|
516
|
+
// Send final status message
|
|
517
|
+
const failureStatusResponseMessage =
|
|
518
|
+
buildToClientStatusResponseMessage(
|
|
519
|
+
this.clientId,
|
|
520
|
+
failureStatusResponsePayload,
|
|
521
|
+
txId
|
|
522
|
+
);
|
|
523
|
+
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
524
|
+
}
|
|
561
525
|
break;
|
|
562
526
|
}
|
|
563
|
-
case
|
|
527
|
+
case device_action: {
|
|
564
528
|
try {
|
|
565
|
-
const
|
|
566
|
-
keyMirrors.statusResponse.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
529
|
+
const statusResponsePayload: StatusResponsePayload = {
|
|
530
|
+
status: keyMirrors.statusResponse.in_progress
|
|
531
|
+
};
|
|
532
|
+
const statusResponseMessage = buildToClientStatusResponseMessage(
|
|
533
|
+
this.clientId,
|
|
534
|
+
statusResponsePayload,
|
|
571
535
|
txId
|
|
572
536
|
);
|
|
573
|
-
this.publisher.publishToClient(
|
|
537
|
+
this.publisher.publishToClient(statusResponseMessage);
|
|
574
538
|
|
|
575
539
|
await this.handleDeviceAction(message.payload);
|
|
540
|
+
|
|
541
|
+
const successStatusResponsePayload: StatusResponsePayload = {
|
|
542
|
+
status: keyMirrors.statusResponse.success
|
|
543
|
+
};
|
|
544
|
+
const successStatusResponseMessage =
|
|
545
|
+
buildToClientStatusResponseMessage(
|
|
546
|
+
this.clientId,
|
|
547
|
+
successStatusResponsePayload,
|
|
548
|
+
txId
|
|
549
|
+
);
|
|
550
|
+
this.publisher.publishToClient(successStatusResponseMessage);
|
|
576
551
|
} catch (e) {
|
|
577
552
|
logger.error(
|
|
578
553
|
`There was a problem performing device action '${message.payload.action}': ${e.message}`
|
|
579
554
|
);
|
|
580
|
-
const failureStatusResponsePayload =
|
|
581
|
-
keyMirrors.statusResponse.failure,
|
|
582
|
-
e.message
|
|
583
|
-
|
|
584
|
-
const failureStatusResponseMessage =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
555
|
+
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
556
|
+
status: keyMirrors.statusResponse.failure,
|
|
557
|
+
message: e.message
|
|
558
|
+
};
|
|
559
|
+
const failureStatusResponseMessage =
|
|
560
|
+
buildToClientStatusResponseMessage(
|
|
561
|
+
this.clientId,
|
|
562
|
+
failureStatusResponsePayload,
|
|
563
|
+
txId
|
|
564
|
+
);
|
|
588
565
|
this.publisher.publishToClient(failureStatusResponseMessage);
|
|
589
566
|
}
|
|
590
567
|
break;
|
|
@@ -609,7 +586,7 @@ export class DeviceAgentCloudConnection {
|
|
|
609
586
|
);
|
|
610
587
|
switch (topic) {
|
|
611
588
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
612
|
-
case this.shadowHandler.shadowTopics.projects.
|
|
589
|
+
case this.shadowHandler.shadowTopics.projects.updateAccepted: {
|
|
613
590
|
const shadowUpdates = await this.shadowHandler.handleShadowTopic({
|
|
614
591
|
topic,
|
|
615
592
|
payload: message.state,
|
|
@@ -620,16 +597,13 @@ export class DeviceAgentCloudConnection {
|
|
|
620
597
|
const projectId = shadowUpdate.projectId;
|
|
621
598
|
const txId = shadowUpdate.txId;
|
|
622
599
|
try {
|
|
623
|
-
this.txnMgr.
|
|
624
|
-
|
|
625
|
-
func: this.handleAppConfigUpdate,
|
|
626
|
-
args: [shadowUpdate, txId],
|
|
600
|
+
await this.txnMgr.runTransactionStep({
|
|
601
|
+
func: () => this.handleAppConfigUpdate(shadowUpdate, txId),
|
|
627
602
|
projectId,
|
|
628
|
-
txId
|
|
603
|
+
txId,
|
|
604
|
+
start: true,
|
|
605
|
+
stepName: topic
|
|
629
606
|
});
|
|
630
|
-
if (completed) {
|
|
631
|
-
this.txnMgr.completeTransaction(txId);
|
|
632
|
-
}
|
|
633
607
|
} catch (e) {
|
|
634
608
|
logger.error(`Error handling shadow message: ${e.message}`);
|
|
635
609
|
}
|
|
@@ -638,7 +612,7 @@ export class DeviceAgentCloudConnection {
|
|
|
638
612
|
break;
|
|
639
613
|
}
|
|
640
614
|
case this.shadowHandler.shadowTopics.projects.getRejected:
|
|
641
|
-
case this.shadowHandler.shadowTopics.projects.
|
|
615
|
+
case this.shadowHandler.shadowTopics.projects.updateDelta:
|
|
642
616
|
case this.shadowHandler.shadowTopics.projects.updateRejected:
|
|
643
617
|
// Not handling these for now
|
|
644
618
|
break;
|
|
@@ -650,8 +624,24 @@ export class DeviceAgentCloudConnection {
|
|
|
650
624
|
break;
|
|
651
625
|
|
|
652
626
|
case this.secureTunnelNotifyTopic:
|
|
653
|
-
await secureTunnelNotifyHandler(message);
|
|
627
|
+
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
628
|
+
break;
|
|
629
|
+
case this.shadowHandler.shadowTopics.secureTunnel.updateDelta: {
|
|
630
|
+
logger.info(`Received secure tunnel update: ${message}`);
|
|
631
|
+
const reported = await this.secureTunnelHandler.syncShadowToDeviceState(
|
|
632
|
+
message
|
|
633
|
+
);
|
|
634
|
+
this.publisher.publish(
|
|
635
|
+
this.shadowHandler.shadowTopics.secureTunnel.update,
|
|
636
|
+
JSON.stringify({ state: { reported } })
|
|
637
|
+
);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
case this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted: {
|
|
641
|
+
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
642
|
+
await this.secureTunnelHandler.destroy();
|
|
654
643
|
break;
|
|
644
|
+
}
|
|
655
645
|
default:
|
|
656
646
|
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
657
647
|
}
|
|
@@ -692,9 +682,63 @@ export class DeviceAgentCloudConnection {
|
|
|
692
682
|
|
|
693
683
|
this.device.on('offline', () => {
|
|
694
684
|
logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
|
|
685
|
+
void this.logConnectionInfo();
|
|
695
686
|
});
|
|
696
687
|
}
|
|
697
688
|
|
|
689
|
+
public async logConnectionInfo() {
|
|
690
|
+
try {
|
|
691
|
+
/**
|
|
692
|
+
* We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
|
|
693
|
+
* This command doesn't always exit (see below), so
|
|
694
|
+
* we use timeout to break out of the prompt
|
|
695
|
+
* and catch the resulting error/parse the resulting stderr
|
|
696
|
+
*
|
|
697
|
+
* Sample command for current host and port:
|
|
698
|
+
* nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
699
|
+
*
|
|
700
|
+
* Sample output when port is not blocked and host is reachable:
|
|
701
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
|
|
702
|
+
* Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
|
|
703
|
+
*
|
|
704
|
+
*
|
|
705
|
+
* Sample output when port is blocked (will repeatedly try until ctrl-C out):
|
|
706
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
707
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
708
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
709
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
710
|
+
* ^C
|
|
711
|
+
*
|
|
712
|
+
*
|
|
713
|
+
* Sample command/output when the port isn't enable on that host:
|
|
714
|
+
* $ nc -zv -w 1 localhost 8883
|
|
715
|
+
* nc: connect to localhost port 8883 (tcp) failed: Connection refused
|
|
716
|
+
*/
|
|
717
|
+
await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
|
|
718
|
+
timeout: 2000
|
|
719
|
+
});
|
|
720
|
+
} catch (err) {
|
|
721
|
+
const output = JSON.stringify(err['stderr']);
|
|
722
|
+
if (output.indexOf('not known') !== -1) {
|
|
723
|
+
logger.warn(
|
|
724
|
+
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
725
|
+
);
|
|
726
|
+
} else if (output.indexOf('timed out') !== -1) {
|
|
727
|
+
logger.warn(
|
|
728
|
+
`Internet connection appears fine, however the endpoint was not reachable on the current connection port: ${this.port}\nPlease check if a firewall is in place.`
|
|
729
|
+
);
|
|
730
|
+
} else if (output.indexOf('refused') !== -1) {
|
|
731
|
+
logger.warn(
|
|
732
|
+
`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
logger.warn(
|
|
736
|
+
`Output from checking connection to ${this.host} on ${this.port}: ${output}`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
698
742
|
public async stop() {
|
|
699
743
|
// FIXME: This method is currently only used by the CLI, and shadow messages
|
|
700
744
|
// can be lost since we aren't waiting for responses so sleep for a short
|
|
@@ -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
|
});
|