@alwaysai/device-agent 2.0.0 → 2.0.2-0
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 +20 -0
- package/lib/cloud-connection/connection-manager.d.ts.map +1 -0
- package/lib/cloud-connection/connection-manager.js +164 -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/publisher.d.ts +3 -2
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +8 -4
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +7 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +77 -1
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +5 -2
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +9 -4
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +22 -11
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +43 -14
- 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 +196 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +104 -817
- 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/publisher.ts +28 -23
- package/src/cloud-connection/shadow-handler.test.ts +6 -2
- package/src/cloud-connection/shadow-handler.ts +129 -1
- package/src/cloud-connection/transaction-manager.test.ts +55 -24
- package/src/cloud-connection/transaction-manager.ts +42 -31
- 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
|
@@ -1,878 +1,165 @@
|
|
|
1
|
-
// eslint-disable-next-line
|
|
2
|
-
const awsIot = require('aws-iot-device-sdk');
|
|
3
1
|
import {
|
|
4
|
-
AppInstallResponsePayload,
|
|
5
|
-
AppStateControlPayload,
|
|
6
|
-
AppVersionControlInstallPayload,
|
|
7
|
-
AppVersionControlUninstallPayload,
|
|
8
|
-
DeviceActionPayload,
|
|
9
|
-
ModelsInstallResponsePayload,
|
|
10
|
-
SignedUrlsRequestPayload,
|
|
11
|
-
ToCloudMessage,
|
|
12
|
-
ToDeviceAgentMessage,
|
|
13
|
-
buildAppLogsMessage,
|
|
14
|
-
buildAppStateMessage,
|
|
15
|
-
buildDeviceStatsMessage,
|
|
16
|
-
buildSignedUrlsRequestMessage,
|
|
17
2
|
buildToClientStatusResponseMessage,
|
|
18
3
|
getToDeviceTopic,
|
|
19
|
-
|
|
20
|
-
keyMirrors,
|
|
21
|
-
validateSecureTunnelShadowUpdate,
|
|
22
|
-
validateToDeviceAgentMessage
|
|
4
|
+
keyMirrors
|
|
23
5
|
} from '@alwaysai/device-agent-schemas';
|
|
24
|
-
import {
|
|
25
|
-
DEVICE_CERTIFICATE_FILE_PATH,
|
|
26
|
-
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
27
|
-
} from 'alwaysai/lib/infrastructure';
|
|
28
|
-
import { stringifyError } from 'alwaysai/lib/util';
|
|
29
|
-
import { exec } from 'child_process';
|
|
30
6
|
import { existsSync } from 'fs';
|
|
31
|
-
import { promisify } from 'util';
|
|
32
|
-
import {
|
|
33
|
-
getAppLogs,
|
|
34
|
-
installApp,
|
|
35
|
-
restartApp,
|
|
36
|
-
startApp,
|
|
37
|
-
stopApp,
|
|
38
|
-
uninstallApp,
|
|
39
|
-
updateAppCfg,
|
|
40
|
-
updateModelsWithPresignedUrls
|
|
41
|
-
} from '../application-control';
|
|
42
|
-
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
43
|
-
import { pruneModels } from '../application-control/models';
|
|
44
|
-
import { reboot } from '../device-control/device-control';
|
|
45
7
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
46
|
-
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
47
8
|
import { getBootstrapPrivateKeyFilePath } from '../infrastructure/device-certificate';
|
|
48
9
|
import { migrateFromLegacyCertsAndTokens } from '../infrastructure/legacy-migration/legacy-migration';
|
|
49
10
|
import { requiredConfigFilesPresentAndValid } from '../infrastructure/required-config-checks';
|
|
50
11
|
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
51
12
|
import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
|
|
52
|
-
import
|
|
13
|
+
import { SecureTunnelMessageHandler } from '../secure-tunneling/secure-tunnel-message-handler';
|
|
53
14
|
import { getDeviceAgentVersion } from '../util/check-for-updates';
|
|
54
|
-
import { AWS_ROOT_CERTIFICATE_FILE_PATH } from '../util/directories';
|
|
55
15
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
56
16
|
import { logger } from '../util/logger';
|
|
57
17
|
import sleep from '../util/sleep';
|
|
58
18
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
59
19
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
60
|
-
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
61
20
|
import { PassthroughHandler } from './passthrough-handler';
|
|
62
21
|
import { Publisher } from './publisher';
|
|
63
|
-
import { ShadowHandler,
|
|
22
|
+
import { ShadowHandler, ProjectShadowMessageHandler } from './shadow-handler';
|
|
23
|
+
import { JobHandler } from '../jobs/job-handler';
|
|
64
24
|
import { TransactionManager } from './transaction-manager';
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
import { ConnectionManager } from './connection-manager';
|
|
26
|
+
import { DeviceAgentMessageHandler } from './device-agent-message-handler';
|
|
27
|
+
import { HandlerContext } from './base-message-handler';
|
|
67
28
|
|
|
68
29
|
export class DeviceAgentCloudConnection {
|
|
30
|
+
private connectionManager: ConnectionManager;
|
|
31
|
+
private transactionManager: TransactionManager;
|
|
32
|
+
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
69
33
|
public shadowHandler: ShadowHandler;
|
|
70
34
|
public publisher: Publisher;
|
|
71
|
-
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
72
|
-
private txnMgr: TransactionManager;
|
|
73
|
-
private device = awsIot.device;
|
|
74
35
|
|
|
75
|
-
private clientId = getDeviceUuid();
|
|
76
|
-
private host = getIoTCoreEndpointUrl();
|
|
77
|
-
private port = 8883;
|
|
36
|
+
private readonly clientId = getDeviceUuid();
|
|
37
|
+
private readonly host = getIoTCoreEndpointUrl();
|
|
38
|
+
private readonly port = 8883;
|
|
78
39
|
private readonly toDeviceTopic = getToDeviceTopic(this.clientId);
|
|
79
|
-
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
80
40
|
private readonly secureTunnelHandler =
|
|
81
41
|
SecureTunnelHandlerSingleton.getInstance();
|
|
82
42
|
|
|
83
43
|
constructor() {
|
|
84
|
-
this.device = awsIot.device({
|
|
85
|
-
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
86
|
-
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
87
|
-
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
88
|
-
clientId: this.clientId,
|
|
89
|
-
host: this.host,
|
|
90
|
-
port: this.port,
|
|
91
|
-
keepalive: 10 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
this.publisher = new Publisher(this.device, this.clientId);
|
|
95
|
-
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
96
44
|
this.liveUpdatesHandler = new LiveUpdatesHandler();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
45
|
+
|
|
46
|
+
// Initialize & setup the connection
|
|
47
|
+
this.connectionManager = new ConnectionManager(
|
|
48
|
+
this.clientId,
|
|
49
|
+
this.host,
|
|
50
|
+
this.port
|
|
100
51
|
);
|
|
101
52
|
|
|
102
|
-
this.
|
|
103
|
-
this.
|
|
104
|
-
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
105
|
-
this.subscribe(topic);
|
|
106
|
-
}
|
|
107
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
108
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
53
|
+
this.publisher = new Publisher(this.connectionManager, this.clientId);
|
|
54
|
+
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
109
55
|
|
|
110
|
-
this.
|
|
111
|
-
|
|
56
|
+
this.connectionManager.connect(() => {
|
|
57
|
+
void this.shadowHandler.initShadows();
|
|
58
|
+
this.publisher.publish(JOB_HANDLER_TOPICS.START_NEXT, JSON.stringify({}));
|
|
59
|
+
});
|
|
112
60
|
|
|
113
|
-
|
|
114
|
-
Public interface
|
|
115
|
-
=================================================================*/
|
|
61
|
+
this.transactionManager = new TransactionManager(this.liveUpdatesHandler);
|
|
116
62
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
63
|
+
// Construct a HandlerContext used by all the message handlers
|
|
64
|
+
const handlerContext: HandlerContext = {
|
|
65
|
+
clientId: this.clientId,
|
|
66
|
+
txnMgr: this.transactionManager,
|
|
67
|
+
publisher: this.publisher,
|
|
68
|
+
shadowHandler: this.shadowHandler,
|
|
69
|
+
liveUpdatesHandler: this.liveUpdatesHandler,
|
|
70
|
+
secureTunnelHandler: this.secureTunnelHandler
|
|
71
|
+
};
|
|
124
72
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
73
|
+
// Instantiate & register message handlers for Project Shadow topics
|
|
74
|
+
const projectShadowMessageHandler = new ProjectShadowMessageHandler(
|
|
75
|
+
handlerContext
|
|
128
76
|
);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await this.handleProjectShadowMessage(topic, message);
|
|
132
|
-
} else if (topic === this.toDeviceTopic) {
|
|
133
|
-
await this.handleDeviceAgentMessage({
|
|
77
|
+
this.shadowHandler.projectShadowTopics.forEach((topic) => {
|
|
78
|
+
this.connectionManager.registerHandler(
|
|
134
79
|
topic,
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
// SecureTunnelNotify messages
|
|
138
|
-
} else if (topic === this.secureTunnelNotifyTopic) {
|
|
139
|
-
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
140
|
-
// SecureTunnel messages
|
|
141
|
-
} else if (
|
|
142
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
143
|
-
) {
|
|
144
|
-
await this.handleSecureTunnelMessage(message);
|
|
145
|
-
} else if (
|
|
146
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
147
|
-
) {
|
|
148
|
-
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
149
|
-
await this.secureTunnelHandler.destroy();
|
|
150
|
-
} else {
|
|
151
|
-
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public async stop() {
|
|
156
|
-
// This method is currently only used by the CLI, and shadow messages can be
|
|
157
|
-
// lost since we aren't waiting for responses so sleep for a short time to
|
|
158
|
-
// receive them
|
|
159
|
-
await sleep(1000);
|
|
160
|
-
this.device.end();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/*=================================================================
|
|
164
|
-
Private interface
|
|
165
|
-
=================================================================*/
|
|
166
|
-
|
|
167
|
-
private setupHandlers() {
|
|
168
|
-
this.device.on('connect', (connack: any) => {
|
|
169
|
-
logger.info('Device Agent has connected to the cloud');
|
|
170
|
-
// FIXME: EI-709 Skip this request for now to prevent kicking off another
|
|
171
|
-
// shadow update process if IoT Core disconnect occurs during app config update
|
|
172
|
-
//this.shadowHandler.getShadowUpdates();
|
|
173
|
-
void this.shadowHandler.updateSystemInfoShadow();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
this.device.on('disconnect', () => {
|
|
177
|
-
logger.warn('Device Agent has been disconnected from the cloud');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
this.device.on('reconnect', () => {
|
|
181
|
-
logger.info(
|
|
182
|
-
`Device Agent attempting to re-connect ${new Date().toLocaleString()}`
|
|
80
|
+
projectShadowMessageHandler
|
|
183
81
|
);
|
|
184
82
|
});
|
|
185
83
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.device.on('offline', () => {
|
|
200
|
-
logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
|
|
201
|
-
void this.logConnectionInfo();
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private async logConnectionInfo() {
|
|
206
|
-
try {
|
|
207
|
-
/**
|
|
208
|
-
* We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
|
|
209
|
-
* This command doesn't always exit (see below), so
|
|
210
|
-
* we use timeout to break out of the prompt
|
|
211
|
-
* and catch the resulting error/parse the resulting stderr
|
|
212
|
-
*
|
|
213
|
-
* Sample command for current host and port:
|
|
214
|
-
* nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
215
|
-
*
|
|
216
|
-
* Sample output when port is not blocked and host is reachable:
|
|
217
|
-
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
|
|
218
|
-
* Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* Sample output when port is blocked (will repeatedly try until ctrl-C out):
|
|
222
|
-
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
223
|
-
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
224
|
-
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
225
|
-
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
226
|
-
* ^C
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* Sample command/output when the port isn't enable on that host:
|
|
230
|
-
* $ nc -zv -w 1 localhost 8883
|
|
231
|
-
* nc: connect to localhost port 8883 (tcp) failed: Connection refused
|
|
232
|
-
*/
|
|
233
|
-
await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
|
|
234
|
-
timeout: 2000
|
|
235
|
-
});
|
|
236
|
-
} catch (err) {
|
|
237
|
-
const output = JSON.stringify(err['stderr']);
|
|
238
|
-
if (output.indexOf('not known') !== -1) {
|
|
239
|
-
logger.warn(
|
|
240
|
-
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
241
|
-
);
|
|
242
|
-
} else if (output.indexOf('timed out') !== -1) {
|
|
243
|
-
logger.warn(
|
|
244
|
-
`Internet connection appears fine, however the endpoint was not reachable on the current connection port: ${this.port}\nPlease check if a firewall is in place.`
|
|
245
|
-
);
|
|
246
|
-
} else if (output.indexOf('refused') !== -1) {
|
|
247
|
-
logger.warn(
|
|
248
|
-
`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
|
|
249
|
-
);
|
|
250
|
-
} else {
|
|
251
|
-
logger.warn(
|
|
252
|
-
`Output from checking connection to ${this.host} on ${this.port}: ${output}`
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private async handleDeviceAgentMessage({
|
|
259
|
-
topic,
|
|
260
|
-
message
|
|
261
|
-
}: {
|
|
262
|
-
topic: string;
|
|
263
|
-
message: ToDeviceAgentMessage;
|
|
264
|
-
}) {
|
|
265
|
-
const valid = validateToDeviceAgentMessage(message);
|
|
266
|
-
if (!valid) {
|
|
267
|
-
logger.error(
|
|
268
|
-
`Error validating message: ${JSON.stringify(
|
|
269
|
-
{ topic, message, errors: validateToDeviceAgentMessage.errors },
|
|
270
|
-
null,
|
|
271
|
-
2
|
|
272
|
-
)}`
|
|
273
|
-
);
|
|
274
|
-
// TODO: Send generic error response
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const txId = message.txId;
|
|
278
|
-
const {
|
|
279
|
-
app_state_control,
|
|
280
|
-
app_version_control,
|
|
281
|
-
live_state_updates,
|
|
282
|
-
app_install_response,
|
|
283
|
-
models_install_response,
|
|
284
|
-
status_response,
|
|
285
|
-
device_action
|
|
286
|
-
} = keyMirrors.toDeviceAgentMessageType;
|
|
287
|
-
switch (message.messageType) {
|
|
288
|
-
case app_state_control: {
|
|
289
|
-
// txId sent from cloud, just need to continue it
|
|
290
|
-
const payload = message.payload;
|
|
291
|
-
const projectId = payload.projectId;
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
await this.txnMgr.runTransactionStep({
|
|
295
|
-
func: () => this.handleAppStateControl(message.payload),
|
|
296
|
-
projectId,
|
|
297
|
-
txId,
|
|
298
|
-
start: true,
|
|
299
|
-
liveUpdatesPublishFn: async () =>
|
|
300
|
-
this.publisher.publishToClient(
|
|
301
|
-
buildToClientStatusResponseMessage(
|
|
302
|
-
this.clientId,
|
|
303
|
-
{ status: keyMirrors.statusResponse.in_progress },
|
|
304
|
-
txId
|
|
305
|
-
),
|
|
306
|
-
logger.silly
|
|
307
|
-
),
|
|
308
|
-
stepName: payload.baseCommand
|
|
309
|
-
});
|
|
310
|
-
} catch (e) {
|
|
311
|
-
logger.error(
|
|
312
|
-
`Error processing application state control request for ${projectId}!\n${stringifyError(
|
|
313
|
-
e
|
|
314
|
-
)}`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case app_version_control: {
|
|
321
|
-
// txId sent from cloud, just need to continue it
|
|
322
|
-
const payload = message.payload;
|
|
323
|
-
const projectId = payload.projectId;
|
|
324
|
-
try {
|
|
325
|
-
await this.txnMgr.runTransactionStep({
|
|
326
|
-
func: () => this.handleAppVersionControl(payload, txId),
|
|
327
|
-
projectId,
|
|
328
|
-
txId,
|
|
329
|
-
start: true,
|
|
330
|
-
liveUpdatesPublishFn: async () =>
|
|
331
|
-
this.publisher.publishToClient(
|
|
332
|
-
buildToClientStatusResponseMessage(
|
|
333
|
-
this.clientId,
|
|
334
|
-
{ status: keyMirrors.statusResponse.in_progress },
|
|
335
|
-
txId
|
|
336
|
-
),
|
|
337
|
-
logger.silly
|
|
338
|
-
),
|
|
339
|
-
stepName: payload.baseCommand
|
|
340
|
-
});
|
|
341
|
-
} catch (e) {
|
|
342
|
-
logger.error(
|
|
343
|
-
`Error processing application install request for ${projectId}!\n${stringifyError(
|
|
344
|
-
e
|
|
345
|
-
)}`
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
case live_state_updates: {
|
|
352
|
-
const { deviceStats, appState, appLogs } = message.payload;
|
|
353
|
-
|
|
354
|
-
if (deviceStats !== undefined) {
|
|
355
|
-
if (deviceStats) {
|
|
356
|
-
await this.liveUpdatesHandler.enable(
|
|
357
|
-
keyMirrors.toClientMessageType.device_stats,
|
|
358
|
-
async () =>
|
|
359
|
-
this.publisher.publishToClient(
|
|
360
|
-
buildDeviceStatsMessage(
|
|
361
|
-
this.clientId,
|
|
362
|
-
await getDeviceStatsPayload(),
|
|
363
|
-
txId
|
|
364
|
-
),
|
|
365
|
-
logger.silly
|
|
366
|
-
)
|
|
367
|
-
);
|
|
368
|
-
} else {
|
|
369
|
-
this.liveUpdatesHandler.disable(
|
|
370
|
-
keyMirrors.toClientMessageType.device_stats
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (appState !== undefined) {
|
|
376
|
-
if (appState) {
|
|
377
|
-
await this.liveUpdatesHandler.enable(
|
|
378
|
-
keyMirrors.toClientMessageType.app_state,
|
|
379
|
-
async () =>
|
|
380
|
-
this.publisher.publishToClient(
|
|
381
|
-
buildAppStateMessage(
|
|
382
|
-
this.clientId,
|
|
383
|
-
await getAppStatePayload(),
|
|
384
|
-
txId
|
|
385
|
-
),
|
|
386
|
-
logger.silly
|
|
387
|
-
)
|
|
388
|
-
);
|
|
389
|
-
} else {
|
|
390
|
-
this.liveUpdatesHandler.disable(
|
|
391
|
-
keyMirrors.toClientMessageType.app_state
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (appLogs !== undefined) {
|
|
397
|
-
if (appLogs.toggle) {
|
|
398
|
-
await this.liveUpdatesHandler.startStream(
|
|
399
|
-
appLogs.projectId,
|
|
400
|
-
async () =>
|
|
401
|
-
await getAppLogs({
|
|
402
|
-
projectId: appLogs.projectId,
|
|
403
|
-
args: ['--tail', '100', '--no-log-prefix']
|
|
404
|
-
}),
|
|
405
|
-
async (logChunk: string) =>
|
|
406
|
-
this.publisher.publishToClient(
|
|
407
|
-
buildAppLogsMessage(
|
|
408
|
-
this.clientId,
|
|
409
|
-
{
|
|
410
|
-
projectId: appLogs.projectId,
|
|
411
|
-
logChunk
|
|
412
|
-
},
|
|
413
|
-
txId
|
|
414
|
-
),
|
|
415
|
-
logger.silly
|
|
416
|
-
)
|
|
417
|
-
);
|
|
418
|
-
} else {
|
|
419
|
-
this.liveUpdatesHandler.stopStream(appLogs.projectId);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
break;
|
|
423
|
-
}
|
|
424
|
-
case app_install_response: {
|
|
425
|
-
const payload = message.payload;
|
|
426
|
-
const { projectId } = payload.appInstallResponse;
|
|
427
|
-
if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
428
|
-
throw new Error(
|
|
429
|
-
`App install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
430
|
-
projectId
|
|
431
|
-
)})!`
|
|
84
|
+
// Instantiate & register message handlers for to-device and secureTunnel topics
|
|
85
|
+
this.connectionManager.registerHandler(
|
|
86
|
+
this.toDeviceTopic,
|
|
87
|
+
new DeviceAgentMessageHandler(
|
|
88
|
+
handlerContext,
|
|
89
|
+
(txId: string, errorMsg: string) => {
|
|
90
|
+
const msg = buildToClientStatusResponseMessage(
|
|
91
|
+
this.publisher.getClientId(),
|
|
92
|
+
{
|
|
93
|
+
status: keyMirrors.statusResponse.failure,
|
|
94
|
+
message: errorMsg
|
|
95
|
+
},
|
|
96
|
+
txId
|
|
432
97
|
);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
});
|
|
441
|
-
break;
|
|
442
|
-
}
|
|
443
|
-
case models_install_response: {
|
|
444
|
-
// This message doesn't have appReleaseHash in it's payload, but
|
|
445
|
-
// atomicCmd should be able to read it from the installed app
|
|
446
|
-
const payload = message.payload;
|
|
447
|
-
const { projectId } = payload.modelsInstallResponse;
|
|
448
|
-
if (txId !== this.txnMgr.getTransactionFromProject(projectId)) {
|
|
449
|
-
throw new Error(
|
|
450
|
-
`Model install response received a message for a transaction ID ${txId} that is not currently underway (${this.txnMgr.getTransactionFromProject(
|
|
451
|
-
projectId
|
|
452
|
-
)})!`
|
|
98
|
+
this.publisher.publishToClient(msg);
|
|
99
|
+
},
|
|
100
|
+
(txId: string) => {
|
|
101
|
+
const msg = buildToClientStatusResponseMessage(
|
|
102
|
+
this.publisher.getClientId(),
|
|
103
|
+
{ status: keyMirrors.statusResponse.success },
|
|
104
|
+
txId
|
|
453
105
|
);
|
|
106
|
+
this.publisher.publishToClient(msg);
|
|
454
107
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.handleModelsInstallCloudResponsePayload(payload, txId),
|
|
458
|
-
projectId,
|
|
459
|
-
txId,
|
|
460
|
-
start: false,
|
|
461
|
-
stepName: message.messageType
|
|
462
|
-
});
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
case status_response: {
|
|
466
|
-
const { failure } = keyMirrors.statusResponse;
|
|
467
|
-
if (message.payload.status === failure) {
|
|
468
|
-
this.txnMgr.completeTransaction(
|
|
469
|
-
txId,
|
|
470
|
-
buildToClientStatusResponseMessage(
|
|
471
|
-
this.clientId,
|
|
472
|
-
{
|
|
473
|
-
status: keyMirrors.statusResponse.failure,
|
|
474
|
-
message: message.payload.message
|
|
475
|
-
},
|
|
476
|
-
txId
|
|
477
|
-
)
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
case device_action: {
|
|
483
|
-
try {
|
|
484
|
-
this.publisher.publishToClient(
|
|
485
|
-
buildToClientStatusResponseMessage(
|
|
486
|
-
this.clientId,
|
|
487
|
-
{
|
|
488
|
-
status: keyMirrors.statusResponse.in_progress
|
|
489
|
-
},
|
|
490
|
-
txId
|
|
491
|
-
)
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
await this.handleDeviceAction(message.payload);
|
|
495
|
-
|
|
496
|
-
this.publisher.publishToClient(
|
|
497
|
-
buildToClientStatusResponseMessage(
|
|
498
|
-
this.clientId,
|
|
499
|
-
{
|
|
500
|
-
status: keyMirrors.statusResponse.success
|
|
501
|
-
},
|
|
502
|
-
txId
|
|
503
|
-
)
|
|
504
|
-
);
|
|
505
|
-
} catch (e) {
|
|
506
|
-
logger.error(
|
|
507
|
-
`There was a problem performing device action '${
|
|
508
|
-
message.payload.action
|
|
509
|
-
}'!\n${stringifyError(e)}`
|
|
510
|
-
);
|
|
511
|
-
this.publisher.publishToClient(
|
|
512
|
-
buildToClientStatusResponseMessage(
|
|
513
|
-
this.clientId,
|
|
514
|
-
{
|
|
515
|
-
status: keyMirrors.statusResponse.failure,
|
|
516
|
-
message: e.message
|
|
517
|
-
},
|
|
518
|
-
txId
|
|
519
|
-
)
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
default:
|
|
525
|
-
logger.error(
|
|
526
|
-
`Invalid client message: '${JSON.stringify(
|
|
527
|
-
{ topic, message, txId },
|
|
528
|
-
null,
|
|
529
|
-
2
|
|
530
|
-
)}'`
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
private handleAppStateControl = async (
|
|
536
|
-
payload: AppStateControlPayload
|
|
537
|
-
): Promise<boolean> => {
|
|
538
|
-
const { baseCommand, projectId } = payload;
|
|
539
|
-
switch (baseCommand) {
|
|
540
|
-
case keyMirrors.appStateControl.start:
|
|
541
|
-
await startApp({ projectId });
|
|
542
|
-
break;
|
|
543
|
-
case keyMirrors.appStateControl.stop:
|
|
544
|
-
await stopApp({ projectId });
|
|
545
|
-
break;
|
|
546
|
-
case keyMirrors.appStateControl.restart:
|
|
547
|
-
await restartApp({ projectId });
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
return true;
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
private handleAppVersionControl = async (
|
|
554
|
-
payload:
|
|
555
|
-
| AppVersionControlInstallPayload
|
|
556
|
-
| AppVersionControlUninstallPayload,
|
|
557
|
-
txId: string
|
|
558
|
-
): Promise<boolean> => {
|
|
559
|
-
switch (payload.baseCommand) {
|
|
560
|
-
case keyMirrors.appVersionControl.install: {
|
|
561
|
-
const { projectId, appReleaseHash } = payload;
|
|
562
|
-
|
|
563
|
-
const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
564
|
-
signedUrlsRequest: {
|
|
565
|
-
projectId,
|
|
566
|
-
appReleaseHash
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
const message = buildSignedUrlsRequestMessage(
|
|
570
|
-
this.clientId,
|
|
571
|
-
signedUrlsRequestPayload,
|
|
572
|
-
txId
|
|
573
|
-
);
|
|
574
|
-
await this.publishCloudRequest(message);
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
577
|
-
case keyMirrors.appVersionControl.uninstall: {
|
|
578
|
-
const { projectId } = payload;
|
|
579
|
-
await this.atomicApplicationUninstall(projectId);
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
default:
|
|
583
|
-
logger.warn(
|
|
584
|
-
`Ignore App Version Control packet: ${JSON.stringify(
|
|
585
|
-
payload,
|
|
586
|
-
null,
|
|
587
|
-
2
|
|
588
|
-
)}`
|
|
589
|
-
);
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
private handleAppInstallCloudResponsePayload = async (
|
|
595
|
-
payload: AppInstallResponsePayload
|
|
596
|
-
): Promise<boolean> => {
|
|
597
|
-
const {
|
|
598
|
-
projectId,
|
|
599
|
-
appReleaseHash,
|
|
600
|
-
appInstallPayload,
|
|
601
|
-
modelsInstallPayload
|
|
602
|
-
} = payload.appInstallResponse;
|
|
603
|
-
const signedUrlsPayload = {
|
|
604
|
-
appInstallPayload,
|
|
605
|
-
modelsInstallPayload
|
|
606
|
-
};
|
|
607
|
-
await this.atomicApplicationUpdate(async () => {
|
|
608
|
-
this.shadowHandler.clearProjectShadow(projectId);
|
|
609
|
-
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
610
|
-
}, projectId);
|
|
611
|
-
return true;
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
private handleModelsInstallCloudResponsePayload = async (
|
|
615
|
-
payload: ModelsInstallResponsePayload,
|
|
616
|
-
txId: string
|
|
617
|
-
): Promise<boolean> => {
|
|
618
|
-
const projectId = payload.modelsInstallResponse.projectId;
|
|
108
|
+
)
|
|
109
|
+
);
|
|
619
110
|
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (appCfgUpdate) {
|
|
628
|
-
await this.atomicApplicationUpdate(
|
|
629
|
-
async () =>
|
|
630
|
-
await updateModelsWithPresignedUrls({
|
|
631
|
-
projectId,
|
|
632
|
-
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
633
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
634
|
-
}),
|
|
635
|
-
projectId
|
|
636
|
-
);
|
|
637
|
-
}
|
|
111
|
+
const secureTunnelMessageHandler = new SecureTunnelMessageHandler(
|
|
112
|
+
handlerContext
|
|
113
|
+
);
|
|
114
|
+
this.connectionManager.registerHandler(
|
|
115
|
+
secureTunnelMessageHandler.getNotifyTopic(),
|
|
116
|
+
secureTunnelMessageHandler
|
|
117
|
+
);
|
|
638
118
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
true
|
|
648
|
-
);
|
|
649
|
-
}
|
|
119
|
+
this.connectionManager.registerHandler(
|
|
120
|
+
this.shadowHandler.shadowTopics.secureTunnel.updateDelta,
|
|
121
|
+
secureTunnelMessageHandler
|
|
122
|
+
);
|
|
123
|
+
this.connectionManager.registerHandler(
|
|
124
|
+
this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted,
|
|
125
|
+
secureTunnelMessageHandler
|
|
126
|
+
);
|
|
650
127
|
|
|
651
|
-
|
|
652
|
-
};
|
|
128
|
+
const jobHandler = new JobHandler(handlerContext);
|
|
653
129
|
|
|
654
|
-
|
|
655
|
-
const { system_restart } = keyMirrors.deviceAction;
|
|
656
|
-
switch (payload.action) {
|
|
657
|
-
case system_restart: {
|
|
658
|
-
await reboot();
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
default: {
|
|
662
|
-
logger.info(
|
|
663
|
-
`Unrecognized device action requested: '${payload.action}'.`
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
130
|
+
const JOB_HANDLER_TOPICS = jobHandler.getJobTopic();
|
|
668
131
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
132
|
+
this.connectionManager.registerHandler(
|
|
133
|
+
JOB_HANDLER_TOPICS.NOTIFY_NEXT,
|
|
134
|
+
jobHandler
|
|
135
|
+
);
|
|
672
136
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
137
|
+
this.connectionManager.registerHandler(
|
|
138
|
+
JOB_HANDLER_TOPICS.START_NEXT_ACCEPTED,
|
|
139
|
+
jobHandler
|
|
140
|
+
);
|
|
676
141
|
}
|
|
677
142
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
logger.error(`Failed to uninstall ${projectId}!\n${stringifyError(e)}`);
|
|
684
|
-
throw e;
|
|
685
|
-
}
|
|
143
|
+
/*=================================================================
|
|
144
|
+
Public interface
|
|
145
|
+
=================================================================*/
|
|
146
|
+
public getClientId(): string {
|
|
147
|
+
return this.clientId;
|
|
686
148
|
}
|
|
687
149
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
func: F,
|
|
691
|
-
projectId: string,
|
|
692
|
-
skipUpdateShadow?: boolean
|
|
693
|
-
): Promise<ReturnType<F>> {
|
|
694
|
-
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
695
|
-
// Reject the application update if app is present but not ready
|
|
696
|
-
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
697
|
-
throw new Error('Application already has installation in progress!');
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Try to create a backup, so that there is one available if something goes wrong in the next try:catch.
|
|
701
|
-
try {
|
|
702
|
-
await createAppBackup({ projectId });
|
|
703
|
-
} catch (e) {
|
|
704
|
-
logger.error(
|
|
705
|
-
`Could not create a backup for the project: ${projectId}!\n${stringifyError(
|
|
706
|
-
e
|
|
707
|
-
)}`
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
try {
|
|
713
|
-
const out: ReturnType<F> = await func();
|
|
714
|
-
if (!skipUpdateShadow)
|
|
715
|
-
await this.shadowHandler.updateProjectShadow(projectId);
|
|
716
|
-
return out;
|
|
717
|
-
} catch (errorAppUpdate) {
|
|
718
|
-
logger.error(
|
|
719
|
-
`Failed to update ${projectId}!\n${stringifyError(errorAppUpdate)}`
|
|
720
|
-
);
|
|
721
|
-
// If something goes wrong, first try to rollback
|
|
722
|
-
try {
|
|
723
|
-
await rollbackApp({ projectId });
|
|
724
|
-
} catch (errorRollbackApp) {
|
|
725
|
-
logger.error(
|
|
726
|
-
`Application rollback failed for ${projectId}!\n${stringifyError(
|
|
727
|
-
errorRollbackApp
|
|
728
|
-
)}`
|
|
729
|
-
);
|
|
730
|
-
// and if that fails, uninstall the app as a last resort.
|
|
731
|
-
try {
|
|
732
|
-
await this.atomicApplicationUninstall(projectId);
|
|
733
|
-
} catch {
|
|
734
|
-
// atomicApplicationUninstall logs failure, so there's nothing to do here.
|
|
735
|
-
}
|
|
736
|
-
throw new AaiError(
|
|
737
|
-
'Application update and rollback failed, uninstalled the application!',
|
|
738
|
-
{ cause: errorAppUpdate }
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
throw new Error(
|
|
742
|
-
'Application update failed, rolled the application back!',
|
|
743
|
-
{ cause: errorAppUpdate }
|
|
744
|
-
);
|
|
745
|
-
}
|
|
150
|
+
public isCmdInProgress(projectId: string): boolean {
|
|
151
|
+
return this.transactionManager.isOngoingTransactionForProjectID(projectId);
|
|
746
152
|
}
|
|
747
153
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (
|
|
755
|
-
appCfgUpdate?.updatedModels &&
|
|
756
|
-
Object.keys(appCfgUpdate.updatedModels).length
|
|
757
|
-
) {
|
|
758
|
-
// When there are model updates request signed URLs and wait to apply config changes
|
|
759
|
-
const { updatedModels } = appCfgUpdate;
|
|
760
|
-
|
|
761
|
-
logger.debug(
|
|
762
|
-
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
763
|
-
updatedModels
|
|
764
|
-
)}`
|
|
765
|
-
);
|
|
766
|
-
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
767
|
-
modelsOnlyUrlsRequest: {
|
|
768
|
-
projectId,
|
|
769
|
-
models: updatedModels
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
const message = buildSignedUrlsRequestMessage(
|
|
773
|
-
this.clientId,
|
|
774
|
-
modelsOnlyUrlsRequestPayload,
|
|
775
|
-
txId
|
|
776
|
-
);
|
|
777
|
-
this.publisher.publishToCloud(message);
|
|
778
|
-
|
|
779
|
-
this.txnMgr.setAppCfgUpdateToTx(txId, update);
|
|
780
|
-
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
if (appCfgUpdate) {
|
|
785
|
-
await this.atomicApplicationUpdate(async () => {
|
|
786
|
-
await pruneModels({
|
|
787
|
-
projectId,
|
|
788
|
-
appCfg: appCfgUpdate.newAppCfg
|
|
789
|
-
});
|
|
790
|
-
await updateAppCfg({
|
|
791
|
-
projectId,
|
|
792
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
793
|
-
});
|
|
794
|
-
}, projectId);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (envVarUpdate) {
|
|
798
|
-
await this.atomicApplicationUpdate(
|
|
799
|
-
async () =>
|
|
800
|
-
await this.shadowHandler.updateProjectEnvVars({
|
|
801
|
-
projectId,
|
|
802
|
-
envVars: envVarUpdate.envVars
|
|
803
|
-
}),
|
|
804
|
-
projectId,
|
|
805
|
-
true
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
return true;
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
812
|
-
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
813
|
-
topic,
|
|
814
|
-
payload: message,
|
|
815
|
-
clientToken: message.clientToken
|
|
816
|
-
});
|
|
817
|
-
if (shadowUpdates.length) {
|
|
818
|
-
const shadowUpdatePromises: Promise<void>[] = [];
|
|
819
|
-
for (const shadowUpdate of shadowUpdates) {
|
|
820
|
-
const projectId = shadowUpdate.projectId;
|
|
821
|
-
const txId = shadowUpdate.txId;
|
|
822
|
-
shadowUpdatePromises.push(
|
|
823
|
-
this.txnMgr
|
|
824
|
-
.runTransactionStep({
|
|
825
|
-
func: () =>
|
|
826
|
-
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
827
|
-
projectId,
|
|
828
|
-
txId,
|
|
829
|
-
start: true,
|
|
830
|
-
liveUpdatesPublishFn: async () =>
|
|
831
|
-
this.publisher.publishToClient(
|
|
832
|
-
buildToClientStatusResponseMessage(
|
|
833
|
-
this.clientId,
|
|
834
|
-
{ status: keyMirrors.statusResponse.in_progress },
|
|
835
|
-
txId
|
|
836
|
-
),
|
|
837
|
-
logger.silly
|
|
838
|
-
),
|
|
839
|
-
stepName: topic
|
|
840
|
-
})
|
|
841
|
-
.catch((e) => {
|
|
842
|
-
logger.error(
|
|
843
|
-
`There was an issue updating project shadow config for ${projectId}!\n${stringifyError(
|
|
844
|
-
e
|
|
845
|
-
)}`
|
|
846
|
-
);
|
|
847
|
-
})
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
await Promise.all(shadowUpdatePromises);
|
|
852
|
-
}
|
|
154
|
+
public async stop() {
|
|
155
|
+
// This method is currently only used by the CLI, and shadow messages can be
|
|
156
|
+
// lost since we aren't waiting for responses so sleep for a short time to
|
|
157
|
+
// receive them
|
|
158
|
+
await sleep(1000);
|
|
159
|
+
this.connectionManager.disconnect();
|
|
853
160
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
|
|
857
|
-
const state = getUpdateDeltaStateFromMessage(payload);
|
|
858
|
-
if (!state) {
|
|
859
|
-
logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const valid = validateSecureTunnelShadowUpdate(state);
|
|
863
|
-
if (!valid) {
|
|
864
|
-
logger.error(
|
|
865
|
-
`Error validating message: ${JSON.stringify(
|
|
866
|
-
{ payload, errors: validateSecureTunnelShadowUpdate.errors },
|
|
867
|
-
null,
|
|
868
|
-
2
|
|
869
|
-
)}`
|
|
870
|
-
);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
const secureTunnelUpdate =
|
|
874
|
-
await this.secureTunnelHandler.syncShadowToDeviceState(payload);
|
|
875
|
-
await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
|
|
161
|
+
public async handleMessage(topic: string, message: any) {
|
|
162
|
+
this.connectionManager.dispatch(topic, message);
|
|
876
163
|
}
|
|
877
164
|
}
|
|
878
165
|
|