@alwaysai/device-agent 1.3.1 → 1.4.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/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +9 -4
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +1 -1
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +6 -2
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +4 -2
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.js +4 -5
- package/lib/application-control/status.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +114 -99
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +1 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +22 -4
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +3 -4
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +14 -21
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +162 -108
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +100 -83
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +7 -14
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +37 -14
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +107 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -1
- package/lib/subcommands/app/analytics.js +9 -13
- package/lib/subcommands/app/analytics.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +11 -16
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +12 -16
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +3 -1
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +14 -6
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/package.json +2 -2
- package/src/application-control/environment-variables.test.ts +1 -1
- package/src/application-control/environment-variables.ts +9 -6
- package/src/application-control/install.ts +7 -3
- package/src/application-control/models.ts +11 -6
- package/src/application-control/status.ts +8 -8
- package/src/cloud-connection/device-agent-cloud-connection.ts +161 -131
- package/src/cloud-connection/live-updates-handler.ts +34 -6
- package/src/cloud-connection/messages.ts +3 -4
- package/src/cloud-connection/shadow-handler.test.ts +101 -84
- package/src/cloud-connection/shadow-handler.ts +275 -133
- package/src/device-control/device-control.ts +46 -19
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -0
- package/src/secure-tunneling/spawner-detached.ts +123 -0
- package/src/subcommands/app/analytics.ts +16 -13
- package/src/subcommands/app/env-vars.ts +18 -16
- package/src/subcommands/app/models.ts +20 -16
- package/src/subcommands/device/clean.ts +4 -1
- package/src/subcommands/device/device.ts +26 -10
- package/src/util/cloud-mode-ready.ts +36 -0
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/src/secure-tunneling/index.ts +0 -74
|
@@ -41,24 +41,27 @@ export async function setEnv(props: { projectId: string; envVars: EnvVars }) {
|
|
|
41
41
|
|
|
42
42
|
const service = composeParsed['services'][s];
|
|
43
43
|
const oldEnv: string[] | undefined = service['environment'];
|
|
44
|
+
const envVarsForService = envVars[s];
|
|
44
45
|
|
|
45
46
|
const newEnvVarsObj = {};
|
|
46
47
|
oldEnv?.forEach((envVarStr: string) => {
|
|
47
48
|
const envVarSplit = envVarStr.split('=');
|
|
48
49
|
const key = envVarSplit[0];
|
|
49
50
|
const value = envVarSplit[1];
|
|
50
|
-
|
|
51
|
+
if (envVarsForService[key] !== null) {
|
|
52
|
+
newEnvVarsObj[key] = value;
|
|
53
|
+
}
|
|
51
54
|
});
|
|
52
55
|
|
|
53
|
-
for (const envVar of Object.keys(
|
|
54
|
-
|
|
56
|
+
for (const envVar of Object.keys(envVarsForService)) {
|
|
57
|
+
if (envVarsForService[envVar] !== null) {
|
|
58
|
+
newEnvVarsObj[envVar] = envVarsForService[envVar];
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
const envVarList: string[] = [];
|
|
58
63
|
for (const envVar of Object.keys(newEnvVarsObj)) {
|
|
59
|
-
envVarList.push(
|
|
60
|
-
`${envVar}=${newEnvVarsObj[envVar] ? newEnvVarsObj[envVar] : ''}`
|
|
61
|
-
);
|
|
64
|
+
envVarList.push(`${envVar}=${newEnvVarsObj[envVar]}`);
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
service['environment'] = envVarList;
|
|
@@ -113,12 +113,18 @@ export async function installApp(props: {
|
|
|
113
113
|
const DACfg = await createAppCfgDeviceAgentConnection();
|
|
114
114
|
await updateAppCfgFile({ projectId, newAppCfg: DACfg });
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// Clean up original dependency files (models, virtual env)
|
|
117
|
+
const targetJsonFile = TargetJsonFile(appDir);
|
|
118
|
+
const hostSpawner = targetJsonFile.readHostSpawner();
|
|
119
|
+
await appCleanDocker({ targetHostSpawner: hostSpawner });
|
|
120
|
+
|
|
121
|
+
// Download model files
|
|
117
122
|
await installModelsWithPresignedURLs(
|
|
118
123
|
signedUrlsPayload.modelsInstallPayload,
|
|
119
124
|
path.join(appDir, 'models')
|
|
120
125
|
);
|
|
121
126
|
|
|
127
|
+
await installAppBuildReqs({ appDir });
|
|
122
128
|
await buildApp({ appDir });
|
|
123
129
|
|
|
124
130
|
await AgentConfigFile().setAppInstalled({
|
|
@@ -153,8 +159,6 @@ async function installAppBuildReqs(props: { appDir: string }) {
|
|
|
153
159
|
async () => {
|
|
154
160
|
const hostSpawner = targetJsonFile.readHostSpawner();
|
|
155
161
|
|
|
156
|
-
await appCleanDocker({ targetHostSpawner: hostSpawner });
|
|
157
|
-
|
|
158
162
|
const dockerImageId = await buildDockerImage({
|
|
159
163
|
targetHostSpawner: hostSpawner,
|
|
160
164
|
targetHardware: targetCfg.targetHardware,
|
|
@@ -89,14 +89,19 @@ export async function replaceModels(props: {
|
|
|
89
89
|
],
|
|
90
90
|
appDir
|
|
91
91
|
);
|
|
92
|
+
const modelsAddPromises: Promise<void>[] = [];
|
|
92
93
|
for (const modelId of modelIds) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
modelsAddPromises.push(
|
|
95
|
+
appModelsAddComponent({
|
|
96
|
+
yes: false,
|
|
97
|
+
dir: appDir,
|
|
98
|
+
id: modelId,
|
|
99
|
+
addToProject: false
|
|
100
|
+
})
|
|
101
|
+
);
|
|
99
102
|
}
|
|
103
|
+
await Promise.all(modelsAddPromises);
|
|
104
|
+
|
|
100
105
|
await buildApp({ appDir });
|
|
101
106
|
}
|
|
102
107
|
|
|
@@ -126,14 +126,14 @@ export async function getAppLogs(props: {
|
|
|
126
126
|
|
|
127
127
|
const appDir = getAppDir(projectId);
|
|
128
128
|
|
|
129
|
-
const serviceList =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const argsList = args
|
|
129
|
+
const serviceList =
|
|
130
|
+
services ||
|
|
131
|
+
(await (async function () {
|
|
132
|
+
const composeServices = await compose.configServices({ cwd: appDir });
|
|
133
|
+
return composeServices.data.services;
|
|
134
|
+
})());
|
|
135
|
+
|
|
136
|
+
const argsList = args || [];
|
|
137
137
|
|
|
138
138
|
// Use direct command with spawner in order to get a readable stream
|
|
139
139
|
return await JsSpawner().runStreaming({
|
|
@@ -1,59 +1,57 @@
|
|
|
1
1
|
// eslint-disable-next-line
|
|
2
|
-
const awsIot = require(
|
|
3
|
-
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
5
|
-
import {
|
|
6
|
-
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
7
|
-
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
8
|
-
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
9
|
-
DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
10
|
-
DEVICE_CERTIFICATE_FILE_PATH
|
|
11
|
-
} from '../util/directories';
|
|
2
|
+
const awsIot = require('aws-iot-device-sdk');
|
|
12
3
|
import {
|
|
13
|
-
keyMirrors,
|
|
14
|
-
SignedUrlsRequestPayload,
|
|
15
|
-
getToDeviceTopic,
|
|
16
4
|
AppInstallResponsePayload,
|
|
17
|
-
validateToDeviceAgentMessage,
|
|
18
|
-
ToDeviceAgentMessage,
|
|
19
|
-
ToCloudMessage,
|
|
20
5
|
AppStateControlPayload,
|
|
21
6
|
AppVersionControlInstallPayload,
|
|
22
7
|
AppVersionControlUninstallPayload,
|
|
23
8
|
DeviceActionPayload,
|
|
9
|
+
ModelsInstallResponsePayload,
|
|
10
|
+
SignedUrlsRequestPayload,
|
|
11
|
+
ToCloudMessage,
|
|
12
|
+
ToDeviceAgentMessage,
|
|
13
|
+
getToDeviceTopic,
|
|
24
14
|
buildSignedUrlsRequestMessage,
|
|
25
15
|
buildToClientStatusResponseMessage,
|
|
26
|
-
StatusResponsePayload
|
|
16
|
+
StatusResponsePayload,
|
|
17
|
+
keyMirrors,
|
|
18
|
+
validateToDeviceAgentMessage
|
|
27
19
|
} from '@alwaysai/device-agent-schemas';
|
|
28
|
-
import {
|
|
29
|
-
import { logger } from '../util/logger';
|
|
30
|
-
import { cloudModeReady } from '../util/cloud-mode-ready';
|
|
31
|
-
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
20
|
+
import { existsSync } from 'fs';
|
|
32
21
|
import {
|
|
22
|
+
installApp,
|
|
23
|
+
restartApp,
|
|
33
24
|
startApp,
|
|
34
25
|
stopApp,
|
|
35
|
-
restartApp,
|
|
36
|
-
updateModelsWithPresignedUrls,
|
|
37
|
-
installApp,
|
|
38
26
|
uninstallApp,
|
|
39
27
|
updateAppCfg,
|
|
40
|
-
|
|
28
|
+
updateModelsWithPresignedUrls
|
|
41
29
|
} from '../application-control';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { Publisher } from './publisher';
|
|
45
|
-
import { bootstrapProvision } from './bootstrap-provision';
|
|
46
|
-
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
30
|
+
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
31
|
+
import { reboot } from '../device-control/device-control';
|
|
47
32
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
48
|
-
import {
|
|
33
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
34
|
+
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
35
|
+
import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
|
|
36
|
+
import { cloudModeReady } from '../util/cloud-mode-ready';
|
|
37
|
+
import {
|
|
38
|
+
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
39
|
+
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
40
|
+
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
41
|
+
DEVICE_CERTIFICATE_FILE_PATH,
|
|
42
|
+
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
43
|
+
} from '../util/directories';
|
|
44
|
+
import { getDeviceUuid } from '../util/get-device-id';
|
|
45
|
+
import { logger } from '../util/logger';
|
|
49
46
|
import sleep from '../util/sleep';
|
|
50
|
-
import {
|
|
47
|
+
import { bootstrapProvision } from './bootstrap-provision';
|
|
48
|
+
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
49
|
+
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
50
|
+
import { Publisher } from './publisher';
|
|
51
|
+
import { ShadowHandler, ShadowUpdate } from './shadow-handler';
|
|
51
52
|
import { TransactionManager } from './transaction-manager';
|
|
52
|
-
import { reboot } from '../device-control/device-control';
|
|
53
|
-
|
|
54
53
|
import { exec } from 'child_process';
|
|
55
54
|
import { promisify } from 'util';
|
|
56
|
-
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
57
55
|
|
|
58
56
|
const exec_promise = promisify(exec);
|
|
59
57
|
|
|
@@ -69,6 +67,8 @@ export class DeviceAgentCloudConnection {
|
|
|
69
67
|
private port = 8883;
|
|
70
68
|
private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
|
|
71
69
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
70
|
+
private readonly secureTunnelHandler =
|
|
71
|
+
SecureTunnelHandlerSingleton.getInstance();
|
|
72
72
|
// FIXME: Add support for multiple simultaneous project updates
|
|
73
73
|
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
74
74
|
|
|
@@ -144,11 +144,10 @@ export class DeviceAgentCloudConnection {
|
|
|
144
144
|
appInstallPayload,
|
|
145
145
|
modelsInstallPayload
|
|
146
146
|
};
|
|
147
|
-
await this.atomicApplicationUpdate(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
);
|
|
147
|
+
await this.atomicApplicationUpdate(async () => {
|
|
148
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
149
|
+
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
150
|
+
}, projectId);
|
|
152
151
|
return true;
|
|
153
152
|
};
|
|
154
153
|
|
|
@@ -165,23 +164,25 @@ export class DeviceAgentCloudConnection {
|
|
|
165
164
|
const projectId = payload.modelsInstallResponse.projectId;
|
|
166
165
|
if (appCfgUpdate) {
|
|
167
166
|
await this.atomicApplicationUpdate(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{
|
|
167
|
+
async () =>
|
|
168
|
+
await updateModelsWithPresignedUrls({
|
|
171
169
|
projectId,
|
|
172
170
|
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
173
171
|
newAppCfg: appCfgUpdate.newAppCfg
|
|
174
|
-
}
|
|
175
|
-
],
|
|
172
|
+
}),
|
|
176
173
|
projectId
|
|
177
174
|
);
|
|
178
175
|
}
|
|
179
176
|
|
|
180
177
|
if (envVarUpdate) {
|
|
181
178
|
await this.atomicApplicationUpdate(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
async () =>
|
|
180
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
181
|
+
projectId,
|
|
182
|
+
envVars: envVarUpdate.envVars
|
|
183
|
+
}),
|
|
184
|
+
projectId,
|
|
185
|
+
true
|
|
185
186
|
);
|
|
186
187
|
}
|
|
187
188
|
return true;
|
|
@@ -214,7 +215,7 @@ export class DeviceAgentCloudConnection {
|
|
|
214
215
|
private async atomicApplicationUninstall(projectId: string) {
|
|
215
216
|
try {
|
|
216
217
|
await uninstallApp({ projectId });
|
|
217
|
-
this.shadowHandler.
|
|
218
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
218
219
|
} catch (e) {
|
|
219
220
|
logger.error(`Failed to uninstall ${projectId}: ${e.message}`);
|
|
220
221
|
throw e;
|
|
@@ -222,11 +223,11 @@ export class DeviceAgentCloudConnection {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
// eslint-disable-next-line
|
|
225
|
-
private async atomicApplicationUpdate<
|
|
226
|
-
func:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
) {
|
|
226
|
+
private async atomicApplicationUpdate <F extends () => any>(
|
|
227
|
+
func: F,
|
|
228
|
+
projectId: string,
|
|
229
|
+
skipUpdateShadow?: boolean
|
|
230
|
+
): Promise<ReturnType<F>> {
|
|
230
231
|
// First try to create a backup, so that there is one available if something goes wrong in the next try:catch.
|
|
231
232
|
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
232
233
|
try {
|
|
@@ -239,9 +240,9 @@ export class DeviceAgentCloudConnection {
|
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
try {
|
|
242
|
-
const out:
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
const out: ReturnType<F> = await func();
|
|
244
|
+
if (!skipUpdateShadow)
|
|
245
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
245
246
|
return out;
|
|
246
247
|
} catch (errorAppUpdate) {
|
|
247
248
|
logger.error(
|
|
@@ -256,9 +257,9 @@ export class DeviceAgentCloudConnection {
|
|
|
256
257
|
} catch (errorRollbackApp) {
|
|
257
258
|
// and if that fails, uninstall the app as a last resort.
|
|
258
259
|
try {
|
|
259
|
-
await
|
|
260
|
-
}
|
|
261
|
-
|
|
260
|
+
await this.atomicApplicationUninstall(projectId);
|
|
261
|
+
} catch {
|
|
262
|
+
// atomicApplicationUninstall handles failing, so there's nothing to handle here.
|
|
262
263
|
}
|
|
263
264
|
logger.error(
|
|
264
265
|
`Application update failed, rolled back to previous version: ${errorAppUpdate}`
|
|
@@ -273,15 +274,14 @@ export class DeviceAgentCloudConnection {
|
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
private
|
|
277
|
+
private handleProjectShadowConfigUpdate = async (
|
|
277
278
|
update: ShadowUpdate,
|
|
278
279
|
txId: string
|
|
279
280
|
): Promise<boolean> => {
|
|
280
281
|
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
281
282
|
|
|
282
283
|
if (
|
|
283
|
-
appCfgUpdate &&
|
|
284
|
-
appCfgUpdate.updatedModels &&
|
|
284
|
+
appCfgUpdate?.updatedModels &&
|
|
285
285
|
Object.keys(appCfgUpdate.updatedModels).length
|
|
286
286
|
) {
|
|
287
287
|
// When there are model updates request signed URLs and wait to apply config changes
|
|
@@ -311,27 +311,64 @@ export class DeviceAgentCloudConnection {
|
|
|
311
311
|
|
|
312
312
|
if (appCfgUpdate) {
|
|
313
313
|
await this.atomicApplicationUpdate(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
{
|
|
314
|
+
async () =>
|
|
315
|
+
await updateAppCfg({
|
|
317
316
|
projectId,
|
|
318
317
|
newAppCfg: appCfgUpdate.newAppCfg
|
|
319
|
-
}
|
|
320
|
-
],
|
|
318
|
+
}),
|
|
321
319
|
projectId
|
|
322
320
|
);
|
|
323
321
|
}
|
|
324
322
|
|
|
325
323
|
if (envVarUpdate) {
|
|
326
324
|
await this.atomicApplicationUpdate(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
async () =>
|
|
326
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
327
|
+
projectId,
|
|
328
|
+
envVars: envVarUpdate.envVars
|
|
329
|
+
}),
|
|
330
|
+
projectId,
|
|
331
|
+
true
|
|
330
332
|
);
|
|
331
333
|
}
|
|
332
334
|
return true;
|
|
333
335
|
};
|
|
334
336
|
|
|
337
|
+
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
338
|
+
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
339
|
+
topic,
|
|
340
|
+
payload: message,
|
|
341
|
+
clientToken: message.clientToken
|
|
342
|
+
});
|
|
343
|
+
if (shadowUpdates.length) {
|
|
344
|
+
const shadowUpdatePromises: Promise<void>[] = [];
|
|
345
|
+
for (const shadowUpdate of shadowUpdates) {
|
|
346
|
+
const projectId = shadowUpdate.projectId;
|
|
347
|
+
const txId = shadowUpdate.txId;
|
|
348
|
+
shadowUpdatePromises.push(
|
|
349
|
+
this.txnMgr
|
|
350
|
+
.runTransactionStep({
|
|
351
|
+
func: () =>
|
|
352
|
+
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
353
|
+
projectId,
|
|
354
|
+
txId,
|
|
355
|
+
start: true,
|
|
356
|
+
stepName: topic
|
|
357
|
+
})
|
|
358
|
+
.catch((e: Error) => {
|
|
359
|
+
logger.error(
|
|
360
|
+
`There was an issue updating project shadow config: ${JSON.stringify(
|
|
361
|
+
e
|
|
362
|
+
)}`
|
|
363
|
+
);
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await Promise.all(shadowUpdatePromises);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
335
372
|
/*=================================================================
|
|
336
373
|
Public interface
|
|
337
374
|
=================================================================*/
|
|
@@ -359,11 +396,11 @@ export class DeviceAgentCloudConnection {
|
|
|
359
396
|
|
|
360
397
|
this.subscribe(this.toDeviceTopic);
|
|
361
398
|
this.subscribe(this.secureTunnelNotifyTopic);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
this.subscribe(this.shadowHandler.shadowTopics.
|
|
366
|
-
this.subscribe(this.shadowHandler.shadowTopics.
|
|
399
|
+
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
400
|
+
this.subscribe(topic);
|
|
401
|
+
}
|
|
402
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
403
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
367
404
|
}
|
|
368
405
|
|
|
369
406
|
public getClientId(): string {
|
|
@@ -374,10 +411,6 @@ export class DeviceAgentCloudConnection {
|
|
|
374
411
|
return this.toDeviceTopic;
|
|
375
412
|
}
|
|
376
413
|
|
|
377
|
-
public getShadowTopics(): ShadowTopics {
|
|
378
|
-
return this.shadowHandler.shadowTopics;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
414
|
public isCmdInProgress(projectId: string): boolean {
|
|
382
415
|
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
383
416
|
}
|
|
@@ -412,7 +445,8 @@ export class DeviceAgentCloudConnection {
|
|
|
412
445
|
live_state_updates,
|
|
413
446
|
app_install_response,
|
|
414
447
|
models_install_response,
|
|
415
|
-
status_response
|
|
448
|
+
status_response,
|
|
449
|
+
device_action
|
|
416
450
|
} = keyMirrors.toDeviceAgentMessageType;
|
|
417
451
|
switch (message.messageType) {
|
|
418
452
|
case app_state_control: {
|
|
@@ -520,8 +554,20 @@ export class DeviceAgentCloudConnection {
|
|
|
520
554
|
}
|
|
521
555
|
break;
|
|
522
556
|
}
|
|
523
|
-
case
|
|
557
|
+
case device_action: {
|
|
524
558
|
try {
|
|
559
|
+
const statusResponsePayload: StatusResponsePayload = {
|
|
560
|
+
status: keyMirrors.statusResponse.in_progress
|
|
561
|
+
};
|
|
562
|
+
const statusResponseMessage = buildToClientStatusResponseMessage(
|
|
563
|
+
this.clientId,
|
|
564
|
+
statusResponsePayload,
|
|
565
|
+
txId
|
|
566
|
+
);
|
|
567
|
+
this.publisher.publishToClient(statusResponseMessage);
|
|
568
|
+
|
|
569
|
+
await this.handleDeviceAction(message.payload);
|
|
570
|
+
|
|
525
571
|
const successStatusResponsePayload: StatusResponsePayload = {
|
|
526
572
|
status: keyMirrors.statusResponse.success
|
|
527
573
|
};
|
|
@@ -532,8 +578,6 @@ export class DeviceAgentCloudConnection {
|
|
|
532
578
|
txId
|
|
533
579
|
);
|
|
534
580
|
this.publisher.publishToClient(successStatusResponseMessage);
|
|
535
|
-
|
|
536
|
-
await this.handleDeviceAction(message.payload);
|
|
537
581
|
} catch (e) {
|
|
538
582
|
logger.error(
|
|
539
583
|
`There was a problem performing device action '${message.payload.action}': ${e.message}`
|
|
@@ -570,50 +614,36 @@ export class DeviceAgentCloudConnection {
|
|
|
570
614
|
logger.debug(
|
|
571
615
|
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
572
616
|
);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
// Not handling these for now
|
|
604
|
-
break;
|
|
605
|
-
case this.toDeviceTopic:
|
|
606
|
-
await this.handleDeviceAgentMessage({
|
|
607
|
-
topic,
|
|
608
|
-
message
|
|
609
|
-
});
|
|
610
|
-
break;
|
|
611
|
-
|
|
612
|
-
case this.secureTunnelNotifyTopic:
|
|
613
|
-
await secureTunnelNotifyHandler(message);
|
|
614
|
-
break;
|
|
615
|
-
default:
|
|
616
|
-
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
617
|
+
// ProjectShadow messages
|
|
618
|
+
if (this.shadowHandler.projectShadowTopics.includes(topic)) {
|
|
619
|
+
await this.handleProjectShadowMessage(topic, message);
|
|
620
|
+
} else if (topic === this.toDeviceTopic) {
|
|
621
|
+
await this.handleDeviceAgentMessage({
|
|
622
|
+
topic,
|
|
623
|
+
message
|
|
624
|
+
});
|
|
625
|
+
// SecureTunnelNotify messages
|
|
626
|
+
} else if (topic === this.secureTunnelNotifyTopic) {
|
|
627
|
+
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
628
|
+
// SecureTunnel messages
|
|
629
|
+
} else if (
|
|
630
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
631
|
+
) {
|
|
632
|
+
logger.info(`Received secure tunnel update: ${message}`);
|
|
633
|
+
const reported = await this.secureTunnelHandler.syncShadowToDeviceState(
|
|
634
|
+
message
|
|
635
|
+
);
|
|
636
|
+
this.publisher.publish(
|
|
637
|
+
this.shadowHandler.shadowTopics.secureTunnel.update,
|
|
638
|
+
JSON.stringify({ state: { reported } })
|
|
639
|
+
);
|
|
640
|
+
} else if (
|
|
641
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
642
|
+
) {
|
|
643
|
+
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
644
|
+
await this.secureTunnelHandler.destroy();
|
|
645
|
+
} else {
|
|
646
|
+
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
617
647
|
}
|
|
618
648
|
}
|
|
619
649
|
|
|
@@ -710,9 +740,9 @@ export class DeviceAgentCloudConnection {
|
|
|
710
740
|
}
|
|
711
741
|
|
|
712
742
|
public async stop() {
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
743
|
+
// This method is currently only used by the CLI, and shadow messages can be
|
|
744
|
+
// lost since we aren't waiting for responses so sleep for a short time to
|
|
745
|
+
// receive them
|
|
716
746
|
await sleep(1000);
|
|
717
747
|
this.device.end();
|
|
718
748
|
}
|
|
@@ -7,15 +7,16 @@ import {
|
|
|
7
7
|
buildAppStateMessage,
|
|
8
8
|
buildDeviceStatsMessage,
|
|
9
9
|
StatusResponsePayload,
|
|
10
|
-
buildToClientStatusResponseMessage
|
|
10
|
+
buildToClientStatusResponseMessage,
|
|
11
|
+
ToClientMessageTypeValue
|
|
11
12
|
} from '@alwaysai/device-agent-schemas';
|
|
12
13
|
import { getAppLogs } from '../application-control';
|
|
13
14
|
import { logger } from '../util/logger';
|
|
14
15
|
import sleep from '../util/sleep';
|
|
15
16
|
import { Publisher } from './publisher';
|
|
16
17
|
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
17
|
-
import { ToClientMessageTypeValue } from '@alwaysai/device-agent-schemas';
|
|
18
18
|
import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
|
|
19
|
+
import { read } from 'fs';
|
|
19
20
|
|
|
20
21
|
const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
|
|
21
22
|
? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
|
|
@@ -42,14 +43,41 @@ export class LiveUpdatesHandler {
|
|
|
42
43
|
private appLogStreams = new Set<string>();
|
|
43
44
|
private transactionStatuses = new Set<string>();
|
|
44
45
|
|
|
46
|
+
private async getAppLogsWithRetry(
|
|
47
|
+
projectId: string
|
|
48
|
+
): Promise<NodeJS.ReadableStream | null> {
|
|
49
|
+
// Retry starting logs until it starts successfully or is terminated
|
|
50
|
+
while (this.appLogStreams.has(projectId)) {
|
|
51
|
+
try {
|
|
52
|
+
return await getAppLogs({
|
|
53
|
+
projectId,
|
|
54
|
+
args: ['--tail', '100', '--no-log-prefix']
|
|
55
|
+
});
|
|
56
|
+
} catch (e) {
|
|
57
|
+
logger.info(
|
|
58
|
+
`Failed to start app logs, retrying in 1 second. Error: ${e}`
|
|
59
|
+
);
|
|
60
|
+
await sleep(1000);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Case where logs were disabled prior to connecting
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
private async startAppLogStream(projectId: string, txId: string) {
|
|
46
68
|
logger.info(`Starting log stream for ${projectId}`);
|
|
47
69
|
|
|
48
70
|
this.appLogStreams.add(projectId);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
|
|
72
|
+
const readable = await this.getAppLogsWithRetry(projectId);
|
|
73
|
+
|
|
74
|
+
if (readable === null) {
|
|
75
|
+
logger.info(
|
|
76
|
+
`App log stream terminated for project ${projectId} prior to starting`
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
readable.on('data', async (chunk: Buffer) => {
|
|
54
82
|
if (!this.appLogStreams.has(projectId)) {
|
|
55
83
|
// why doesn't typescript know about this function?
|
|
@@ -12,15 +12,14 @@ import {
|
|
|
12
12
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
13
13
|
|
|
14
14
|
export async function getAppStatePayload(): Promise<AppStatePayload> {
|
|
15
|
-
const
|
|
15
|
+
const appStatePromises: Promise<AppState>[] = [];
|
|
16
16
|
const apps = await AgentConfigFile().getApps();
|
|
17
17
|
for (const app of apps) {
|
|
18
18
|
const projectId = app.projectId;
|
|
19
|
-
|
|
20
|
-
appState.push(status);
|
|
19
|
+
appStatePromises.push(getAppState({ projectId }));
|
|
21
20
|
}
|
|
22
21
|
const appStatePayload = {
|
|
23
|
-
appState:
|
|
22
|
+
appState: await Promise.all(appStatePromises)
|
|
24
23
|
};
|
|
25
24
|
return appStatePayload;
|
|
26
25
|
}
|