@alwaysai/device-agent 0.0.6 → 0.0.8
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.js +2 -2
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/install.d.ts +14 -10
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +44 -44
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +4 -1
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +3 -2
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +8 -6
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +4 -0
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +20 -6
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +35 -15
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +282 -196
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/subcommands/app/app.js +1 -1
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/util/sleep.d.ts +2 -0
- package/lib/util/sleep.d.ts.map +1 -0
- package/lib/util/sleep.js +9 -0
- package/lib/util/sleep.js.map +1 -0
- package/package.json +4 -3
- package/src/application-control/environment-variables.ts +2 -2
- package/src/application-control/install.ts +66 -56
- package/src/application-control/models.ts +4 -1
- package/src/application-control/status.ts +19 -9
- package/src/application-control/utils.ts +25 -5
- package/src/cloud-connection/device-agent-cloud-connection.ts +374 -223
- package/src/subcommands/app/app.ts +1 -1
- package/src/util/sleep.ts +5 -0
|
@@ -5,178 +5,162 @@ import {
|
|
|
5
5
|
getCertificateFilePath,
|
|
6
6
|
getRootCertificateFilePath,
|
|
7
7
|
} from '../util/directories';
|
|
8
|
+
import sleep from '../util/sleep';
|
|
8
9
|
import {
|
|
9
10
|
startApp,
|
|
10
11
|
stopApp,
|
|
11
12
|
restartApp,
|
|
12
|
-
getAppStatus,
|
|
13
13
|
getAppLogs,
|
|
14
|
+
getAppStatus,
|
|
14
15
|
} from '../application-control/status';
|
|
15
|
-
import { installApp } from '../application-control/install';
|
|
16
|
-
import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
|
|
16
|
+
import { installApp, uninstallApp } from '../application-control/install';
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
keyMirrors,
|
|
19
|
+
validateClientMessage,
|
|
20
|
+
DeviceAgentMessagePayload,
|
|
21
|
+
AppInstallStatusPacket,
|
|
22
|
+
AppStateControlPacket,
|
|
23
|
+
AppVersionControlPacket,
|
|
24
|
+
LiveUpdatesToggles,
|
|
25
|
+
AppLogsPacket,
|
|
26
|
+
AppStatePacket,
|
|
27
|
+
LiveUpdatesToggleMessage,
|
|
19
28
|
DeviceStatsMessage,
|
|
20
|
-
AppState,
|
|
21
29
|
AppStateMessage,
|
|
22
|
-
ActionMessage,
|
|
23
|
-
AppLogs,
|
|
24
30
|
AppLogsMessage,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
AppInstallStatusMessage,
|
|
32
|
+
AppInstallSignedUrlsRequestMessage,
|
|
33
|
+
DeviceAgentMessage,
|
|
34
|
+
ClientMessage,
|
|
28
35
|
} from '@alwaysai/device-agent-schemas';
|
|
29
36
|
import { getDeviceId } from '../util/get-device-id';
|
|
37
|
+
import { logger } from 'alwaysai/lib/util';
|
|
38
|
+
import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
|
|
30
39
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
31
40
|
|
|
32
41
|
export class DeviceAgentCloudConnection {
|
|
33
42
|
private clientId = getDeviceId();
|
|
34
43
|
private host = getIoTCoreEndpointUrl();
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
private appInstallStatus: AppInstallStatusPacket;
|
|
45
|
+
private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
|
|
46
|
+
private liveUpdatesAlive = {
|
|
47
|
+
[keyMirrors.agentMessageType.device_stats]: false,
|
|
48
|
+
[keyMirrors.agentMessageType.app_state]: false,
|
|
49
|
+
[keyMirrors.agentMessageType.app_logs]: false,
|
|
50
|
+
};
|
|
51
|
+
private liveUpdatesSleepIntervals = {
|
|
52
|
+
[keyMirrors.agentMessageType.device_stats]: 5000,
|
|
53
|
+
[keyMirrors.agentMessageType.app_state]: 5000,
|
|
54
|
+
[keyMirrors.agentMessageType.app_logs]: 5000,
|
|
55
|
+
[keyMirrors.agentMessageType.app_install_status]: 5000,
|
|
56
|
+
};
|
|
57
|
+
private appLogStreams = new Set<string>();
|
|
58
|
+
private readonly agentTopicPrefix = `destination/agent/device/${this.clientId}/topic/`;
|
|
59
|
+
private readonly cloudTopicPrefix = `destination/cloud/device/${this.clientId}/topic/`;
|
|
60
|
+
private readonly publishableTopics = {
|
|
61
|
+
deviceStats: `${this.cloudTopicPrefix}device-management`,
|
|
62
|
+
appState: `${this.cloudTopicPrefix}application-management`,
|
|
63
|
+
appLogs: `${this.cloudTopicPrefix}application-management`,
|
|
64
|
+
appInstallStatus: `${this.cloudTopicPrefix}installation-status`,
|
|
65
|
+
cloudRequest: `${this.cloudTopicPrefix}request`,
|
|
66
|
+
};
|
|
67
|
+
private readonly subsribedTopics = {
|
|
68
|
+
command: `${this.agentTopicPrefix}command`,
|
|
69
|
+
response: `${this.agentTopicPrefix}response`,
|
|
70
|
+
};
|
|
44
71
|
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
72
|
+
public async startAppLogStream(projectId: string) {
|
|
73
|
+
this.appLogStreams.add(projectId);
|
|
74
|
+
const readable = await getAppLogs({
|
|
75
|
+
projectId,
|
|
76
|
+
args: ['--tail', '100', '--no-log-prefix'],
|
|
77
|
+
});
|
|
78
|
+
readable.on('data', (chunk: Buffer) => {
|
|
79
|
+
if (!this.appLogStreams.has(projectId)) {
|
|
80
|
+
// why doesn't typescript know about this function?
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
readable.destroy();
|
|
83
|
+
logger.info(`App log stream terminated for project ${projectId}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const logStr = chunk.toString();
|
|
87
|
+
const message = {
|
|
88
|
+
messageType: keyMirrors.agentMessageType.app_logs,
|
|
89
|
+
appLogs: {
|
|
90
|
+
projectId,
|
|
91
|
+
logChunk: logStr,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
const packet = this.buildMessagePacket(
|
|
95
|
+
this.getClientId(),
|
|
96
|
+
this.publishableTopics.appLogs,
|
|
97
|
+
message,
|
|
98
|
+
);
|
|
99
|
+
this.publishMessage(this.publishableTopics.appLogs, JSON.stringify(packet));
|
|
52
100
|
});
|
|
53
101
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
public setInstallationStatus(installationStatus: InstallationStatus) {
|
|
59
|
-
this.installationStatus = installationStatus;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public getInstallationStatus(): InstallationStatus {
|
|
63
|
-
return this.installationStatus;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public getPublishable(): boolean {
|
|
67
|
-
return this.publishable;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public setPublishable(enabled: boolean) {
|
|
71
|
-
this.publishable = enabled;
|
|
72
|
-
}
|
|
102
|
+
readable.on('error', (error) => {
|
|
103
|
+
logger.error(`App log stream terminated for project ${projectId}: ${error}`);
|
|
104
|
+
});
|
|
73
105
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.setPublishable(false);
|
|
78
|
-
}, 600000); // 10 min
|
|
106
|
+
readable.on('finished', () => {
|
|
107
|
+
console.log(`App logs finished piping for project ${projectId}`);
|
|
108
|
+
});
|
|
79
109
|
}
|
|
80
110
|
|
|
81
|
-
|
|
82
|
-
|
|
111
|
+
// must contain app release hash
|
|
112
|
+
private initAppInstallStatus(installationStatus: AppInstallStatusPacket) {
|
|
113
|
+
this.appInstallStatus = installationStatus;
|
|
83
114
|
}
|
|
84
115
|
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
private updateAppInstallStatus(
|
|
117
|
+
installationStatus: Omit<AppInstallStatusPacket, 'appReleaseHash'>,
|
|
118
|
+
) {
|
|
119
|
+
this.appInstallStatus.status = installationStatus.status;
|
|
120
|
+
this.appInstallStatus.message = installationStatus.message;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
|
-
|
|
90
|
-
return this.
|
|
123
|
+
private getAppInstallStatus(): AppInstallStatusPacket {
|
|
124
|
+
return this.appInstallStatus;
|
|
91
125
|
}
|
|
92
126
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function runDeviceAgentCloudInterface() {
|
|
99
|
-
const deviceAgent = new DeviceAgentCloudConnection();
|
|
100
|
-
|
|
101
|
-
async function buildMessagePacket(
|
|
127
|
+
// Message Builders
|
|
128
|
+
private buildMessagePacket(
|
|
129
|
+
deviceId: string,
|
|
102
130
|
topic: string,
|
|
103
|
-
payload:
|
|
104
|
-
|
|
105
|
-
| ActionMessage
|
|
106
|
-
| AppStateMessage
|
|
107
|
-
| AppLogsMessage
|
|
108
|
-
| InstallationStatusMessage,
|
|
109
|
-
): Promise<DeviceAgentMessage> {
|
|
131
|
+
payload: DeviceAgentMessagePayload,
|
|
132
|
+
): DeviceAgentMessage {
|
|
110
133
|
const packet = {
|
|
111
134
|
timestamp: new Date().toUTCString(),
|
|
112
|
-
deviceId
|
|
135
|
+
deviceId,
|
|
113
136
|
topic,
|
|
114
137
|
payload,
|
|
115
138
|
};
|
|
116
139
|
return packet;
|
|
117
140
|
}
|
|
118
141
|
|
|
119
|
-
async
|
|
120
|
-
const
|
|
142
|
+
private async getAppStateMessage(): Promise<AppStateMessage> {
|
|
143
|
+
const appStateMessage: AppStatePacket[] = [];
|
|
121
144
|
const apps = await AgentConfigFile().getReadyApps();
|
|
122
|
-
for (const app of apps) {
|
|
123
|
-
const projectId = app.projectId;
|
|
124
|
-
const readable = await getAppLogs({ projectId });
|
|
125
|
-
const logs: any[] = [];
|
|
126
|
-
readable.setEncoding('utf8');
|
|
127
|
-
for await (const chunk of readable) {
|
|
128
|
-
logs.push(chunk);
|
|
129
|
-
}
|
|
130
|
-
const appLogs = {
|
|
131
|
-
projectId,
|
|
132
|
-
logs,
|
|
133
|
-
};
|
|
134
|
-
appLogsList.push(appLogs);
|
|
135
|
-
}
|
|
136
|
-
const applicationStatePackage = {
|
|
137
|
-
applicationLogs: appLogsList,
|
|
138
|
-
};
|
|
139
|
-
return applicationStatePackage;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// * This was causing massive CPU overhead
|
|
143
|
-
// const publishAppLogs = setInterval(async function () {
|
|
144
|
-
// const appLogsMessage = await getAppLogsMessage();
|
|
145
|
-
// const topic = `device/${deviceAgent.getClientId()}/topic/application-management`;
|
|
146
|
-
// const appLogsPacket = await buildMessagePacket(topic, appLogsMessage);
|
|
147
|
-
|
|
148
|
-
// deviceAgent.publishMessage(topic, JSON.stringify({ appLogsPacket }));
|
|
149
|
-
// }, deviceAgent.getPublishInterval());
|
|
150
|
-
|
|
151
|
-
async function getAppStateMessage(): Promise<AppStateMessage> {
|
|
152
|
-
const appStateMessage: AppState[] = [];
|
|
153
|
-
const apps = await AgentConfigFile().getApps();
|
|
154
145
|
for (const app of apps) {
|
|
155
146
|
const projectId = app.projectId;
|
|
156
147
|
const status = await getAppStatus({ projectId });
|
|
157
148
|
appStateMessage.push(status);
|
|
158
149
|
}
|
|
159
|
-
const
|
|
160
|
-
|
|
150
|
+
const appStatePackage = {
|
|
151
|
+
messageType: keyMirrors.agentMessageType.app_state,
|
|
152
|
+
appState: appStateMessage,
|
|
161
153
|
};
|
|
162
|
-
return
|
|
154
|
+
return appStatePackage;
|
|
163
155
|
}
|
|
164
156
|
|
|
165
|
-
|
|
166
|
-
if (deviceAgent.getPublishable()) {
|
|
167
|
-
const topic = `${deviceAgent.cloudTopicPrefix}application-management`;
|
|
168
|
-
const appStateMessage = await getAppStateMessage();
|
|
169
|
-
const appStatePacket = await buildMessagePacket(topic, appStateMessage);
|
|
170
|
-
deviceAgent.publishMessage(topic, JSON.stringify({ appStatePacket }));
|
|
171
|
-
}
|
|
172
|
-
}, deviceAgent.getPublishInterval());
|
|
173
|
-
|
|
174
|
-
async function getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
|
|
157
|
+
private async getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
|
|
175
158
|
const cpuUsage = await getCpuUtil();
|
|
176
159
|
const diskUtil = await getDiskUtil();
|
|
177
160
|
const memUtil = await getMemUtil();
|
|
178
161
|
|
|
179
162
|
const deviceStatsMessage = {
|
|
163
|
+
messageType: keyMirrors.agentMessageType.device_stats,
|
|
180
164
|
deviceStats: {
|
|
181
165
|
cpuUsage,
|
|
182
166
|
diskUtil,
|
|
@@ -186,124 +170,286 @@ export function runDeviceAgentCloudInterface() {
|
|
|
186
170
|
return deviceStatsMessage;
|
|
187
171
|
}
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
}, deviceAgent.getPublishInterval());
|
|
197
|
-
|
|
198
|
-
const publishDeviceRequest = async ({ projectId, releaseHash }) => {
|
|
199
|
-
const topic = `${deviceAgent.cloudTopicPrefix}request`;
|
|
200
|
-
const deviceRequestPackage = {
|
|
201
|
-
timestamp: new Date().toUTCString(),
|
|
202
|
-
deviceId: deviceAgent.getClientId(),
|
|
203
|
-
projectId,
|
|
204
|
-
releaseHash,
|
|
205
|
-
topic,
|
|
173
|
+
// must be arrow function due to this context when function is passed as param
|
|
174
|
+
private getAppInstallStatusMessage = async (): Promise<AppInstallStatusMessage> => {
|
|
175
|
+
const appInstallStatus = this.getAppInstallStatus();
|
|
176
|
+
const appInstallStatusMessage = {
|
|
177
|
+
messageType: keyMirrors.agentMessageType.app_install_status,
|
|
178
|
+
appInstallStatus,
|
|
206
179
|
};
|
|
207
|
-
|
|
208
|
-
topic,
|
|
209
|
-
JSON.stringify({ device_request: deviceRequestPackage }),
|
|
210
|
-
);
|
|
180
|
+
return appInstallStatusMessage;
|
|
211
181
|
};
|
|
212
182
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
183
|
+
private async startPublishingLiveUpdates(
|
|
184
|
+
topic: string,
|
|
185
|
+
messageType: string,
|
|
186
|
+
getMessageData: () => Promise<DeviceAgentMessagePayload>,
|
|
187
|
+
) {
|
|
188
|
+
while (true) {
|
|
189
|
+
try {
|
|
190
|
+
const message = await getMessageData();
|
|
191
|
+
const packet = this.buildMessagePacket(this.getClientId(), topic, message);
|
|
192
|
+
this.publishMessage(topic, JSON.stringify(packet));
|
|
193
|
+
} catch (e) {
|
|
194
|
+
logger.error(`Error publishing live updates for ${messageType}: ${e.message}`);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
if (!this.continuePublishing(messageType)) {
|
|
198
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
222
202
|
}
|
|
223
|
-
}
|
|
203
|
+
}
|
|
224
204
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
205
|
+
private continuePublishing(flag: string): boolean {
|
|
206
|
+
switch (flag) {
|
|
207
|
+
case keyMirrors.agentMessageType.device_stats:
|
|
208
|
+
case keyMirrors.agentMessageType.app_state:
|
|
209
|
+
return this.liveUpdatesAlive[flag];
|
|
210
|
+
case keyMirrors.agentMessageType.app_install_status:
|
|
211
|
+
return this.appInstallStatus.status === keyMirrors.appInstallStatus.in_progress;
|
|
212
|
+
default:
|
|
213
|
+
logger.error(`Unrecognized publishable flag ${flag}`);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
229
217
|
|
|
230
|
-
|
|
231
|
-
|
|
218
|
+
private getLiveUpdatesInterval(flag: string): number {
|
|
219
|
+
const exists = this.liveUpdatesSleepIntervals[flag];
|
|
220
|
+
if (exists) {
|
|
221
|
+
return exists;
|
|
232
222
|
}
|
|
223
|
+
logger.error(`Unrecognized live updates flag ${flag}`);
|
|
224
|
+
return -1;
|
|
225
|
+
}
|
|
233
226
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"install": {
|
|
243
|
-
"releaseHash": "7fb2a812f9e7aa193208dac353521965da50d755085162066c125592f1ed760b",
|
|
244
|
-
"projectId": "786e4686-a681-4cff-9e17-1e7d385c0fdb"
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
*/
|
|
248
|
-
if (type === 'response') {
|
|
249
|
-
deviceAgent.setInstallationStatus({
|
|
250
|
-
status: InstallationStatusEnum.IN_PROGRESS,
|
|
251
|
-
applicationReleaseHash: actionPayload.releaseHash as string,
|
|
252
|
-
});
|
|
227
|
+
private setLiveUpdates(toggles: LiveUpdatesToggles) {
|
|
228
|
+
if (toggles.deviceStats) {
|
|
229
|
+
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
230
|
+
}
|
|
231
|
+
if (toggles.appState) {
|
|
232
|
+
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
253
235
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
236
|
+
private async handleCloudResponse(message: ClientMessage) {
|
|
237
|
+
const payload = message.payload;
|
|
238
|
+
switch (payload.messageType) {
|
|
239
|
+
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
240
|
+
const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } =
|
|
241
|
+
payload.appInstallCloudResponse;
|
|
242
|
+
|
|
243
|
+
this.initAppInstallStatus({
|
|
244
|
+
status: keyMirrors.appInstallStatus.in_progress,
|
|
245
|
+
appReleaseHash,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
this.startPublishingLiveUpdates(
|
|
249
|
+
this.publishableTopics.appInstallStatus,
|
|
250
|
+
keyMirrors.agentMessageType.app_install_status,
|
|
251
|
+
this.getAppInstallStatusMessage,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Install the app and models
|
|
255
|
+
try {
|
|
256
|
+
const signedUrlsPayload = {
|
|
257
|
+
appInstallPayload,
|
|
258
|
+
modelsInstallPayload,
|
|
259
|
+
};
|
|
260
|
+
await installApp({
|
|
261
|
+
projectId,
|
|
262
|
+
appReleaseHash,
|
|
263
|
+
signedUrlsPayload,
|
|
264
|
+
});
|
|
265
|
+
this.updateAppInstallStatus({
|
|
266
|
+
status: keyMirrors.appInstallStatus.success,
|
|
267
|
+
});
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.error(e);
|
|
270
|
+
const message: string = e.message;
|
|
271
|
+
|
|
272
|
+
// uninstall the failed app to put system back in good state
|
|
273
|
+
await uninstallApp({ projectId });
|
|
274
|
+
this.updateAppInstallStatus({
|
|
275
|
+
status: keyMirrors.appInstallStatus.failure,
|
|
276
|
+
message,
|
|
277
|
+
});
|
|
277
278
|
}
|
|
278
|
-
publishDeviceRequest(actionPayload);
|
|
279
|
-
break;
|
|
280
|
-
case 'start':
|
|
281
|
-
startApp({ projectId: actionPayload.projectId });
|
|
282
279
|
break;
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
}
|
|
281
|
+
default:
|
|
282
|
+
logger.error(`Invalid cloud response message type '${message}'`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private handleClientMessage(message: ClientMessage) {
|
|
287
|
+
const payload = message.payload;
|
|
288
|
+
switch (payload.messageType) {
|
|
289
|
+
case keyMirrors.clientMessageType.app_state_control:
|
|
290
|
+
this.handleAppStateControl(payload.appStateControl);
|
|
285
291
|
break;
|
|
286
|
-
case
|
|
287
|
-
|
|
292
|
+
case keyMirrors.clientMessageType.app_version_control:
|
|
293
|
+
this.handleAppVersionControl(payload.appVersionControl);
|
|
288
294
|
break;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
*/
|
|
292
|
-
case 'publishable':
|
|
293
|
-
/**
|
|
294
|
-
{
|
|
295
|
-
"action": "publishable",
|
|
296
|
-
"publishable": true | false
|
|
297
|
-
}
|
|
298
|
-
*/
|
|
299
|
-
deviceAgent.setPublishable(actionPayload);
|
|
300
|
-
deviceAgent.restartPublishableTimeout();
|
|
295
|
+
case keyMirrors.clientMessageType.live_state_updates:
|
|
296
|
+
this.handleAgentCommand(payload);
|
|
301
297
|
break;
|
|
302
298
|
default:
|
|
299
|
+
logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private handleAppStateControl(payload: AppStateControlPacket) {
|
|
304
|
+
const { baseCommand, projectId } = payload;
|
|
305
|
+
switch (baseCommand) {
|
|
306
|
+
case keyMirrors.appStateControl.start:
|
|
307
|
+
startApp({ projectId });
|
|
308
|
+
break;
|
|
309
|
+
case keyMirrors.appStateControl.stop:
|
|
310
|
+
stopApp({ projectId });
|
|
311
|
+
break;
|
|
312
|
+
case keyMirrors.appStateControl.restart:
|
|
313
|
+
restartApp({ projectId });
|
|
303
314
|
break;
|
|
304
315
|
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private handleAppVersionControl(payload: AppVersionControlPacket) {
|
|
319
|
+
const { projectId, appReleaseHash } = payload;
|
|
320
|
+
const signedUrlsRequest = { projectId, appReleaseHash };
|
|
321
|
+
this.publishCloudRequest({
|
|
322
|
+
messageType: keyMirrors.agentMessageType.app_install_signed_urls_request,
|
|
323
|
+
signedUrlsRequest,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private handleDeviceCommand = async (packet: any) => {
|
|
328
|
+
// TODO
|
|
305
329
|
};
|
|
306
330
|
|
|
331
|
+
private handleAgentCommand(message: LiveUpdatesToggleMessage) {
|
|
332
|
+
switch (message.messageType) {
|
|
333
|
+
case keyMirrors.clientMessageType.live_state_updates:
|
|
334
|
+
this.liveUpdatesBroker(message.liveUpdatesToggles);
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
logger.error(`Invalid agent action message type from message '${message}'`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private restartLiveUpdatesTimeout() {
|
|
342
|
+
clearTimeout(this.liveUpdatesTimeout);
|
|
343
|
+
this.liveUpdatesTimeout = setTimeout(() => {
|
|
344
|
+
this.setLiveUpdates({
|
|
345
|
+
deviceStats: false,
|
|
346
|
+
appState: false,
|
|
347
|
+
});
|
|
348
|
+
this.appLogStreams.clear();
|
|
349
|
+
// TODO: Make constant, not hard coded
|
|
350
|
+
}, 600000); // 10 min
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private async liveUpdatesBroker({
|
|
354
|
+
deviceStats,
|
|
355
|
+
appState,
|
|
356
|
+
appLogs,
|
|
357
|
+
}: {
|
|
358
|
+
deviceStats?: boolean;
|
|
359
|
+
appState?: boolean;
|
|
360
|
+
appLogs?: {
|
|
361
|
+
projectId: string;
|
|
362
|
+
toggle: boolean;
|
|
363
|
+
};
|
|
364
|
+
}) {
|
|
365
|
+
this.restartLiveUpdatesTimeout();
|
|
366
|
+
|
|
367
|
+
if (deviceStats !== undefined) {
|
|
368
|
+
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
369
|
+
if (deviceStats) {
|
|
370
|
+
this.startPublishingLiveUpdates(
|
|
371
|
+
this.publishableTopics.deviceStats,
|
|
372
|
+
keyMirrors.agentMessageType.device_stats,
|
|
373
|
+
this.getDeviceStatsMessage,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (appState !== undefined) {
|
|
379
|
+
this.liveUpdatesAlive.app_state = appState;
|
|
380
|
+
if (appState) {
|
|
381
|
+
this.startPublishingLiveUpdates(
|
|
382
|
+
this.publishableTopics.appState,
|
|
383
|
+
keyMirrors.agentMessageType.app_state,
|
|
384
|
+
this.getAppStateMessage,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (appLogs !== undefined) {
|
|
390
|
+
if (appLogs.toggle) {
|
|
391
|
+
this.startAppLogStream(appLogs.projectId);
|
|
392
|
+
} else {
|
|
393
|
+
this.appLogStreams.delete(appLogs.projectId);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async publishCloudRequest(payload: AppInstallSignedUrlsRequestMessage) {
|
|
399
|
+
const topic = this.publishableTopics.cloudRequest;
|
|
400
|
+
const deviceRequestPacket = this.buildMessagePacket(
|
|
401
|
+
this.getClientId(),
|
|
402
|
+
topic,
|
|
403
|
+
payload,
|
|
404
|
+
);
|
|
405
|
+
this.publishMessage(topic, JSON.stringify({ deviceRequestPacket }));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Public Methods
|
|
409
|
+
|
|
410
|
+
public device = awsIot.device;
|
|
411
|
+
|
|
412
|
+
constructor() {
|
|
413
|
+
this.device = awsIot.device({
|
|
414
|
+
keyPath: getPrivateKeyFilePath(),
|
|
415
|
+
certPath: getCertificateFilePath(),
|
|
416
|
+
caPath: getRootCertificateFilePath(),
|
|
417
|
+
clientId: this.clientId,
|
|
418
|
+
host: this.host,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
Object.values(this.subsribedTopics).forEach((topic: string) => {
|
|
422
|
+
this.device.subscribe(topic);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
public getClientId(): string {
|
|
427
|
+
return this.clientId;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
public publishMessage(topic: string, message: string) {
|
|
431
|
+
// TODO: topic validation
|
|
432
|
+
this.device.publish(topic, message);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public handleMessageTopic({
|
|
436
|
+
topic,
|
|
437
|
+
message,
|
|
438
|
+
}: {
|
|
439
|
+
topic: string;
|
|
440
|
+
message: ClientMessage;
|
|
441
|
+
}) {
|
|
442
|
+
if (topic === this.subsribedTopics.response) {
|
|
443
|
+
this.handleCloudResponse(message);
|
|
444
|
+
} else {
|
|
445
|
+
this.handleClientMessage(message);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function runDeviceAgentCloudInterface() {
|
|
451
|
+
const deviceAgent = new DeviceAgentCloudConnection();
|
|
452
|
+
|
|
307
453
|
deviceAgent.device.on('connect', function () {
|
|
308
454
|
deviceAgent.publishMessage('connection', deviceAgent.getClientId());
|
|
309
455
|
console.log('Device Agent has connected to the cloud');
|
|
@@ -313,12 +459,17 @@ export function runDeviceAgentCloudInterface() {
|
|
|
313
459
|
console.log('Device Agent has been disconnected from the cloud');
|
|
314
460
|
});
|
|
315
461
|
|
|
316
|
-
deviceAgent.device.on('message', function (topic, payload) {
|
|
317
|
-
// ToDo: insert valdiation here for incoming messages. Maybe we will use a JSON schema for the message structure.
|
|
462
|
+
deviceAgent.device.on('message', function (topic: string, payload: string) {
|
|
318
463
|
try {
|
|
319
|
-
|
|
464
|
+
const jsonPacket = JSON.parse(payload);
|
|
465
|
+
const valid = validateClientMessage(jsonPacket);
|
|
466
|
+
if (!valid) {
|
|
467
|
+
logger.error(JSON.stringify(validateClientMessage.errors));
|
|
468
|
+
} else {
|
|
469
|
+
deviceAgent.handleMessageTopic({ topic, message: jsonPacket });
|
|
470
|
+
}
|
|
320
471
|
} catch (error) {
|
|
321
|
-
|
|
472
|
+
logger.error(error);
|
|
322
473
|
}
|
|
323
474
|
});
|
|
324
475
|
}
|