@alwaysai/device-agent 0.0.1-2.1-beta-provision
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/backup.d.ts +8 -0
- package/lib/application-control/backup.d.ts.map +1 -0
- package/lib/application-control/backup.js +37 -0
- package/lib/application-control/backup.js.map +1 -0
- package/lib/application-control/config.d.ts +17 -0
- package/lib/application-control/config.d.ts.map +1 -0
- package/lib/application-control/config.js +62 -0
- package/lib/application-control/config.js.map +1 -0
- package/lib/application-control/environment-variables.d.ts +9 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -0
- package/lib/application-control/environment-variables.js +73 -0
- package/lib/application-control/environment-variables.js.map +1 -0
- package/lib/application-control/index.d.ts +10 -0
- package/lib/application-control/index.d.ts.map +1 -0
- package/lib/application-control/index.js +32 -0
- package/lib/application-control/index.js.map +1 -0
- package/lib/application-control/install.d.ts +22 -0
- package/lib/application-control/install.d.ts.map +1 -0
- package/lib/application-control/install.js +156 -0
- package/lib/application-control/install.js.map +1 -0
- package/lib/application-control/models.d.ts +23 -0
- package/lib/application-control/models.d.ts.map +1 -0
- package/lib/application-control/models.js +154 -0
- package/lib/application-control/models.js.map +1 -0
- package/lib/application-control/status.d.ts +27 -0
- package/lib/application-control/status.d.ts.map +1 -0
- package/lib/application-control/status.js +153 -0
- package/lib/application-control/status.js.map +1 -0
- package/lib/application-control/types.d.ts +5 -0
- package/lib/application-control/types.d.ts.map +1 -0
- package/lib/application-control/types.js +3 -0
- package/lib/application-control/types.js.map +1 -0
- package/lib/application-control/utils.d.ts +14 -0
- package/lib/application-control/utils.d.ts.map +1 -0
- package/lib/application-control/utils.js +82 -0
- package/lib/application-control/utils.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +51 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.js +490 -0
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -0
- package/lib/cloud-connection/device-agent.d.ts +21 -0
- package/lib/cloud-connection/device-agent.d.ts.map +1 -0
- package/lib/cloud-connection/device-agent.js +65 -0
- package/lib/cloud-connection/device-agent.js.map +1 -0
- package/lib/device-control/device-control.d.ts +4 -0
- package/lib/device-control/device-control.d.ts.map +1 -0
- package/lib/device-control/device-control.js +20 -0
- package/lib/device-control/device-control.js.map +1 -0
- package/lib/docker/docker-cmd.d.ts +4 -0
- package/lib/docker/docker-cmd.d.ts.map +1 -0
- package/lib/docker/docker-cmd.js +16 -0
- package/lib/docker/docker-cmd.js.map +1 -0
- package/lib/docker/docker-compose-cmd.d.ts +5 -0
- package/lib/docker/docker-compose-cmd.d.ts.map +1 -0
- package/lib/docker/docker-compose-cmd.js +16 -0
- package/lib/docker/docker-compose-cmd.js.map +1 -0
- package/lib/endpoints.d.ts +3 -0
- package/lib/endpoints.d.ts.map +1 -0
- package/lib/endpoints.js +28 -0
- package/lib/endpoints.js.map +1 -0
- package/lib/environment.d.ts +7 -0
- package/lib/environment.d.ts.map +1 -0
- package/lib/environment.js +24 -0
- package/lib/environment.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +26 -0
- package/lib/index.js.map +1 -0
- package/lib/infrastructure/agent-config.d.ts +73 -0
- package/lib/infrastructure/agent-config.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.js +175 -0
- package/lib/infrastructure/agent-config.js.map +1 -0
- package/lib/infrastructure/agent-config.test.d.ts +2 -0
- package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
- package/lib/infrastructure/agent-config.test.js +182 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts +6 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
- package/lib/infrastructure/certificates-and-tokens.js +37 -0
- package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
- package/lib/infrastructure/urls.d.ts +2 -0
- package/lib/infrastructure/urls.d.ts.map +1 -0
- package/lib/infrastructure/urls.js +25 -0
- package/lib/infrastructure/urls.js.map +1 -0
- package/lib/root.d.ts +2 -0
- package/lib/root.d.ts.map +1 -0
- package/lib/root.js +12 -0
- package/lib/root.js.map +1 -0
- package/lib/subcommands/app/app.d.ts +59 -0
- package/lib/subcommands/app/app.d.ts.map +1 -0
- package/lib/subcommands/app/app.js +317 -0
- package/lib/subcommands/app/app.js.map +1 -0
- package/lib/subcommands/app/index.d.ts +2 -0
- package/lib/subcommands/app/index.d.ts.map +1 -0
- package/lib/subcommands/app/index.js +30 -0
- package/lib/subcommands/app/index.js.map +1 -0
- package/lib/subcommands/device/clean.d.ts +2 -0
- package/lib/subcommands/device/clean.d.ts.map +1 -0
- package/lib/subcommands/device/clean.js +20 -0
- package/lib/subcommands/device/clean.js.map +1 -0
- package/lib/subcommands/device/device.d.ts +6 -0
- package/lib/subcommands/device/device.d.ts.map +1 -0
- package/lib/subcommands/device/device.js +92 -0
- package/lib/subcommands/device/device.js.map +1 -0
- package/lib/subcommands/device/index.d.ts +2 -0
- package/lib/subcommands/device/index.d.ts.map +1 -0
- package/lib/subcommands/device/index.js +12 -0
- package/lib/subcommands/device/index.js.map +1 -0
- package/lib/subcommands/get-model-package.d.ts +5 -0
- package/lib/subcommands/get-model-package.d.ts.map +1 -0
- package/lib/subcommands/get-model-package.js +35 -0
- package/lib/subcommands/get-model-package.js.map +1 -0
- package/lib/subcommands/index.d.ts +9 -0
- package/lib/subcommands/index.d.ts.map +1 -0
- package/lib/subcommands/index.js +14 -0
- package/lib/subcommands/index.js.map +1 -0
- package/lib/subcommands/login.d.ts +6 -0
- package/lib/subcommands/login.d.ts.map +1 -0
- package/lib/subcommands/login.js +35 -0
- package/lib/subcommands/login.js.map +1 -0
- package/lib/util/clean-certs.d.ts +2 -0
- package/lib/util/clean-certs.d.ts.map +1 -0
- package/lib/util/clean-certs.js +16 -0
- package/lib/util/clean-certs.js.map +1 -0
- package/lib/util/copy-dir.d.ts +5 -0
- package/lib/util/copy-dir.d.ts.map +1 -0
- package/lib/util/copy-dir.js +16 -0
- package/lib/util/copy-dir.js.map +1 -0
- package/lib/util/directories.d.ts +23 -0
- package/lib/util/directories.d.ts.map +1 -0
- package/lib/util/directories.js +50 -0
- package/lib/util/directories.js.map +1 -0
- package/lib/util/get-device-id.d.ts +2 -0
- package/lib/util/get-device-id.d.ts.map +1 -0
- package/lib/util/get-device-id.js +24 -0
- package/lib/util/get-device-id.js.map +1 -0
- package/lib/util/http-client.d.ts +3 -0
- package/lib/util/http-client.d.ts.map +1 -0
- package/lib/util/http-client.js +30 -0
- package/lib/util/http-client.js.map +1 -0
- package/lib/util/logger.d.ts +4 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/logger.js +24 -0
- package/lib/util/logger.js.map +1 -0
- package/lib/util/run-in-dir.d.ts +2 -0
- package/lib/util/run-in-dir.d.ts.map +1 -0
- package/lib/util/run-in-dir.js +17 -0
- package/lib/util/run-in-dir.js.map +1 -0
- package/lib/util/sleep.d.ts +2 -0
- package/lib/util/sleep.d.ts.map +1 -0
- package/lib/util/sleep.js +9 -0
- package/lib/util/sleep.js.map +1 -0
- package/package.json +98 -0
- package/readme.md +219 -0
- package/src/application-control/backup.ts +36 -0
- package/src/application-control/config.ts +61 -0
- package/src/application-control/environment-variables.ts +74 -0
- package/src/application-control/index.ts +45 -0
- package/src/application-control/install.ts +206 -0
- package/src/application-control/models.ts +194 -0
- package/src/application-control/status.ts +187 -0
- package/src/application-control/types.ts +1 -0
- package/src/application-control/utils.ts +95 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +673 -0
- package/src/cloud-connection/device-agent.ts +120 -0
- package/src/device-control/device-control.ts +16 -0
- package/src/docker/docker-cmd.ts +12 -0
- package/src/docker/docker-compose-cmd.ts +12 -0
- package/src/endpoints.ts +24 -0
- package/src/environment.ts +28 -0
- package/src/index.ts +26 -0
- package/src/infrastructure/agent-config.test.ts +199 -0
- package/src/infrastructure/agent-config.ts +208 -0
- package/src/infrastructure/certificates-and-tokens.ts +47 -0
- package/src/infrastructure/urls.ts +21 -0
- package/src/root.ts +11 -0
- package/src/subcommands/app/app.ts +337 -0
- package/src/subcommands/app/index.ts +46 -0
- package/src/subcommands/device/clean.ts +16 -0
- package/src/subcommands/device/device.ts +126 -0
- package/src/subcommands/device/index.ts +9 -0
- package/src/subcommands/get-model-package.ts +33 -0
- package/src/subcommands/index.ts +11 -0
- package/src/subcommands/login.ts +33 -0
- package/src/util/clean-certs.ts +12 -0
- package/src/util/copy-dir.ts +12 -0
- package/src/util/directories.ts +82 -0
- package/src/util/get-device-id.ts +22 -0
- package/src/util/http-client.ts +35 -0
- package/src/util/logger.ts +28 -0
- package/src/util/run-in-dir.ts +15 -0
- package/src/util/sleep.ts +5 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
const awsIot = require("aws-iot-device-sdk");
|
|
2
|
+
import { getIoTCoreEndpointUrl } from "../infrastructure/urls";
|
|
3
|
+
import { existsSync, createReadStream } from "fs";
|
|
4
|
+
import { rmBootstrapCertsAndClose } from "../util/clean-certs";
|
|
5
|
+
import {
|
|
6
|
+
getPrivateKeyFilePath,
|
|
7
|
+
getCertificateFilePath,
|
|
8
|
+
BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
|
|
9
|
+
BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
10
|
+
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
11
|
+
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
12
|
+
} from "../util/directories";
|
|
13
|
+
|
|
14
|
+
import { BootstrapAgent } from "./device-agent";
|
|
15
|
+
|
|
16
|
+
import sleep from "../util/sleep";
|
|
17
|
+
import {
|
|
18
|
+
startApp,
|
|
19
|
+
stopApp,
|
|
20
|
+
restartApp,
|
|
21
|
+
getAppLogs,
|
|
22
|
+
getAppStatus,
|
|
23
|
+
} from "../application-control/status";
|
|
24
|
+
import { installApp, uninstallApp } from "../application-control/install";
|
|
25
|
+
import {
|
|
26
|
+
keyMirrors,
|
|
27
|
+
validateClientMessage,
|
|
28
|
+
DeviceAgentMessagePayload,
|
|
29
|
+
AppInstallStatusPacket,
|
|
30
|
+
AppStateControlPacket,
|
|
31
|
+
AppVersionControlPacket,
|
|
32
|
+
LiveUpdatesToggles,
|
|
33
|
+
AppStatePacket,
|
|
34
|
+
LiveUpdatesToggleMessage,
|
|
35
|
+
DeviceStatsMessage,
|
|
36
|
+
AppStateMessage,
|
|
37
|
+
AppInstallStatusMessage,
|
|
38
|
+
SignedUrlsRequestMessage,
|
|
39
|
+
DeviceAgentMessage,
|
|
40
|
+
ClientMessage,
|
|
41
|
+
getClientTopic,
|
|
42
|
+
getCloudTopic,
|
|
43
|
+
getDeviceTopic,
|
|
44
|
+
} from "@alwaysai/device-agent-schemas";
|
|
45
|
+
import { getDeviceId } from "../util/get-device-id";
|
|
46
|
+
import { logger } from "../util/logger";
|
|
47
|
+
import {
|
|
48
|
+
getCpuUtil,
|
|
49
|
+
getDiskUtil,
|
|
50
|
+
getMemUtil,
|
|
51
|
+
} from "../device-control/device-control";
|
|
52
|
+
import { AgentConfigFile } from "../infrastructure/agent-config";
|
|
53
|
+
import {
|
|
54
|
+
buildApp,
|
|
55
|
+
getAppConfig,
|
|
56
|
+
getAppDir,
|
|
57
|
+
} from "../application-control/utils";
|
|
58
|
+
import { updateModelsWithPresignedUrls } from "../application-control/models";
|
|
59
|
+
import { updateAppConfig } from "../application-control/config";
|
|
60
|
+
|
|
61
|
+
export class DeviceAgentCloudConnection {
|
|
62
|
+
private clientId = getDeviceId();
|
|
63
|
+
private host = getIoTCoreEndpointUrl();
|
|
64
|
+
private appInstallStatus: AppInstallStatusPacket;
|
|
65
|
+
private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
|
|
66
|
+
private liveUpdatesAlive = {
|
|
67
|
+
[keyMirrors.agentMessageType.device_stats]: false,
|
|
68
|
+
[keyMirrors.agentMessageType.app_state]: false,
|
|
69
|
+
[keyMirrors.agentMessageType.app_logs]: false,
|
|
70
|
+
};
|
|
71
|
+
private liveUpdatesSleepIntervals = {
|
|
72
|
+
[keyMirrors.agentMessageType.device_stats]: 5000,
|
|
73
|
+
[keyMirrors.agentMessageType.app_state]: 5000,
|
|
74
|
+
[keyMirrors.agentMessageType.app_logs]: 5000,
|
|
75
|
+
[keyMirrors.agentMessageType.app_install_status]: 5000,
|
|
76
|
+
};
|
|
77
|
+
private appLogStreams = new Set<string>();
|
|
78
|
+
private readonly shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
|
|
79
|
+
private readonly shadowTopics = {
|
|
80
|
+
projects: {
|
|
81
|
+
updateDelta: `${this.shadowPrefix}projects/update/delta`,
|
|
82
|
+
getAccepted: `${this.shadowPrefix}projects/get/accepted`,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
private readonly toCloudTopic = getCloudTopic(this.clientId);
|
|
86
|
+
private readonly toClientTopic = getClientTopic(this.clientId);
|
|
87
|
+
private readonly toDeviceTopic = getDeviceTopic(this.clientId);
|
|
88
|
+
|
|
89
|
+
// device shadow utils
|
|
90
|
+
|
|
91
|
+
public getShadowPrefix() {
|
|
92
|
+
return this.shadowPrefix;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async handleNamedShadowUpdate({ payload }: { payload: string }) {
|
|
96
|
+
const delta = JSON.parse(payload);
|
|
97
|
+
const deltaKeys = Object.keys(delta);
|
|
98
|
+
|
|
99
|
+
for (const projectId of deltaKeys) {
|
|
100
|
+
const projectShadow = delta[projectId];
|
|
101
|
+
if (projectShadow.appConfig) {
|
|
102
|
+
const appConfig = projectShadow.appConfig;
|
|
103
|
+
const appDir = getAppDir(projectId);
|
|
104
|
+
await updateAppConfig(projectId, appConfig);
|
|
105
|
+
|
|
106
|
+
if (appConfig.models) {
|
|
107
|
+
this.publishCloudRequest({
|
|
108
|
+
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
109
|
+
modelsOnlyUrlsRequest: {
|
|
110
|
+
projectId,
|
|
111
|
+
models: appConfig.models,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (appConfig.scripts && !appConfig.models) {
|
|
117
|
+
const appState = await getAppStatus({ projectId });
|
|
118
|
+
|
|
119
|
+
await buildApp({ appDir });
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
appState.services.length &&
|
|
123
|
+
appState.services[0].state !== keyMirrors.appState.stopped
|
|
124
|
+
) {
|
|
125
|
+
restartApp({ projectId });
|
|
126
|
+
}
|
|
127
|
+
await this.publishReportedState(projectId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async startAppLogStream(projectId: string) {
|
|
134
|
+
this.appLogStreams.add(projectId);
|
|
135
|
+
const readable = await getAppLogs({
|
|
136
|
+
projectId,
|
|
137
|
+
args: ["--tail", "100", "--no-log-prefix"],
|
|
138
|
+
});
|
|
139
|
+
readable.on("data", (chunk: Buffer) => {
|
|
140
|
+
if (!this.appLogStreams.has(projectId)) {
|
|
141
|
+
// why doesn't typescript know about this function?
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
readable.destroy();
|
|
144
|
+
logger.info(`App log stream terminated for project ${projectId}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const logStr = chunk.toString();
|
|
148
|
+
const message = {
|
|
149
|
+
messageType: keyMirrors.agentMessageType.app_logs,
|
|
150
|
+
appLogs: {
|
|
151
|
+
projectId,
|
|
152
|
+
logChunk: logStr,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
const packet = this.buildMessagePacket(
|
|
156
|
+
this.getClientId(),
|
|
157
|
+
this.toClientTopic,
|
|
158
|
+
message
|
|
159
|
+
);
|
|
160
|
+
this.publishMessage(this.toClientTopic, JSON.stringify(packet));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
readable.on("error", (error) => {
|
|
164
|
+
logger.error(
|
|
165
|
+
`App log stream terminated for project ${projectId}: ${error}`
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
readable.on("finished", () => {
|
|
170
|
+
logger.info(`App logs finished piping for project ${projectId}`);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// must contain app release hash
|
|
175
|
+
private initAppInstallStatus(installationStatus: AppInstallStatusPacket) {
|
|
176
|
+
this.appInstallStatus = installationStatus;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private updateAppInstallStatus(
|
|
180
|
+
installationStatus: Omit<AppInstallStatusPacket, "appReleaseHash">
|
|
181
|
+
) {
|
|
182
|
+
this.appInstallStatus.status = installationStatus.status;
|
|
183
|
+
this.appInstallStatus.message = installationStatus.message;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private getAppInstallStatus(): AppInstallStatusPacket {
|
|
187
|
+
return this.appInstallStatus;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Message Builders
|
|
191
|
+
private buildMessagePacket(
|
|
192
|
+
deviceId: string,
|
|
193
|
+
topic: string,
|
|
194
|
+
payload: DeviceAgentMessagePayload
|
|
195
|
+
): DeviceAgentMessage {
|
|
196
|
+
const packet = {
|
|
197
|
+
timestamp: new Date().toUTCString(),
|
|
198
|
+
deviceId,
|
|
199
|
+
topic,
|
|
200
|
+
payload,
|
|
201
|
+
};
|
|
202
|
+
return packet;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async getAppStateMessage(): Promise<AppStateMessage> {
|
|
206
|
+
const appStateMessage: AppStatePacket[] = [];
|
|
207
|
+
const apps = await AgentConfigFile().getReadyApps();
|
|
208
|
+
for (const app of apps) {
|
|
209
|
+
const projectId = app.projectId;
|
|
210
|
+
const status = await getAppStatus({ projectId });
|
|
211
|
+
appStateMessage.push(status);
|
|
212
|
+
}
|
|
213
|
+
const appStatePackage = {
|
|
214
|
+
messageType: keyMirrors.agentMessageType.app_state,
|
|
215
|
+
appState: appStateMessage,
|
|
216
|
+
};
|
|
217
|
+
return appStatePackage;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private async getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
|
|
221
|
+
const cpuUsage = await getCpuUtil();
|
|
222
|
+
const diskUtil = await getDiskUtil();
|
|
223
|
+
const memUtil = await getMemUtil();
|
|
224
|
+
|
|
225
|
+
const deviceStatsMessage = {
|
|
226
|
+
messageType: keyMirrors.agentMessageType.device_stats,
|
|
227
|
+
deviceStats: {
|
|
228
|
+
cpuUsage,
|
|
229
|
+
diskUtil,
|
|
230
|
+
usedMemoryPercentage: memUtil,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
return deviceStatsMessage;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// must be arrow function due to this context when function is passed as param
|
|
237
|
+
private getAppInstallStatusMessage =
|
|
238
|
+
async (): Promise<AppInstallStatusMessage> => {
|
|
239
|
+
const appInstallStatus = this.getAppInstallStatus();
|
|
240
|
+
const appInstallStatusMessage = {
|
|
241
|
+
messageType: keyMirrors.agentMessageType.app_install_status,
|
|
242
|
+
appInstallStatus,
|
|
243
|
+
};
|
|
244
|
+
return appInstallStatusMessage;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
private async startPublishingLiveUpdates(
|
|
248
|
+
topic: string,
|
|
249
|
+
messageType: string,
|
|
250
|
+
getMessageData: () => Promise<DeviceAgentMessagePayload>
|
|
251
|
+
) {
|
|
252
|
+
while (true) {
|
|
253
|
+
try {
|
|
254
|
+
const message = await getMessageData();
|
|
255
|
+
const packet = this.buildMessagePacket(
|
|
256
|
+
this.getClientId(),
|
|
257
|
+
topic,
|
|
258
|
+
message
|
|
259
|
+
);
|
|
260
|
+
this.publishMessage(topic, JSON.stringify(packet));
|
|
261
|
+
} catch (e) {
|
|
262
|
+
logger.error(
|
|
263
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
264
|
+
);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
if (!this.continuePublishing(messageType)) {
|
|
268
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private continuePublishing(flag: string): boolean {
|
|
276
|
+
switch (flag) {
|
|
277
|
+
case keyMirrors.agentMessageType.device_stats:
|
|
278
|
+
case keyMirrors.agentMessageType.app_state:
|
|
279
|
+
return this.liveUpdatesAlive[flag];
|
|
280
|
+
case keyMirrors.agentMessageType.app_install_status:
|
|
281
|
+
return (
|
|
282
|
+
this.appInstallStatus.status ===
|
|
283
|
+
keyMirrors.appInstallStatus.in_progress
|
|
284
|
+
);
|
|
285
|
+
default:
|
|
286
|
+
logger.error(`Unrecognized publishable flag ${flag}`);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getLiveUpdatesInterval(flag: string): number {
|
|
292
|
+
const exists = this.liveUpdatesSleepIntervals[flag];
|
|
293
|
+
if (exists) {
|
|
294
|
+
return exists;
|
|
295
|
+
}
|
|
296
|
+
logger.error(`Unrecognized live updates flag ${flag}`);
|
|
297
|
+
return -1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private setLiveUpdates(toggles: LiveUpdatesToggles) {
|
|
301
|
+
if (toggles.deviceStats) {
|
|
302
|
+
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
303
|
+
}
|
|
304
|
+
if (toggles.appState) {
|
|
305
|
+
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private handleAppStateControl(payload: AppStateControlPacket) {
|
|
310
|
+
const { baseCommand, projectId } = payload;
|
|
311
|
+
switch (baseCommand) {
|
|
312
|
+
case keyMirrors.appStateControl.start:
|
|
313
|
+
startApp({ projectId });
|
|
314
|
+
break;
|
|
315
|
+
case keyMirrors.appStateControl.stop:
|
|
316
|
+
stopApp({ projectId });
|
|
317
|
+
break;
|
|
318
|
+
case keyMirrors.appStateControl.restart:
|
|
319
|
+
restartApp({ projectId });
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private handleAppVersionControl(payload: AppVersionControlPacket) {
|
|
325
|
+
const { projectId, appReleaseHash } = payload;
|
|
326
|
+
const signedUrlsRequest = { projectId, appReleaseHash };
|
|
327
|
+
this.publishCloudRequest({
|
|
328
|
+
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
329
|
+
signedUrlsRequest,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private handleDeviceCommand = async (packet: any) => {
|
|
334
|
+
// TODO
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
private handleAgentCommand(message: LiveUpdatesToggleMessage) {
|
|
338
|
+
switch (message.messageType) {
|
|
339
|
+
case keyMirrors.clientMessageType.live_state_updates:
|
|
340
|
+
this.liveUpdatesBroker(message.liveUpdatesToggles);
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
logger.error(
|
|
344
|
+
`Invalid agent action message type from message '${message}'`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private restartLiveUpdatesTimeout() {
|
|
350
|
+
clearTimeout(this.liveUpdatesTimeout);
|
|
351
|
+
this.liveUpdatesTimeout = setTimeout(() => {
|
|
352
|
+
this.setLiveUpdates({
|
|
353
|
+
deviceStats: false,
|
|
354
|
+
appState: false,
|
|
355
|
+
});
|
|
356
|
+
this.appLogStreams.clear();
|
|
357
|
+
// TODO: Make constant, not hard coded
|
|
358
|
+
}, 600000); // 10 min
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private async liveUpdatesBroker({
|
|
362
|
+
deviceStats,
|
|
363
|
+
appState,
|
|
364
|
+
appLogs,
|
|
365
|
+
}: {
|
|
366
|
+
deviceStats?: boolean;
|
|
367
|
+
appState?: boolean;
|
|
368
|
+
appLogs?: {
|
|
369
|
+
projectId: string;
|
|
370
|
+
toggle: boolean;
|
|
371
|
+
};
|
|
372
|
+
}) {
|
|
373
|
+
this.restartLiveUpdatesTimeout();
|
|
374
|
+
if (deviceStats !== undefined) {
|
|
375
|
+
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
376
|
+
if (deviceStats) {
|
|
377
|
+
this.startPublishingLiveUpdates(
|
|
378
|
+
this.toClientTopic,
|
|
379
|
+
keyMirrors.agentMessageType.device_stats,
|
|
380
|
+
this.getDeviceStatsMessage
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (appState !== undefined) {
|
|
386
|
+
this.liveUpdatesAlive.app_state = appState;
|
|
387
|
+
if (appState) {
|
|
388
|
+
this.startPublishingLiveUpdates(
|
|
389
|
+
this.toClientTopic,
|
|
390
|
+
keyMirrors.agentMessageType.app_state,
|
|
391
|
+
this.getAppStateMessage
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (appLogs !== undefined) {
|
|
397
|
+
if (appLogs.toggle) {
|
|
398
|
+
this.startAppLogStream(appLogs.projectId);
|
|
399
|
+
} else {
|
|
400
|
+
this.appLogStreams.delete(appLogs.projectId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private async publishReportedState(projectId) {
|
|
406
|
+
const newAppCfg = await getAppConfig(projectId);
|
|
407
|
+
const packet = {
|
|
408
|
+
state: {
|
|
409
|
+
reported: {
|
|
410
|
+
[projectId]: { appConfig: newAppCfg },
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
this.publishMessage(
|
|
415
|
+
`${this.shadowPrefix}projects/update`,
|
|
416
|
+
JSON.stringify(packet)
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
|
|
421
|
+
const topic = this.toCloudTopic;
|
|
422
|
+
const deviceRequestPacket = this.buildMessagePacket(
|
|
423
|
+
this.getClientId(),
|
|
424
|
+
topic,
|
|
425
|
+
payload
|
|
426
|
+
);
|
|
427
|
+
this.publishMessage(topic, JSON.stringify({ deviceRequestPacket }));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Public Methods
|
|
431
|
+
|
|
432
|
+
public device = awsIot.device;
|
|
433
|
+
|
|
434
|
+
constructor() {
|
|
435
|
+
this.device = awsIot.device({
|
|
436
|
+
keyPath: getPrivateKeyFilePath(),
|
|
437
|
+
certPath: getCertificateFilePath(),
|
|
438
|
+
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
439
|
+
clientId: this.clientId,
|
|
440
|
+
host: this.host,
|
|
441
|
+
port: 8883,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
this.device.subscribe(this.toDeviceTopic);
|
|
445
|
+
this.device.subscribe(this.shadowTopics.projects.getAccepted);
|
|
446
|
+
this.device.subscribe(this.shadowTopics.projects.updateDelta);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
public getClientId(): string {
|
|
450
|
+
return this.clientId;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
public publishMessage(topic: string, message: string) {
|
|
454
|
+
// TODO: topic validation
|
|
455
|
+
this.device.publish(topic, message, (err: any) => {});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public async handleClientMessage({
|
|
459
|
+
topic,
|
|
460
|
+
message,
|
|
461
|
+
}: {
|
|
462
|
+
topic: string;
|
|
463
|
+
message: ClientMessage;
|
|
464
|
+
}) {
|
|
465
|
+
const payload = message.payload;
|
|
466
|
+
switch (payload.messageType) {
|
|
467
|
+
case keyMirrors.clientMessageType.app_state_control: {
|
|
468
|
+
this.handleAppStateControl(payload.appStateControl);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case keyMirrors.clientMessageType.app_version_control: {
|
|
472
|
+
this.handleAppVersionControl(payload.appVersionControl);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case keyMirrors.clientMessageType.live_state_updates: {
|
|
476
|
+
this.handleAgentCommand(payload);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
480
|
+
const {
|
|
481
|
+
projectId,
|
|
482
|
+
appReleaseHash,
|
|
483
|
+
appInstallPayload,
|
|
484
|
+
modelsInstallPayload,
|
|
485
|
+
} = payload.appInstallCloudResponse;
|
|
486
|
+
|
|
487
|
+
this.initAppInstallStatus({
|
|
488
|
+
status: keyMirrors.appInstallStatus.in_progress,
|
|
489
|
+
appReleaseHash,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
this.startPublishingLiveUpdates(
|
|
493
|
+
this.toClientTopic,
|
|
494
|
+
keyMirrors.agentMessageType.app_install_status,
|
|
495
|
+
this.getAppInstallStatusMessage
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Install the app and models
|
|
499
|
+
try {
|
|
500
|
+
const signedUrlsPayload = {
|
|
501
|
+
appInstallPayload,
|
|
502
|
+
modelsInstallPayload,
|
|
503
|
+
};
|
|
504
|
+
await installApp({
|
|
505
|
+
projectId,
|
|
506
|
+
appReleaseHash,
|
|
507
|
+
signedUrlsPayload,
|
|
508
|
+
});
|
|
509
|
+
this.updateAppInstallStatus({
|
|
510
|
+
status: keyMirrors.appInstallStatus.success,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// update app config shadow for project
|
|
514
|
+
await this.publishReportedState(projectId);
|
|
515
|
+
} catch (e) {
|
|
516
|
+
logger.error(e);
|
|
517
|
+
const message: string = e.message;
|
|
518
|
+
|
|
519
|
+
// uninstall the failed app to put system back in good state
|
|
520
|
+
await uninstallApp({ projectId });
|
|
521
|
+
this.updateAppInstallStatus({
|
|
522
|
+
status: keyMirrors.appInstallStatus.failure,
|
|
523
|
+
message,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// delete shadow for project
|
|
527
|
+
this.publishMessage(`${this.shadowPrefix}${projectId}/delete`, "");
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
case keyMirrors.clientMessageType.models_install_cloud_response: {
|
|
532
|
+
const { projectId, newModels } = payload.modelsInstallCloudResponse;
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
await updateModelsWithPresignedUrls(projectId, newModels);
|
|
536
|
+
|
|
537
|
+
await this.publishReportedState(projectId);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
logger.error(e);
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
default:
|
|
544
|
+
logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public async handleShadowTopic({
|
|
549
|
+
topic,
|
|
550
|
+
payload,
|
|
551
|
+
}: {
|
|
552
|
+
topic: string;
|
|
553
|
+
payload: string;
|
|
554
|
+
}) {
|
|
555
|
+
const shadowName = topic.split("/")[5];
|
|
556
|
+
const message = JSON.parse(payload);
|
|
557
|
+
if (topic === this.shadowTopics.projects.updateDelta) {
|
|
558
|
+
this.handleNamedShadowUpdate({ payload });
|
|
559
|
+
} else if (topic === this.shadowTopics.projects.getAccepted) {
|
|
560
|
+
if (message.delta) {
|
|
561
|
+
this.handleNamedShadowUpdate({
|
|
562
|
+
payload: JSON.stringify(message.delta),
|
|
563
|
+
});
|
|
564
|
+
} else {
|
|
565
|
+
logger.info(`No delta updates in shadow ${shadowName}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export async function runDeviceAgentCloudInterface() {
|
|
572
|
+
switch (true) {
|
|
573
|
+
case existsSync(getCertificateFilePath()):
|
|
574
|
+
{
|
|
575
|
+
const deviceAgent = new DeviceAgentCloudConnection();
|
|
576
|
+
|
|
577
|
+
deviceAgent.device.on("connect", function (connack: any) {
|
|
578
|
+
logger.info("Device Agent has connected to the cloud");
|
|
579
|
+
|
|
580
|
+
// Get shadow updates
|
|
581
|
+
deviceAgent.publishMessage(
|
|
582
|
+
`${deviceAgent.getShadowPrefix()}projects/get`,
|
|
583
|
+
""
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
deviceAgent.device.on("disconnect", function () {
|
|
588
|
+
logger.info("Device Agent has been disconnected from the cloud");
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
deviceAgent.device.on(
|
|
592
|
+
"message",
|
|
593
|
+
function (topic: string, payload: string) {
|
|
594
|
+
logger.debug(JSON.stringify({ topic }));
|
|
595
|
+
try {
|
|
596
|
+
const jsonPacket = JSON.parse(payload);
|
|
597
|
+
if (jsonPacket.hasOwnProperty("state")) {
|
|
598
|
+
deviceAgent.handleShadowTopic({
|
|
599
|
+
topic,
|
|
600
|
+
payload: JSON.stringify(jsonPacket.state),
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
const valid = validateClientMessage(jsonPacket);
|
|
604
|
+
if (!valid) {
|
|
605
|
+
console.error(JSON.stringify(validateClientMessage.errors));
|
|
606
|
+
} else {
|
|
607
|
+
deviceAgent.handleClientMessage({
|
|
608
|
+
topic,
|
|
609
|
+
message: jsonPacket,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
logger.error(error);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
deviceAgent.device.on("packetsend", (packet: any) => {
|
|
620
|
+
logger.debug(JSON.stringify({ packet: packet }, null, 2));
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
break;
|
|
625
|
+
case existsSync(BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH):
|
|
626
|
+
{
|
|
627
|
+
setTimeout(rmBootstrapCertsAndClose, 60000);
|
|
628
|
+
|
|
629
|
+
const clientId = getDeviceId();
|
|
630
|
+
const bootstrapConfig = {
|
|
631
|
+
keyPath: BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
632
|
+
certPath: BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
|
|
633
|
+
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
634
|
+
clientId,
|
|
635
|
+
host: getIoTCoreEndpointUrl(),
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const bootstrapAgent = new BootstrapAgent(bootstrapConfig);
|
|
639
|
+
bootstrapAgent.subscribeToAllTopics();
|
|
640
|
+
|
|
641
|
+
bootstrapAgent.publishMessage("$aws/certificates/create/json", "");
|
|
642
|
+
|
|
643
|
+
bootstrapAgent.device.on("connect", () => {
|
|
644
|
+
logger.info("Your device is being provisioned");
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
bootstrapAgent.device.on(
|
|
648
|
+
"message",
|
|
649
|
+
(topic: string, payload: string) => {
|
|
650
|
+
bootstrapAgent.handleAwsCertificateTopics(topic, payload);
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
bootstrapAgent.device.on("packetsend", (packet: any) => {
|
|
655
|
+
logger.debug(
|
|
656
|
+
JSON.stringify({ packet: packet.subscriptions }, null, 2)
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
break;
|
|
661
|
+
case existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH): {
|
|
662
|
+
logger.info(
|
|
663
|
+
"Device has not been created using 'device-agent device create' or there has been an issue with device creation"
|
|
664
|
+
);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
default: {
|
|
668
|
+
logger.info(
|
|
669
|
+
"Set device agent to local mode and retry the 'aai-agent device init' command"
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|