@alwaysai/device-agent 1.5.0 → 2.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/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +8 -3
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts +1 -5
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +9 -26
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +27 -7
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/index.d.ts +4 -4
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +1 -4
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +8 -7
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +0 -11
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +5 -54
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/utils.d.ts +0 -4
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +1 -24
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/bootstrap-provision.js +3 -2
- package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +10 -15
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +279 -250
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent.js +11 -9
- package/lib/cloud-connection/device-agent.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +18 -28
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +54 -169
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +71 -165
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +4 -1
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +30 -11
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +5 -3
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +59 -27
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +45 -57
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/shadow.d.ts.map +1 -1
- package/lib/cloud-connection/shadow.js +2 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +4 -2
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +18 -29
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +3 -3
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +8 -8
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +95 -71
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts.map +1 -1
- package/lib/docker/docker-compose.js +2 -1
- package/lib/docker/docker-compose.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +2 -1
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +7 -7
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +3 -1
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/infrastructure/config-check-utility.d.ts +6 -0
- package/lib/infrastructure/config-check-utility.d.ts.map +1 -0
- package/lib/infrastructure/config-check-utility.js +67 -0
- package/lib/infrastructure/config-check-utility.js.map +1 -0
- package/lib/infrastructure/config-check-utility.test.d.ts +2 -0
- package/lib/infrastructure/config-check-utility.test.d.ts.map +1 -0
- package/lib/infrastructure/config-check-utility.test.js +109 -0
- package/lib/infrastructure/config-check-utility.test.js.map +1 -0
- package/lib/infrastructure/device-certificate.d.ts +10 -0
- package/lib/infrastructure/device-certificate.d.ts.map +1 -0
- package/lib/infrastructure/device-certificate.js +47 -0
- package/lib/infrastructure/device-certificate.js.map +1 -0
- package/lib/infrastructure/device-certificate.test.d.ts +2 -0
- package/lib/infrastructure/device-certificate.test.d.ts.map +1 -0
- package/lib/infrastructure/device-certificate.test.js +24 -0
- package/lib/infrastructure/device-certificate.test.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts +2 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.js +61 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-files.d.ts +75 -0
- package/lib/infrastructure/legacy-migration/legacy-files.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-files.js +75 -0
- package/lib/infrastructure/legacy-migration/legacy-files.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts +6 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.js +149 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts +2 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.js +226 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.js.map +1 -0
- package/lib/infrastructure/require-files-present-ready.test.d.ts +2 -0
- package/lib/infrastructure/require-files-present-ready.test.d.ts.map +1 -0
- package/lib/infrastructure/require-files-present-ready.test.js +44 -0
- package/lib/infrastructure/require-files-present-ready.test.js.map +1 -0
- package/lib/infrastructure/required-config-checks.d.ts +2 -0
- package/lib/infrastructure/required-config-checks.d.ts.map +1 -0
- package/lib/infrastructure/required-config-checks.js +30 -0
- package/lib/infrastructure/required-config-checks.js.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -1
- package/lib/infrastructure/tokens-and-device-cfg.js +11 -8
- package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +14 -14
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +9 -9
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.js +21 -16
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.test.js +11 -13
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
- package/lib/subcommands/app/analytics.d.ts.map +1 -1
- package/lib/subcommands/app/analytics.js +1 -2
- package/lib/subcommands/app/analytics.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts +4 -0
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +52 -6
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +1 -3
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -11
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +2 -58
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/shadow.d.ts.map +1 -1
- package/lib/subcommands/app/shadow.js +6 -5
- package/lib/subcommands/app/shadow.js.map +1 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +2 -4
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/subcommands/config.d.ts +2 -0
- package/lib/subcommands/config.d.ts.map +1 -0
- package/lib/subcommands/config.js +39 -0
- package/lib/subcommands/config.js.map +1 -0
- package/lib/subcommands/device/clean.d.ts +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +23 -13
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +3 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/init.js +8 -8
- package/lib/subcommands/device/init.js.map +1 -1
- package/lib/subcommands/device/migrate.d.ts +2 -0
- package/lib/subcommands/device/migrate.d.ts.map +1 -0
- package/lib/subcommands/device/migrate.js +24 -0
- package/lib/subcommands/device/migrate.js.map +1 -0
- package/lib/subcommands/device/refresh.d.ts.map +1 -1
- package/lib/subcommands/device/refresh.js +1 -0
- package/lib/subcommands/device/refresh.js.map +1 -1
- package/lib/subcommands/index.d.ts +1 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +3 -1
- package/lib/subcommands/index.js.map +1 -1
- package/lib/subcommands/rabbitmq-connection.d.ts +1 -1
- package/lib/subcommands/rabbitmq-connection.d.ts.map +1 -1
- package/lib/util/aai-error.d.ts +12 -0
- package/lib/util/aai-error.d.ts.map +1 -0
- package/lib/util/aai-error.js +11 -0
- package/lib/util/aai-error.js.map +1 -0
- package/lib/util/aws-regions.d.ts +2 -0
- package/lib/util/aws-regions.d.ts.map +1 -0
- package/lib/util/{cloud-mode-ready.js → aws-regions.js} +2 -20
- package/lib/util/aws-regions.js.map +1 -0
- package/lib/util/check-for-updates.d.ts.map +1 -1
- package/lib/util/check-for-updates.js +5 -28
- package/lib/util/check-for-updates.js.map +1 -1
- package/lib/util/clean-certs.d.ts.map +1 -1
- package/lib/util/clean-certs.js +5 -4
- package/lib/util/clean-certs.js.map +1 -1
- package/lib/util/directories.d.ts +4 -18
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +18 -32
- package/lib/util/directories.js.map +1 -1
- package/lib/util/file.d.ts +4 -0
- package/lib/util/file.d.ts.map +1 -1
- package/lib/util/file.js +65 -4
- package/lib/util/file.js.map +1 -1
- package/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +7 -1
- package/lib/util/get-device-id.js.map +1 -1
- package/lib/util/http-client.js +3 -3
- package/lib/util/http-client.js.map +1 -1
- package/package.json +19 -17
- package/readme.md +12 -32
- package/src/application-control/config.ts +9 -12
- package/src/application-control/environment-variables.test.ts +28 -7
- package/src/application-control/environment-variables.ts +13 -40
- package/src/application-control/index.ts +3 -16
- package/src/application-control/install.ts +15 -10
- package/src/application-control/models.ts +6 -87
- package/src/application-control/utils.ts +0 -28
- package/src/cloud-connection/bootstrap-provision.ts +7 -7
- package/src/cloud-connection/device-agent-cloud-connection.ts +639 -525
- package/src/cloud-connection/device-agent.ts +16 -7
- package/src/cloud-connection/live-updates-handler.test.ts +121 -189
- package/src/cloud-connection/live-updates-handler.ts +99 -234
- package/src/cloud-connection/passthrough-handler.ts +55 -18
- package/src/cloud-connection/shadow-handler.test.ts +45 -57
- package/src/cloud-connection/shadow-handler.ts +103 -57
- package/src/cloud-connection/shadow.ts +4 -1
- package/src/cloud-connection/transaction-manager.test.ts +3 -3
- package/src/cloud-connection/transaction-manager.ts +53 -39
- package/src/device-control/device-control.ts +102 -70
- package/src/docker/docker-compose.ts +3 -2
- package/src/infrastructure/agent-config.test.ts +6 -2
- package/src/infrastructure/agent-config.ts +8 -7
- package/src/infrastructure/config-check-utility.test.ts +154 -0
- package/src/infrastructure/config-check-utility.ts +77 -0
- package/src/infrastructure/device-certificate.test.ts +40 -0
- package/src/infrastructure/device-certificate.ts +58 -0
- package/src/infrastructure/legacy-migration/legacy-file.test.ts +88 -0
- package/src/infrastructure/legacy-migration/legacy-files.ts +101 -0
- package/src/infrastructure/legacy-migration/legacy-migration.test.ts +396 -0
- package/src/infrastructure/legacy-migration/legacy-migration.ts +229 -0
- package/src/infrastructure/require-files-present-ready.test.ts +53 -0
- package/src/infrastructure/required-config-checks.ts +33 -0
- package/src/infrastructure/tokens-and-device-cfg.ts +12 -10
- package/src/local-connection/rabbitmq-connection.ts +22 -17
- package/src/secure-tunneling/secure-tunneling.test.ts +20 -22
- package/src/secure-tunneling/secure-tunneling.ts +41 -29
- package/src/subcommands/app/analytics.ts +2 -4
- package/src/subcommands/app/env-vars.ts +72 -9
- package/src/subcommands/app/index.ts +3 -11
- package/src/subcommands/app/models.ts +5 -81
- package/src/subcommands/app/shadow.ts +6 -5
- package/src/subcommands/app/version.ts +3 -4
- package/src/subcommands/config.ts +42 -0
- package/src/subcommands/device/clean.ts +31 -17
- package/src/subcommands/device/index.ts +3 -1
- package/src/subcommands/device/init.ts +11 -11
- package/src/subcommands/device/migrate.ts +20 -0
- package/src/subcommands/device/refresh.ts +1 -0
- package/src/subcommands/index.ts +3 -1
- package/src/util/aai-error.ts +20 -0
- package/src/util/{cloud-mode-ready.ts → aws-regions.ts} +0 -24
- package/src/util/check-for-updates.ts +14 -30
- package/src/util/clean-certs.ts +8 -4
- package/src/util/directories.ts +23 -67
- package/src/util/file.ts +83 -3
- package/src/util/get-device-id.ts +7 -7
- package/src/util/http-client.ts +2 -2
- package/lib/util/cloud-mode-ready.d.ts +0 -3
- package/lib/util/cloud-mode-ready.d.ts.map +0 -1
- package/lib/util/cloud-mode-ready.js.map +0 -1
- package/lib/util/download-file.d.ts +0 -6
- package/lib/util/download-file.d.ts.map +0 -1
- package/lib/util/download-file.js +0 -25
- package/lib/util/download-file.js.map +0 -1
- package/lib/util/fetch-with-timeout.d.ts +0 -4
- package/lib/util/fetch-with-timeout.d.ts.map +0 -1
- package/lib/util/fetch-with-timeout.js +0 -30
- package/lib/util/fetch-with-timeout.js.map +0 -1
- package/lib/util/parsing.d.ts +0 -2
- package/lib/util/parsing.d.ts.map +0 -1
- package/lib/util/parsing.js +0 -17
- package/lib/util/parsing.js.map +0 -1
- package/src/util/download-file.ts +0 -25
- package/src/util/fetch-with-timeout.ts +0 -35
- package/src/util/parsing.ts +0 -11
|
@@ -10,16 +10,27 @@ import {
|
|
|
10
10
|
SignedUrlsRequestPayload,
|
|
11
11
|
ToCloudMessage,
|
|
12
12
|
ToDeviceAgentMessage,
|
|
13
|
-
|
|
13
|
+
buildAppLogsMessage,
|
|
14
|
+
buildAppStateMessage,
|
|
15
|
+
buildDeviceStatsMessage,
|
|
14
16
|
buildSignedUrlsRequestMessage,
|
|
15
17
|
buildToClientStatusResponseMessage,
|
|
16
|
-
|
|
18
|
+
getToDeviceTopic,
|
|
19
|
+
getUpdateDeltaStateFromMessage,
|
|
17
20
|
keyMirrors,
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
validateSecureTunnelShadowUpdate,
|
|
22
|
+
validateToDeviceAgentMessage
|
|
20
23
|
} from '@alwaysai/device-agent-schemas';
|
|
24
|
+
import {
|
|
25
|
+
DEVICE_CERTIFICATE_FILE_PATH,
|
|
26
|
+
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
27
|
+
} from 'alwaysai/lib/infrastructure';
|
|
28
|
+
import { stringifyError } from 'alwaysai/lib/util';
|
|
29
|
+
import { exec } from 'child_process';
|
|
21
30
|
import { existsSync } from 'fs';
|
|
31
|
+
import { promisify } from 'util';
|
|
22
32
|
import {
|
|
33
|
+
getAppLogs,
|
|
23
34
|
installApp,
|
|
24
35
|
restartApp,
|
|
25
36
|
startApp,
|
|
@@ -29,37 +40,33 @@ import {
|
|
|
29
40
|
updateModelsWithPresignedUrls
|
|
30
41
|
} from '../application-control';
|
|
31
42
|
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
43
|
+
import { pruneModels } from '../application-control/models';
|
|
32
44
|
import { reboot } from '../device-control/device-control';
|
|
33
45
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
34
46
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
47
|
+
import { getBootstrapPrivateKeyFilePath } from '../infrastructure/device-certificate';
|
|
48
|
+
import { migrateFromLegacyCertsAndTokens } from '../infrastructure/legacy-migration/legacy-migration';
|
|
49
|
+
import { requiredConfigFilesPresentAndValid } from '../infrastructure/required-config-checks';
|
|
35
50
|
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
36
51
|
import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
|
|
37
|
-
import
|
|
38
|
-
import {
|
|
39
|
-
|
|
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';
|
|
52
|
+
import AaiError from '../util/aai-error';
|
|
53
|
+
import { getDeviceAgentVersion } from '../util/check-for-updates';
|
|
54
|
+
import { AWS_ROOT_CERTIFICATE_FILE_PATH } from '../util/directories';
|
|
45
55
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
46
56
|
import { logger } from '../util/logger';
|
|
47
57
|
import sleep from '../util/sleep';
|
|
48
58
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
49
59
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
60
|
+
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
50
61
|
import { PassthroughHandler } from './passthrough-handler';
|
|
51
62
|
import { Publisher } from './publisher';
|
|
52
63
|
import { ShadowHandler, ShadowUpdate } from './shadow-handler';
|
|
53
64
|
import { TransactionManager } from './transaction-manager';
|
|
54
|
-
import { exec } from 'child_process';
|
|
55
|
-
import { promisify } from 'util';
|
|
56
|
-
import { pruneModels } from '../application-control/models';
|
|
57
|
-
import { getDeviceAgentVersion } from '../util/check-for-updates';
|
|
58
65
|
|
|
59
66
|
const exec_promise = promisify(exec);
|
|
60
67
|
|
|
61
68
|
export class DeviceAgentCloudConnection {
|
|
62
|
-
|
|
69
|
+
public shadowHandler: ShadowHandler;
|
|
63
70
|
public publisher: Publisher;
|
|
64
71
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
65
72
|
private txnMgr: TransactionManager;
|
|
@@ -73,384 +80,182 @@ export class DeviceAgentCloudConnection {
|
|
|
73
80
|
private readonly secureTunnelHandler =
|
|
74
81
|
SecureTunnelHandlerSingleton.getInstance();
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
break;
|
|
87
|
-
case keyMirrors.appStateControl.restart:
|
|
88
|
-
await restartApp({ projectId });
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
return true;
|
|
92
|
-
};
|
|
83
|
+
constructor() {
|
|
84
|
+
this.device = awsIot.device({
|
|
85
|
+
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
86
|
+
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
87
|
+
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
88
|
+
clientId: this.clientId,
|
|
89
|
+
host: this.host,
|
|
90
|
+
port: this.port,
|
|
91
|
+
keepalive: 10 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
92
|
+
});
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
case keyMirrors.appVersionControl.install: {
|
|
102
|
-
const { projectId, appReleaseHash } = payload;
|
|
94
|
+
this.publisher = new Publisher(this.device, this.clientId);
|
|
95
|
+
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
96
|
+
this.liveUpdatesHandler = new LiveUpdatesHandler();
|
|
97
|
+
this.txnMgr = new TransactionManager(
|
|
98
|
+
this.publisher,
|
|
99
|
+
this.liveUpdatesHandler
|
|
100
|
+
);
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const message = buildSignedUrlsRequestMessage(
|
|
111
|
-
this.clientId,
|
|
112
|
-
signedUrlsRequestPayload,
|
|
113
|
-
txId
|
|
114
|
-
);
|
|
115
|
-
await this.publishCloudRequest(message);
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
case keyMirrors.appVersionControl.uninstall: {
|
|
119
|
-
const { projectId } = payload;
|
|
120
|
-
await this.atomicApplicationUninstall(projectId);
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
default:
|
|
124
|
-
logger.warn(
|
|
125
|
-
`Ignore App Version Control packet: ${JSON.stringify(
|
|
126
|
-
payload,
|
|
127
|
-
null,
|
|
128
|
-
2
|
|
129
|
-
)}`
|
|
130
|
-
);
|
|
131
|
-
return true;
|
|
102
|
+
this.subscribe(this.toDeviceTopic);
|
|
103
|
+
this.subscribe(this.secureTunnelNotifyTopic);
|
|
104
|
+
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
105
|
+
this.subscribe(topic);
|
|
132
106
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
private handleAppInstallCloudResponsePayload = async (
|
|
136
|
-
payload: AppInstallResponsePayload
|
|
137
|
-
): Promise<boolean> => {
|
|
138
|
-
const {
|
|
139
|
-
projectId,
|
|
140
|
-
appReleaseHash,
|
|
141
|
-
appInstallPayload,
|
|
142
|
-
modelsInstallPayload
|
|
143
|
-
} = payload.appInstallResponse;
|
|
144
|
-
const signedUrlsPayload = {
|
|
145
|
-
appInstallPayload,
|
|
146
|
-
modelsInstallPayload
|
|
147
|
-
};
|
|
148
|
-
await this.atomicApplicationUpdate(async () => {
|
|
149
|
-
this.shadowHandler.clearProjectShadow(projectId);
|
|
150
|
-
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
151
|
-
}, projectId);
|
|
152
|
-
return true;
|
|
153
|
-
};
|
|
107
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
108
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
154
109
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
txId: string
|
|
158
|
-
): Promise<boolean> => {
|
|
159
|
-
const projectId = payload.modelsInstallResponse.projectId;
|
|
110
|
+
this.setupHandlers();
|
|
111
|
+
}
|
|
160
112
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
'Unknown error while updating models via application config! No config present for model update.'
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
const { appCfgUpdate, envVarUpdate } = update;
|
|
168
|
-
if (appCfgUpdate) {
|
|
169
|
-
await this.atomicApplicationUpdate(
|
|
170
|
-
async () =>
|
|
171
|
-
await updateModelsWithPresignedUrls({
|
|
172
|
-
projectId,
|
|
173
|
-
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
174
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
175
|
-
}),
|
|
176
|
-
projectId
|
|
177
|
-
);
|
|
178
|
-
}
|
|
113
|
+
/*=================================================================
|
|
114
|
+
Public interface
|
|
115
|
+
=================================================================*/
|
|
179
116
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
await this.shadowHandler.updateProjectEnvVars({
|
|
184
|
-
projectId,
|
|
185
|
-
envVars: envVarUpdate.envVars
|
|
186
|
-
}),
|
|
187
|
-
projectId,
|
|
188
|
-
true
|
|
189
|
-
);
|
|
190
|
-
}
|
|
117
|
+
public getClientId(): string {
|
|
118
|
+
return this.clientId;
|
|
119
|
+
}
|
|
191
120
|
|
|
192
|
-
|
|
193
|
-
|
|
121
|
+
public isCmdInProgress(projectId: string): boolean {
|
|
122
|
+
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
123
|
+
}
|
|
194
124
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
125
|
+
public async handleMessage(topic: string, message: any) {
|
|
126
|
+
logger.debug(
|
|
127
|
+
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
128
|
+
);
|
|
129
|
+
// ProjectShadow messages
|
|
130
|
+
if (this.shadowHandler.projectShadowTopics.includes(topic)) {
|
|
131
|
+
await this.handleProjectShadowMessage(topic, message);
|
|
132
|
+
} else if (topic === this.toDeviceTopic) {
|
|
133
|
+
await this.handleDeviceAgentMessage({
|
|
134
|
+
topic,
|
|
135
|
+
message
|
|
136
|
+
});
|
|
137
|
+
// SecureTunnelNotify messages
|
|
138
|
+
} else if (topic === this.secureTunnelNotifyTopic) {
|
|
139
|
+
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
140
|
+
// SecureTunnel messages
|
|
141
|
+
} else if (
|
|
142
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
143
|
+
) {
|
|
144
|
+
await this.handleSecureTunnelMessage(message);
|
|
145
|
+
} else if (
|
|
146
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
147
|
+
) {
|
|
148
|
+
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
149
|
+
await this.secureTunnelHandler.destroy();
|
|
150
|
+
} else {
|
|
151
|
+
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
207
152
|
}
|
|
208
153
|
}
|
|
209
154
|
|
|
210
|
-
|
|
211
|
-
|
|
155
|
+
public async stop() {
|
|
156
|
+
// This method is currently only used by the CLI, and shadow messages can be
|
|
157
|
+
// lost since we aren't waiting for responses so sleep for a short time to
|
|
158
|
+
// receive them
|
|
159
|
+
await sleep(1000);
|
|
160
|
+
this.device.end();
|
|
212
161
|
}
|
|
213
162
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
163
|
+
/*=================================================================
|
|
164
|
+
Private interface
|
|
165
|
+
=================================================================*/
|
|
218
166
|
|
|
219
|
-
private
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
}
|
|
167
|
+
private setupHandlers() {
|
|
168
|
+
this.device.on('connect', (connack: any) => {
|
|
169
|
+
logger.info('Device Agent has connected to the cloud');
|
|
170
|
+
// FIXME: EI-709 Skip this request for now to prevent kicking off another
|
|
171
|
+
// shadow update process if IoT Core disconnect occurs during app config update
|
|
172
|
+
//this.shadowHandler.getShadowUpdates();
|
|
173
|
+
void this.shadowHandler.updateSystemInfoShadow();
|
|
174
|
+
});
|
|
228
175
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
176
|
+
this.device.on('disconnect', () => {
|
|
177
|
+
logger.warn('Device Agent has been disconnected from the cloud');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.device.on('reconnect', () => {
|
|
181
|
+
logger.info(
|
|
182
|
+
`Device Agent attempting to re-connect ${new Date().toLocaleString()}`
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.device.on('error', function (e) {
|
|
187
|
+
logger.error(`Error connecting to cloud!\n${stringifyError(e)}`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this.device.on('message', async (topic: string, payload: string) => {
|
|
237
191
|
try {
|
|
238
|
-
|
|
192
|
+
const jsonPacket = JSON.parse(payload);
|
|
193
|
+
await this.handleMessage(topic, jsonPacket);
|
|
239
194
|
} catch (e) {
|
|
240
|
-
logger.error(
|
|
241
|
-
`Could not create a backup for the project: ${projectId}:\n${e.message}\n${e.stack}`
|
|
242
|
-
);
|
|
195
|
+
logger.error(`Error parsing message!\n${stringifyError(e)}`);
|
|
243
196
|
}
|
|
244
|
-
}
|
|
197
|
+
});
|
|
245
198
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
199
|
+
this.device.on('offline', () => {
|
|
200
|
+
logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
|
|
201
|
+
void this.logConnectionInfo();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async logConnectionInfo() {
|
|
206
|
+
try {
|
|
207
|
+
/**
|
|
208
|
+
* We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
|
|
209
|
+
* This command doesn't always exit (see below), so
|
|
210
|
+
* we use timeout to break out of the prompt
|
|
211
|
+
* and catch the resulting error/parse the resulting stderr
|
|
212
|
+
*
|
|
213
|
+
* Sample command for current host and port:
|
|
214
|
+
* nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
215
|
+
*
|
|
216
|
+
* Sample output when port is not blocked and host is reachable:
|
|
217
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
|
|
218
|
+
* Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
|
|
219
|
+
*
|
|
220
|
+
*
|
|
221
|
+
* Sample output when port is blocked (will repeatedly try until ctrl-C out):
|
|
222
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
223
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
224
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
225
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
226
|
+
* ^C
|
|
227
|
+
*
|
|
228
|
+
*
|
|
229
|
+
* Sample command/output when the port isn't enable on that host:
|
|
230
|
+
* $ nc -zv -w 1 localhost 8883
|
|
231
|
+
* nc: connect to localhost port 8883 (tcp) failed: Connection refused
|
|
232
|
+
*/
|
|
233
|
+
await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
|
|
234
|
+
timeout: 2000
|
|
235
|
+
});
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const output = JSON.stringify(err['stderr']);
|
|
238
|
+
if (output.indexOf('not known') !== -1) {
|
|
239
|
+
logger.warn(
|
|
240
|
+
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
260
241
|
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
await this.atomicApplicationUninstall(projectId);
|
|
265
|
-
} catch {
|
|
266
|
-
// atomicApplicationUninstall handles failing, so there's nothing to handle here.
|
|
267
|
-
}
|
|
268
|
-
logger.error(
|
|
269
|
-
`Application update failed, rolled back to previous version: ${errorAppUpdate}`
|
|
242
|
+
} else if (output.indexOf('timed out') !== -1) {
|
|
243
|
+
logger.warn(
|
|
244
|
+
`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.`
|
|
270
245
|
);
|
|
271
|
-
|
|
272
|
-
|
|
246
|
+
} else if (output.indexOf('refused') !== -1) {
|
|
247
|
+
logger.warn(
|
|
248
|
+
`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
|
|
273
249
|
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private handleProjectShadowConfigUpdate = async (
|
|
282
|
-
update: ShadowUpdate,
|
|
283
|
-
txId: string
|
|
284
|
-
): Promise<boolean> => {
|
|
285
|
-
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
appCfgUpdate?.updatedModels &&
|
|
289
|
-
Object.keys(appCfgUpdate.updatedModels).length
|
|
290
|
-
) {
|
|
291
|
-
// When there are model updates request signed URLs and wait to apply config changes
|
|
292
|
-
const { updatedModels } = appCfgUpdate;
|
|
293
|
-
|
|
294
|
-
logger.debug(
|
|
295
|
-
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
296
|
-
updatedModels
|
|
297
|
-
)}`
|
|
298
|
-
);
|
|
299
|
-
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
300
|
-
modelsOnlyUrlsRequest: {
|
|
301
|
-
projectId,
|
|
302
|
-
models: updatedModels
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
const message = buildSignedUrlsRequestMessage(
|
|
306
|
-
this.clientId,
|
|
307
|
-
modelsOnlyUrlsRequestPayload,
|
|
308
|
-
txId
|
|
309
|
-
);
|
|
310
|
-
this.publisher.publishToCloud(message);
|
|
311
|
-
|
|
312
|
-
this.txnMgr.setAppCfgUpdateToTx(txId, update);
|
|
313
|
-
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (appCfgUpdate) {
|
|
318
|
-
await this.atomicApplicationUpdate(async () => {
|
|
319
|
-
await pruneModels({
|
|
320
|
-
projectId,
|
|
321
|
-
appCfg: appCfgUpdate.newAppCfg
|
|
322
|
-
});
|
|
323
|
-
await updateAppCfg({
|
|
324
|
-
projectId,
|
|
325
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
326
|
-
});
|
|
327
|
-
}, projectId);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (envVarUpdate) {
|
|
331
|
-
await this.atomicApplicationUpdate(
|
|
332
|
-
async () =>
|
|
333
|
-
await this.shadowHandler.updateProjectEnvVars({
|
|
334
|
-
projectId,
|
|
335
|
-
envVars: envVarUpdate.envVars
|
|
336
|
-
}),
|
|
337
|
-
projectId,
|
|
338
|
-
true
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
return true;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
345
|
-
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
346
|
-
topic,
|
|
347
|
-
payload: message,
|
|
348
|
-
clientToken: message.clientToken
|
|
349
|
-
});
|
|
350
|
-
if (shadowUpdates.length) {
|
|
351
|
-
const shadowUpdatePromises: Promise<void>[] = [];
|
|
352
|
-
for (const shadowUpdate of shadowUpdates) {
|
|
353
|
-
const projectId = shadowUpdate.projectId;
|
|
354
|
-
const txId = shadowUpdate.txId;
|
|
355
|
-
shadowUpdatePromises.push(
|
|
356
|
-
this.txnMgr
|
|
357
|
-
.runTransactionStep({
|
|
358
|
-
func: () =>
|
|
359
|
-
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
360
|
-
projectId,
|
|
361
|
-
txId,
|
|
362
|
-
start: true,
|
|
363
|
-
stepName: topic
|
|
364
|
-
})
|
|
365
|
-
.catch((e: Error) => {
|
|
366
|
-
logger.error(
|
|
367
|
-
`There was an issue updating project shadow config: ${JSON.stringify(
|
|
368
|
-
e
|
|
369
|
-
)}`
|
|
370
|
-
);
|
|
371
|
-
})
|
|
250
|
+
} else {
|
|
251
|
+
logger.warn(
|
|
252
|
+
`Output from checking connection to ${this.host} on ${this.port}: ${output}`
|
|
372
253
|
);
|
|
373
254
|
}
|
|
374
|
-
|
|
375
|
-
await Promise.all(shadowUpdatePromises);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
public async handleSecureTunnelMessage(payload: any): Promise<void> {
|
|
380
|
-
logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
|
|
381
|
-
const state = payload.state;
|
|
382
|
-
if (!state) {
|
|
383
|
-
logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const valid = validateSecureTunnelShadowUpdate(state);
|
|
387
|
-
if (!valid) {
|
|
388
|
-
logger.error(
|
|
389
|
-
`Error validating message: ${JSON.stringify(
|
|
390
|
-
{ payload, errors: validateSecureTunnelShadowUpdate.errors },
|
|
391
|
-
null,
|
|
392
|
-
2
|
|
393
|
-
)}`
|
|
394
|
-
);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
const secureTunnelUpdate =
|
|
398
|
-
await this.secureTunnelHandler.syncShadowToDeviceState(payload);
|
|
399
|
-
await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/*=================================================================
|
|
404
|
-
Public interface
|
|
405
|
-
=================================================================*/
|
|
406
|
-
|
|
407
|
-
constructor() {
|
|
408
|
-
this.device = awsIot.device({
|
|
409
|
-
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
410
|
-
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
411
|
-
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
412
|
-
clientId: this.clientId,
|
|
413
|
-
host: this.host,
|
|
414
|
-
port: this.port,
|
|
415
|
-
keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
416
|
-
});
|
|
417
|
-
this.publisher = new Publisher(this.device, this.clientId);
|
|
418
|
-
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
419
|
-
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
420
|
-
this.publisher,
|
|
421
|
-
this.clientId
|
|
422
|
-
);
|
|
423
|
-
this.txnMgr = new TransactionManager(
|
|
424
|
-
this.publisher,
|
|
425
|
-
this.liveUpdatesHandler
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
this.subscribe(this.toDeviceTopic);
|
|
429
|
-
this.subscribe(this.secureTunnelNotifyTopic);
|
|
430
|
-
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
431
|
-
this.subscribe(topic);
|
|
432
255
|
}
|
|
433
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
434
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
public getClientId(): string {
|
|
438
|
-
return this.clientId;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
public getToDeviceTopic() {
|
|
442
|
-
return this.toDeviceTopic;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
public isCmdInProgress(projectId: string): boolean {
|
|
446
|
-
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
public async updateProjectShadow(projectId: string) {
|
|
450
|
-
await this.shadowHandler.updateProjectShadow(projectId);
|
|
451
256
|
}
|
|
452
257
|
|
|
453
|
-
|
|
258
|
+
private async handleDeviceAgentMessage({
|
|
454
259
|
topic,
|
|
455
260
|
message
|
|
456
261
|
}: {
|
|
@@ -491,11 +296,22 @@ export class DeviceAgentCloudConnection {
|
|
|
491
296
|
projectId,
|
|
492
297
|
txId,
|
|
493
298
|
start: true,
|
|
299
|
+
liveUpdatesPublishFn: async () =>
|
|
300
|
+
this.publisher.publishToClient(
|
|
301
|
+
buildToClientStatusResponseMessage(
|
|
302
|
+
this.clientId,
|
|
303
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
304
|
+
txId
|
|
305
|
+
),
|
|
306
|
+
logger.silly
|
|
307
|
+
),
|
|
494
308
|
stepName: payload.baseCommand
|
|
495
309
|
});
|
|
496
310
|
} catch (e) {
|
|
497
311
|
logger.error(
|
|
498
|
-
`Error processing application state control request
|
|
312
|
+
`Error processing application state control request for ${projectId}!\n${stringifyError(
|
|
313
|
+
e
|
|
314
|
+
)}`
|
|
499
315
|
);
|
|
500
316
|
}
|
|
501
317
|
|
|
@@ -511,18 +327,98 @@ export class DeviceAgentCloudConnection {
|
|
|
511
327
|
projectId,
|
|
512
328
|
txId,
|
|
513
329
|
start: true,
|
|
330
|
+
liveUpdatesPublishFn: async () =>
|
|
331
|
+
this.publisher.publishToClient(
|
|
332
|
+
buildToClientStatusResponseMessage(
|
|
333
|
+
this.clientId,
|
|
334
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
335
|
+
txId
|
|
336
|
+
),
|
|
337
|
+
logger.silly
|
|
338
|
+
),
|
|
514
339
|
stepName: payload.baseCommand
|
|
515
340
|
});
|
|
516
341
|
} catch (e) {
|
|
517
|
-
logger.error(
|
|
342
|
+
logger.error(
|
|
343
|
+
`Error processing application install request for ${projectId}!\n${stringifyError(
|
|
344
|
+
e
|
|
345
|
+
)}`
|
|
346
|
+
);
|
|
518
347
|
}
|
|
519
348
|
|
|
520
349
|
break;
|
|
521
350
|
}
|
|
522
351
|
case live_state_updates: {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
352
|
+
const { deviceStats, appState, appLogs } = message.payload;
|
|
353
|
+
|
|
354
|
+
if (deviceStats !== undefined) {
|
|
355
|
+
if (deviceStats) {
|
|
356
|
+
await this.liveUpdatesHandler.enable(
|
|
357
|
+
keyMirrors.toClientMessageType.device_stats,
|
|
358
|
+
async () =>
|
|
359
|
+
this.publisher.publishToClient(
|
|
360
|
+
buildDeviceStatsMessage(
|
|
361
|
+
this.clientId,
|
|
362
|
+
await getDeviceStatsPayload(),
|
|
363
|
+
txId
|
|
364
|
+
),
|
|
365
|
+
logger.silly
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
this.liveUpdatesHandler.disable(
|
|
370
|
+
keyMirrors.toClientMessageType.device_stats
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (appState !== undefined) {
|
|
376
|
+
if (appState) {
|
|
377
|
+
await this.liveUpdatesHandler.enable(
|
|
378
|
+
keyMirrors.toClientMessageType.app_state,
|
|
379
|
+
async () =>
|
|
380
|
+
this.publisher.publishToClient(
|
|
381
|
+
buildAppStateMessage(
|
|
382
|
+
this.clientId,
|
|
383
|
+
await getAppStatePayload(),
|
|
384
|
+
txId
|
|
385
|
+
),
|
|
386
|
+
logger.silly
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
} else {
|
|
390
|
+
this.liveUpdatesHandler.disable(
|
|
391
|
+
keyMirrors.toClientMessageType.app_state
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (appLogs !== undefined) {
|
|
397
|
+
if (appLogs.toggle) {
|
|
398
|
+
await this.liveUpdatesHandler.startStream(
|
|
399
|
+
appLogs.projectId,
|
|
400
|
+
async () =>
|
|
401
|
+
await getAppLogs({
|
|
402
|
+
projectId: appLogs.projectId,
|
|
403
|
+
args: ['--tail', '100', '--no-log-prefix']
|
|
404
|
+
}),
|
|
405
|
+
async (logChunk: string) =>
|
|
406
|
+
this.publisher.publishToClient(
|
|
407
|
+
buildAppLogsMessage(
|
|
408
|
+
this.clientId,
|
|
409
|
+
{
|
|
410
|
+
projectId: appLogs.projectId,
|
|
411
|
+
logChunk
|
|
412
|
+
},
|
|
413
|
+
txId
|
|
414
|
+
),
|
|
415
|
+
logger.silly
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
} else {
|
|
419
|
+
this.liveUpdatesHandler.stopStream(appLogs.projectId);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
526
422
|
break;
|
|
527
423
|
}
|
|
528
424
|
case app_install_response: {
|
|
@@ -569,62 +465,59 @@ export class DeviceAgentCloudConnection {
|
|
|
569
465
|
case status_response: {
|
|
570
466
|
const { failure } = keyMirrors.statusResponse;
|
|
571
467
|
if (message.payload.status === failure) {
|
|
572
|
-
this.txnMgr.completeTransaction(
|
|
573
|
-
|
|
574
|
-
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
575
|
-
status: keyMirrors.statusResponse.failure,
|
|
576
|
-
message: message.payload.message
|
|
577
|
-
};
|
|
578
|
-
// Send final status message
|
|
579
|
-
const failureStatusResponseMessage =
|
|
468
|
+
this.txnMgr.completeTransaction(
|
|
469
|
+
txId,
|
|
580
470
|
buildToClientStatusResponseMessage(
|
|
581
471
|
this.clientId,
|
|
582
|
-
|
|
472
|
+
{
|
|
473
|
+
status: keyMirrors.statusResponse.failure,
|
|
474
|
+
message: message.payload.message
|
|
475
|
+
},
|
|
583
476
|
txId
|
|
584
|
-
)
|
|
585
|
-
|
|
477
|
+
)
|
|
478
|
+
);
|
|
586
479
|
}
|
|
587
480
|
break;
|
|
588
481
|
}
|
|
589
482
|
case device_action: {
|
|
590
483
|
try {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
484
|
+
this.publisher.publishToClient(
|
|
485
|
+
buildToClientStatusResponseMessage(
|
|
486
|
+
this.clientId,
|
|
487
|
+
{
|
|
488
|
+
status: keyMirrors.statusResponse.in_progress
|
|
489
|
+
},
|
|
490
|
+
txId
|
|
491
|
+
)
|
|
598
492
|
);
|
|
599
|
-
this.publisher.publishToClient(statusResponseMessage);
|
|
600
493
|
|
|
601
494
|
await this.handleDeviceAction(message.payload);
|
|
602
495
|
|
|
603
|
-
|
|
604
|
-
status: keyMirrors.statusResponse.success
|
|
605
|
-
};
|
|
606
|
-
const successStatusResponseMessage =
|
|
496
|
+
this.publisher.publishToClient(
|
|
607
497
|
buildToClientStatusResponseMessage(
|
|
608
498
|
this.clientId,
|
|
609
|
-
|
|
499
|
+
{
|
|
500
|
+
status: keyMirrors.statusResponse.success
|
|
501
|
+
},
|
|
610
502
|
txId
|
|
611
|
-
)
|
|
612
|
-
|
|
503
|
+
)
|
|
504
|
+
);
|
|
613
505
|
} catch (e) {
|
|
614
506
|
logger.error(
|
|
615
|
-
`There was a problem performing device action '${
|
|
507
|
+
`There was a problem performing device action '${
|
|
508
|
+
message.payload.action
|
|
509
|
+
}'!\n${stringifyError(e)}`
|
|
616
510
|
);
|
|
617
|
-
|
|
618
|
-
status: keyMirrors.statusResponse.failure,
|
|
619
|
-
message: e.message
|
|
620
|
-
};
|
|
621
|
-
const failureStatusResponseMessage =
|
|
511
|
+
this.publisher.publishToClient(
|
|
622
512
|
buildToClientStatusResponseMessage(
|
|
623
513
|
this.clientId,
|
|
624
|
-
|
|
514
|
+
{
|
|
515
|
+
status: keyMirrors.statusResponse.failure,
|
|
516
|
+
message: e.message
|
|
517
|
+
},
|
|
625
518
|
txId
|
|
626
|
-
)
|
|
627
|
-
|
|
519
|
+
)
|
|
520
|
+
);
|
|
628
521
|
}
|
|
629
522
|
break;
|
|
630
523
|
}
|
|
@@ -639,134 +532,347 @@ export class DeviceAgentCloudConnection {
|
|
|
639
532
|
}
|
|
640
533
|
}
|
|
641
534
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
657
|
-
// SecureTunnel messages
|
|
658
|
-
} else if (
|
|
659
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
660
|
-
) {
|
|
661
|
-
await this.handleSecureTunnelMessage(message);
|
|
662
|
-
} else if (
|
|
663
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
664
|
-
) {
|
|
665
|
-
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
666
|
-
await this.secureTunnelHandler.destroy();
|
|
667
|
-
} else {
|
|
668
|
-
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
535
|
+
private handleAppStateControl = async (
|
|
536
|
+
payload: AppStateControlPayload
|
|
537
|
+
): Promise<boolean> => {
|
|
538
|
+
const { baseCommand, projectId } = payload;
|
|
539
|
+
switch (baseCommand) {
|
|
540
|
+
case keyMirrors.appStateControl.start:
|
|
541
|
+
await startApp({ projectId });
|
|
542
|
+
break;
|
|
543
|
+
case keyMirrors.appStateControl.stop:
|
|
544
|
+
await stopApp({ projectId });
|
|
545
|
+
break;
|
|
546
|
+
case keyMirrors.appStateControl.restart:
|
|
547
|
+
await restartApp({ projectId });
|
|
548
|
+
break;
|
|
669
549
|
}
|
|
670
|
-
|
|
550
|
+
return true;
|
|
551
|
+
};
|
|
671
552
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
553
|
+
private handleAppVersionControl = async (
|
|
554
|
+
payload:
|
|
555
|
+
| AppVersionControlInstallPayload
|
|
556
|
+
| AppVersionControlUninstallPayload,
|
|
557
|
+
txId: string
|
|
558
|
+
): Promise<boolean> => {
|
|
559
|
+
switch (payload.baseCommand) {
|
|
560
|
+
case keyMirrors.appVersionControl.install: {
|
|
561
|
+
const { projectId, appReleaseHash } = payload;
|
|
680
562
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
563
|
+
const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
564
|
+
signedUrlsRequest: {
|
|
565
|
+
projectId,
|
|
566
|
+
appReleaseHash
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const message = buildSignedUrlsRequestMessage(
|
|
570
|
+
this.clientId,
|
|
571
|
+
signedUrlsRequestPayload,
|
|
572
|
+
txId
|
|
573
|
+
);
|
|
574
|
+
await this.publishCloudRequest(message);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
case keyMirrors.appVersionControl.uninstall: {
|
|
578
|
+
const { projectId } = payload;
|
|
579
|
+
await this.atomicApplicationUninstall(projectId);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
default:
|
|
583
|
+
logger.warn(
|
|
584
|
+
`Ignore App Version Control packet: ${JSON.stringify(
|
|
585
|
+
payload,
|
|
586
|
+
null,
|
|
587
|
+
2
|
|
588
|
+
)}`
|
|
589
|
+
);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
684
593
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
594
|
+
private handleAppInstallCloudResponsePayload = async (
|
|
595
|
+
payload: AppInstallResponsePayload
|
|
596
|
+
): Promise<boolean> => {
|
|
597
|
+
const {
|
|
598
|
+
projectId,
|
|
599
|
+
appReleaseHash,
|
|
600
|
+
appInstallPayload,
|
|
601
|
+
modelsInstallPayload
|
|
602
|
+
} = payload.appInstallResponse;
|
|
603
|
+
const signedUrlsPayload = {
|
|
604
|
+
appInstallPayload,
|
|
605
|
+
modelsInstallPayload
|
|
606
|
+
};
|
|
607
|
+
await this.atomicApplicationUpdate(async () => {
|
|
608
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
609
|
+
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
610
|
+
}, projectId);
|
|
611
|
+
return true;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
private handleModelsInstallCloudResponsePayload = async (
|
|
615
|
+
payload: ModelsInstallResponsePayload,
|
|
616
|
+
txId: string
|
|
617
|
+
): Promise<boolean> => {
|
|
618
|
+
const projectId = payload.modelsInstallResponse.projectId;
|
|
619
|
+
|
|
620
|
+
const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
|
|
621
|
+
if (update === undefined) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
'Unknown error while updating models via application config! No config present for model update.'
|
|
688
624
|
);
|
|
689
|
-
}
|
|
625
|
+
}
|
|
626
|
+
const { appCfgUpdate, envVarUpdate } = update;
|
|
627
|
+
if (appCfgUpdate) {
|
|
628
|
+
await this.atomicApplicationUpdate(
|
|
629
|
+
async () =>
|
|
630
|
+
await updateModelsWithPresignedUrls({
|
|
631
|
+
projectId,
|
|
632
|
+
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
633
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
634
|
+
}),
|
|
635
|
+
projectId
|
|
636
|
+
);
|
|
637
|
+
}
|
|
690
638
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
639
|
+
if (envVarUpdate) {
|
|
640
|
+
await this.atomicApplicationUpdate(
|
|
641
|
+
async () =>
|
|
642
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
643
|
+
projectId,
|
|
644
|
+
envVars: envVarUpdate.envVars
|
|
645
|
+
}),
|
|
646
|
+
projectId,
|
|
647
|
+
true
|
|
648
|
+
);
|
|
649
|
+
}
|
|
695
650
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
651
|
+
return true;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
private async handleDeviceAction(payload: DeviceActionPayload) {
|
|
655
|
+
const { system_restart } = keyMirrors.deviceAction;
|
|
656
|
+
switch (payload.action) {
|
|
657
|
+
case system_restart: {
|
|
658
|
+
await reboot();
|
|
659
|
+
break;
|
|
702
660
|
}
|
|
703
|
-
|
|
661
|
+
default: {
|
|
662
|
+
logger.info(
|
|
663
|
+
`Unrecognized device action requested: '${payload.action}'.`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
704
668
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
669
|
+
private async publishCloudRequest(message: ToCloudMessage) {
|
|
670
|
+
this.publisher.publishToCloud(message);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private subscribe(topic: string) {
|
|
674
|
+
logger.info(`Subscribing to ${topic}`);
|
|
675
|
+
this.device.subscribe(topic);
|
|
709
676
|
}
|
|
710
677
|
|
|
711
|
-
|
|
678
|
+
private async atomicApplicationUninstall(projectId: string) {
|
|
712
679
|
try {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
});
|
|
742
|
-
} catch (err) {
|
|
743
|
-
const output = JSON.stringify(err['stderr']);
|
|
744
|
-
if (output.indexOf('not known') !== -1) {
|
|
745
|
-
logger.warn(
|
|
746
|
-
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
680
|
+
await uninstallApp({ projectId });
|
|
681
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
682
|
+
} catch (e) {
|
|
683
|
+
logger.error(`Failed to uninstall ${projectId}!\n${stringifyError(e)}`);
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// eslint-disable-next-line
|
|
689
|
+
private async atomicApplicationUpdate<F extends () => any>(
|
|
690
|
+
func: F,
|
|
691
|
+
projectId: string,
|
|
692
|
+
skipUpdateShadow?: boolean
|
|
693
|
+
): Promise<ReturnType<F>> {
|
|
694
|
+
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
695
|
+
// Reject the application update if app is present but not ready
|
|
696
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
697
|
+
throw new Error('Application already has installation in progress!');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Try to create a backup, so that there is one available if something goes wrong in the next try:catch.
|
|
701
|
+
try {
|
|
702
|
+
await createAppBackup({ projectId });
|
|
703
|
+
} catch (e) {
|
|
704
|
+
logger.error(
|
|
705
|
+
`Could not create a backup for the project: ${projectId}!\n${stringifyError(
|
|
706
|
+
e
|
|
707
|
+
)}`
|
|
747
708
|
);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const out: ReturnType<F> = await func();
|
|
714
|
+
if (!skipUpdateShadow)
|
|
715
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
716
|
+
return out;
|
|
717
|
+
} catch (errorAppUpdate) {
|
|
718
|
+
logger.error(
|
|
719
|
+
`Failed to update ${projectId}!\n${stringifyError(errorAppUpdate)}`
|
|
720
|
+
);
|
|
721
|
+
// If something goes wrong, first try to rollback
|
|
722
|
+
try {
|
|
723
|
+
await rollbackApp({ projectId });
|
|
724
|
+
} catch (errorRollbackApp) {
|
|
725
|
+
logger.error(
|
|
726
|
+
`Application rollback failed for ${projectId}!\n${stringifyError(
|
|
727
|
+
errorRollbackApp
|
|
728
|
+
)}`
|
|
751
729
|
);
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
730
|
+
// and if that fails, uninstall the app as a last resort.
|
|
731
|
+
try {
|
|
732
|
+
await this.atomicApplicationUninstall(projectId);
|
|
733
|
+
} catch {
|
|
734
|
+
// atomicApplicationUninstall logs failure, so there's nothing to do here.
|
|
735
|
+
}
|
|
736
|
+
throw new AaiError(
|
|
737
|
+
'Application update and rollback failed, uninstalled the application!',
|
|
738
|
+
{ cause: errorAppUpdate }
|
|
755
739
|
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
740
|
+
}
|
|
741
|
+
throw new Error(
|
|
742
|
+
'Application update failed, rolled the application back!',
|
|
743
|
+
{ cause: errorAppUpdate }
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private handleProjectShadowConfigUpdate = async (
|
|
749
|
+
update: ShadowUpdate,
|
|
750
|
+
txId: string
|
|
751
|
+
): Promise<boolean> => {
|
|
752
|
+
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
753
|
+
|
|
754
|
+
if (
|
|
755
|
+
appCfgUpdate?.updatedModels &&
|
|
756
|
+
Object.keys(appCfgUpdate.updatedModels).length
|
|
757
|
+
) {
|
|
758
|
+
// When there are model updates request signed URLs and wait to apply config changes
|
|
759
|
+
const { updatedModels } = appCfgUpdate;
|
|
760
|
+
|
|
761
|
+
logger.debug(
|
|
762
|
+
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
763
|
+
updatedModels
|
|
764
|
+
)}`
|
|
765
|
+
);
|
|
766
|
+
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
767
|
+
modelsOnlyUrlsRequest: {
|
|
768
|
+
projectId,
|
|
769
|
+
models: updatedModels
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
const message = buildSignedUrlsRequestMessage(
|
|
773
|
+
this.clientId,
|
|
774
|
+
modelsOnlyUrlsRequestPayload,
|
|
775
|
+
txId
|
|
776
|
+
);
|
|
777
|
+
this.publisher.publishToCloud(message);
|
|
778
|
+
|
|
779
|
+
this.txnMgr.setAppCfgUpdateToTx(txId, update);
|
|
780
|
+
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (appCfgUpdate) {
|
|
785
|
+
await this.atomicApplicationUpdate(async () => {
|
|
786
|
+
await pruneModels({
|
|
787
|
+
projectId,
|
|
788
|
+
appCfg: appCfgUpdate.newAppCfg
|
|
789
|
+
});
|
|
790
|
+
await updateAppCfg({
|
|
791
|
+
projectId,
|
|
792
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
793
|
+
});
|
|
794
|
+
}, projectId);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (envVarUpdate) {
|
|
798
|
+
await this.atomicApplicationUpdate(
|
|
799
|
+
async () =>
|
|
800
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
801
|
+
projectId,
|
|
802
|
+
envVars: envVarUpdate.envVars
|
|
803
|
+
}),
|
|
804
|
+
projectId,
|
|
805
|
+
true
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
812
|
+
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
813
|
+
topic,
|
|
814
|
+
payload: message,
|
|
815
|
+
clientToken: message.clientToken
|
|
816
|
+
});
|
|
817
|
+
if (shadowUpdates.length) {
|
|
818
|
+
const shadowUpdatePromises: Promise<void>[] = [];
|
|
819
|
+
for (const shadowUpdate of shadowUpdates) {
|
|
820
|
+
const projectId = shadowUpdate.projectId;
|
|
821
|
+
const txId = shadowUpdate.txId;
|
|
822
|
+
shadowUpdatePromises.push(
|
|
823
|
+
this.txnMgr
|
|
824
|
+
.runTransactionStep({
|
|
825
|
+
func: () =>
|
|
826
|
+
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
827
|
+
projectId,
|
|
828
|
+
txId,
|
|
829
|
+
start: true,
|
|
830
|
+
liveUpdatesPublishFn: async () =>
|
|
831
|
+
this.publisher.publishToClient(
|
|
832
|
+
buildToClientStatusResponseMessage(
|
|
833
|
+
this.clientId,
|
|
834
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
835
|
+
txId
|
|
836
|
+
),
|
|
837
|
+
logger.silly
|
|
838
|
+
),
|
|
839
|
+
stepName: topic
|
|
840
|
+
})
|
|
841
|
+
.catch((e) => {
|
|
842
|
+
logger.error(
|
|
843
|
+
`There was an issue updating project shadow config for ${projectId}!\n${stringifyError(
|
|
844
|
+
e
|
|
845
|
+
)}`
|
|
846
|
+
);
|
|
847
|
+
})
|
|
759
848
|
);
|
|
760
849
|
}
|
|
850
|
+
|
|
851
|
+
await Promise.all(shadowUpdatePromises);
|
|
761
852
|
}
|
|
762
853
|
}
|
|
763
854
|
|
|
764
|
-
public async
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
855
|
+
public async handleSecureTunnelMessage(payload: any): Promise<void> {
|
|
856
|
+
logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
|
|
857
|
+
const state = getUpdateDeltaStateFromMessage(payload);
|
|
858
|
+
if (!state) {
|
|
859
|
+
logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const valid = validateSecureTunnelShadowUpdate(state);
|
|
863
|
+
if (!valid) {
|
|
864
|
+
logger.error(
|
|
865
|
+
`Error validating message: ${JSON.stringify(
|
|
866
|
+
{ payload, errors: validateSecureTunnelShadowUpdate.errors },
|
|
867
|
+
null,
|
|
868
|
+
2
|
|
869
|
+
)}`
|
|
870
|
+
);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const secureTunnelUpdate =
|
|
874
|
+
await this.secureTunnelHandler.syncShadowToDeviceState(payload);
|
|
875
|
+
await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
|
|
770
876
|
}
|
|
771
877
|
}
|
|
772
878
|
|
|
@@ -774,20 +880,28 @@ export async function runDeviceAgentCloudInterface() {
|
|
|
774
880
|
logger.info(
|
|
775
881
|
`Starting alwaysAI Device Agent v${await getDeviceAgentVersion()}`
|
|
776
882
|
);
|
|
777
|
-
if (
|
|
883
|
+
if (existsSync(getBootstrapPrivateKeyFilePath())) {
|
|
884
|
+
await bootstrapProvision();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const filesAlreadyMigrated = await requiredConfigFilesPresentAndValid();
|
|
889
|
+
if (!filesAlreadyMigrated) {
|
|
890
|
+
logger.debug('Attempting configuration file migration.');
|
|
891
|
+
await migrateFromLegacyCertsAndTokens();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (await requiredConfigFilesPresentAndValid()) {
|
|
778
895
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
779
|
-
await deviceAgent.setupHandlers();
|
|
780
896
|
if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
|
|
897
|
+
const shadowHandler = deviceAgent.shadowHandler;
|
|
781
898
|
const publisher = deviceAgent.publisher;
|
|
782
|
-
const passthroughHandler = new PassthroughHandler(
|
|
899
|
+
const passthroughHandler = new PassthroughHandler(
|
|
900
|
+
publisher,
|
|
901
|
+
shadowHandler
|
|
902
|
+
);
|
|
783
903
|
await passthroughHandler.setup();
|
|
784
904
|
}
|
|
785
|
-
} else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
|
|
786
|
-
await bootstrapProvision();
|
|
787
|
-
} else if (existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH())) {
|
|
788
|
-
throw new Error(
|
|
789
|
-
"Device has not been created using 'aai-agent device init' or there has been an issue with device initialization"
|
|
790
|
-
);
|
|
791
905
|
} else {
|
|
792
906
|
throw new Error(
|
|
793
907
|
"Set device agent to local mode and retry the 'aai-agent device init' command"
|