@alwaysai/device-agent 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/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 +201 -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/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 +220 -155
- 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/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,37 @@ 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
|
+
appReleaseHash = '';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
await this.cmdStatusMgr.start(projectId);
|
|
243
|
+
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
244
|
+
projectId,
|
|
245
|
+
appReleaseHash
|
|
246
|
+
});
|
|
247
|
+
const completed = await func(...args);
|
|
248
|
+
if (completed) {
|
|
216
249
|
await this.cmdStatusMgr.stop(projectId);
|
|
217
250
|
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
218
251
|
projectId
|
|
@@ -220,30 +253,48 @@ export class DeviceAgentCloudConnection {
|
|
|
220
253
|
// Send final status message
|
|
221
254
|
this.publisher.publishToClient(
|
|
222
255
|
await getAppInstallStatusMessage(
|
|
223
|
-
keyMirrors.appInstallStatus.
|
|
224
|
-
|
|
256
|
+
keyMirrors.appInstallStatus.success,
|
|
257
|
+
'',
|
|
225
258
|
appReleaseHash
|
|
226
259
|
)
|
|
227
260
|
);
|
|
228
261
|
}
|
|
229
|
-
|
|
230
|
-
|
|
262
|
+
return completed;
|
|
263
|
+
} catch (e) {
|
|
264
|
+
logger.error(
|
|
265
|
+
`Failed to execute cmd for ${projectId}:\n${e.message}\n${e.stack}`
|
|
266
|
+
);
|
|
267
|
+
const message: string = e.message;
|
|
231
268
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
269
|
+
// uninstall the failed app to put system back in good state
|
|
270
|
+
await this.cmdStatusMgr.stop(projectId);
|
|
271
|
+
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
236
272
|
projectId
|
|
237
273
|
});
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
// Send final status message
|
|
275
|
+
this.publisher.publishToClient(
|
|
276
|
+
await getAppInstallStatusMessage(
|
|
277
|
+
keyMirrors.appInstallStatus.failure,
|
|
278
|
+
message,
|
|
279
|
+
appReleaseHash
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
243
284
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
285
|
+
private handleAppConfigUpdates = async (
|
|
286
|
+
updates: ShadowUpdate[]
|
|
287
|
+
): Promise<boolean> => {
|
|
288
|
+
for (const update of updates) {
|
|
289
|
+
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
290
|
+
|
|
291
|
+
if (
|
|
292
|
+
appCfgUpdate &&
|
|
293
|
+
appCfgUpdate.updatedModels &&
|
|
294
|
+
Object.keys(appCfgUpdate.updatedModels).length
|
|
295
|
+
) {
|
|
296
|
+
// When there are model updates request signed URLs and wait to apply config changes
|
|
297
|
+
const { updatedModels } = appCfgUpdate;
|
|
247
298
|
|
|
248
299
|
logger.debug(
|
|
249
300
|
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
@@ -257,28 +308,34 @@ export class DeviceAgentCloudConnection {
|
|
|
257
308
|
models: updatedModels
|
|
258
309
|
}
|
|
259
310
|
});
|
|
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
311
|
|
|
312
|
+
this.appCfgUpdateQueue.push(update);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (appCfgUpdate) {
|
|
267
317
|
await this.atomicApplicationUpdate(
|
|
268
318
|
updateAppCfg,
|
|
269
319
|
[
|
|
270
320
|
{
|
|
271
321
|
projectId,
|
|
272
|
-
|
|
273
|
-
newAppCfg
|
|
322
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
274
323
|
}
|
|
275
324
|
],
|
|
276
|
-
projectId
|
|
277
|
-
|
|
325
|
+
projectId
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (envVarUpdate) {
|
|
330
|
+
await this.atomicApplicationUpdate(
|
|
331
|
+
setEnv,
|
|
332
|
+
[{ projectId, envVars: envVarUpdate.envVars }],
|
|
333
|
+
projectId
|
|
278
334
|
);
|
|
279
335
|
}
|
|
280
336
|
}
|
|
281
|
-
|
|
337
|
+
return true;
|
|
338
|
+
};
|
|
282
339
|
|
|
283
340
|
/*=================================================================
|
|
284
341
|
Public interface
|
|
@@ -323,6 +380,10 @@ export class DeviceAgentCloudConnection {
|
|
|
323
380
|
return this.cmdStatusMgr.isCmdInProgress(projectId);
|
|
324
381
|
}
|
|
325
382
|
|
|
383
|
+
public async updateProjectShadow(projectId: string) {
|
|
384
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
385
|
+
}
|
|
386
|
+
|
|
326
387
|
public async handleClientMessage({
|
|
327
388
|
topic,
|
|
328
389
|
message
|
|
@@ -339,70 +400,58 @@ export class DeviceAgentCloudConnection {
|
|
|
339
400
|
2
|
|
340
401
|
)}`
|
|
341
402
|
);
|
|
403
|
+
// TODO: Send generic error response
|
|
342
404
|
return;
|
|
343
405
|
}
|
|
344
406
|
const payload = message.payload;
|
|
345
407
|
switch (payload.messageType) {
|
|
346
408
|
case keyMirrors.clientMessageType.app_state_control: {
|
|
347
|
-
|
|
409
|
+
const projectId = payload.appStateControl.projectId;
|
|
410
|
+
await this.atomicCmd({
|
|
411
|
+
func: this.handleAppStateControl,
|
|
412
|
+
args: [payload.appStateControl],
|
|
413
|
+
projectId
|
|
414
|
+
});
|
|
348
415
|
break;
|
|
349
416
|
}
|
|
350
417
|
case keyMirrors.clientMessageType.app_version_control: {
|
|
351
|
-
|
|
418
|
+
const projectId = payload.appVersionControl.projectId;
|
|
419
|
+
const appReleaseHash =
|
|
420
|
+
payload.appVersionControl.baseCommand ===
|
|
421
|
+
keyMirrors.appVersionControl.install
|
|
422
|
+
? payload.appVersionControl.appReleaseHash
|
|
423
|
+
: undefined;
|
|
424
|
+
await this.atomicCmd({
|
|
425
|
+
func: this.handleAppVersionControl,
|
|
426
|
+
args: [payload.appVersionControl],
|
|
427
|
+
projectId,
|
|
428
|
+
appReleaseHash
|
|
429
|
+
});
|
|
352
430
|
break;
|
|
353
431
|
}
|
|
354
432
|
case keyMirrors.clientMessageType.live_state_updates: {
|
|
433
|
+
// TODO: Send response?
|
|
355
434
|
await this.handleAgentCommand(payload);
|
|
356
435
|
break;
|
|
357
436
|
}
|
|
358
437
|
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
const signedUrlsPayload = {
|
|
366
|
-
appInstallPayload,
|
|
367
|
-
modelsInstallPayload
|
|
368
|
-
};
|
|
369
|
-
await this.atomicApplicationUpdate(
|
|
370
|
-
installApp,
|
|
371
|
-
[{ projectId, appReleaseHash, signedUrlsPayload }],
|
|
372
|
-
projectId,
|
|
373
|
-
appReleaseHash
|
|
374
|
-
);
|
|
438
|
+
const projectId = payload.appInstallCloudResponse.projectId;
|
|
439
|
+
await this.atomicCmd({
|
|
440
|
+
func: this.handleAppInstallCloudResponse,
|
|
441
|
+
args: [payload],
|
|
442
|
+
projectId
|
|
443
|
+
});
|
|
375
444
|
|
|
376
445
|
break;
|
|
377
446
|
}
|
|
378
447
|
case keyMirrors.clientMessageType.models_install_cloud_response: {
|
|
379
|
-
const { projectId
|
|
380
|
-
|
|
448
|
+
const { projectId } = payload.modelsInstallCloudResponse;
|
|
449
|
+
await this.atomicCmd({
|
|
450
|
+
func: this.handleModelsInstallCloudResponse,
|
|
451
|
+
args: [payload],
|
|
381
452
|
projectId
|
|
382
453
|
});
|
|
383
454
|
|
|
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
455
|
break;
|
|
407
456
|
}
|
|
408
457
|
default:
|
|
@@ -423,12 +472,25 @@ export class DeviceAgentCloudConnection {
|
|
|
423
472
|
switch (topic) {
|
|
424
473
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
425
474
|
case this.shadowHandler.shadowTopics.projects.updateDelta: {
|
|
426
|
-
const
|
|
475
|
+
const shadowUpdates = await this.shadowHandler.handleShadowTopic({
|
|
427
476
|
topic,
|
|
428
477
|
payload: message.state,
|
|
429
478
|
clientToken: message.clientToken
|
|
430
479
|
});
|
|
431
|
-
|
|
480
|
+
if (shadowUpdates.length) {
|
|
481
|
+
// FIXME: Take project ID of first shadow update. Most likely there will only be one update
|
|
482
|
+
// so this should be sufficient for now.
|
|
483
|
+
const projectId = shadowUpdates[0].projectId;
|
|
484
|
+
try {
|
|
485
|
+
await this.atomicCmd({
|
|
486
|
+
func: this.handleAppConfigUpdates,
|
|
487
|
+
args: [shadowUpdates],
|
|
488
|
+
projectId
|
|
489
|
+
});
|
|
490
|
+
} catch (e) {
|
|
491
|
+
logger.error(`Error handling shadow message: ${e.message}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
432
494
|
break;
|
|
433
495
|
}
|
|
434
496
|
case this.shadowHandler.shadowTopics.projects.getRejected:
|
|
@@ -483,14 +545,17 @@ export class DeviceAgentCloudConnection {
|
|
|
483
545
|
});
|
|
484
546
|
}
|
|
485
547
|
|
|
486
|
-
public stop() {
|
|
548
|
+
public async stop() {
|
|
549
|
+
// FIXME: This method is currently only used by the CLI, and shadow messages
|
|
550
|
+
// can be lost since we aren't waiting for responses so sleep for a short
|
|
551
|
+
// time to receive them
|
|
552
|
+
await sleep(1000);
|
|
487
553
|
this.device.end();
|
|
488
554
|
}
|
|
489
555
|
}
|
|
490
556
|
|
|
491
557
|
export async function runDeviceAgentCloudInterface() {
|
|
492
|
-
|
|
493
|
-
if (existsSync(DEVICE_CERTIFICATE_FILE_PATH)) {
|
|
558
|
+
if (cloudModeReady()) {
|
|
494
559
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
495
560
|
await deviceAgent.setupHandlers();
|
|
496
561
|
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
|
+
});
|