@alwaysai/device-agent 0.1.0 → 0.1.2
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 +0 -1
- package/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +15 -29
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts +7 -3
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +71 -35
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.d.ts +2 -0
- package/lib/application-control/environment-variables.test.d.ts.map +1 -0
- package/lib/application-control/environment-variables.test.js +163 -0
- package/lib/application-control/environment-variables.test.js.map +1 -0
- package/lib/application-control/index.d.ts +3 -3
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +1 -3
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/models.d.ts +0 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +12 -26
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +3 -0
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +19 -1
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +2 -2
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +6 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +205 -151
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +3 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +23 -7
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.d.ts +2 -0
- package/lib/cloud-connection/live-updates-handler.test.d.ts.map +1 -0
- package/lib/cloud-connection/live-updates-handler.test.js +57 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -0
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +6 -3
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +11 -3
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +22 -7
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +313 -228
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/shadow.js +1 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +2 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +3 -1
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +32 -5
- 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 +4 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +6 -1
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/shadow.d.ts +7 -0
- package/lib/subcommands/app/shadow.d.ts.map +1 -0
- package/lib/subcommands/app/shadow.js +48 -0
- package/lib/subcommands/app/shadow.js.map +1 -0
- package/lib/subcommands/app/version.js +2 -2
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +2 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -0
- package/lib/util/cloud-mode-ready.js +22 -0
- package/lib/util/cloud-mode-ready.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +2 -2
- package/src/application-control/config.ts +30 -31
- package/src/application-control/environment-variables.test.ts +171 -0
- package/src/application-control/environment-variables.ts +102 -43
- package/src/application-control/index.ts +3 -9
- package/src/application-control/models.ts +14 -29
- package/src/application-control/status.ts +20 -0
- package/src/application-control/utils.ts +4 -2
- package/src/cloud-connection/device-agent-cloud-connection.ts +222 -153
- package/src/cloud-connection/live-updates-handler.test.ts +68 -0
- package/src/cloud-connection/live-updates-handler.ts +30 -7
- package/src/cloud-connection/passthrough-handler.ts +10 -3
- package/src/cloud-connection/shadow-handler.test.ts +329 -239
- package/src/cloud-connection/shadow-handler.ts +38 -12
- package/src/cloud-connection/shadow.ts +1 -1
- package/src/environment.ts +2 -0
- package/src/infrastructure/agent-config.ts +1 -1
- package/src/subcommands/app/env-vars.ts +38 -8
- package/src/subcommands/app/index.ts +4 -1
- package/src/subcommands/app/models.ts +10 -1
- package/src/subcommands/app/shadow.ts +48 -0
- package/src/subcommands/app/version.ts +2 -2
- package/src/util/cloud-mode-ready.ts +23 -0
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
LiveUpdatesToggleMessage,
|
|
18
18
|
SignedUrlsRequestMessage,
|
|
19
19
|
ClientMessage,
|
|
20
|
-
getDeviceTopic
|
|
20
|
+
getDeviceTopic,
|
|
21
|
+
AppInstallCloudResponseMessage
|
|
21
22
|
} from '@alwaysai/device-agent-schemas';
|
|
22
23
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
23
24
|
import { logger } from '../util/logger';
|
|
25
|
+
import { cloudModeReady } from '../util/cloud-mode-ready';
|
|
24
26
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
25
27
|
import {
|
|
26
28
|
startApp,
|
|
@@ -29,17 +31,19 @@ import {
|
|
|
29
31
|
updateModelsWithPresignedUrls,
|
|
30
32
|
installApp,
|
|
31
33
|
uninstallApp,
|
|
32
|
-
updateAppCfg
|
|
34
|
+
updateAppCfg,
|
|
35
|
+
setEnv
|
|
33
36
|
} from '../application-control';
|
|
34
|
-
import {
|
|
37
|
+
import { ShadowHandler, ShadowTopics, ShadowUpdate } from './shadow-handler';
|
|
35
38
|
import { Publisher } from './publisher';
|
|
36
39
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
37
40
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
38
|
-
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
39
41
|
import { CmdStatusManager } from './cmd-status';
|
|
40
42
|
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
41
43
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
42
44
|
import { getAppInstallStatusMessage } from './messages';
|
|
45
|
+
import { ModelsInstallCloudResponseMessage } from '@alwaysai/device-agent-schemas/lib/schemas/client/application-action-schema';
|
|
46
|
+
import sleep from '../util/sleep';
|
|
43
47
|
|
|
44
48
|
export class DeviceAgentCloudConnection {
|
|
45
49
|
private shadowHandler: ShadowHandler;
|
|
@@ -52,9 +56,11 @@ export class DeviceAgentCloudConnection {
|
|
|
52
56
|
private host = getIoTCoreEndpointUrl();
|
|
53
57
|
private readonly toDeviceTopic = getDeviceTopic(this.clientId);
|
|
54
58
|
// FIXME: Add support for multiple simultaneous project updates
|
|
55
|
-
private
|
|
59
|
+
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
56
60
|
|
|
57
|
-
private async
|
|
61
|
+
private handleAppStateControl = async (
|
|
62
|
+
payload: AppStateControlPacket
|
|
63
|
+
): Promise<boolean> => {
|
|
58
64
|
const { baseCommand, projectId } = payload;
|
|
59
65
|
switch (baseCommand) {
|
|
60
66
|
case keyMirrors.appStateControl.start:
|
|
@@ -67,29 +73,27 @@ export class DeviceAgentCloudConnection {
|
|
|
67
73
|
await restartApp({ projectId });
|
|
68
74
|
break;
|
|
69
75
|
}
|
|
70
|
-
|
|
76
|
+
return true;
|
|
77
|
+
};
|
|
71
78
|
|
|
72
|
-
private async
|
|
79
|
+
private handleAppVersionControl = async (
|
|
80
|
+
payload: AppVersionControlPacket
|
|
81
|
+
): Promise<boolean> => {
|
|
73
82
|
switch (payload.baseCommand) {
|
|
74
83
|
case keyMirrors.appVersionControl.install: {
|
|
75
84
|
const { projectId, appReleaseHash } = payload;
|
|
76
|
-
await this.cmdStatusMgr.start(projectId);
|
|
77
|
-
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
78
|
-
projectId,
|
|
79
|
-
appReleaseHash
|
|
80
|
-
});
|
|
81
85
|
|
|
82
86
|
const signedUrlsRequest = { projectId, appReleaseHash };
|
|
83
87
|
await this.publishCloudRequest({
|
|
84
88
|
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
85
89
|
signedUrlsRequest
|
|
86
90
|
});
|
|
87
|
-
|
|
91
|
+
return false;
|
|
88
92
|
}
|
|
89
93
|
case keyMirrors.appVersionControl.uninstall: {
|
|
90
94
|
const { projectId } = payload;
|
|
91
95
|
await this.atomicApplicationUninstall(projectId);
|
|
92
|
-
|
|
96
|
+
return true;
|
|
93
97
|
}
|
|
94
98
|
default:
|
|
95
99
|
logger.warn(
|
|
@@ -99,14 +103,13 @@ export class DeviceAgentCloudConnection {
|
|
|
99
103
|
2
|
|
100
104
|
)}`
|
|
101
105
|
);
|
|
106
|
+
return true;
|
|
102
107
|
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private handleDeviceCommand = async (packet: any) => {
|
|
106
|
-
// TODO
|
|
107
108
|
};
|
|
108
109
|
|
|
109
|
-
private async
|
|
110
|
+
private handleAgentCommand = async (
|
|
111
|
+
message: LiveUpdatesToggleMessage
|
|
112
|
+
): Promise<void> => {
|
|
110
113
|
switch (message.messageType) {
|
|
111
114
|
case keyMirrors.clientMessageType.live_state_updates:
|
|
112
115
|
await this.liveUpdatesHandler.handleToggles(message.liveUpdatesToggles);
|
|
@@ -116,7 +119,63 @@ export class DeviceAgentCloudConnection {
|
|
|
116
119
|
`Invalid agent action message type from message '${message}'`
|
|
117
120
|
);
|
|
118
121
|
}
|
|
119
|
-
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
private handleAppInstallCloudResponse = async (
|
|
125
|
+
payload: AppInstallCloudResponseMessage
|
|
126
|
+
): Promise<boolean> => {
|
|
127
|
+
const {
|
|
128
|
+
projectId,
|
|
129
|
+
appReleaseHash,
|
|
130
|
+
appInstallPayload,
|
|
131
|
+
modelsInstallPayload
|
|
132
|
+
} = payload.appInstallCloudResponse;
|
|
133
|
+
const signedUrlsPayload = {
|
|
134
|
+
appInstallPayload,
|
|
135
|
+
modelsInstallPayload
|
|
136
|
+
};
|
|
137
|
+
await this.atomicApplicationUpdate(
|
|
138
|
+
installApp,
|
|
139
|
+
[{ projectId, appReleaseHash, signedUrlsPayload }],
|
|
140
|
+
projectId
|
|
141
|
+
);
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
private handleModelsInstallCloudResponse = async (
|
|
146
|
+
payload: ModelsInstallCloudResponseMessage
|
|
147
|
+
): Promise<boolean> => {
|
|
148
|
+
const update = this.appCfgUpdateQueue.shift();
|
|
149
|
+
if (update === undefined) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
'Unknown error while updating models via application config! No config present for model update.'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const { appCfgUpdate, envVarUpdate } = update;
|
|
155
|
+
const projectId = payload.modelsInstallCloudResponse.projectId;
|
|
156
|
+
if (appCfgUpdate) {
|
|
157
|
+
await this.atomicApplicationUpdate(
|
|
158
|
+
updateModelsWithPresignedUrls,
|
|
159
|
+
[
|
|
160
|
+
{
|
|
161
|
+
projectId,
|
|
162
|
+
modelInstallPayloads: payload.modelsInstallCloudResponse.newModels,
|
|
163
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
projectId
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (envVarUpdate) {
|
|
171
|
+
await this.atomicApplicationUpdate(
|
|
172
|
+
setEnv,
|
|
173
|
+
[{ projectId, envVars: envVarUpdate.envVars }],
|
|
174
|
+
projectId
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
120
179
|
|
|
121
180
|
private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
|
|
122
181
|
this.publisher.publishToCloud(payload);
|
|
@@ -128,45 +187,12 @@ export class DeviceAgentCloudConnection {
|
|
|
128
187
|
}
|
|
129
188
|
|
|
130
189
|
private async atomicApplicationUninstall(projectId: string) {
|
|
131
|
-
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
132
|
-
projectId
|
|
133
|
-
});
|
|
134
|
-
await this.cmdStatusMgr.start(projectId);
|
|
135
|
-
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
136
|
-
projectId,
|
|
137
|
-
appReleaseHash
|
|
138
|
-
});
|
|
139
190
|
try {
|
|
140
191
|
await uninstallApp({ projectId });
|
|
141
192
|
this.shadowHandler.clearAppConfig(projectId);
|
|
142
|
-
|
|
143
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
144
|
-
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
145
|
-
projectId
|
|
146
|
-
});
|
|
147
|
-
// Send final status message
|
|
148
|
-
this.publisher.publishToClient(
|
|
149
|
-
await getAppInstallStatusMessage(
|
|
150
|
-
keyMirrors.appInstallStatus.success,
|
|
151
|
-
'',
|
|
152
|
-
appReleaseHash
|
|
153
|
-
)
|
|
154
|
-
);
|
|
155
193
|
} catch (e) {
|
|
156
194
|
logger.error(`Failed to uninstall ${projectId}: ${e.message}`);
|
|
157
|
-
|
|
158
|
-
await this.cmdStatusMgr.stop(projectId);
|
|
159
|
-
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
160
|
-
projectId
|
|
161
|
-
});
|
|
162
|
-
// Send final status message
|
|
163
|
-
this.publisher.publishToClient(
|
|
164
|
-
await getAppInstallStatusMessage(
|
|
165
|
-
keyMirrors.appInstallStatus.failure,
|
|
166
|
-
message,
|
|
167
|
-
appReleaseHash
|
|
168
|
-
)
|
|
169
|
-
);
|
|
195
|
+
throw e;
|
|
170
196
|
}
|
|
171
197
|
}
|
|
172
198
|
|
|
@@ -174,37 +200,14 @@ export class DeviceAgentCloudConnection {
|
|
|
174
200
|
private async atomicApplicationUpdate<T extends any[], R extends any>(
|
|
175
201
|
func: (...args: T) => R,
|
|
176
202
|
args: T,
|
|
177
|
-
projectId: string
|
|
178
|
-
appReleaseHash: string
|
|
203
|
+
projectId: string
|
|
179
204
|
) {
|
|
180
|
-
await this.cmdStatusMgr.start(projectId);
|
|
181
|
-
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
182
|
-
projectId,
|
|
183
|
-
appReleaseHash
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Install the app and models
|
|
187
205
|
try {
|
|
188
206
|
const out: R = await func(...args);
|
|
189
|
-
await this.
|
|
190
|
-
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
191
|
-
projectId
|
|
192
|
-
});
|
|
193
|
-
// Send final status message
|
|
194
|
-
this.publisher.publishToClient(
|
|
195
|
-
await getAppInstallStatusMessage(
|
|
196
|
-
keyMirrors.appInstallStatus.success,
|
|
197
|
-
'',
|
|
198
|
-
appReleaseHash
|
|
199
|
-
)
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
// update app config shadow for project
|
|
203
|
-
await this.shadowHandler.publishAppConfig(projectId);
|
|
207
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
204
208
|
return out;
|
|
205
209
|
} catch (e) {
|
|
206
|
-
logger.error(`Failed to
|
|
207
|
-
const message: string = e.message;
|
|
210
|
+
logger.error(`Failed to update ${projectId}:\n${e.message}\n${e.stack}`);
|
|
208
211
|
|
|
209
212
|
// uninstall the failed app to put system back in good state
|
|
210
213
|
// TODO: Replace this with rollback
|
|
@@ -212,7 +215,38 @@ export class DeviceAgentCloudConnection {
|
|
|
212
215
|
await uninstallApp({ projectId });
|
|
213
216
|
} finally {
|
|
214
217
|
this.shadowHandler.clearAppConfig(projectId);
|
|
218
|
+
}
|
|
219
|
+
throw e;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
215
222
|
|
|
223
|
+
// eslint-disable-next-line
|
|
224
|
+
private async atomicCmd<T extends any[]>(props: {
|
|
225
|
+
func: (...args: T) => Promise<boolean>;
|
|
226
|
+
args: T;
|
|
227
|
+
projectId: string;
|
|
228
|
+
appReleaseHash?: string;
|
|
229
|
+
}) {
|
|
230
|
+
const { func, args, projectId } = props;
|
|
231
|
+
let appReleaseHash = props.appReleaseHash;
|
|
232
|
+
if (appReleaseHash === undefined) {
|
|
233
|
+
try {
|
|
234
|
+
appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
235
|
+
projectId
|
|
236
|
+
});
|
|
237
|
+
} catch (e) {
|
|
238
|
+
logger.warn(`Unable to get app release hash for ${projectId}!`);
|
|
239
|
+
appReleaseHash = '';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await this.cmdStatusMgr.start(projectId);
|
|
244
|
+
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
245
|
+
projectId,
|
|
246
|
+
appReleaseHash
|
|
247
|
+
});
|
|
248
|
+
const completed = await func(...args);
|
|
249
|
+
if (completed) {
|
|
216
250
|
await this.cmdStatusMgr.stop(projectId);
|
|
217
251
|
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
218
252
|
projectId
|
|
@@ -220,30 +254,48 @@ export class DeviceAgentCloudConnection {
|
|
|
220
254
|
// Send final status message
|
|
221
255
|
this.publisher.publishToClient(
|
|
222
256
|
await getAppInstallStatusMessage(
|
|
223
|
-
keyMirrors.appInstallStatus.
|
|
224
|
-
|
|
257
|
+
keyMirrors.appInstallStatus.success,
|
|
258
|
+
'',
|
|
225
259
|
appReleaseHash
|
|
226
260
|
)
|
|
227
261
|
);
|
|
228
262
|
}
|
|
229
|
-
|
|
230
|
-
|
|
263
|
+
return completed;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
logger.error(
|
|
266
|
+
`Failed to execute cmd for ${projectId}:\n${e.message}\n${e.stack}`
|
|
267
|
+
);
|
|
268
|
+
const message: string = e.message;
|
|
231
269
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
270
|
+
// uninstall the failed app to put system back in good state
|
|
271
|
+
await this.cmdStatusMgr.stop(projectId);
|
|
272
|
+
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
236
273
|
projectId
|
|
237
274
|
});
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
275
|
+
// Send final status message
|
|
276
|
+
this.publisher.publishToClient(
|
|
277
|
+
await getAppInstallStatusMessage(
|
|
278
|
+
keyMirrors.appInstallStatus.failure,
|
|
279
|
+
message,
|
|
280
|
+
appReleaseHash
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
243
285
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
286
|
+
private handleAppConfigUpdates = async (
|
|
287
|
+
updates: ShadowUpdate[]
|
|
288
|
+
): Promise<boolean> => {
|
|
289
|
+
for (const update of updates) {
|
|
290
|
+
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
291
|
+
|
|
292
|
+
if (
|
|
293
|
+
appCfgUpdate &&
|
|
294
|
+
appCfgUpdate.updatedModels &&
|
|
295
|
+
Object.keys(appCfgUpdate.updatedModels).length
|
|
296
|
+
) {
|
|
297
|
+
// When there are model updates request signed URLs and wait to apply config changes
|
|
298
|
+
const { updatedModels } = appCfgUpdate;
|
|
247
299
|
|
|
248
300
|
logger.debug(
|
|
249
301
|
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
@@ -257,28 +309,34 @@ export class DeviceAgentCloudConnection {
|
|
|
257
309
|
models: updatedModels
|
|
258
310
|
}
|
|
259
311
|
});
|
|
260
|
-
} else {
|
|
261
|
-
// FIXME: do we need to send this up to the cloud?
|
|
262
|
-
// should it be something other than appReleaseHash?
|
|
263
|
-
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
264
|
-
projectId
|
|
265
|
-
});
|
|
266
312
|
|
|
313
|
+
this.appCfgUpdateQueue.push(update);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (appCfgUpdate) {
|
|
267
318
|
await this.atomicApplicationUpdate(
|
|
268
319
|
updateAppCfg,
|
|
269
320
|
[
|
|
270
321
|
{
|
|
271
322
|
projectId,
|
|
272
|
-
|
|
273
|
-
newAppCfg
|
|
323
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
274
324
|
}
|
|
275
325
|
],
|
|
276
|
-
projectId
|
|
277
|
-
|
|
326
|
+
projectId
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (envVarUpdate) {
|
|
331
|
+
await this.atomicApplicationUpdate(
|
|
332
|
+
setEnv,
|
|
333
|
+
[{ projectId, envVars: envVarUpdate.envVars }],
|
|
334
|
+
projectId
|
|
278
335
|
);
|
|
279
336
|
}
|
|
280
337
|
}
|
|
281
|
-
|
|
338
|
+
return true;
|
|
339
|
+
};
|
|
282
340
|
|
|
283
341
|
/*=================================================================
|
|
284
342
|
Public interface
|
|
@@ -323,6 +381,10 @@ export class DeviceAgentCloudConnection {
|
|
|
323
381
|
return this.cmdStatusMgr.isCmdInProgress(projectId);
|
|
324
382
|
}
|
|
325
383
|
|
|
384
|
+
public async updateProjectShadow(projectId: string) {
|
|
385
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
386
|
+
}
|
|
387
|
+
|
|
326
388
|
public async handleClientMessage({
|
|
327
389
|
topic,
|
|
328
390
|
message
|
|
@@ -339,70 +401,61 @@ export class DeviceAgentCloudConnection {
|
|
|
339
401
|
2
|
|
340
402
|
)}`
|
|
341
403
|
);
|
|
404
|
+
// TODO: Send generic error response
|
|
342
405
|
return;
|
|
343
406
|
}
|
|
344
407
|
const payload = message.payload;
|
|
345
408
|
switch (payload.messageType) {
|
|
346
409
|
case keyMirrors.clientMessageType.app_state_control: {
|
|
347
|
-
|
|
410
|
+
const projectId = payload.appStateControl.projectId;
|
|
411
|
+
await this.atomicCmd({
|
|
412
|
+
func: this.handleAppStateControl,
|
|
413
|
+
args: [payload.appStateControl],
|
|
414
|
+
projectId
|
|
415
|
+
});
|
|
348
416
|
break;
|
|
349
417
|
}
|
|
350
418
|
case keyMirrors.clientMessageType.app_version_control: {
|
|
351
|
-
|
|
419
|
+
const projectId = payload.appVersionControl.projectId;
|
|
420
|
+
const appReleaseHash =
|
|
421
|
+
payload.appVersionControl.baseCommand ===
|
|
422
|
+
keyMirrors.appVersionControl.install
|
|
423
|
+
? payload.appVersionControl.appReleaseHash
|
|
424
|
+
: undefined;
|
|
425
|
+
await this.atomicCmd({
|
|
426
|
+
func: this.handleAppVersionControl,
|
|
427
|
+
args: [payload.appVersionControl],
|
|
428
|
+
projectId,
|
|
429
|
+
appReleaseHash
|
|
430
|
+
});
|
|
352
431
|
break;
|
|
353
432
|
}
|
|
354
433
|
case keyMirrors.clientMessageType.live_state_updates: {
|
|
434
|
+
// TODO: Send response?
|
|
355
435
|
await this.handleAgentCommand(payload);
|
|
356
436
|
break;
|
|
357
437
|
}
|
|
358
438
|
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
359
|
-
const {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
modelsInstallPayload
|
|
364
|
-
} = payload.appInstallCloudResponse;
|
|
365
|
-
const signedUrlsPayload = {
|
|
366
|
-
appInstallPayload,
|
|
367
|
-
modelsInstallPayload
|
|
368
|
-
};
|
|
369
|
-
await this.atomicApplicationUpdate(
|
|
370
|
-
installApp,
|
|
371
|
-
[{ projectId, appReleaseHash, signedUrlsPayload }],
|
|
439
|
+
const { projectId, appReleaseHash } = payload.appInstallCloudResponse;
|
|
440
|
+
await this.atomicCmd({
|
|
441
|
+
func: this.handleAppInstallCloudResponse,
|
|
442
|
+
args: [payload],
|
|
372
443
|
projectId,
|
|
373
444
|
appReleaseHash
|
|
374
|
-
);
|
|
445
|
+
});
|
|
375
446
|
|
|
376
447
|
break;
|
|
377
448
|
}
|
|
378
449
|
case keyMirrors.clientMessageType.models_install_cloud_response: {
|
|
379
|
-
|
|
380
|
-
|
|
450
|
+
// This message doesn't have appReleaseHash in it's payload, but
|
|
451
|
+
// atomicCmd should be able to read it from the installed app
|
|
452
|
+
const { projectId } = payload.modelsInstallCloudResponse;
|
|
453
|
+
await this.atomicCmd({
|
|
454
|
+
func: this.handleModelsInstallCloudResponse,
|
|
455
|
+
args: [payload],
|
|
381
456
|
projectId
|
|
382
457
|
});
|
|
383
458
|
|
|
384
|
-
const newAppCfg = this.newAppCfgQueue.shift();
|
|
385
|
-
if (newAppCfg === undefined) {
|
|
386
|
-
logger.error(
|
|
387
|
-
'Unknown error while updating models via application config! No config present for model update.'
|
|
388
|
-
);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
await this.atomicApplicationUpdate(
|
|
393
|
-
updateModelsWithPresignedUrls,
|
|
394
|
-
[
|
|
395
|
-
{
|
|
396
|
-
projectId,
|
|
397
|
-
modelInstallPayloads: newModels,
|
|
398
|
-
newAppCfg,
|
|
399
|
-
appReleaseHash
|
|
400
|
-
}
|
|
401
|
-
],
|
|
402
|
-
projectId,
|
|
403
|
-
appReleaseHash
|
|
404
|
-
);
|
|
405
|
-
|
|
406
459
|
break;
|
|
407
460
|
}
|
|
408
461
|
default:
|
|
@@ -423,12 +476,25 @@ export class DeviceAgentCloudConnection {
|
|
|
423
476
|
switch (topic) {
|
|
424
477
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
425
478
|
case this.shadowHandler.shadowTopics.projects.updateDelta: {
|
|
426
|
-
const
|
|
479
|
+
const shadowUpdates = await this.shadowHandler.handleShadowTopic({
|
|
427
480
|
topic,
|
|
428
481
|
payload: message.state,
|
|
429
482
|
clientToken: message.clientToken
|
|
430
483
|
});
|
|
431
|
-
|
|
484
|
+
if (shadowUpdates.length) {
|
|
485
|
+
// FIXME: Take project ID of first shadow update. Most likely there will only be one update
|
|
486
|
+
// so this should be sufficient for now.
|
|
487
|
+
const projectId = shadowUpdates[0].projectId;
|
|
488
|
+
try {
|
|
489
|
+
await this.atomicCmd({
|
|
490
|
+
func: this.handleAppConfigUpdates,
|
|
491
|
+
args: [shadowUpdates],
|
|
492
|
+
projectId
|
|
493
|
+
});
|
|
494
|
+
} catch (e) {
|
|
495
|
+
logger.error(`Error handling shadow message: ${e.message}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
432
498
|
break;
|
|
433
499
|
}
|
|
434
500
|
case this.shadowHandler.shadowTopics.projects.getRejected:
|
|
@@ -483,14 +549,17 @@ export class DeviceAgentCloudConnection {
|
|
|
483
549
|
});
|
|
484
550
|
}
|
|
485
551
|
|
|
486
|
-
public stop() {
|
|
552
|
+
public async stop() {
|
|
553
|
+
// FIXME: This method is currently only used by the CLI, and shadow messages
|
|
554
|
+
// can be lost since we aren't waiting for responses so sleep for a short
|
|
555
|
+
// time to receive them
|
|
556
|
+
await sleep(1000);
|
|
487
557
|
this.device.end();
|
|
488
558
|
}
|
|
489
559
|
}
|
|
490
560
|
|
|
491
561
|
export async function runDeviceAgentCloudInterface() {
|
|
492
|
-
|
|
493
|
-
if (existsSync(DEVICE_CERTIFICATE_FILE_PATH)) {
|
|
562
|
+
if (cloudModeReady()) {
|
|
494
563
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
495
564
|
await deviceAgent.setupHandlers();
|
|
496
565
|
if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
2
|
+
import { Publisher } from './publisher';
|
|
3
|
+
|
|
4
|
+
const testTrueToggles = {
|
|
5
|
+
deviceStats: true,
|
|
6
|
+
appState: true
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const testFalseToggles = {
|
|
10
|
+
deviceStats: false,
|
|
11
|
+
appState: false
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockClient = jest.fn();
|
|
15
|
+
const clientId = 'test-client';
|
|
16
|
+
|
|
17
|
+
jest.spyOn(global, 'setTimeout');
|
|
18
|
+
|
|
19
|
+
// NOTE: this was the way I found to mock private class functions
|
|
20
|
+
const mockStartPublishingLiveUpdates = jest.spyOn(
|
|
21
|
+
LiveUpdatesHandler.prototype as any,
|
|
22
|
+
'startPublishingLiveUpdates'
|
|
23
|
+
);
|
|
24
|
+
mockStartPublishingLiveUpdates.mockResolvedValue(null);
|
|
25
|
+
|
|
26
|
+
describe('Test Live Updates Handler', () => {
|
|
27
|
+
let liveUpdatesHandler: LiveUpdatesHandler;
|
|
28
|
+
let publisher: Publisher;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
publisher = new Publisher(mockClient, clientId);
|
|
32
|
+
liveUpdatesHandler = new LiveUpdatesHandler(publisher);
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('ignore subsequent enables', async () => {
|
|
37
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
38
|
+
// called twice, once for device stats, once for app state
|
|
39
|
+
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
|
|
40
|
+
// restartLiveUpdatesTimeout is always called once when handleToggles is called
|
|
41
|
+
expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
|
|
42
|
+
|
|
43
|
+
// Second call -> should not call startPublishingLiveUpdates should not be called
|
|
44
|
+
jest.clearAllMocks();
|
|
45
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
46
|
+
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
|
|
47
|
+
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('test disable live updates', async () => {
|
|
51
|
+
// Test calling handleToggles one time, enabling it
|
|
52
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles);
|
|
53
|
+
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
|
|
54
|
+
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
55
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
56
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
|
|
57
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
58
|
+
|
|
59
|
+
// Check to see that attributes are properly set to false when disabled
|
|
60
|
+
jest.clearAllMocks();
|
|
61
|
+
void liveUpdatesHandler.handleToggles(testFalseToggles);
|
|
62
|
+
expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
|
|
63
|
+
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
64
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|
|
65
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
66
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|