@alwaysai/device-agent 2.0.0 → 2.0.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/environment-variables.d.ts +4 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +17 -13
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/install.d.ts +4 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +16 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +13 -0
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/base-message-handler.d.ts +27 -0
- package/lib/cloud-connection/base-message-handler.d.ts.map +1 -0
- package/lib/cloud-connection/base-message-handler.js +72 -0
- package/lib/cloud-connection/base-message-handler.js.map +1 -0
- package/lib/cloud-connection/connection-manager.d.ts +21 -0
- package/lib/cloud-connection/connection-manager.d.ts.map +1 -0
- package/lib/cloud-connection/connection-manager.js +158 -0
- package/lib/cloud-connection/connection-manager.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +7 -23
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +49 -517
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent-message-handler.d.ts +22 -0
- package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -0
- package/lib/cloud-connection/device-agent-message-handler.js +357 -0
- package/lib/cloud-connection/device-agent-message-handler.js.map +1 -0
- 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 +13 -10
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/message-dispatcher.d.ts +10 -0
- package/lib/cloud-connection/message-dispatcher.d.ts.map +1 -0
- package/lib/cloud-connection/message-dispatcher.js +27 -0
- package/lib/cloud-connection/message-dispatcher.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.d.ts +6 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +74 -1
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +8 -1
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +21 -9
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +43 -2
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/jobs/job-handler.d.ts +23 -0
- package/lib/jobs/job-handler.d.ts.map +1 -0
- package/lib/jobs/job-handler.js +131 -0
- package/lib/jobs/job-handler.js.map +1 -0
- package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts +8 -0
- package/lib/secure-tunneling/secure-tunnel-message-handler.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunnel-message-handler.js +42 -0
- package/lib/secure-tunneling/secure-tunnel-message-handler.js.map +1 -0
- package/lib/subcommands/app/version.d.ts +2 -0
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +14 -2
- package/lib/subcommands/app/version.js.map +1 -1
- package/package.json +2 -2
- package/src/application-control/environment-variables.ts +31 -21
- package/src/application-control/install.ts +24 -3
- package/src/application-control/utils.ts +13 -0
- package/src/cloud-connection/base-message-handler.ts +118 -0
- package/src/cloud-connection/connection-manager.ts +187 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +109 -816
- package/src/cloud-connection/device-agent-message-handler.ts +642 -0
- package/src/cloud-connection/live-updates-handler.ts +26 -18
- package/src/cloud-connection/message-dispatcher.ts +33 -0
- package/src/cloud-connection/shadow-handler.ts +125 -1
- package/src/cloud-connection/transaction-manager.test.ts +65 -3
- package/src/cloud-connection/transaction-manager.ts +41 -27
- package/src/jobs/job-handler.ts +146 -0
- package/src/secure-tunneling/secure-tunnel-message-handler.ts +56 -0
- package/src/subcommands/app/version.ts +20 -2
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import {
|
|
2
|
+
keyMirrors,
|
|
3
|
+
AppStateControlMessage,
|
|
4
|
+
buildToClientStatusResponseMessage,
|
|
5
|
+
AppStateControlPayload,
|
|
6
|
+
AppVersionControlMessage,
|
|
7
|
+
AppVersionControlInstallPayload,
|
|
8
|
+
AppVersionControlUninstallPayload,
|
|
9
|
+
SignedUrlsRequestPayload,
|
|
10
|
+
buildSignedUrlsRequestMessage,
|
|
11
|
+
ToCloudMessage,
|
|
12
|
+
LiveStateUpdatesToggleMessage,
|
|
13
|
+
buildDeviceStatsMessage,
|
|
14
|
+
buildAppStateMessage,
|
|
15
|
+
buildAppLogsMessage,
|
|
16
|
+
AppInstallResponsePayload,
|
|
17
|
+
ModelsInstallResponsePayload,
|
|
18
|
+
ToDeviceStatusResponseMessage,
|
|
19
|
+
DeviceActionPayload,
|
|
20
|
+
validateToDeviceAgentMessage,
|
|
21
|
+
ToDeviceAgentMessage,
|
|
22
|
+
EnvVars,
|
|
23
|
+
validateEnvVarSchemaShadowUpdate
|
|
24
|
+
} from '@alwaysai/device-agent-schemas';
|
|
25
|
+
import {
|
|
26
|
+
AppInstallResponseMessage,
|
|
27
|
+
ModelsInstallResponseMessage
|
|
28
|
+
} from '@alwaysai/device-agent-schemas/lib/app-action-schema';
|
|
29
|
+
import { DeviceActionMessage } from '@alwaysai/device-agent-schemas/lib/device-action-schema';
|
|
30
|
+
import { logger, stringifyError } from 'alwaysai/lib/util';
|
|
31
|
+
import {
|
|
32
|
+
startApp,
|
|
33
|
+
stopApp,
|
|
34
|
+
restartApp,
|
|
35
|
+
getAppLogs,
|
|
36
|
+
installApp,
|
|
37
|
+
updateModelsWithPresignedUrls
|
|
38
|
+
} from '../application-control';
|
|
39
|
+
import { reboot } from '../device-control/device-control';
|
|
40
|
+
import { MessageDispatcher, MessageHandler } from './message-dispatcher';
|
|
41
|
+
import { getDeviceStatsPayload, getAppStatePayload } from './messages';
|
|
42
|
+
import { BaseHandler, HandlerContext } from './base-message-handler';
|
|
43
|
+
import { ErrorFunction, SuccessFunction } from './transaction-manager';
|
|
44
|
+
import {
|
|
45
|
+
AppConfig,
|
|
46
|
+
validateAppConfig
|
|
47
|
+
} from '@alwaysai/app-configuration-schemas';
|
|
48
|
+
|
|
49
|
+
export type AppContent = {
|
|
50
|
+
projectId: string;
|
|
51
|
+
appCfg?: AppConfig;
|
|
52
|
+
envVars?: EnvVars;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type AppContentUpdate = {
|
|
56
|
+
appCfg?: string;
|
|
57
|
+
envVars?: string;
|
|
58
|
+
};
|
|
59
|
+
export class DeviceAgentMessageHandler
|
|
60
|
+
extends MessageDispatcher<ToDeviceAgentMessage>
|
|
61
|
+
implements MessageHandler<any>
|
|
62
|
+
{
|
|
63
|
+
private readonly handlerContext: HandlerContext;
|
|
64
|
+
private readonly errorFn?: ErrorFunction;
|
|
65
|
+
private readonly successFn?: SuccessFunction;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
handlerContext: HandlerContext,
|
|
69
|
+
errorFn?: ErrorFunction,
|
|
70
|
+
successFn?: SuccessFunction
|
|
71
|
+
) {
|
|
72
|
+
super();
|
|
73
|
+
this.handlerContext = handlerContext;
|
|
74
|
+
this.errorFn = errorFn;
|
|
75
|
+
this.successFn = successFn;
|
|
76
|
+
|
|
77
|
+
const handlerMapping: {
|
|
78
|
+
[key: string]: new (
|
|
79
|
+
context: HandlerContext,
|
|
80
|
+
errorFn?: ErrorFunction,
|
|
81
|
+
successFn?: SuccessFunction
|
|
82
|
+
) => MessageHandler<ToDeviceAgentMessage>;
|
|
83
|
+
} = {
|
|
84
|
+
[keyMirrors.toDeviceAgentMessageType.app_state_control]:
|
|
85
|
+
AppStateMessageHandler,
|
|
86
|
+
[keyMirrors.toDeviceAgentMessageType.live_state_updates]:
|
|
87
|
+
LiveStateUpdatesMessageHandler,
|
|
88
|
+
[keyMirrors.toDeviceAgentMessageType.app_version_control]:
|
|
89
|
+
AppVersionControlMessageHandler,
|
|
90
|
+
[keyMirrors.toDeviceAgentMessageType.app_install_response]:
|
|
91
|
+
AppInstallResponseMessageHandler,
|
|
92
|
+
[keyMirrors.toDeviceAgentMessageType.models_install_response]:
|
|
93
|
+
ModelsInstallResponseMessageHandler,
|
|
94
|
+
[keyMirrors.toDeviceAgentMessageType.status_response]:
|
|
95
|
+
StatusResponseMessageHandler,
|
|
96
|
+
[keyMirrors.toDeviceAgentMessageType.device_action]:
|
|
97
|
+
DeviceActionMessageHandler
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Object.entries(handlerMapping).forEach(([keyMirror, HandlerClass]) => {
|
|
101
|
+
this.registerHandler(
|
|
102
|
+
keyMirror,
|
|
103
|
+
new HandlerClass(this.handlerContext, this.errorFn, this.successFn)
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public handle(message: any, topic?: string): void {
|
|
109
|
+
const valid = validateToDeviceAgentMessage(message);
|
|
110
|
+
if (!valid) {
|
|
111
|
+
logger.error(
|
|
112
|
+
`Error validating message: ${JSON.stringify(
|
|
113
|
+
{ topic, message, errors: validateToDeviceAgentMessage.errors },
|
|
114
|
+
null,
|
|
115
|
+
2
|
|
116
|
+
)}`
|
|
117
|
+
);
|
|
118
|
+
if (this.errorFn) {
|
|
119
|
+
this.errorFn(
|
|
120
|
+
'',
|
|
121
|
+
`Error validating message: ${JSON.stringify(
|
|
122
|
+
{ topic, message, errors: validateToDeviceAgentMessage.errors },
|
|
123
|
+
null,
|
|
124
|
+
2
|
|
125
|
+
)}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.dispatch(message.messageType, message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class AppStateMessageHandler
|
|
135
|
+
extends BaseHandler
|
|
136
|
+
implements MessageHandler<AppStateControlMessage>
|
|
137
|
+
{
|
|
138
|
+
public async handle(message: AppStateControlMessage): Promise<void> {
|
|
139
|
+
const payload = message.payload;
|
|
140
|
+
const projectId = payload.projectId;
|
|
141
|
+
const txId = message.txId;
|
|
142
|
+
try {
|
|
143
|
+
await this.txnMgr.runTransactionStep({
|
|
144
|
+
func: () => this.handleAppStateControl(message.payload),
|
|
145
|
+
projectId,
|
|
146
|
+
txId,
|
|
147
|
+
start: true,
|
|
148
|
+
liveUpdatesPublishFn: async () =>
|
|
149
|
+
this.publisher.publishToClient(
|
|
150
|
+
buildToClientStatusResponseMessage(
|
|
151
|
+
this.clientId,
|
|
152
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
153
|
+
txId
|
|
154
|
+
),
|
|
155
|
+
logger.silly
|
|
156
|
+
),
|
|
157
|
+
stepName: payload.baseCommand,
|
|
158
|
+
errorFn: this.errorFn,
|
|
159
|
+
successFn: this.successFn
|
|
160
|
+
});
|
|
161
|
+
} catch (e) {
|
|
162
|
+
logger.error(
|
|
163
|
+
`Error processing application state control request for ${projectId}!\n${stringifyError(
|
|
164
|
+
e
|
|
165
|
+
)}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private handleAppStateControl = async (
|
|
171
|
+
payload: AppStateControlPayload
|
|
172
|
+
): Promise<boolean> => {
|
|
173
|
+
const { baseCommand, projectId } = payload;
|
|
174
|
+
switch (baseCommand) {
|
|
175
|
+
case keyMirrors.appStateControl.start:
|
|
176
|
+
await startApp({ projectId });
|
|
177
|
+
break;
|
|
178
|
+
case keyMirrors.appStateControl.stop:
|
|
179
|
+
await stopApp({ projectId });
|
|
180
|
+
break;
|
|
181
|
+
case keyMirrors.appStateControl.restart:
|
|
182
|
+
await restartApp({ projectId });
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
class AppVersionControlMessageHandler
|
|
190
|
+
extends BaseHandler
|
|
191
|
+
implements MessageHandler<AppVersionControlMessage>
|
|
192
|
+
{
|
|
193
|
+
public async handle(message: AppVersionControlMessage): Promise<void> {
|
|
194
|
+
const payload = message.payload;
|
|
195
|
+
const projectId = payload.projectId;
|
|
196
|
+
const txId = message.txId;
|
|
197
|
+
try {
|
|
198
|
+
await this.txnMgr.runTransactionStep({
|
|
199
|
+
func: () => this.handleAppVersionControl(payload, txId),
|
|
200
|
+
projectId,
|
|
201
|
+
txId,
|
|
202
|
+
start: true,
|
|
203
|
+
liveUpdatesPublishFn: async () =>
|
|
204
|
+
this.publisher.publishToClient(
|
|
205
|
+
buildToClientStatusResponseMessage(
|
|
206
|
+
this.clientId,
|
|
207
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
208
|
+
txId
|
|
209
|
+
),
|
|
210
|
+
logger.silly
|
|
211
|
+
),
|
|
212
|
+
stepName: payload.baseCommand,
|
|
213
|
+
errorFn: this.errorFn,
|
|
214
|
+
successFn: this.successFn
|
|
215
|
+
});
|
|
216
|
+
} catch (e) {
|
|
217
|
+
logger.error(
|
|
218
|
+
`Error processing application install request for ${projectId}!\n${stringifyError(
|
|
219
|
+
e
|
|
220
|
+
)}`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private handleAppVersionControl = async (
|
|
226
|
+
payload:
|
|
227
|
+
| AppVersionControlInstallPayload
|
|
228
|
+
| AppVersionControlUninstallPayload,
|
|
229
|
+
txId: string
|
|
230
|
+
): Promise<boolean> => {
|
|
231
|
+
switch (payload.baseCommand) {
|
|
232
|
+
case keyMirrors.appVersionControl.install: {
|
|
233
|
+
const { projectId, appReleaseHash, appCfg, envVars } = payload;
|
|
234
|
+
const appContentUpdate: AppContentUpdate = {
|
|
235
|
+
appCfg,
|
|
236
|
+
envVars
|
|
237
|
+
};
|
|
238
|
+
const appContent = await this.processAppContentUpdate(
|
|
239
|
+
projectId,
|
|
240
|
+
appContentUpdate
|
|
241
|
+
);
|
|
242
|
+
if (appContent) {
|
|
243
|
+
this.txnMgr.setAppContentToTx(txId, appContent);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
247
|
+
signedUrlsRequest: {
|
|
248
|
+
projectId,
|
|
249
|
+
appReleaseHash
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const message = buildSignedUrlsRequestMessage(
|
|
253
|
+
this.clientId,
|
|
254
|
+
signedUrlsRequestPayload,
|
|
255
|
+
txId
|
|
256
|
+
);
|
|
257
|
+
await this.publishCloudRequest(message);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
case keyMirrors.appVersionControl.uninstall: {
|
|
261
|
+
const { projectId } = payload;
|
|
262
|
+
await this.atomicApplicationUninstall(projectId);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
default:
|
|
266
|
+
logger.warn(
|
|
267
|
+
`Ignore App Version Control packet: ${JSON.stringify(
|
|
268
|
+
payload,
|
|
269
|
+
null,
|
|
270
|
+
2
|
|
271
|
+
)}`
|
|
272
|
+
);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
private async publishCloudRequest(message: ToCloudMessage) {
|
|
278
|
+
this.publisher.publishToCloud(message);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private async processAppContentUpdate(
|
|
282
|
+
projectId: string,
|
|
283
|
+
appContentUpdate: AppContentUpdate
|
|
284
|
+
): Promise<AppContent | null> {
|
|
285
|
+
const appContent: AppContent = { projectId };
|
|
286
|
+
// appCfg
|
|
287
|
+
if (!appContentUpdate.appCfg) {
|
|
288
|
+
logger.info(`No appCfgUpdate for ${projectId}`);
|
|
289
|
+
} else {
|
|
290
|
+
// Handle errors and validation
|
|
291
|
+
try {
|
|
292
|
+
const appCfgUpdate = JSON.parse(appContentUpdate.appCfg);
|
|
293
|
+
if (!validateAppConfig(appCfgUpdate)) {
|
|
294
|
+
logger.error(
|
|
295
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
296
|
+
validateAppConfig.errors,
|
|
297
|
+
null,
|
|
298
|
+
2
|
|
299
|
+
)}`
|
|
300
|
+
);
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
303
|
+
validateAppConfig.errors,
|
|
304
|
+
null,
|
|
305
|
+
2
|
|
306
|
+
)}`
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
appContent.appCfg = appCfgUpdate;
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
logger.error(
|
|
313
|
+
`Could not parse the appConfig for transaction!\n${stringifyError(e)}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// envVars
|
|
319
|
+
if (!appContentUpdate.envVars) {
|
|
320
|
+
logger.info(`No envVars for ${projectId}`);
|
|
321
|
+
} else {
|
|
322
|
+
try {
|
|
323
|
+
const envvarsUpdate = JSON.parse(appContentUpdate.envVars);
|
|
324
|
+
if (!validateEnvVarSchemaShadowUpdate(envvarsUpdate)) {
|
|
325
|
+
logger.error(
|
|
326
|
+
`Received invalid environment variables update for ${projectId}!\n${JSON.stringify(
|
|
327
|
+
validateEnvVarSchemaShadowUpdate.errors,
|
|
328
|
+
null,
|
|
329
|
+
2
|
|
330
|
+
)}`
|
|
331
|
+
);
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Received invalid environment variables update for${projectId}!\n${JSON.stringify(
|
|
334
|
+
validateEnvVarSchemaShadowUpdate.errors,
|
|
335
|
+
null,
|
|
336
|
+
2
|
|
337
|
+
)}`
|
|
338
|
+
);
|
|
339
|
+
} else {
|
|
340
|
+
appContent.envVars = envvarsUpdate;
|
|
341
|
+
}
|
|
342
|
+
} catch (e) {
|
|
343
|
+
// throw here
|
|
344
|
+
logger.error(
|
|
345
|
+
`Could not parse the environment variables for transaction!\n${stringifyError(
|
|
346
|
+
e
|
|
347
|
+
)}`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return appContent.appCfg || appContent.envVars ? appContent : null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
class LiveStateUpdatesMessageHandler
|
|
357
|
+
extends BaseHandler
|
|
358
|
+
implements MessageHandler<LiveStateUpdatesToggleMessage>
|
|
359
|
+
{
|
|
360
|
+
public async handle(message: LiveStateUpdatesToggleMessage): Promise<void> {
|
|
361
|
+
const { deviceStats, appState, appLogs } = message.payload;
|
|
362
|
+
const txId = message.txId;
|
|
363
|
+
|
|
364
|
+
if (deviceStats !== undefined) {
|
|
365
|
+
if (deviceStats) {
|
|
366
|
+
await this.liveUpdatesHandler.enable(
|
|
367
|
+
keyMirrors.toClientMessageType.device_stats,
|
|
368
|
+
async () =>
|
|
369
|
+
this.publisher.publishToClient(
|
|
370
|
+
buildDeviceStatsMessage(
|
|
371
|
+
this.clientId,
|
|
372
|
+
await getDeviceStatsPayload(),
|
|
373
|
+
txId
|
|
374
|
+
),
|
|
375
|
+
logger.silly
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
this.liveUpdatesHandler.disable(
|
|
380
|
+
keyMirrors.toClientMessageType.device_stats
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (appState !== undefined) {
|
|
386
|
+
if (appState) {
|
|
387
|
+
await this.liveUpdatesHandler.enable(
|
|
388
|
+
keyMirrors.toClientMessageType.app_state,
|
|
389
|
+
async () =>
|
|
390
|
+
this.publisher.publishToClient(
|
|
391
|
+
buildAppStateMessage(
|
|
392
|
+
this.clientId,
|
|
393
|
+
await getAppStatePayload(),
|
|
394
|
+
txId
|
|
395
|
+
),
|
|
396
|
+
logger.silly
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
} else {
|
|
400
|
+
this.liveUpdatesHandler.disable(
|
|
401
|
+
keyMirrors.toClientMessageType.app_state
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (appLogs !== undefined) {
|
|
407
|
+
if (appLogs.toggle) {
|
|
408
|
+
await this.liveUpdatesHandler.startStream(
|
|
409
|
+
appLogs.projectId,
|
|
410
|
+
async () =>
|
|
411
|
+
await getAppLogs({
|
|
412
|
+
projectId: appLogs.projectId,
|
|
413
|
+
args: ['--tail', '100', '--no-log-prefix']
|
|
414
|
+
}),
|
|
415
|
+
async (logChunk: string) =>
|
|
416
|
+
this.publisher.publishToClient(
|
|
417
|
+
buildAppLogsMessage(
|
|
418
|
+
this.clientId,
|
|
419
|
+
{
|
|
420
|
+
projectId: appLogs.projectId,
|
|
421
|
+
logChunk
|
|
422
|
+
},
|
|
423
|
+
txId
|
|
424
|
+
),
|
|
425
|
+
logger.silly
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
} else {
|
|
429
|
+
this.liveUpdatesHandler.stopStream(appLogs.projectId);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
class AppInstallResponseMessageHandler
|
|
436
|
+
extends BaseHandler
|
|
437
|
+
implements MessageHandler<AppInstallResponseMessage>
|
|
438
|
+
{
|
|
439
|
+
public async handle(message: AppInstallResponseMessage): Promise<void> {
|
|
440
|
+
const payload = message.payload;
|
|
441
|
+
const txId = message.txId;
|
|
442
|
+
const { projectId } = payload.appInstallResponse;
|
|
443
|
+
const start = !this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
444
|
+
|
|
445
|
+
if (!start && txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
448
|
+
projectId
|
|
449
|
+
)})!`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const appContent = this.txnMgr.getAppContentFromTxId(txId);
|
|
453
|
+
await this.txnMgr.runTransactionStep({
|
|
454
|
+
func: () =>
|
|
455
|
+
this.handleAppInstallCloudResponsePayload(payload, appContent),
|
|
456
|
+
projectId,
|
|
457
|
+
txId,
|
|
458
|
+
start,
|
|
459
|
+
stepName: message.messageType,
|
|
460
|
+
errorFn: this.errorFn,
|
|
461
|
+
successFn: this.successFn
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private handleAppInstallCloudResponsePayload = async (
|
|
466
|
+
payload: AppInstallResponsePayload,
|
|
467
|
+
appContent?: AppContent
|
|
468
|
+
): Promise<boolean> => {
|
|
469
|
+
const {
|
|
470
|
+
projectId,
|
|
471
|
+
appReleaseHash,
|
|
472
|
+
appInstallPayload,
|
|
473
|
+
modelsInstallPayload
|
|
474
|
+
} = payload.appInstallResponse;
|
|
475
|
+
const signedUrlsPayload = {
|
|
476
|
+
appInstallPayload,
|
|
477
|
+
modelsInstallPayload
|
|
478
|
+
};
|
|
479
|
+
await this.atomicApplicationUpdate(async () => {
|
|
480
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
481
|
+
await installApp({
|
|
482
|
+
projectId,
|
|
483
|
+
appReleaseHash,
|
|
484
|
+
signedUrlsPayload,
|
|
485
|
+
appCfg: appContent?.appCfg,
|
|
486
|
+
envVars: appContent?.envVars
|
|
487
|
+
});
|
|
488
|
+
}, projectId);
|
|
489
|
+
return true;
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
class ModelsInstallResponseMessageHandler
|
|
494
|
+
extends BaseHandler
|
|
495
|
+
implements MessageHandler<ModelsInstallResponseMessage>
|
|
496
|
+
{
|
|
497
|
+
public async handle(message: ModelsInstallResponseMessage): Promise<void> {
|
|
498
|
+
const payload = message.payload;
|
|
499
|
+
const txId = message.txId;
|
|
500
|
+
const { projectId } = payload.modelsInstallResponse;
|
|
501
|
+
if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
`Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
504
|
+
projectId
|
|
505
|
+
)})!`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
await this.txnMgr.runTransactionStep({
|
|
509
|
+
func: () => this.handleModelsInstallCloudResponsePayload(payload, txId),
|
|
510
|
+
projectId,
|
|
511
|
+
txId,
|
|
512
|
+
start: false,
|
|
513
|
+
stepName: message.messageType,
|
|
514
|
+
errorFn: this.errorFn,
|
|
515
|
+
successFn: this.successFn
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private handleModelsInstallCloudResponsePayload = async (
|
|
520
|
+
payload: ModelsInstallResponsePayload,
|
|
521
|
+
txId: string
|
|
522
|
+
): Promise<boolean> => {
|
|
523
|
+
const projectId = payload.modelsInstallResponse.projectId;
|
|
524
|
+
|
|
525
|
+
const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
|
|
526
|
+
if (update === undefined) {
|
|
527
|
+
throw new Error(
|
|
528
|
+
'Unknown error while updating models via application config! No config present for model update.'
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
const { appCfgUpdate, envVarUpdate } = update;
|
|
532
|
+
if (appCfgUpdate) {
|
|
533
|
+
await this.atomicApplicationUpdate(
|
|
534
|
+
async () =>
|
|
535
|
+
await updateModelsWithPresignedUrls({
|
|
536
|
+
projectId,
|
|
537
|
+
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
538
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
539
|
+
}),
|
|
540
|
+
projectId
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (envVarUpdate) {
|
|
545
|
+
await this.atomicApplicationUpdate(
|
|
546
|
+
async () =>
|
|
547
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
548
|
+
projectId,
|
|
549
|
+
envVars: envVarUpdate.envVars
|
|
550
|
+
}),
|
|
551
|
+
projectId,
|
|
552
|
+
true
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
return true;
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
class StatusResponseMessageHandler
|
|
560
|
+
extends BaseHandler
|
|
561
|
+
implements MessageHandler<ToDeviceStatusResponseMessage>
|
|
562
|
+
{
|
|
563
|
+
public async handle(message: ToDeviceStatusResponseMessage): Promise<void> {
|
|
564
|
+
const txId = message.txId;
|
|
565
|
+
const { failure } = keyMirrors.statusResponse;
|
|
566
|
+
if (message.payload.status === failure) {
|
|
567
|
+
this.txnMgr.completeTransaction(txId);
|
|
568
|
+
const msg = buildToClientStatusResponseMessage(
|
|
569
|
+
this.clientId,
|
|
570
|
+
{
|
|
571
|
+
status: keyMirrors.statusResponse.failure,
|
|
572
|
+
message: message.payload.message
|
|
573
|
+
},
|
|
574
|
+
txId
|
|
575
|
+
);
|
|
576
|
+
this.publisher.publishToClient(msg);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
class DeviceActionMessageHandler
|
|
582
|
+
extends BaseHandler
|
|
583
|
+
implements MessageHandler<DeviceActionMessage>
|
|
584
|
+
{
|
|
585
|
+
public async handle(message: DeviceActionMessage): Promise<void> {
|
|
586
|
+
const txId = message.txId;
|
|
587
|
+
try {
|
|
588
|
+
this.publisher.publishToClient(
|
|
589
|
+
buildToClientStatusResponseMessage(
|
|
590
|
+
this.clientId,
|
|
591
|
+
{
|
|
592
|
+
status: keyMirrors.statusResponse.in_progress
|
|
593
|
+
},
|
|
594
|
+
txId
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
await this.handleDeviceAction(message.payload);
|
|
599
|
+
|
|
600
|
+
this.publisher.publishToClient(
|
|
601
|
+
buildToClientStatusResponseMessage(
|
|
602
|
+
this.clientId,
|
|
603
|
+
{
|
|
604
|
+
status: keyMirrors.statusResponse.success
|
|
605
|
+
},
|
|
606
|
+
txId
|
|
607
|
+
)
|
|
608
|
+
);
|
|
609
|
+
} catch (e) {
|
|
610
|
+
logger.error(
|
|
611
|
+
`There was a problem performing device action '${
|
|
612
|
+
message.payload.action
|
|
613
|
+
}'!\n${stringifyError(e)}`
|
|
614
|
+
);
|
|
615
|
+
this.publisher.publishToClient(
|
|
616
|
+
buildToClientStatusResponseMessage(
|
|
617
|
+
this.clientId,
|
|
618
|
+
{
|
|
619
|
+
status: keyMirrors.statusResponse.failure,
|
|
620
|
+
message: e.message
|
|
621
|
+
},
|
|
622
|
+
txId
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private async handleDeviceAction(payload: DeviceActionPayload) {
|
|
629
|
+
const { system_restart } = keyMirrors.deviceAction;
|
|
630
|
+
switch (payload.action) {
|
|
631
|
+
case system_restart: {
|
|
632
|
+
await reboot();
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
default: {
|
|
636
|
+
logger.info(
|
|
637
|
+
`Unrecognized device action requested: '${payload.action}'.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
@@ -35,25 +35,9 @@ export class LiveUpdatesHandler {
|
|
|
35
35
|
|
|
36
36
|
const key = this.generateIntervalKey(intervalType, transactionId);
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
this.safeSetInterval(key, publishingFn, options);
|
|
39
39
|
|
|
40
|
-
// initial publish
|
|
41
40
|
await publishingFn();
|
|
42
|
-
|
|
43
|
-
this.livingIntervals[key] = setInterval(
|
|
44
|
-
async () => {
|
|
45
|
-
try {
|
|
46
|
-
await publishingFn();
|
|
47
|
-
} catch (e) {
|
|
48
|
-
logger.error(
|
|
49
|
-
`Error getting live updates: ${JSON.stringify(
|
|
50
|
-
e
|
|
51
|
-
)}: intervalKey: ${key}. Continuing...`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
options?.ms ? options.ms : DEFAULT_INTERVALS_MS
|
|
56
|
-
);
|
|
57
41
|
}
|
|
58
42
|
|
|
59
43
|
public disable(
|
|
@@ -133,6 +117,30 @@ export class LiveUpdatesHandler {
|
|
|
133
117
|
return null;
|
|
134
118
|
}
|
|
135
119
|
|
|
120
|
+
// do not await any functions in the setSafeInterval other than inside setInterval() as per EI-1694.
|
|
121
|
+
private safeSetInterval(
|
|
122
|
+
key: string,
|
|
123
|
+
publishingFn: () => Promise<void>,
|
|
124
|
+
options?: IntervalOptions
|
|
125
|
+
) {
|
|
126
|
+
clearInterval(this.livingIntervals[key]);
|
|
127
|
+
|
|
128
|
+
this.livingIntervals[key] = setInterval(
|
|
129
|
+
async () => {
|
|
130
|
+
try {
|
|
131
|
+
await publishingFn();
|
|
132
|
+
} catch (e) {
|
|
133
|
+
logger.error(
|
|
134
|
+
`Error getting live updates: ${JSON.stringify(
|
|
135
|
+
e
|
|
136
|
+
)}: intervalKey: ${key}. Continuing...`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
options?.ms ? options.ms : DEFAULT_INTERVALS_MS
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
private restartKillAllTimeout() {
|
|
137
145
|
clearTimeout(this.killAllTimeout);
|
|
138
146
|
this.killAllTimeout = setTimeout(() => {
|
|
@@ -147,7 +155,7 @@ export class LiveUpdatesHandler {
|
|
|
147
155
|
private generateIntervalKey(
|
|
148
156
|
intervalType: ToClientMessageTypeValue,
|
|
149
157
|
transactionId?: string
|
|
150
|
-
) {
|
|
158
|
+
): string {
|
|
151
159
|
return intervalType + `${transactionId ? ':' + transactionId : ''}`;
|
|
152
160
|
}
|
|
153
161
|
}
|