@alwaysai/device-agent 0.0.14 → 0.0.16
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/install.d.ts.map +1 -1
- package/lib/application-control/install.js +1 -0
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +8 -13
- package/lib/application-control/status.js.map +1 -1
- package/lib/cloud-connection/bootstrap-provision.d.ts +1 -1
- package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -1
- package/lib/cloud-connection/bootstrap-provision.js +9 -9
- package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
- package/lib/cloud-connection/cmd-status.d.ts +3 -11
- package/lib/cloud-connection/cmd-status.d.ts.map +1 -1
- package/lib/cloud-connection/cmd-status.js +24 -11
- package/lib/cloud-connection/cmd-status.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +93 -43
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent.js +4 -3
- package/lib/cloud-connection/device-agent.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +10 -18
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +50 -50
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +3 -1
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +13 -1
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +1 -1
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +11 -4
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +3 -3
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +1 -1
- package/lib/environment.js.map +1 -1
- package/lib/index.js +5 -3
- package/lib/index.js.map +1 -1
- package/lib/subcommands/app/app.d.ts +2 -2
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +28 -10
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/app/index.js +2 -2
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +2 -1
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/util/directories.d.ts +11 -12
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +24 -29
- package/lib/util/directories.js.map +1 -1
- package/lib/util/logger.js +1 -0
- package/lib/util/logger.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +3 -3
- package/src/application-control/install.ts +3 -1
- package/src/application-control/status.ts +10 -16
- package/src/cloud-connection/bootstrap-provision.ts +13 -10
- package/src/cloud-connection/cmd-status.ts +30 -11
- package/src/cloud-connection/device-agent-cloud-connection.ts +128 -66
- package/src/cloud-connection/device-agent.ts +7 -4
- package/src/cloud-connection/live-updates-handler.ts +79 -86
- package/src/cloud-connection/messages.ts +22 -1
- package/src/cloud-connection/passthrough-handler.ts +1 -1
- package/src/cloud-connection/shadow-handler.test.ts +3 -3
- package/src/cloud-connection/shadow-handler.ts +15 -11
- package/src/environment.ts +1 -1
- package/src/index.ts +6 -3
- package/src/subcommands/app/app.ts +27 -10
- package/src/subcommands/app/index.ts +4 -4
- package/src/subcommands/device/device.ts +5 -2
- package/src/util/directories.ts +31 -29
- package/src/util/logger.ts +2 -0
- package/lib/cloud-connection/app-install-status.d.ts +0 -16
- package/lib/cloud-connection/app-install-status.d.ts.map +0 -1
- package/lib/cloud-connection/app-install-status.js +0 -53
- package/lib/cloud-connection/app-install-status.js.map +0 -1
- package/lib/util/timer.d.ts +0 -2
- package/lib/util/timer.d.ts.map +0 -1
- package/lib/util/timer.js +0 -6
- package/lib/util/timer.js.map +0 -1
- package/src/cloud-connection/app-install-status.ts +0 -62
- package/src/util/timer.ts +0 -1
|
@@ -3,11 +3,11 @@ const awsIot = require('aws-iot-device-sdk');
|
|
|
3
3
|
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
getCertificateFilePath,
|
|
8
|
-
BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
6
|
+
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
9
7
|
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
10
|
-
BOOTSTRAP_CERTIFICATES_DIR_PATH
|
|
8
|
+
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
9
|
+
DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
10
|
+
DEVICE_CERTIFICATE_FILE_PATH
|
|
11
11
|
} from '../util/directories';
|
|
12
12
|
import {
|
|
13
13
|
keyMirrors,
|
|
@@ -35,18 +35,17 @@ import { AppConfigUpdate, ShadowHandler, ShadowTopics } from './shadow-handler';
|
|
|
35
35
|
import { Publisher } from './publisher';
|
|
36
36
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
37
37
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
38
|
-
import { AppInstallStatusManager } from './app-install-status';
|
|
39
38
|
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
40
|
-
import { CmdStatusManager
|
|
39
|
+
import { CmdStatusManager } from './cmd-status';
|
|
41
40
|
import { PassthroughHandler, runChannel } from './passthrough-handler';
|
|
42
41
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
42
|
+
import { getAppInstallStatusMessage } from './messages';
|
|
43
43
|
|
|
44
44
|
export class DeviceAgentCloudConnection {
|
|
45
45
|
private shadowHandler: ShadowHandler;
|
|
46
46
|
public publisher: Publisher;
|
|
47
47
|
private cmdStatusMgr: CmdStatusManager;
|
|
48
48
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
49
|
-
private appInstallStatusMgr: AppInstallStatusManager;
|
|
50
49
|
private device = awsIot.device;
|
|
51
50
|
|
|
52
51
|
private clientId = getDeviceUuid();
|
|
@@ -55,33 +54,43 @@ export class DeviceAgentCloudConnection {
|
|
|
55
54
|
// FIXME: Add support for multiple simultaneous project updates
|
|
56
55
|
private newAppCfgQueue: AppConfig[] = [];
|
|
57
56
|
|
|
58
|
-
private handleAppStateControl(payload: AppStateControlPacket) {
|
|
57
|
+
private async handleAppStateControl(payload: AppStateControlPacket) {
|
|
59
58
|
const { baseCommand, projectId } = payload;
|
|
60
59
|
switch (baseCommand) {
|
|
61
60
|
case keyMirrors.appStateControl.start:
|
|
62
|
-
startApp({ projectId });
|
|
61
|
+
await startApp({ projectId });
|
|
63
62
|
break;
|
|
64
63
|
case keyMirrors.appStateControl.stop:
|
|
65
|
-
stopApp({ projectId });
|
|
64
|
+
await stopApp({ projectId });
|
|
66
65
|
break;
|
|
67
66
|
case keyMirrors.appStateControl.restart:
|
|
68
|
-
restartApp({ projectId });
|
|
67
|
+
await restartApp({ projectId });
|
|
69
68
|
break;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
private handleAppVersionControl(payload: AppVersionControlPacket) {
|
|
72
|
+
private async handleAppVersionControl(payload: AppVersionControlPacket) {
|
|
74
73
|
switch (payload.baseCommand) {
|
|
75
74
|
case keyMirrors.appVersionControl.install: {
|
|
76
75
|
const { projectId, appReleaseHash } = payload;
|
|
77
|
-
this.cmdStatusMgr.
|
|
76
|
+
await this.cmdStatusMgr.start(projectId);
|
|
77
|
+
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
78
|
+
projectId,
|
|
79
|
+
appReleaseHash
|
|
80
|
+
});
|
|
81
|
+
|
|
78
82
|
const signedUrlsRequest = { projectId, appReleaseHash };
|
|
79
|
-
this.publishCloudRequest({
|
|
83
|
+
await this.publishCloudRequest({
|
|
80
84
|
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
81
85
|
signedUrlsRequest
|
|
82
86
|
});
|
|
83
87
|
break;
|
|
84
88
|
}
|
|
89
|
+
case keyMirrors.appVersionControl.uninstall: {
|
|
90
|
+
const { projectId } = payload;
|
|
91
|
+
await this.atomicApplicationUninstall(projectId);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
85
94
|
default:
|
|
86
95
|
logger.warn(
|
|
87
96
|
`Ignore App Version Control packet: ${JSON.stringify(
|
|
@@ -97,10 +106,10 @@ export class DeviceAgentCloudConnection {
|
|
|
97
106
|
// TODO
|
|
98
107
|
};
|
|
99
108
|
|
|
100
|
-
private handleAgentCommand(message: LiveUpdatesToggleMessage) {
|
|
109
|
+
private async handleAgentCommand(message: LiveUpdatesToggleMessage) {
|
|
101
110
|
switch (message.messageType) {
|
|
102
111
|
case keyMirrors.clientMessageType.live_state_updates:
|
|
103
|
-
this.liveUpdatesHandler.
|
|
112
|
+
await this.liveUpdatesHandler.handleToggles(message.liveUpdatesToggles);
|
|
104
113
|
break;
|
|
105
114
|
default:
|
|
106
115
|
logger.error(
|
|
@@ -118,6 +127,49 @@ export class DeviceAgentCloudConnection {
|
|
|
118
127
|
this.device.subscribe(topic);
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
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
|
+
try {
|
|
140
|
+
await uninstallApp({ projectId });
|
|
141
|
+
this.shadowHandler.deleteProjectShadow(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
|
+
} catch (e) {
|
|
156
|
+
logger.error(`Failed to uninstall ${projectId}: ${e}`);
|
|
157
|
+
const message: string = e.message;
|
|
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
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
121
173
|
// eslint-disable-next-line
|
|
122
174
|
private async atomicApplicationUpdate<T extends any[], R extends any>(
|
|
123
175
|
func: (...args: T) => R,
|
|
@@ -125,55 +177,67 @@ export class DeviceAgentCloudConnection {
|
|
|
125
177
|
projectId: string,
|
|
126
178
|
appReleaseHash: string
|
|
127
179
|
) {
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.liveUpdatesHandler.update({
|
|
134
|
-
appInstallStatus: { toggle: true, appReleaseHash }
|
|
180
|
+
await this.cmdStatusMgr.start(projectId);
|
|
181
|
+
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
182
|
+
projectId,
|
|
183
|
+
appReleaseHash
|
|
135
184
|
});
|
|
136
185
|
|
|
137
186
|
// Install the app and models
|
|
138
187
|
try {
|
|
139
188
|
const out: R = await func(...args);
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
this.liveUpdatesHandler.update({
|
|
145
|
-
appInstallStatus: { toggle: false, appReleaseHash }
|
|
189
|
+
await this.cmdStatusMgr.stop(projectId);
|
|
190
|
+
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
191
|
+
projectId
|
|
146
192
|
});
|
|
147
|
-
|
|
193
|
+
// Send final status message
|
|
194
|
+
this.publisher.publishToClient(
|
|
195
|
+
await getAppInstallStatusMessage(
|
|
196
|
+
keyMirrors.appInstallStatus.success,
|
|
197
|
+
'',
|
|
198
|
+
appReleaseHash
|
|
199
|
+
)
|
|
200
|
+
);
|
|
148
201
|
|
|
149
202
|
// update app config shadow for project
|
|
150
203
|
await this.shadowHandler.publishAppState(projectId);
|
|
151
204
|
return out;
|
|
152
205
|
} catch (e) {
|
|
153
|
-
logger.error(e);
|
|
206
|
+
logger.error(`Failed to install ${projectId}: ${e}`);
|
|
154
207
|
const message: string = e.message;
|
|
155
208
|
|
|
156
209
|
// uninstall the failed app to put system back in good state
|
|
210
|
+
// TODO: Replace this with rollback
|
|
157
211
|
await uninstallApp({ projectId });
|
|
158
|
-
this.appInstallStatusMgr.update(
|
|
159
|
-
appReleaseHash,
|
|
160
|
-
keyMirrors.appInstallStatus.failure,
|
|
161
|
-
message
|
|
162
|
-
);
|
|
163
|
-
this.liveUpdatesHandler.update({
|
|
164
|
-
appInstallStatus: { toggle: false, appReleaseHash }
|
|
165
|
-
});
|
|
166
|
-
this.cmdStatusMgr.update(projectId, 'idle');
|
|
167
|
-
|
|
168
|
-
// delete shadow for project
|
|
169
212
|
this.shadowHandler.deleteProjectShadow(projectId);
|
|
213
|
+
|
|
214
|
+
await this.cmdStatusMgr.stop(projectId);
|
|
215
|
+
await this.liveUpdatesHandler.disableAppInstallStatus({
|
|
216
|
+
projectId
|
|
217
|
+
});
|
|
218
|
+
// Send final status message
|
|
219
|
+
this.publisher.publishToClient(
|
|
220
|
+
await getAppInstallStatusMessage(
|
|
221
|
+
keyMirrors.appInstallStatus.failure,
|
|
222
|
+
message,
|
|
223
|
+
appReleaseHash
|
|
224
|
+
)
|
|
225
|
+
);
|
|
170
226
|
}
|
|
171
227
|
}
|
|
172
228
|
|
|
173
|
-
private async handleAppConfigUpdates(
|
|
174
|
-
for (const appConfigUpdate of
|
|
229
|
+
private async handleAppConfigUpdates(appCfgUpdates: AppConfigUpdate[]) {
|
|
230
|
+
for (const appConfigUpdate of appCfgUpdates) {
|
|
175
231
|
const { projectId, newAppCfg, updatedModels } = appConfigUpdate;
|
|
176
|
-
|
|
232
|
+
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
233
|
+
projectId
|
|
234
|
+
});
|
|
235
|
+
await this.cmdStatusMgr.start(projectId);
|
|
236
|
+
await this.liveUpdatesHandler.enableAppInstallStatus({
|
|
237
|
+
projectId,
|
|
238
|
+
appReleaseHash
|
|
239
|
+
});
|
|
240
|
+
|
|
177
241
|
if (updatedModels && Object.keys(updatedModels).length) {
|
|
178
242
|
// Publish request for model urls
|
|
179
243
|
this.newAppCfgQueue.push(newAppCfg);
|
|
@@ -213,12 +277,14 @@ export class DeviceAgentCloudConnection {
|
|
|
213
277
|
}
|
|
214
278
|
}
|
|
215
279
|
|
|
216
|
-
|
|
280
|
+
/*=================================================================
|
|
281
|
+
Public interface
|
|
282
|
+
=================================================================*/
|
|
217
283
|
|
|
218
284
|
constructor() {
|
|
219
285
|
this.device = awsIot.device({
|
|
220
|
-
keyPath:
|
|
221
|
-
certPath:
|
|
286
|
+
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
287
|
+
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
222
288
|
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
223
289
|
clientId: this.clientId,
|
|
224
290
|
host: this.host,
|
|
@@ -228,11 +294,7 @@ export class DeviceAgentCloudConnection {
|
|
|
228
294
|
this.publisher = new Publisher(this.device, this.clientId);
|
|
229
295
|
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
230
296
|
this.cmdStatusMgr = new CmdStatusManager();
|
|
231
|
-
this.
|
|
232
|
-
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
233
|
-
this.publisher,
|
|
234
|
-
this.appInstallStatusMgr
|
|
235
|
-
);
|
|
297
|
+
this.liveUpdatesHandler = new LiveUpdatesHandler(this.publisher);
|
|
236
298
|
|
|
237
299
|
this.subscribe(this.toDeviceTopic);
|
|
238
300
|
this.subscribe(this.shadowHandler.shadowTopics.projects.getAccepted);
|
|
@@ -251,8 +313,8 @@ export class DeviceAgentCloudConnection {
|
|
|
251
313
|
return this.shadowHandler.shadowTopics;
|
|
252
314
|
}
|
|
253
315
|
|
|
254
|
-
public
|
|
255
|
-
return this.cmdStatusMgr.
|
|
316
|
+
public isCmdInProgress(projectId: string): boolean {
|
|
317
|
+
return this.cmdStatusMgr.isCmdInProgress(projectId);
|
|
256
318
|
}
|
|
257
319
|
|
|
258
320
|
public async handleClientMessage({
|
|
@@ -276,15 +338,15 @@ export class DeviceAgentCloudConnection {
|
|
|
276
338
|
const payload = message.payload;
|
|
277
339
|
switch (payload.messageType) {
|
|
278
340
|
case keyMirrors.clientMessageType.app_state_control: {
|
|
279
|
-
this.handleAppStateControl(payload.appStateControl);
|
|
341
|
+
await this.handleAppStateControl(payload.appStateControl);
|
|
280
342
|
break;
|
|
281
343
|
}
|
|
282
344
|
case keyMirrors.clientMessageType.app_version_control: {
|
|
283
|
-
this.handleAppVersionControl(payload.appVersionControl);
|
|
345
|
+
await this.handleAppVersionControl(payload.appVersionControl);
|
|
284
346
|
break;
|
|
285
347
|
}
|
|
286
348
|
case keyMirrors.clientMessageType.live_state_updates: {
|
|
287
|
-
this.handleAgentCommand(payload);
|
|
349
|
+
await this.handleAgentCommand(payload);
|
|
288
350
|
break;
|
|
289
351
|
}
|
|
290
352
|
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
@@ -349,6 +411,9 @@ export class DeviceAgentCloudConnection {
|
|
|
349
411
|
}
|
|
350
412
|
|
|
351
413
|
public async handleMessage(topic: string, message: ClientMessage | any) {
|
|
414
|
+
logger.debug(
|
|
415
|
+
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
416
|
+
);
|
|
352
417
|
switch (topic) {
|
|
353
418
|
case this.shadowHandler.shadowTopics.projects.getAccepted:
|
|
354
419
|
case this.shadowHandler.shadowTopics.projects.updateDelta: {
|
|
@@ -360,7 +425,7 @@ export class DeviceAgentCloudConnection {
|
|
|
360
425
|
break;
|
|
361
426
|
}
|
|
362
427
|
case this.toDeviceTopic:
|
|
363
|
-
this.handleClientMessage({
|
|
428
|
+
await this.handleClientMessage({
|
|
364
429
|
topic,
|
|
365
430
|
message
|
|
366
431
|
});
|
|
@@ -395,9 +460,6 @@ export class DeviceAgentCloudConnection {
|
|
|
395
460
|
this.device.on('message', async (topic: string, payload: string) => {
|
|
396
461
|
try {
|
|
397
462
|
const jsonPacket = JSON.parse(payload);
|
|
398
|
-
logger.debug(
|
|
399
|
-
`Received message: ${JSON.stringify({ topic, jsonPacket }, null, 2)}`
|
|
400
|
-
);
|
|
401
463
|
await this.handleMessage(topic, jsonPacket);
|
|
402
464
|
} catch (error) {
|
|
403
465
|
logger.error(`Error parsing message: ${error}`);
|
|
@@ -416,17 +478,17 @@ export class DeviceAgentCloudConnection {
|
|
|
416
478
|
|
|
417
479
|
export async function runDeviceAgentCloudInterface() {
|
|
418
480
|
// FIXME: Check for KeyPath as well
|
|
419
|
-
if (existsSync(
|
|
481
|
+
if (existsSync(DEVICE_CERTIFICATE_FILE_PATH)) {
|
|
420
482
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
421
483
|
await deviceAgent.setupHandlers();
|
|
422
484
|
if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
|
|
423
485
|
const publisher = deviceAgent.publisher;
|
|
424
486
|
const passthroughHandler = new PassthroughHandler(publisher);
|
|
425
487
|
await passthroughHandler.setup();
|
|
426
|
-
runChannel(passthroughHandler);
|
|
488
|
+
await runChannel(passthroughHandler);
|
|
427
489
|
}
|
|
428
|
-
} else if (existsSync(
|
|
429
|
-
bootstrapProvision();
|
|
490
|
+
} else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
|
|
491
|
+
await bootstrapProvision();
|
|
430
492
|
} else if (existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH())) {
|
|
431
493
|
throw new Error(
|
|
432
494
|
"Device has not been created using 'aai-agent device init' or there has been an issue with device initialization"
|
|
@@ -3,7 +3,7 @@ const awsIot = require('aws-iot-device-sdk');
|
|
|
3
3
|
import {
|
|
4
4
|
DEVICE_PRIVATE_KEY_FILE_NAME,
|
|
5
5
|
CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
|
|
6
|
-
|
|
6
|
+
DEVICE_ID_FILE_NAME,
|
|
7
7
|
DEVICE_CERTIFICATE_FILE_NAME
|
|
8
8
|
} from '../util/directories';
|
|
9
9
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
@@ -66,6 +66,9 @@ export class BootstrapAgent extends DeviceAgent {
|
|
|
66
66
|
public async handleAwsCertificateTopics(topic: string, payload: any) {
|
|
67
67
|
switch (topic) {
|
|
68
68
|
case '$aws/certificates/create/json/accepted': {
|
|
69
|
+
logger.debug(
|
|
70
|
+
`Received Certs: ${JSON.stringify(JSON.parse(payload), null, 2)}`
|
|
71
|
+
);
|
|
69
72
|
const {
|
|
70
73
|
certificateId,
|
|
71
74
|
certificatePem,
|
|
@@ -76,13 +79,13 @@ export class BootstrapAgent extends DeviceAgent {
|
|
|
76
79
|
const certSpawner = JsSpawner({ path: LOCAL_CERT_AND_KEY_DIR });
|
|
77
80
|
|
|
78
81
|
await certSpawner.writeFile(
|
|
79
|
-
DEVICE_CERTIFICATE_FILE_NAME
|
|
82
|
+
DEVICE_CERTIFICATE_FILE_NAME,
|
|
80
83
|
certificatePem
|
|
81
84
|
);
|
|
82
85
|
|
|
83
|
-
await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME
|
|
86
|
+
await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME, privateKey);
|
|
84
87
|
|
|
85
|
-
await certSpawner.writeFile(
|
|
88
|
+
await certSpawner.writeFile(DEVICE_ID_FILE_NAME, certificateId);
|
|
86
89
|
|
|
87
90
|
await certSpawner.writeFile(
|
|
88
91
|
CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
|
|
@@ -8,13 +8,15 @@ import { getAppLogs } from '../application-control';
|
|
|
8
8
|
import { logger } from '../util/logger';
|
|
9
9
|
import sleep from '../util/sleep';
|
|
10
10
|
import { Publisher } from './publisher';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getAppInstallStatusMessage,
|
|
13
|
+
getAppStateMessage,
|
|
14
|
+
getDeviceStatsMessage
|
|
15
|
+
} from './messages';
|
|
12
16
|
import { AgentMessageTypeValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
13
|
-
import { AppInstallStatusManager } from './app-install-status';
|
|
14
17
|
|
|
15
18
|
export class LiveUpdatesHandler {
|
|
16
19
|
private publisher: Publisher;
|
|
17
|
-
private appInstallStatusMgr: AppInstallStatusManager;
|
|
18
20
|
|
|
19
21
|
private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
|
|
20
22
|
private liveUpdatesAlive = {
|
|
@@ -32,14 +34,6 @@ export class LiveUpdatesHandler {
|
|
|
32
34
|
private appLogStreams = new Set<string>();
|
|
33
35
|
private appInstallStatuses = new Set<string>();
|
|
34
36
|
|
|
35
|
-
constructor(
|
|
36
|
-
publisher: Publisher,
|
|
37
|
-
appInstallStatusMgr: AppInstallStatusManager
|
|
38
|
-
) {
|
|
39
|
-
this.publisher = publisher;
|
|
40
|
-
this.appInstallStatusMgr = appInstallStatusMgr;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
37
|
private async startAppLogStream(projectId: string) {
|
|
44
38
|
logger.info(`Starting log stream for ${projectId}`);
|
|
45
39
|
this.appLogStreams.add(projectId);
|
|
@@ -77,50 +71,19 @@ export class LiveUpdatesHandler {
|
|
|
77
71
|
});
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const appInstallStatusMessage = {
|
|
85
|
-
messageType: keyMirrors.agentMessageType.app_install_status,
|
|
86
|
-
appInstallStatus
|
|
87
|
-
};
|
|
88
|
-
return appInstallStatusMessage;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
public async startPublishingLiveUpdates<T extends any[]>(
|
|
92
|
-
messageType: AgentMessageTypeValue,
|
|
93
|
-
getMessageData: (...args: T) => Promise<DeviceAgentMessagePayload>,
|
|
94
|
-
args: T
|
|
95
|
-
) {
|
|
96
|
-
logger.info(`Turned on live updates for ${messageType}`);
|
|
97
|
-
// eslint-disable-next-line no-constant-condition
|
|
98
|
-
while (true) {
|
|
99
|
-
try {
|
|
100
|
-
const message = await getMessageData(...args);
|
|
101
|
-
this.publisher.publishToClient(message);
|
|
102
|
-
} catch (e) {
|
|
103
|
-
logger.error(
|
|
104
|
-
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
105
|
-
);
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
if (!this.continuePublishing(messageType, ...args)) {
|
|
109
|
-
logger.info(`Turned off live updates for ${messageType}`);
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private continuePublishing(flag: AgentMessageTypeValue, data?: any): boolean {
|
|
74
|
+
private continuePublishing(
|
|
75
|
+
flag: AgentMessageTypeValue,
|
|
76
|
+
projectId?: string
|
|
77
|
+
): boolean {
|
|
117
78
|
switch (flag) {
|
|
118
79
|
case keyMirrors.agentMessageType.device_stats:
|
|
119
80
|
case keyMirrors.agentMessageType.app_state:
|
|
120
81
|
return this.liveUpdatesAlive[flag];
|
|
121
82
|
case keyMirrors.agentMessageType.app_install_status: {
|
|
122
|
-
|
|
123
|
-
|
|
83
|
+
if (!projectId) {
|
|
84
|
+
throw new Error(`Project ID not provided to continuePublishing!`);
|
|
85
|
+
}
|
|
86
|
+
return this.appInstallStatuses.has(projectId);
|
|
124
87
|
}
|
|
125
88
|
default:
|
|
126
89
|
logger.error(`Unrecognized publishable flag ${flag}`);
|
|
@@ -158,28 +121,73 @@ export class LiveUpdatesHandler {
|
|
|
158
121
|
}, 600000); // 10 min
|
|
159
122
|
}
|
|
160
123
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
124
|
+
private async startPublishingLiveUpdates<T extends any[]>(
|
|
125
|
+
messageType: AgentMessageTypeValue,
|
|
126
|
+
getMessageData: (...args: T) => Promise<DeviceAgentMessagePayload>,
|
|
127
|
+
args: T,
|
|
128
|
+
projectId?: string
|
|
129
|
+
) {
|
|
130
|
+
logger.info(`Turned on live updates for ${messageType}`);
|
|
131
|
+
// eslint-disable-next-line no-constant-condition
|
|
132
|
+
while (true) {
|
|
133
|
+
try {
|
|
134
|
+
const message = await getMessageData(...args);
|
|
135
|
+
this.publisher.publishToClient(message);
|
|
136
|
+
} catch (e) {
|
|
137
|
+
logger.error(
|
|
138
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
139
|
+
);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (!this.continuePublishing(messageType, projectId)) {
|
|
143
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*=================================================================
|
|
151
|
+
Public interface
|
|
152
|
+
=================================================================*/
|
|
153
|
+
|
|
154
|
+
constructor(publisher: Publisher) {
|
|
155
|
+
this.publisher = publisher;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public async enableAppInstallStatus(props: {
|
|
159
|
+
projectId: string;
|
|
160
|
+
appReleaseHash: string;
|
|
177
161
|
}) {
|
|
162
|
+
const { projectId, appReleaseHash } = props;
|
|
163
|
+
this.liveUpdatesAlive.app_install_status = true;
|
|
164
|
+
this.appInstallStatuses.add(projectId);
|
|
165
|
+
// Don't wait for this call to finish since it loops until disabled
|
|
166
|
+
void this.startPublishingLiveUpdates(
|
|
167
|
+
keyMirrors.agentMessageType.app_install_status,
|
|
168
|
+
getAppInstallStatusMessage,
|
|
169
|
+
[keyMirrors.appInstallStatus.in_progress, '', appReleaseHash],
|
|
170
|
+
projectId
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public async disableAppInstallStatus(props: { projectId: string }) {
|
|
175
|
+
const { projectId } = props;
|
|
176
|
+
this.appInstallStatuses.delete(projectId);
|
|
177
|
+
|
|
178
|
+
if (this.appInstallStatuses.size === 0) {
|
|
179
|
+
this.liveUpdatesAlive.app_install_status = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public async handleToggles(toggles: LiveUpdatesToggles) {
|
|
184
|
+
const { deviceStats, appState, appLogs } = toggles;
|
|
178
185
|
this.restartLiveUpdatesTimeout();
|
|
179
186
|
if (deviceStats !== undefined) {
|
|
180
187
|
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
181
188
|
if (deviceStats) {
|
|
182
|
-
this
|
|
189
|
+
// Don't wait for this call to finish since it loops until disabled
|
|
190
|
+
void this.startPublishingLiveUpdates(
|
|
183
191
|
keyMirrors.agentMessageType.device_stats,
|
|
184
192
|
getDeviceStatsMessage,
|
|
185
193
|
[]
|
|
@@ -190,7 +198,8 @@ export class LiveUpdatesHandler {
|
|
|
190
198
|
if (appState !== undefined) {
|
|
191
199
|
this.liveUpdatesAlive.app_state = appState;
|
|
192
200
|
if (appState) {
|
|
193
|
-
this
|
|
201
|
+
// Don't wait for this call to finish since it loops until disabled
|
|
202
|
+
void this.startPublishingLiveUpdates(
|
|
194
203
|
keyMirrors.agentMessageType.app_state,
|
|
195
204
|
getAppStateMessage,
|
|
196
205
|
[]
|
|
@@ -200,27 +209,11 @@ export class LiveUpdatesHandler {
|
|
|
200
209
|
|
|
201
210
|
if (appLogs !== undefined) {
|
|
202
211
|
if (appLogs.toggle) {
|
|
203
|
-
this
|
|
212
|
+
// Don't wait for this call to finish since it loops until disabled
|
|
213
|
+
void this.startAppLogStream(appLogs.projectId);
|
|
204
214
|
} else {
|
|
205
215
|
this.appLogStreams.delete(appLogs.projectId);
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
|
-
|
|
209
|
-
if (appInstallStatus !== undefined) {
|
|
210
|
-
if (appInstallStatus.toggle) {
|
|
211
|
-
this.liveUpdatesAlive.app_install_status = appInstallStatus.toggle;
|
|
212
|
-
this.appInstallStatuses.add(appInstallStatus.appReleaseHash);
|
|
213
|
-
this.startPublishingLiveUpdates(
|
|
214
|
-
keyMirrors.agentMessageType.app_install_status,
|
|
215
|
-
this.getAppInstallStatusMessage,
|
|
216
|
-
[appInstallStatus.appReleaseHash]
|
|
217
|
-
);
|
|
218
|
-
} else {
|
|
219
|
-
this.appInstallStatuses.delete(appInstallStatus.appReleaseHash);
|
|
220
|
-
}
|
|
221
|
-
if (this.appInstallStatuses.size === 0) {
|
|
222
|
-
this.liveUpdatesAlive.app_install_status = false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
218
|
}
|
|
226
219
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AppInstallStatusMessage,
|
|
3
|
+
AppStatePacket,
|
|
4
|
+
keyMirrors
|
|
5
|
+
} from '@alwaysai/device-agent-schemas';
|
|
6
|
+
import { AppInstallStatusValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
2
7
|
import { getAppStatus } from '../application-control';
|
|
3
8
|
import {
|
|
4
9
|
getCpuUtil,
|
|
@@ -22,6 +27,22 @@ export async function getAppStateMessage() {
|
|
|
22
27
|
return appStatePackage;
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
export async function getAppInstallStatusMessage(
|
|
31
|
+
status: AppInstallStatusValue,
|
|
32
|
+
message: string,
|
|
33
|
+
appReleaseHash: string
|
|
34
|
+
): Promise<AppInstallStatusMessage> {
|
|
35
|
+
const appInstallStatusMessage: AppInstallStatusMessage = {
|
|
36
|
+
messageType: keyMirrors.agentMessageType.app_install_status,
|
|
37
|
+
appInstallStatus: {
|
|
38
|
+
status,
|
|
39
|
+
message,
|
|
40
|
+
appReleaseHash
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return appInstallStatusMessage;
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
export async function getDeviceStatsMessage() {
|
|
26
47
|
const cpuUsage = await getCpuUtil();
|
|
27
48
|
const diskUtil = await getDiskUtil();
|
|
@@ -29,7 +29,7 @@ export class PassthroughHandler {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
function processPublish(passthroughHandler: PassthroughHandler) {
|
|
33
33
|
while (messageQueue.length > 0) {
|
|
34
34
|
const entry = messageQueue.shift();
|
|
35
35
|
const { packet, msg } = entry;
|
|
@@ -21,8 +21,8 @@ describe('Test Shadow Handler', () => {
|
|
|
21
21
|
|
|
22
22
|
test.skip('reject buffer payload', async () => {
|
|
23
23
|
//FIXME: Invalid input is silently ignored, need input validation
|
|
24
|
-
expect(() => {
|
|
25
|
-
shadowHandler.handleShadowTopic({
|
|
24
|
+
expect(async () => {
|
|
25
|
+
await shadowHandler.handleShadowTopic({
|
|
26
26
|
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
27
27
|
payload: Buffer.from('test-payload')
|
|
28
28
|
});
|
|
@@ -317,7 +317,7 @@ describe('Test Shadow Handler', () => {
|
|
|
317
317
|
};
|
|
318
318
|
jest.mocked(readAppCfgFile).mockResolvedValue(testAppCfg);
|
|
319
319
|
|
|
320
|
-
shadowHandler.publishAppState(projectId1);
|
|
320
|
+
await shadowHandler.publishAppState(projectId1);
|
|
321
321
|
expect(jest.mocked(readAppCfgFile)).toBeCalledWith({ projectId1 });
|
|
322
322
|
const packet = {
|
|
323
323
|
state: {
|