@alwaysai/device-agent 1.4.0 → 1.5.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/config.js +2 -2
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +1 -0
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +5 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +24 -12
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +10 -12
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.js +2 -2
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -2
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +35 -15
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +3 -2
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +24 -21
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +132 -16
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +76 -62
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +2 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +5 -5
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +3 -0
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +11 -0
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +102 -0
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +10 -2
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +86 -10
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts +14 -0
- package/lib/docker/docker-compose.d.ts.map +1 -0
- package/lib/docker/docker-compose.js +56 -0
- package/lib/docker/docker-compose.js.map +1 -0
- package/lib/index.js +2 -5
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +45 -14
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +30 -15
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +3 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +11 -11
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +14 -22
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.js +34 -34
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.test.js +18 -18
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
- package/lib/subcommands/device/clean.js +5 -5
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/get-info.d.ts +2 -0
- package/lib/subcommands/device/get-info.d.ts.map +1 -0
- package/lib/subcommands/device/get-info.js +36 -0
- package/lib/subcommands/device/get-info.js.map +1 -0
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +11 -2
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/init.d.ts +5 -0
- package/lib/subcommands/device/init.d.ts.map +1 -0
- package/lib/subcommands/device/{device.js → init.js} +2 -41
- package/lib/subcommands/device/init.js.map +1 -0
- package/lib/subcommands/device/refresh.d.ts +2 -0
- package/lib/subcommands/device/refresh.d.ts.map +1 -0
- package/lib/subcommands/device/refresh.js +24 -0
- package/lib/subcommands/device/refresh.js.map +1 -0
- package/lib/subcommands/device/restart.d.ts +2 -0
- package/lib/subcommands/device/restart.d.ts.map +1 -0
- package/lib/subcommands/device/restart.js +14 -0
- package/lib/subcommands/device/restart.js.map +1 -0
- package/lib/util/check-for-updates.d.ts +3 -0
- package/lib/util/check-for-updates.d.ts.map +1 -0
- package/lib/util/check-for-updates.js +69 -0
- package/lib/util/check-for-updates.js.map +1 -0
- package/lib/util/file.d.ts +7 -0
- package/lib/util/file.d.ts.map +1 -0
- package/lib/util/file.js +66 -0
- package/lib/util/file.js.map +1 -0
- package/lib/util/file.test.d.ts +2 -0
- package/lib/util/file.test.d.ts.map +1 -0
- package/lib/util/file.test.js +87 -0
- package/lib/util/file.test.js.map +1 -0
- package/package.json +8 -7
- package/readme.md +3 -3
- package/src/application-control/config.ts +1 -1
- package/src/application-control/install.ts +1 -0
- package/src/application-control/models.ts +36 -13
- package/src/application-control/status.ts +9 -7
- package/src/application-control/utils.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +54 -30
- package/src/cloud-connection/live-updates-handler.test.ts +161 -20
- package/src/cloud-connection/live-updates-handler.ts +30 -26
- package/src/cloud-connection/passthrough-handler.ts +98 -76
- package/src/cloud-connection/shadow-handler.ts +19 -7
- package/src/cloud-connection/transaction-manager.test.ts +124 -0
- package/src/cloud-connection/transaction-manager.ts +15 -0
- package/src/device-control/device-control.ts +86 -11
- package/src/docker/docker-compose.ts +60 -0
- package/src/index.ts +2 -6
- package/src/infrastructure/agent-config.test.ts +3 -0
- package/src/infrastructure/agent-config.ts +38 -40
- package/src/local-connection/rabbitmq-connection.ts +8 -8
- package/src/secure-tunneling/secure-tunneling.test.ts +26 -26
- package/src/secure-tunneling/secure-tunneling.ts +48 -55
- package/src/subcommands/device/clean.ts +1 -1
- package/src/subcommands/device/get-info.ts +49 -0
- package/src/subcommands/device/index.ts +11 -2
- package/src/subcommands/device/{device.ts → init.ts} +0 -58
- package/src/subcommands/device/refresh.ts +22 -0
- package/src/subcommands/device/restart.ts +11 -0
- package/src/util/check-for-updates.ts +69 -0
- package/src/util/file.test.ts +90 -0
- package/src/util/file.ts +76 -0
- package/lib/docker/docker-compose-cmd.d.ts +0 -5
- package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
- package/lib/docker/docker-compose-cmd.js +0 -16
- package/lib/docker/docker-compose-cmd.js.map +0 -1
- package/lib/subcommands/device/device.d.ts +0 -7
- package/lib/subcommands/device/device.d.ts.map +0 -1
- package/lib/subcommands/device/device.js.map +0 -1
- package/lib/util/safe-rimraf.d.ts +0 -2
- package/lib/util/safe-rimraf.d.ts.map +0 -1
- package/lib/util/safe-rimraf.js +0 -16
- package/lib/util/safe-rimraf.js.map +0 -1
- package/src/docker/docker-compose-cmd.ts +0 -15
- package/src/util/safe-rimraf.ts +0 -14
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
buildToClientStatusResponseMessage,
|
|
16
16
|
StatusResponsePayload,
|
|
17
17
|
keyMirrors,
|
|
18
|
-
validateToDeviceAgentMessage
|
|
18
|
+
validateToDeviceAgentMessage,
|
|
19
|
+
validateSecureTunnelShadowUpdate
|
|
19
20
|
} from '@alwaysai/device-agent-schemas';
|
|
20
21
|
import { existsSync } from 'fs';
|
|
21
22
|
import {
|
|
@@ -46,12 +47,14 @@ import { logger } from '../util/logger';
|
|
|
46
47
|
import sleep from '../util/sleep';
|
|
47
48
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
48
49
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
49
|
-
import { PassthroughHandler
|
|
50
|
+
import { PassthroughHandler } from './passthrough-handler';
|
|
50
51
|
import { Publisher } from './publisher';
|
|
51
52
|
import { ShadowHandler, ShadowUpdate } from './shadow-handler';
|
|
52
53
|
import { TransactionManager } from './transaction-manager';
|
|
53
54
|
import { exec } from 'child_process';
|
|
54
55
|
import { promisify } from 'util';
|
|
56
|
+
import { pruneModels } from '../application-control/models';
|
|
57
|
+
import { getDeviceAgentVersion } from '../util/check-for-updates';
|
|
55
58
|
|
|
56
59
|
const exec_promise = promisify(exec);
|
|
57
60
|
|
|
@@ -69,8 +72,6 @@ export class DeviceAgentCloudConnection {
|
|
|
69
72
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
70
73
|
private readonly secureTunnelHandler =
|
|
71
74
|
SecureTunnelHandlerSingleton.getInstance();
|
|
72
|
-
// FIXME: Add support for multiple simultaneous project updates
|
|
73
|
-
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
74
75
|
|
|
75
76
|
private handleAppStateControl = async (
|
|
76
77
|
payload: AppStateControlPayload
|
|
@@ -152,16 +153,18 @@ export class DeviceAgentCloudConnection {
|
|
|
152
153
|
};
|
|
153
154
|
|
|
154
155
|
private handleModelsInstallCloudResponsePayload = async (
|
|
155
|
-
payload: ModelsInstallResponsePayload
|
|
156
|
+
payload: ModelsInstallResponsePayload,
|
|
157
|
+
txId: string
|
|
156
158
|
): Promise<boolean> => {
|
|
157
|
-
const
|
|
159
|
+
const projectId = payload.modelsInstallResponse.projectId;
|
|
160
|
+
|
|
161
|
+
const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
|
|
158
162
|
if (update === undefined) {
|
|
159
163
|
throw new Error(
|
|
160
164
|
'Unknown error while updating models via application config! No config present for model update.'
|
|
161
165
|
);
|
|
162
166
|
}
|
|
163
167
|
const { appCfgUpdate, envVarUpdate } = update;
|
|
164
|
-
const projectId = payload.modelsInstallResponse.projectId;
|
|
165
168
|
if (appCfgUpdate) {
|
|
166
169
|
await this.atomicApplicationUpdate(
|
|
167
170
|
async () =>
|
|
@@ -185,6 +188,7 @@ export class DeviceAgentCloudConnection {
|
|
|
185
188
|
true
|
|
186
189
|
);
|
|
187
190
|
}
|
|
191
|
+
|
|
188
192
|
return true;
|
|
189
193
|
};
|
|
190
194
|
|
|
@@ -305,19 +309,22 @@ export class DeviceAgentCloudConnection {
|
|
|
305
309
|
);
|
|
306
310
|
this.publisher.publishToCloud(message);
|
|
307
311
|
|
|
308
|
-
this.
|
|
312
|
+
this.txnMgr.setAppCfgUpdateToTx(txId, update);
|
|
313
|
+
|
|
309
314
|
return false;
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
if (appCfgUpdate) {
|
|
313
|
-
await this.atomicApplicationUpdate(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
await this.atomicApplicationUpdate(async () => {
|
|
319
|
+
await pruneModels({
|
|
320
|
+
projectId,
|
|
321
|
+
appCfg: appCfgUpdate.newAppCfg
|
|
322
|
+
});
|
|
323
|
+
await updateAppCfg({
|
|
324
|
+
projectId,
|
|
325
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
326
|
+
});
|
|
327
|
+
}, projectId);
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
if (envVarUpdate) {
|
|
@@ -369,6 +376,30 @@ export class DeviceAgentCloudConnection {
|
|
|
369
376
|
}
|
|
370
377
|
}
|
|
371
378
|
|
|
379
|
+
public async handleSecureTunnelMessage(payload: any): Promise<void> {
|
|
380
|
+
logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
|
|
381
|
+
const state = payload.state;
|
|
382
|
+
if (!state) {
|
|
383
|
+
logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const valid = validateSecureTunnelShadowUpdate(state);
|
|
387
|
+
if (!valid) {
|
|
388
|
+
logger.error(
|
|
389
|
+
`Error validating message: ${JSON.stringify(
|
|
390
|
+
{ payload, errors: validateSecureTunnelShadowUpdate.errors },
|
|
391
|
+
null,
|
|
392
|
+
2
|
|
393
|
+
)}`
|
|
394
|
+
);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const secureTunnelUpdate =
|
|
398
|
+
await this.secureTunnelHandler.syncShadowToDeviceState(payload);
|
|
399
|
+
await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
372
403
|
/*=================================================================
|
|
373
404
|
Public interface
|
|
374
405
|
=================================================================*/
|
|
@@ -526,7 +557,8 @@ export class DeviceAgentCloudConnection {
|
|
|
526
557
|
);
|
|
527
558
|
}
|
|
528
559
|
await this.txnMgr.runTransactionStep({
|
|
529
|
-
func: () =>
|
|
560
|
+
func: () =>
|
|
561
|
+
this.handleModelsInstallCloudResponsePayload(payload, txId),
|
|
530
562
|
projectId,
|
|
531
563
|
txId,
|
|
532
564
|
start: false,
|
|
@@ -607,10 +639,7 @@ export class DeviceAgentCloudConnection {
|
|
|
607
639
|
}
|
|
608
640
|
}
|
|
609
641
|
|
|
610
|
-
public async handleMessage(
|
|
611
|
-
topic: string,
|
|
612
|
-
message: ToDeviceAgentMessage | any
|
|
613
|
-
) {
|
|
642
|
+
public async handleMessage(topic: string, message: any) {
|
|
614
643
|
logger.debug(
|
|
615
644
|
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
616
645
|
);
|
|
@@ -629,14 +658,7 @@ export class DeviceAgentCloudConnection {
|
|
|
629
658
|
} else if (
|
|
630
659
|
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
631
660
|
) {
|
|
632
|
-
|
|
633
|
-
const reported = await this.secureTunnelHandler.syncShadowToDeviceState(
|
|
634
|
-
message
|
|
635
|
-
);
|
|
636
|
-
this.publisher.publish(
|
|
637
|
-
this.shadowHandler.shadowTopics.secureTunnel.update,
|
|
638
|
-
JSON.stringify({ state: { reported } })
|
|
639
|
-
);
|
|
661
|
+
await this.handleSecureTunnelMessage(message);
|
|
640
662
|
} else if (
|
|
641
663
|
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
642
664
|
) {
|
|
@@ -749,6 +771,9 @@ export class DeviceAgentCloudConnection {
|
|
|
749
771
|
}
|
|
750
772
|
|
|
751
773
|
export async function runDeviceAgentCloudInterface() {
|
|
774
|
+
logger.info(
|
|
775
|
+
`Starting alwaysAI Device Agent v${await getDeviceAgentVersion()}`
|
|
776
|
+
);
|
|
752
777
|
if (cloudModeReady()) {
|
|
753
778
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
754
779
|
await deviceAgent.setupHandlers();
|
|
@@ -756,7 +781,6 @@ export async function runDeviceAgentCloudInterface() {
|
|
|
756
781
|
const publisher = deviceAgent.publisher;
|
|
757
782
|
const passthroughHandler = new PassthroughHandler(publisher);
|
|
758
783
|
await passthroughHandler.setup();
|
|
759
|
-
await runChannel(passthroughHandler);
|
|
760
784
|
}
|
|
761
785
|
} else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
|
|
762
786
|
await bootstrapProvision();
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
2
2
|
import { Publisher } from './publisher';
|
|
3
|
+
import sleep from '../util/sleep';
|
|
4
|
+
import { getDeviceStatsPayload } from './messages';
|
|
5
|
+
|
|
6
|
+
jest.mock('../util/sleep');
|
|
7
|
+
jest.mock('./messages');
|
|
8
|
+
|
|
9
|
+
jest.mocked(getDeviceStatsPayload).mockResolvedValue({});
|
|
3
10
|
|
|
4
11
|
global.setTimeout = jest.fn() as unknown as typeof setTimeout;
|
|
5
12
|
|
|
@@ -18,64 +25,163 @@ const testFalseToggles = {
|
|
|
18
25
|
appState: false
|
|
19
26
|
};
|
|
20
27
|
|
|
21
|
-
const mockClient =
|
|
28
|
+
const mockClient = {
|
|
29
|
+
publish: jest.fn()
|
|
30
|
+
};
|
|
22
31
|
const clientId = 'test-client';
|
|
23
32
|
const emptyTxId = '';
|
|
24
33
|
|
|
25
|
-
// NOTE: this was the way I found to mock private class functions
|
|
26
|
-
const mockStartPublishingLiveUpdates = jest.spyOn(
|
|
27
|
-
LiveUpdatesHandler.prototype as any,
|
|
28
|
-
'startPublishingLiveUpdates'
|
|
29
|
-
);
|
|
30
|
-
mockStartPublishingLiveUpdates.mockResolvedValue(null);
|
|
31
|
-
|
|
32
34
|
describe('Test Live Updates Handler', () => {
|
|
33
35
|
let liveUpdatesHandler: LiveUpdatesHandler;
|
|
34
36
|
let publisher: Publisher;
|
|
35
37
|
|
|
36
38
|
beforeEach(() => {
|
|
39
|
+
mockClient.publish = jest.fn();
|
|
40
|
+
jest.mocked(sleep).mockImplementation(async () => {
|
|
41
|
+
return;
|
|
42
|
+
});
|
|
37
43
|
publisher = new Publisher(mockClient, clientId);
|
|
38
44
|
liveUpdatesHandler = new LiveUpdatesHandler(publisher, clientId);
|
|
39
45
|
jest.clearAllMocks();
|
|
40
46
|
});
|
|
41
47
|
|
|
48
|
+
test('enable device stats', async () => {
|
|
49
|
+
mockClient.publish = jest.fn().mockImplementation(async () => {
|
|
50
|
+
liveUpdatesHandler.disableDeviceStatsLiveUpdates();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const enable = { deviceStats: true };
|
|
54
|
+
const promises = await liveUpdatesHandler.handleToggles(enable, emptyTxId);
|
|
55
|
+
await Promise.all(promises);
|
|
56
|
+
|
|
57
|
+
expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
|
|
58
|
+
expect(mockClient.publish).toBeCalledTimes(1);
|
|
59
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('enable app state', async () => {
|
|
63
|
+
mockClient.publish = jest.fn().mockImplementation(async () => {
|
|
64
|
+
liveUpdatesHandler.disableAppStateLiveUpdates();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const enable = { appState: true };
|
|
68
|
+
const promises = await liveUpdatesHandler.handleToggles(enable, emptyTxId);
|
|
69
|
+
await Promise.all(promises);
|
|
70
|
+
|
|
71
|
+
expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
|
|
72
|
+
expect(mockClient.publish).toBeCalledTimes(1);
|
|
73
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
42
76
|
test('ignore subsequent enables', async () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
// Block on the first sleep call
|
|
78
|
+
let doneWaiting;
|
|
79
|
+
const waitPromise = new Promise(function (resolve) {
|
|
80
|
+
doneWaiting = resolve;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let startSleeping;
|
|
84
|
+
const sleepPromise = new Promise(function (resolve) {
|
|
85
|
+
startSleeping = resolve;
|
|
86
|
+
});
|
|
87
|
+
jest.mocked(sleep).mockImplementation(async () => {
|
|
88
|
+
startSleeping();
|
|
89
|
+
await waitPromise;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const enable = { deviceStats: true };
|
|
93
|
+
await liveUpdatesHandler.handleToggles(enable, emptyTxId);
|
|
94
|
+
await sleepPromise;
|
|
95
|
+
|
|
47
96
|
expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
|
|
97
|
+
expect(mockClient.publish).toBeCalledTimes(1);
|
|
98
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(1);
|
|
48
99
|
|
|
49
|
-
// Second call
|
|
100
|
+
// Second call
|
|
50
101
|
jest.clearAllMocks();
|
|
51
|
-
|
|
52
|
-
|
|
102
|
+
|
|
103
|
+
await liveUpdatesHandler.handleToggles(enable, emptyTxId);
|
|
104
|
+
|
|
53
105
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(mockClient.publish).toBeCalledTimes(0);
|
|
107
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(0);
|
|
108
|
+
|
|
109
|
+
// Disable and clean up
|
|
110
|
+
liveUpdatesHandler.disableDeviceStatsLiveUpdates();
|
|
111
|
+
doneWaiting();
|
|
54
112
|
});
|
|
55
113
|
|
|
56
114
|
test('test disable live updates', async () => {
|
|
115
|
+
let doneWaiting;
|
|
116
|
+
const waitPromise = new Promise(function (resolve) {
|
|
117
|
+
doneWaiting = resolve;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
let startSleeping;
|
|
121
|
+
const sleepPromise = new Promise(function (resolve) {
|
|
122
|
+
startSleeping = resolve;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Wait for two sleep calls, one for Device Stats and one for App State
|
|
126
|
+
jest
|
|
127
|
+
.mocked(sleep)
|
|
128
|
+
.mockImplementationOnce(async () => {
|
|
129
|
+
await waitPromise;
|
|
130
|
+
})
|
|
131
|
+
.mockImplementationOnce(async () => {
|
|
132
|
+
startSleeping();
|
|
133
|
+
await waitPromise;
|
|
134
|
+
});
|
|
135
|
+
|
|
57
136
|
// Test calling handleToggles one time, enabling it
|
|
58
|
-
|
|
59
|
-
|
|
137
|
+
await liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
138
|
+
await sleepPromise;
|
|
139
|
+
|
|
60
140
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(2);
|
|
61
142
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
62
143
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
|
|
63
144
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
64
145
|
|
|
65
146
|
// Check to see that attributes are properly set to false when disabled
|
|
66
147
|
jest.clearAllMocks();
|
|
67
|
-
|
|
68
|
-
|
|
148
|
+
await liveUpdatesHandler.handleToggles(testFalseToggles, emptyTxId);
|
|
149
|
+
|
|
69
150
|
expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
|
|
151
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(0);
|
|
70
152
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|
|
71
153
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
72
154
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
155
|
+
|
|
156
|
+
doneWaiting();
|
|
73
157
|
});
|
|
74
158
|
|
|
75
159
|
test('timeout turns off live updates', async () => {
|
|
76
160
|
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
161
|
+
let doneWaiting;
|
|
162
|
+
const waitPromise = new Promise(function (resolve) {
|
|
163
|
+
doneWaiting = resolve;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
let startSleeping;
|
|
167
|
+
const sleepPromise = new Promise(function (resolve) {
|
|
168
|
+
startSleeping = resolve;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Wait for two sleep calls, one for Device Stats and one for App State
|
|
172
|
+
jest
|
|
173
|
+
.mocked(sleep)
|
|
174
|
+
.mockImplementationOnce(async () => {
|
|
175
|
+
await waitPromise;
|
|
176
|
+
})
|
|
177
|
+
.mockImplementationOnce(async () => {
|
|
178
|
+
startSleeping();
|
|
179
|
+
await waitPromise;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
183
|
+
await sleepPromise;
|
|
77
184
|
|
|
78
|
-
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
79
185
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
80
186
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
|
|
81
187
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
@@ -85,5 +191,40 @@ describe('Test Live Updates Handler', () => {
|
|
|
85
191
|
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|
|
86
192
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
87
193
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
194
|
+
|
|
195
|
+
doneWaiting();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("failure doesn't kill publish loop", async () => {
|
|
199
|
+
// Block on the first sleep call
|
|
200
|
+
let doneWaiting;
|
|
201
|
+
const waitPromise = new Promise(function (resolve) {
|
|
202
|
+
doneWaiting = resolve;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
let startSleeping;
|
|
206
|
+
const sleepPromise = new Promise(function (resolve) {
|
|
207
|
+
startSleeping = resolve;
|
|
208
|
+
});
|
|
209
|
+
jest
|
|
210
|
+
.mocked(sleep)
|
|
211
|
+
.mockImplementationOnce(async () => {
|
|
212
|
+
throw new Error('Test error!');
|
|
213
|
+
})
|
|
214
|
+
.mockImplementationOnce(async () => {
|
|
215
|
+
startSleeping();
|
|
216
|
+
await waitPromise;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const enable = { deviceStats: true };
|
|
220
|
+
await liveUpdatesHandler.handleToggles(enable, emptyTxId);
|
|
221
|
+
await sleepPromise;
|
|
222
|
+
|
|
223
|
+
expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
|
|
224
|
+
expect(mockClient.publish).toBeCalledTimes(2);
|
|
225
|
+
expect(jest.mocked(sleep)).toBeCalledTimes(2);
|
|
226
|
+
|
|
227
|
+
liveUpdatesHandler.disableDeviceStatsLiveUpdates();
|
|
228
|
+
doneWaiting();
|
|
88
229
|
});
|
|
89
230
|
});
|
|
@@ -16,11 +16,10 @@ import sleep from '../util/sleep';
|
|
|
16
16
|
import { Publisher } from './publisher';
|
|
17
17
|
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
18
18
|
import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
|
|
19
|
-
import { read } from 'fs';
|
|
20
19
|
|
|
21
20
|
const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
|
|
22
21
|
? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
|
|
23
|
-
:
|
|
22
|
+
: 80000;
|
|
24
23
|
|
|
25
24
|
export class LiveUpdatesHandler {
|
|
26
25
|
private publisher: Publisher;
|
|
@@ -135,22 +134,11 @@ export class LiveUpdatesHandler {
|
|
|
135
134
|
return -1;
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
private setLiveUpdates(toggles: LiveStateUpdatesTogglePayload) {
|
|
139
|
-
if (toggles.deviceStats !== undefined) {
|
|
140
|
-
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
141
|
-
}
|
|
142
|
-
if (toggles.appState !== undefined) {
|
|
143
|
-
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
137
|
private restartLiveUpdatesTimeout() {
|
|
148
138
|
clearTimeout(this.liveUpdatesTimeout);
|
|
149
139
|
this.liveUpdatesTimeout = setTimeout(() => {
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
appState: false
|
|
153
|
-
});
|
|
140
|
+
this.disableAppStateLiveUpdates();
|
|
141
|
+
this.disableDeviceStatsLiveUpdates();
|
|
154
142
|
this.appLogStreams.clear();
|
|
155
143
|
}, LIVE_UPDATES_TIMEOUT);
|
|
156
144
|
}
|
|
@@ -161,17 +149,17 @@ export class LiveUpdatesHandler {
|
|
|
161
149
|
txId: string
|
|
162
150
|
) {
|
|
163
151
|
logger.info(`Turned on live updates for ${messageType}`);
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
while (this.continuePublishing(messageType, txId)) {
|
|
153
|
+
try {
|
|
166
154
|
const message = await getMessage();
|
|
167
155
|
this.publisher.publishToClient(message, logger.silly);
|
|
168
156
|
|
|
169
157
|
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
158
|
+
} catch (e) {
|
|
159
|
+
logger.error(
|
|
160
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
161
|
+
);
|
|
170
162
|
}
|
|
171
|
-
} catch (e) {
|
|
172
|
-
logger.error(
|
|
173
|
-
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
174
|
-
);
|
|
175
163
|
}
|
|
176
164
|
logger.info(`Turned off live updates for ${messageType}`);
|
|
177
165
|
}
|
|
@@ -188,14 +176,24 @@ export class LiveUpdatesHandler {
|
|
|
188
176
|
);
|
|
189
177
|
}
|
|
190
178
|
|
|
191
|
-
public getDeviceStatsLiveUpdates() {
|
|
179
|
+
public getDeviceStatsLiveUpdates(): boolean {
|
|
192
180
|
return this.liveUpdatesAlive.device_stats;
|
|
193
181
|
}
|
|
194
182
|
|
|
195
|
-
public
|
|
183
|
+
public disableDeviceStatsLiveUpdates() {
|
|
184
|
+
logger.info('Disabled live updates for device_stats');
|
|
185
|
+
this.liveUpdatesAlive.device_stats = false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public getAppStateLiveUpdates(): boolean {
|
|
196
189
|
return this.liveUpdatesAlive.app_state;
|
|
197
190
|
}
|
|
198
191
|
|
|
192
|
+
public disableAppStateLiveUpdates() {
|
|
193
|
+
logger.info('Disabled live updates for app_state');
|
|
194
|
+
this.liveUpdatesAlive.app_state = false;
|
|
195
|
+
}
|
|
196
|
+
|
|
199
197
|
public getAppLogsLiveUpdates() {
|
|
200
198
|
return this.liveUpdatesAlive.app_logs;
|
|
201
199
|
}
|
|
@@ -239,12 +237,14 @@ export class LiveUpdatesHandler {
|
|
|
239
237
|
const { deviceStats, appState, appLogs } = toggles;
|
|
240
238
|
this.restartLiveUpdatesTimeout();
|
|
241
239
|
|
|
240
|
+
const promises: Promise<void>[] = [];
|
|
241
|
+
|
|
242
242
|
if (deviceStats !== undefined) {
|
|
243
243
|
const currentDeviceStats = this.getDeviceStatsLiveUpdates();
|
|
244
244
|
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
245
245
|
if (deviceStats && currentDeviceStats !== true) {
|
|
246
246
|
// Don't wait for this call to finish since it loops until disabled
|
|
247
|
-
|
|
247
|
+
const deviceStatsPromise = this.startPublishingLiveUpdates(
|
|
248
248
|
keyMirrors.toClientMessageType.device_stats,
|
|
249
249
|
async () => {
|
|
250
250
|
const payload = await getDeviceStatsPayload();
|
|
@@ -252,6 +252,7 @@ export class LiveUpdatesHandler {
|
|
|
252
252
|
},
|
|
253
253
|
txId
|
|
254
254
|
);
|
|
255
|
+
promises.push(deviceStatsPromise);
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
|
|
@@ -260,7 +261,7 @@ export class LiveUpdatesHandler {
|
|
|
260
261
|
this.liveUpdatesAlive.app_state = appState;
|
|
261
262
|
if (appState && currentAppState !== true) {
|
|
262
263
|
// Don't wait for this call to finish since it loops until disabled
|
|
263
|
-
|
|
264
|
+
const appStatePromise = this.startPublishingLiveUpdates(
|
|
264
265
|
keyMirrors.toClientMessageType.app_state,
|
|
265
266
|
async () => {
|
|
266
267
|
const payload = await getAppStatePayload();
|
|
@@ -268,6 +269,7 @@ export class LiveUpdatesHandler {
|
|
|
268
269
|
},
|
|
269
270
|
txId
|
|
270
271
|
);
|
|
272
|
+
promises.push(appStatePromise);
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
|
|
@@ -275,10 +277,12 @@ export class LiveUpdatesHandler {
|
|
|
275
277
|
const currentAppLogs = this.getAppLogsLiveUpdates();
|
|
276
278
|
if (appLogs.toggle && currentAppLogs !== true) {
|
|
277
279
|
// Don't wait for this call to finish since it loops until disabled
|
|
278
|
-
|
|
280
|
+
const appLogPromise = this.startAppLogStream(appLogs.projectId, txId);
|
|
281
|
+
promises.push(appLogPromise);
|
|
279
282
|
} else {
|
|
280
283
|
this.appLogStreams.delete(appLogs.projectId);
|
|
281
284
|
}
|
|
282
285
|
}
|
|
286
|
+
return promises;
|
|
283
287
|
}
|
|
284
288
|
}
|