@alwaysai/device-agent 1.3.0 → 1.3.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/environment-variables.d.ts +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +22 -20
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +37 -2
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.js +1 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +4 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +149 -113
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +30 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +15 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +1 -3
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +1 -9
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +3 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +12 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +36 -22
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +84 -40
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +26 -6
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +103 -22
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +179 -13
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +90 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts +10 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -0
- package/lib/subcommands/app/analytics.js +83 -0
- package/lib/subcommands/app/analytics.js.map +1 -0
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +3 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -5
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +11 -47
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.d.ts +1 -0
- package/lib/subcommands/app/status.d.ts.map +1 -1
- package/lib/subcommands/app/status.js +14 -3
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts +2 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +16 -3
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/lib/util/parsing.d.ts +2 -0
- package/lib/util/parsing.d.ts.map +1 -0
- package/lib/util/parsing.js +17 -0
- package/lib/util/parsing.js.map +1 -0
- package/package.json +4 -6
- package/readme.md +146 -92
- package/src/application-control/environment-variables.test.ts +43 -3
- package/src/application-control/environment-variables.ts +29 -19
- package/src/application-control/install.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +216 -172
- package/src/cloud-connection/live-updates-handler.test.ts +20 -0
- package/src/cloud-connection/live-updates-handler.ts +45 -52
- package/src/cloud-connection/messages.ts +1 -14
- package/src/cloud-connection/publisher.ts +4 -0
- package/src/cloud-connection/shadow-handler.test.ts +93 -41
- package/src/cloud-connection/shadow-handler.ts +57 -21
- package/src/cloud-connection/transaction-manager.test.ts +193 -18
- package/src/cloud-connection/transaction-manager.ts +174 -26
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -0
- package/src/secure-tunneling/spawner-detached.ts +107 -0
- package/src/subcommands/app/analytics.ts +99 -0
- package/src/subcommands/app/index.ts +4 -3
- package/src/subcommands/app/models.ts +13 -49
- package/src/subcommands/app/status.ts +20 -3
- package/src/subcommands/app/version.ts +19 -4
- package/src/util/cloud-mode-ready.ts +36 -0
- package/src/util/parsing.ts +11 -0
- package/lib/cloud-connection/cmd-status.d.ts +0 -8
- package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
- package/lib/cloud-connection/cmd-status.js +0 -62
- package/lib/cloud-connection/cmd-status.js.map +0 -1
- package/lib/cloud-connection/message-builder.d.ts +0 -7
- package/lib/cloud-connection/message-builder.d.ts.map +0 -1
- package/lib/cloud-connection/message-builder.js +0 -63
- package/lib/cloud-connection/message-builder.js.map +0 -1
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/src/cloud-connection/cmd-status.ts +0 -71
- package/src/cloud-connection/message-builder.ts +0 -117
- package/src/secure-tunneling/index.ts +0 -74
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AppLogsPayload,
|
|
3
|
-
ToClientMessagePayload,
|
|
4
3
|
keyMirrors,
|
|
5
4
|
LiveStateUpdatesTogglePayload,
|
|
6
|
-
ToClientMessage
|
|
5
|
+
ToClientMessage,
|
|
6
|
+
buildAppLogsMessage,
|
|
7
|
+
buildAppStateMessage,
|
|
8
|
+
buildDeviceStatsMessage,
|
|
9
|
+
StatusResponsePayload,
|
|
10
|
+
buildToClientStatusResponseMessage
|
|
7
11
|
} from '@alwaysai/device-agent-schemas';
|
|
8
12
|
import { getAppLogs } from '../application-control';
|
|
9
13
|
import { logger } from '../util/logger';
|
|
10
14
|
import sleep from '../util/sleep';
|
|
11
15
|
import { Publisher } from './publisher';
|
|
12
|
-
import {
|
|
13
|
-
getStatusResponsePayload,
|
|
14
|
-
getAppStatePayload,
|
|
15
|
-
getDeviceStatsPayload
|
|
16
|
-
} from './messages';
|
|
16
|
+
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
17
17
|
import { ToClientMessageTypeValue } from '@alwaysai/device-agent-schemas';
|
|
18
18
|
import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
|
|
19
|
-
import {
|
|
20
|
-
buildAppLogsMessage,
|
|
21
|
-
buildAppStateMessage,
|
|
22
|
-
buildDeviceStatsMessage,
|
|
23
|
-
buildStatusResponseMessage
|
|
24
|
-
} from './message-builder';
|
|
25
19
|
|
|
26
20
|
const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
|
|
27
21
|
? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
|
|
@@ -48,8 +42,9 @@ export class LiveUpdatesHandler {
|
|
|
48
42
|
private appLogStreams = new Set<string>();
|
|
49
43
|
private transactionStatuses = new Set<string>();
|
|
50
44
|
|
|
51
|
-
private async startAppLogStream(projectId: string) {
|
|
45
|
+
private async startAppLogStream(projectId: string, txId: string) {
|
|
52
46
|
logger.info(`Starting log stream for ${projectId}`);
|
|
47
|
+
|
|
53
48
|
this.appLogStreams.add(projectId);
|
|
54
49
|
const readable = await getAppLogs({
|
|
55
50
|
projectId,
|
|
@@ -68,7 +63,7 @@ export class LiveUpdatesHandler {
|
|
|
68
63
|
projectId,
|
|
69
64
|
logChunk: logStr
|
|
70
65
|
};
|
|
71
|
-
const message =
|
|
66
|
+
const message = buildAppLogsMessage(this.clientId, payload, txId);
|
|
72
67
|
this.publisher.publishToClient(message, logger.silly);
|
|
73
68
|
});
|
|
74
69
|
|
|
@@ -117,7 +112,7 @@ export class LiveUpdatesHandler {
|
|
|
117
112
|
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
118
113
|
}
|
|
119
114
|
if (toggles.appState !== undefined) {
|
|
120
|
-
this.liveUpdatesAlive.
|
|
115
|
+
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
|
|
@@ -132,36 +127,25 @@ export class LiveUpdatesHandler {
|
|
|
132
127
|
}, LIVE_UPDATES_TIMEOUT);
|
|
133
128
|
}
|
|
134
129
|
|
|
135
|
-
private async startPublishingLiveUpdates
|
|
130
|
+
private async startPublishingLiveUpdates(
|
|
136
131
|
messageType: ToClientMessageTypeValue,
|
|
137
|
-
|
|
138
|
-
messageBuilderFunction: (
|
|
139
|
-
payload: ToClientMessagePayload,
|
|
140
|
-
txId: string
|
|
141
|
-
) => Promise<ToClientMessage>,
|
|
142
|
-
args: T,
|
|
132
|
+
getMessage: () => Promise<ToClientMessage>,
|
|
143
133
|
txId: string
|
|
144
134
|
) {
|
|
145
135
|
logger.info(`Turned on live updates for ${messageType}`);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!this.continuePublishing(messageType, txId)) {
|
|
150
|
-
logger.info(`Turned off live updates for ${messageType}`);
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
const payload: ToClientMessagePayload = await payloadBuilderFunction(
|
|
154
|
-
...args
|
|
155
|
-
);
|
|
156
|
-
const message = await messageBuilderFunction(payload, txId);
|
|
136
|
+
try {
|
|
137
|
+
while (this.continuePublishing(messageType, txId)) {
|
|
138
|
+
const message = await getMessage();
|
|
157
139
|
this.publisher.publishToClient(message, logger.silly);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
161
|
-
);
|
|
140
|
+
|
|
141
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
162
142
|
}
|
|
163
|
-
|
|
143
|
+
} catch (e) {
|
|
144
|
+
logger.error(
|
|
145
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
146
|
+
);
|
|
164
147
|
}
|
|
148
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
165
149
|
}
|
|
166
150
|
|
|
167
151
|
/*=================================================================
|
|
@@ -196,9 +180,16 @@ export class LiveUpdatesHandler {
|
|
|
196
180
|
// Don't wait for this call to finish since it loops until disabled
|
|
197
181
|
void this.startPublishingLiveUpdates(
|
|
198
182
|
keyMirrors.toClientMessageType.status_response,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
183
|
+
async () => {
|
|
184
|
+
const payload: StatusResponsePayload = {
|
|
185
|
+
status: keyMirrors.statusResponse.in_progress
|
|
186
|
+
};
|
|
187
|
+
return buildToClientStatusResponseMessage(
|
|
188
|
+
this.clientId,
|
|
189
|
+
payload,
|
|
190
|
+
txId
|
|
191
|
+
);
|
|
192
|
+
},
|
|
202
193
|
txId
|
|
203
194
|
);
|
|
204
195
|
}
|
|
@@ -220,41 +211,43 @@ export class LiveUpdatesHandler {
|
|
|
220
211
|
const { deviceStats, appState, appLogs } = toggles;
|
|
221
212
|
this.restartLiveUpdatesTimeout();
|
|
222
213
|
|
|
223
|
-
const currentDeviceStats = this.getDeviceStatsLiveUpdates();
|
|
224
214
|
if (deviceStats !== undefined) {
|
|
215
|
+
const currentDeviceStats = this.getDeviceStatsLiveUpdates();
|
|
225
216
|
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
226
217
|
if (deviceStats && currentDeviceStats !== true) {
|
|
227
218
|
// Don't wait for this call to finish since it loops until disabled
|
|
228
219
|
void this.startPublishingLiveUpdates(
|
|
229
220
|
keyMirrors.toClientMessageType.device_stats,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
async () => {
|
|
222
|
+
const payload = await getDeviceStatsPayload();
|
|
223
|
+
return buildDeviceStatsMessage(this.clientId, payload, txId);
|
|
224
|
+
},
|
|
233
225
|
txId
|
|
234
226
|
);
|
|
235
227
|
}
|
|
236
228
|
}
|
|
237
229
|
|
|
238
|
-
const currentAppState = this.getAppStateLiveUpdates();
|
|
239
230
|
if (appState !== undefined) {
|
|
231
|
+
const currentAppState = this.getAppStateLiveUpdates();
|
|
240
232
|
this.liveUpdatesAlive.app_state = appState;
|
|
241
233
|
if (appState && currentAppState !== true) {
|
|
242
234
|
// Don't wait for this call to finish since it loops until disabled
|
|
243
235
|
void this.startPublishingLiveUpdates(
|
|
244
236
|
keyMirrors.toClientMessageType.app_state,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
237
|
+
async () => {
|
|
238
|
+
const payload = await getAppStatePayload();
|
|
239
|
+
return buildAppStateMessage(this.clientId, payload, txId);
|
|
240
|
+
},
|
|
248
241
|
txId
|
|
249
242
|
);
|
|
250
243
|
}
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
const currentAppLogs = this.getAppLogsLiveUpdates();
|
|
254
246
|
if (appLogs !== undefined) {
|
|
247
|
+
const currentAppLogs = this.getAppLogsLiveUpdates();
|
|
255
248
|
if (appLogs.toggle && currentAppLogs !== true) {
|
|
256
249
|
// Don't wait for this call to finish since it loops until disabled
|
|
257
|
-
void this.startAppLogStream(appLogs.projectId);
|
|
250
|
+
void this.startAppLogStream(appLogs.projectId, txId);
|
|
258
251
|
} else {
|
|
259
252
|
this.appLogStreams.delete(appLogs.projectId);
|
|
260
253
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AppState,
|
|
3
3
|
AppStatePayload,
|
|
4
|
-
DeviceStatsPayload
|
|
5
|
-
StatusResponsePayload
|
|
4
|
+
DeviceStatsPayload
|
|
6
5
|
} from '@alwaysai/device-agent-schemas';
|
|
7
|
-
import { StatusResponseValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
8
6
|
import { getAppState } from '../application-control';
|
|
9
7
|
import {
|
|
10
8
|
getCpuDetails,
|
|
@@ -27,17 +25,6 @@ export async function getAppStatePayload(): Promise<AppStatePayload> {
|
|
|
27
25
|
return appStatePayload;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export async function getStatusResponsePayload(
|
|
31
|
-
status: StatusResponseValue,
|
|
32
|
-
message: string
|
|
33
|
-
): Promise<StatusResponsePayload> {
|
|
34
|
-
const statusResponsePayload: StatusResponsePayload = {
|
|
35
|
-
status,
|
|
36
|
-
message
|
|
37
|
-
};
|
|
38
|
-
return statusResponsePayload;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
28
|
export async function getDeviceStatsPayload(): Promise<DeviceStatsPayload> {
|
|
42
29
|
const cpuDetails = await getCpuDetails();
|
|
43
30
|
const diskDetails = await getDiskDetails();
|
|
@@ -2,6 +2,8 @@ import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
|
2
2
|
import { readAppCfgFile } from '../application-control';
|
|
3
3
|
import { Publisher } from './publisher';
|
|
4
4
|
import { ShadowHandler } from './shadow-handler';
|
|
5
|
+
import { Logger } from 'winston';
|
|
6
|
+
import { logger } from '../util/logger';
|
|
5
7
|
|
|
6
8
|
jest.mock('../application-control');
|
|
7
9
|
jest.mock('./publisher');
|
|
@@ -23,7 +25,7 @@ describe('Test Shadow Handler', () => {
|
|
|
23
25
|
//FIXME: Invalid input is silently ignored, need input validation
|
|
24
26
|
expect(async () => {
|
|
25
27
|
await shadowHandler.handleShadowTopic({
|
|
26
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
28
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
27
29
|
payload: Buffer.from('test-payload'),
|
|
28
30
|
clientToken: ''
|
|
29
31
|
});
|
|
@@ -56,7 +58,7 @@ describe('Test Shadow Handler', () => {
|
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
const updates = await shadowHandler.handleShadowTopic({
|
|
59
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
61
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
60
62
|
payload,
|
|
61
63
|
clientToken: clientId
|
|
62
64
|
});
|
|
@@ -65,11 +67,13 @@ describe('Test Shadow Handler', () => {
|
|
|
65
67
|
|
|
66
68
|
test('handle project shadow empty delta', async () => {
|
|
67
69
|
const payload = {
|
|
68
|
-
|
|
70
|
+
desired: {
|
|
71
|
+
[projectId1]: {}
|
|
72
|
+
}
|
|
69
73
|
};
|
|
70
74
|
|
|
71
75
|
const updates = await shadowHandler.handleShadowTopic({
|
|
72
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
76
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
73
77
|
payload,
|
|
74
78
|
clientToken: ''
|
|
75
79
|
});
|
|
@@ -98,7 +102,7 @@ describe('Test Shadow Handler', () => {
|
|
|
98
102
|
};
|
|
99
103
|
|
|
100
104
|
const payload = {
|
|
101
|
-
|
|
105
|
+
desired: {
|
|
102
106
|
[projectId1]: {
|
|
103
107
|
appConfig: JSON.stringify(appCfg1)
|
|
104
108
|
}
|
|
@@ -106,7 +110,7 @@ describe('Test Shadow Handler', () => {
|
|
|
106
110
|
};
|
|
107
111
|
|
|
108
112
|
const updates = await shadowHandler.handleShadowTopic({
|
|
109
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
113
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
110
114
|
payload,
|
|
111
115
|
clientToken: ''
|
|
112
116
|
});
|
|
@@ -145,13 +149,15 @@ describe('Test Shadow Handler', () => {
|
|
|
145
149
|
};
|
|
146
150
|
|
|
147
151
|
const payload = {
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
desired: {
|
|
153
|
+
[projectId1]: {
|
|
154
|
+
appConfig: JSON.stringify(appCfg1)
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
};
|
|
152
158
|
|
|
153
159
|
const updates = await shadowHandler.handleShadowTopic({
|
|
154
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
160
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
155
161
|
payload,
|
|
156
162
|
clientToken: ''
|
|
157
163
|
});
|
|
@@ -208,16 +214,18 @@ describe('Test Shadow Handler', () => {
|
|
|
208
214
|
}
|
|
209
215
|
};
|
|
210
216
|
const payload = {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
217
|
+
desired: {
|
|
218
|
+
[projectId1]: {
|
|
219
|
+
appConfig: JSON.stringify(appCfg1)
|
|
220
|
+
},
|
|
221
|
+
[projectId2]: {
|
|
222
|
+
appConfig: JSON.stringify(appCfg2)
|
|
223
|
+
}
|
|
216
224
|
}
|
|
217
225
|
};
|
|
218
226
|
|
|
219
227
|
const updates = await shadowHandler.handleShadowTopic({
|
|
220
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
228
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
221
229
|
payload,
|
|
222
230
|
clientToken: ''
|
|
223
231
|
});
|
|
@@ -265,13 +273,15 @@ describe('Test Shadow Handler', () => {
|
|
|
265
273
|
};
|
|
266
274
|
|
|
267
275
|
const payload = {
|
|
268
|
-
|
|
269
|
-
|
|
276
|
+
desired: {
|
|
277
|
+
[projectId1]: {
|
|
278
|
+
appConfig: JSON.stringify(appCfg1)
|
|
279
|
+
}
|
|
270
280
|
}
|
|
271
281
|
};
|
|
272
282
|
|
|
273
283
|
const updates = await shadowHandler.handleShadowTopic({
|
|
274
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
284
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
275
285
|
payload,
|
|
276
286
|
clientToken: ''
|
|
277
287
|
});
|
|
@@ -307,27 +317,73 @@ describe('Test Shadow Handler', () => {
|
|
|
307
317
|
};
|
|
308
318
|
|
|
309
319
|
const payload = {
|
|
310
|
-
|
|
311
|
-
|
|
320
|
+
desired: {
|
|
321
|
+
[projectId1]: {
|
|
322
|
+
appConfig: JSON.stringify(appCfg1)
|
|
323
|
+
}
|
|
312
324
|
}
|
|
313
325
|
};
|
|
314
326
|
|
|
315
327
|
const updates = await shadowHandler.handleShadowTopic({
|
|
316
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
328
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
317
329
|
payload,
|
|
318
330
|
clientToken: ''
|
|
319
331
|
});
|
|
320
332
|
expect(updates.length).toBe(0);
|
|
321
333
|
});
|
|
334
|
+
|
|
335
|
+
test('handles an unparsable object in a project shadow appCfg delta', async () => {
|
|
336
|
+
const ogAppCfg1: AppConfig = {
|
|
337
|
+
scripts: {
|
|
338
|
+
start: 'python app.py'
|
|
339
|
+
},
|
|
340
|
+
models: {}
|
|
341
|
+
};
|
|
342
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
343
|
+
|
|
344
|
+
// This appCfg is invalid on it's own (see values below)
|
|
345
|
+
const appCfg1 = {
|
|
346
|
+
scripts: {
|
|
347
|
+
start: 'python app.py'
|
|
348
|
+
},
|
|
349
|
+
models: {
|
|
350
|
+
'alwaysai/mobilenet_ssd': '3', // string instead of int
|
|
351
|
+
'alwaysai/yolo_v4': '5' // string instead of int
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const payload = {
|
|
356
|
+
desired: {
|
|
357
|
+
[projectId1]: {
|
|
358
|
+
appConfig: appCfg1 // This is missing JSON.stringify() making this an unparsable object.
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const loggerSpy = jest
|
|
364
|
+
.spyOn(logger, 'error')
|
|
365
|
+
.mockReturnValue({} as unknown as Logger);
|
|
366
|
+
|
|
367
|
+
const updates = await shadowHandler.handleShadowTopic({
|
|
368
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
369
|
+
payload,
|
|
370
|
+
clientToken: ''
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
expect(updates.length).toBe(0);
|
|
374
|
+
expect(loggerSpy).toHaveBeenCalledWith(
|
|
375
|
+
expect.stringContaining('Could not parse the appConfig')
|
|
376
|
+
);
|
|
377
|
+
});
|
|
322
378
|
});
|
|
323
379
|
|
|
324
380
|
describe('handle project shadow env vars', () => {
|
|
325
|
-
test('handle
|
|
381
|
+
test('handle a response from the getAccepted from the cloud', async () => {
|
|
326
382
|
const envVars1 = {
|
|
327
383
|
VAR0: 'value0'
|
|
328
384
|
};
|
|
329
385
|
const payload = {
|
|
330
|
-
|
|
386
|
+
reported: {
|
|
331
387
|
[projectId1]: {
|
|
332
388
|
envVars: envVars1
|
|
333
389
|
}
|
|
@@ -339,28 +395,22 @@ describe('Test Shadow Handler', () => {
|
|
|
339
395
|
payload,
|
|
340
396
|
clientToken: ''
|
|
341
397
|
});
|
|
342
|
-
expect(updates.length).toBe(
|
|
343
|
-
expect(updates[0]).toEqual({
|
|
344
|
-
projectId: projectId1,
|
|
345
|
-
txId: expect.any(String),
|
|
346
|
-
envVarUpdate: {
|
|
347
|
-
envVars: envVars1
|
|
348
|
-
}
|
|
349
|
-
});
|
|
398
|
+
expect(updates.length).toBe(0);
|
|
350
399
|
});
|
|
351
|
-
|
|
352
400
|
test('handle project shadow env vars update delta', async () => {
|
|
353
401
|
const envVars1 = {
|
|
354
402
|
VAR1: 'value1'
|
|
355
403
|
};
|
|
356
404
|
const payload = {
|
|
357
|
-
|
|
358
|
-
|
|
405
|
+
desired: {
|
|
406
|
+
[projectId1]: {
|
|
407
|
+
envVars: envVars1
|
|
408
|
+
}
|
|
359
409
|
}
|
|
360
410
|
};
|
|
361
411
|
|
|
362
412
|
const updates = await shadowHandler.handleShadowTopic({
|
|
363
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
413
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
364
414
|
payload,
|
|
365
415
|
clientToken: ''
|
|
366
416
|
});
|
|
@@ -382,16 +432,18 @@ describe('Test Shadow Handler', () => {
|
|
|
382
432
|
VAR2: null
|
|
383
433
|
};
|
|
384
434
|
const payload = {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
435
|
+
desired: {
|
|
436
|
+
[projectId1]: {
|
|
437
|
+
envVars: envVars1
|
|
438
|
+
},
|
|
439
|
+
[projectId2]: {
|
|
440
|
+
envVars: envVars2
|
|
441
|
+
}
|
|
390
442
|
}
|
|
391
443
|
};
|
|
392
444
|
|
|
393
445
|
const updates = await shadowHandler.handleShadowTopic({
|
|
394
|
-
topic: shadowHandler.shadowTopics.projects.
|
|
446
|
+
topic: shadowHandler.shadowTopics.projects.updateAccepted,
|
|
395
447
|
payload,
|
|
396
448
|
clientToken: ''
|
|
397
449
|
});
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
AppConfig,
|
|
3
3
|
validateAppConfig
|
|
4
4
|
} from '@alwaysai/app-configuration-schemas';
|
|
5
|
+
import { generateTxId } from '@alwaysai/device-agent-schemas';
|
|
5
6
|
import { EnvVars, getAllEnvs, readAppCfgFile } from '../application-control';
|
|
7
|
+
import { getSystemInformation } from '../device-control/device-control';
|
|
6
8
|
import { logger } from '../util/logger';
|
|
7
9
|
import { Publisher } from './publisher';
|
|
8
10
|
import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
|
|
9
|
-
import { getSystemInformation } from '../device-control/device-control';
|
|
10
|
-
import { generateTxId } from '@alwaysai/device-agent-schemas';
|
|
11
11
|
|
|
12
12
|
export interface ShadowTopics {
|
|
13
13
|
projects: {
|
|
@@ -23,6 +23,18 @@ export interface ShadowTopics {
|
|
|
23
23
|
systemInfo: {
|
|
24
24
|
update: string;
|
|
25
25
|
};
|
|
26
|
+
secureTunnel: {
|
|
27
|
+
get: string;
|
|
28
|
+
getAccepted: string;
|
|
29
|
+
getRejected: string;
|
|
30
|
+
update: string;
|
|
31
|
+
updateDelta: string;
|
|
32
|
+
updateAccepted: string;
|
|
33
|
+
updateRejected: string;
|
|
34
|
+
delete: string;
|
|
35
|
+
deleteAccepted: string;
|
|
36
|
+
deleteRejected: string;
|
|
37
|
+
};
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
export type AppConfigUpdate = {
|
|
@@ -50,20 +62,32 @@ export class ShadowHandler {
|
|
|
50
62
|
constructor(clientId: string, publisher: Publisher) {
|
|
51
63
|
this.clientId = clientId;
|
|
52
64
|
this.publisher = publisher;
|
|
53
|
-
this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name
|
|
65
|
+
this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name`;
|
|
54
66
|
this.shadowTopics = {
|
|
55
67
|
projects: {
|
|
56
|
-
get: `${this.shadowPrefix}projects/get`,
|
|
57
|
-
getAccepted: `${this.shadowPrefix}projects/get/accepted`,
|
|
58
|
-
getRejected: `${this.shadowPrefix}projects/get/rejected`,
|
|
59
|
-
update: `${this.shadowPrefix}projects/update`,
|
|
60
|
-
updateDelta: `${this.shadowPrefix}projects/update/delta`,
|
|
61
|
-
updateAccepted: `${this.shadowPrefix}projects/update/accepted`,
|
|
62
|
-
updateRejected: `${this.shadowPrefix}projects/update/rejected`,
|
|
63
|
-
delete: `${this.shadowPrefix}projects/delete`
|
|
68
|
+
get: `${this.shadowPrefix}/projects/get`,
|
|
69
|
+
getAccepted: `${this.shadowPrefix}/projects/get/accepted`,
|
|
70
|
+
getRejected: `${this.shadowPrefix}/projects/get/rejected`,
|
|
71
|
+
update: `${this.shadowPrefix}/projects/update`,
|
|
72
|
+
updateDelta: `${this.shadowPrefix}/projects/update/delta`,
|
|
73
|
+
updateAccepted: `${this.shadowPrefix}/projects/update/accepted`,
|
|
74
|
+
updateRejected: `${this.shadowPrefix}/projects/update/rejected`,
|
|
75
|
+
delete: `${this.shadowPrefix}/projects/delete`
|
|
64
76
|
},
|
|
65
77
|
systemInfo: {
|
|
66
|
-
update: `${this.shadowPrefix}system-info/update`
|
|
78
|
+
update: `${this.shadowPrefix}/system-info/update`
|
|
79
|
+
},
|
|
80
|
+
secureTunnel: {
|
|
81
|
+
get: `${this.shadowPrefix}/secure-tunnel/get`,
|
|
82
|
+
getAccepted: `${this.shadowPrefix}/secure-tunnel/get/accepted`,
|
|
83
|
+
getRejected: `${this.shadowPrefix}/secure-tunnel/get/rejected`,
|
|
84
|
+
update: `${this.shadowPrefix}/secure-tunnel/update`,
|
|
85
|
+
updateDelta: `${this.shadowPrefix}/secure-tunnel/update/delta`,
|
|
86
|
+
updateAccepted: `${this.shadowPrefix}/secure-tunnel/update/accepted`,
|
|
87
|
+
updateRejected: `${this.shadowPrefix}/secure-tunnel/update/rejected`,
|
|
88
|
+
delete: `${this.shadowPrefix}/secure-tunnel/delete`,
|
|
89
|
+
deleteAccepted: `${this.shadowPrefix}/secure-tunnel/delete/accepted`,
|
|
90
|
+
deleteRejected: `${this.shadowPrefix}/secure-tunnel/delete/rejected`
|
|
67
91
|
}
|
|
68
92
|
};
|
|
69
93
|
}
|
|
@@ -84,7 +108,15 @@ export class ShadowHandler {
|
|
|
84
108
|
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
85
109
|
|
|
86
110
|
if (projectShadow.appConfig) {
|
|
87
|
-
|
|
111
|
+
let newAppCfg: any;
|
|
112
|
+
try {
|
|
113
|
+
newAppCfg = JSON.parse(projectShadow.appConfig);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.error(
|
|
116
|
+
`Could not parse the appConfig for transaction ${txId}!\n${error}`
|
|
117
|
+
);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
88
120
|
if (!validateAppConfig(newAppCfg)) {
|
|
89
121
|
// FIXME: Raise an exception to be handled at higher layer
|
|
90
122
|
logger.error(
|
|
@@ -124,6 +156,7 @@ export class ShadowHandler {
|
|
|
124
156
|
updates.push(shadowUpdate);
|
|
125
157
|
}
|
|
126
158
|
}
|
|
159
|
+
|
|
127
160
|
return updates;
|
|
128
161
|
}
|
|
129
162
|
|
|
@@ -141,7 +174,7 @@ export class ShadowHandler {
|
|
|
141
174
|
// TODO: make use a function like the other topic getters
|
|
142
175
|
const shadowName = topic.split('/')[5];
|
|
143
176
|
switch (topic) {
|
|
144
|
-
case this.shadowTopics.projects.
|
|
177
|
+
case this.shadowTopics.projects.updateAccepted:
|
|
145
178
|
if (clientToken === this.clientId) {
|
|
146
179
|
logger.debug(
|
|
147
180
|
`Ignoring delta caused by Device Agent: ${JSON.stringify(
|
|
@@ -152,14 +185,16 @@ export class ShadowHandler {
|
|
|
152
185
|
);
|
|
153
186
|
break;
|
|
154
187
|
}
|
|
155
|
-
return await this.handleNamedShadowUpdate({ delta: payload });
|
|
188
|
+
return await this.handleNamedShadowUpdate({ delta: payload.desired });
|
|
156
189
|
case this.shadowTopics.projects.getAccepted:
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
if (clientToken !== this.clientId) {
|
|
191
|
+
logger.debug(
|
|
192
|
+
`Ignoring get message initiated by the cloud/console: ${JSON.stringify(
|
|
193
|
+
{ topic, payload },
|
|
194
|
+
null,
|
|
195
|
+
2
|
|
196
|
+
)}`
|
|
197
|
+
);
|
|
163
198
|
}
|
|
164
199
|
break;
|
|
165
200
|
default:
|
|
@@ -189,6 +224,7 @@ export class ShadowHandler {
|
|
|
189
224
|
public async updateProjectShadow(projectId: string) {
|
|
190
225
|
const appCfg = await readAppCfgFile({ projectId });
|
|
191
226
|
const envVars = await getAllEnvs({ projectId });
|
|
227
|
+
|
|
192
228
|
const packet = {
|
|
193
229
|
state: {
|
|
194
230
|
reported: {
|