@alwaysai/device-agent 0.0.13 → 0.0.14
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/backup.js +3 -3
- package/lib/application-control/backup.js.map +1 -1
- package/lib/application-control/index.d.ts +4 -4
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +1 -4
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +41 -54
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +0 -4
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +13 -22
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +0 -6
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +3 -19
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +3 -0
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +50 -21
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/cmd-status.d.ts +16 -0
- package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
- package/lib/cloud-connection/cmd-status.js +49 -0
- package/lib/cloud-connection/cmd-status.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +10 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +73 -33
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +11 -0
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
- package/lib/cloud-connection/passthrough-handler.js +59 -0
- package/lib/cloud-connection/passthrough-handler.js.map +1 -0
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +14 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +2 -3
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +18 -4
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
- package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.test.js +321 -0
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -0
- package/lib/environment.d.ts +1 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +2 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +15 -48
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +0 -6
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/infrastructure/system-id.js +2 -2
- package/lib/infrastructure/system-id.js.map +1 -1
- package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -1
- package/lib/infrastructure/tokens-and-device-cfg.js +5 -9
- package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.d.ts +4 -0
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-connection.js +58 -0
- package/lib/local-connection/rabbitmq-connection.js.map +1 -0
- package/lib/subcommands/app/app.d.ts +2 -1
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +56 -23
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/device/clean.js +4 -4
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/device.d.ts +1 -1
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +7 -9
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/subcommands/index.d.ts +0 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/login.d.ts +0 -1
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +1 -9
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/fetch-with-timeout.d.ts +4 -0
- package/lib/util/fetch-with-timeout.d.ts.map +1 -0
- package/lib/util/fetch-with-timeout.js +15 -0
- package/lib/util/fetch-with-timeout.js.map +1 -0
- package/lib/util/require-logged-in-and-paid-plan.d.ts +2 -0
- package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
- package/lib/util/require-logged-in-and-paid-plan.js +18 -0
- package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
- package/lib/util/timer.d.ts +2 -0
- package/lib/util/timer.d.ts.map +1 -0
- package/lib/util/timer.js +6 -0
- package/lib/util/timer.js.map +1 -0
- package/package.json +20 -32
- package/readme.md +100 -89
- package/src/application-control/backup.ts +3 -3
- package/src/application-control/index.ts +0 -6
- package/src/application-control/install.ts +53 -73
- package/src/application-control/models.ts +7 -19
- package/src/application-control/status.ts +3 -19
- package/src/application-control/utils.ts +61 -22
- package/src/cloud-connection/cmd-status.ts +52 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +94 -47
- package/src/cloud-connection/passthrough-handler.ts +67 -0
- package/src/cloud-connection/publisher.ts +21 -0
- package/src/cloud-connection/shadow-handler.test.ts +361 -0
- package/src/cloud-connection/shadow-handler.ts +28 -7
- package/src/environment.ts +3 -0
- package/src/infrastructure/agent-config.test.ts +0 -7
- package/src/infrastructure/agent-config.ts +24 -2
- package/src/infrastructure/system-id.ts +1 -1
- package/src/infrastructure/tokens-and-device-cfg.ts +8 -13
- package/src/local-connection/rabbitmq-connection.ts +53 -0
- package/src/subcommands/app/app.ts +61 -27
- package/src/subcommands/device/clean.ts +4 -4
- package/src/subcommands/device/device.ts +8 -11
- package/src/subcommands/login.ts +1 -9
- package/src/util/fetch-with-timeout.ts +18 -0
- package/src/util/require-logged-in-and-paid-plan.ts +16 -0
- package/src/util/timer.ts +1 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
2
|
+
import { readAppCfgFile } from '../application-control';
|
|
3
|
+
import { Publisher } from './publisher';
|
|
4
|
+
import { ShadowHandler } from './shadow-handler';
|
|
5
|
+
|
|
6
|
+
jest.mock('../application-control');
|
|
7
|
+
jest.mock('./publisher');
|
|
8
|
+
const mockClient = jest.fn();
|
|
9
|
+
const clientId = 'test-client';
|
|
10
|
+
const projectId1 = 'test-project';
|
|
11
|
+
const projectId2 = 'test-project-2';
|
|
12
|
+
|
|
13
|
+
describe('Test Shadow Handler', () => {
|
|
14
|
+
let publisher: Publisher;
|
|
15
|
+
let shadowHandler: ShadowHandler;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
publisher = new Publisher(mockClient, clientId);
|
|
19
|
+
shadowHandler = new ShadowHandler(clientId, publisher);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test.skip('reject buffer payload', async () => {
|
|
23
|
+
//FIXME: Invalid input is silently ignored, need input validation
|
|
24
|
+
expect(() => {
|
|
25
|
+
shadowHandler.handleShadowTopic({
|
|
26
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
27
|
+
payload: Buffer.from('test-payload')
|
|
28
|
+
});
|
|
29
|
+
}).toThrow(Error);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('ignore message from self', async () => {
|
|
33
|
+
const ogAppCfg1: AppConfig = {
|
|
34
|
+
scripts: {
|
|
35
|
+
start: 'python app.py'
|
|
36
|
+
},
|
|
37
|
+
models: {
|
|
38
|
+
'alwaysai/mobilenet_ssd': 2
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
42
|
+
|
|
43
|
+
const appCfg1: AppConfig = {
|
|
44
|
+
scripts: {
|
|
45
|
+
start: 'python app.py'
|
|
46
|
+
},
|
|
47
|
+
models: {
|
|
48
|
+
'alwaysai/yolo_v3': 4
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const payload = {
|
|
52
|
+
[projectId1]: {
|
|
53
|
+
appConfig: JSON.stringify(appCfg1)
|
|
54
|
+
},
|
|
55
|
+
clientToken: clientId
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
59
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
60
|
+
payload
|
|
61
|
+
});
|
|
62
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('handle shadow get response with updated models', async () => {
|
|
66
|
+
const ogAppCfg1: AppConfig = {
|
|
67
|
+
scripts: {
|
|
68
|
+
start: 'python app.py'
|
|
69
|
+
},
|
|
70
|
+
models: {
|
|
71
|
+
'alwaysai/mobilenet_ssd': 3
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
76
|
+
const appCfg1: AppConfig = {
|
|
77
|
+
scripts: {
|
|
78
|
+
start: 'python app.py'
|
|
79
|
+
},
|
|
80
|
+
models: {
|
|
81
|
+
'alwaysai/yolo_v3': 4
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const payload = {
|
|
86
|
+
delta: {
|
|
87
|
+
[projectId1]: {
|
|
88
|
+
appConfig: JSON.stringify(appCfg1)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
94
|
+
topic: shadowHandler.shadowTopics.projects.getAccepted,
|
|
95
|
+
payload
|
|
96
|
+
});
|
|
97
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
98
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
99
|
+
projectId: projectId1,
|
|
100
|
+
newAppCfg: appCfg1,
|
|
101
|
+
updatedModels: {
|
|
102
|
+
'alwaysai/yolo_v3': 4
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('handle shadow delta without app config', async () => {
|
|
108
|
+
const ogAppCfg1: AppConfig = {
|
|
109
|
+
scripts: {
|
|
110
|
+
start: 'python app.py'
|
|
111
|
+
},
|
|
112
|
+
models: {
|
|
113
|
+
'alwaysai/mobilenet_ssd': 2
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
117
|
+
|
|
118
|
+
const payload = {
|
|
119
|
+
[projectId1]: {}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
123
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
124
|
+
payload
|
|
125
|
+
});
|
|
126
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('handle shadow delta with updated models', async () => {
|
|
130
|
+
const ogAppCfg1: AppConfig = {
|
|
131
|
+
scripts: {
|
|
132
|
+
start: 'python app.py'
|
|
133
|
+
},
|
|
134
|
+
models: {
|
|
135
|
+
'alwaysai/mobilenet_ssd': 2
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
140
|
+
const appCfg1: AppConfig = {
|
|
141
|
+
scripts: {
|
|
142
|
+
start: 'python app.py'
|
|
143
|
+
},
|
|
144
|
+
models: {
|
|
145
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
146
|
+
'alwaysai/yolo_v4': 5
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const payload = {
|
|
151
|
+
[projectId1]: {
|
|
152
|
+
appConfig: JSON.stringify(appCfg1)
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
157
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
158
|
+
payload
|
|
159
|
+
});
|
|
160
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
161
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
162
|
+
projectId: projectId1,
|
|
163
|
+
newAppCfg: appCfg1,
|
|
164
|
+
updatedModels: {
|
|
165
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
166
|
+
'alwaysai/yolo_v4': 5
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('handle shadow delta with updated models for two apps', async () => {
|
|
172
|
+
const ogAppCfg1: AppConfig = {
|
|
173
|
+
scripts: {
|
|
174
|
+
start: 'python app.py'
|
|
175
|
+
},
|
|
176
|
+
models: {
|
|
177
|
+
'alwaysai/mobilenet_ssd': 2
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
jest.mocked(readAppCfgFile).mockResolvedValueOnce(ogAppCfg1);
|
|
181
|
+
const ogAppCfg2: AppConfig = {
|
|
182
|
+
scripts: {
|
|
183
|
+
start: 'python app.py'
|
|
184
|
+
},
|
|
185
|
+
models: {
|
|
186
|
+
'alwaysai/yolo_v4': 5
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
jest.mocked(readAppCfgFile).mockResolvedValueOnce(ogAppCfg2);
|
|
190
|
+
|
|
191
|
+
const appCfg1: AppConfig = {
|
|
192
|
+
scripts: {
|
|
193
|
+
start: 'python app.py'
|
|
194
|
+
},
|
|
195
|
+
models: {
|
|
196
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
197
|
+
'alwaysai/yolo_v4': 5
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const appCfg2: AppConfig = {
|
|
201
|
+
scripts: {
|
|
202
|
+
start: 'python app.py'
|
|
203
|
+
},
|
|
204
|
+
models: {
|
|
205
|
+
'alwaysai/yolo_v4': 5,
|
|
206
|
+
'alwaysai/human_pose': 7
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const payload = {
|
|
210
|
+
[projectId1]: {
|
|
211
|
+
appConfig: JSON.stringify(appCfg1)
|
|
212
|
+
},
|
|
213
|
+
[projectId2]: {
|
|
214
|
+
appConfig: JSON.stringify(appCfg2)
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
219
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
220
|
+
payload
|
|
221
|
+
});
|
|
222
|
+
expect(appCfgUpdates.length).toBe(2);
|
|
223
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
224
|
+
projectId: projectId1,
|
|
225
|
+
newAppCfg: appCfg1,
|
|
226
|
+
updatedModels: {
|
|
227
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
228
|
+
'alwaysai/yolo_v4': 5
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
expect(appCfgUpdates[1]).toEqual({
|
|
232
|
+
projectId: projectId2,
|
|
233
|
+
newAppCfg: appCfg2,
|
|
234
|
+
updatedModels: {
|
|
235
|
+
'alwaysai/human_pose': 7
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('handle shadow delta with updated analytics', async () => {
|
|
241
|
+
const ogAppCfg1: AppConfig = {
|
|
242
|
+
scripts: {
|
|
243
|
+
start: 'python app.py'
|
|
244
|
+
},
|
|
245
|
+
models: {}
|
|
246
|
+
};
|
|
247
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
248
|
+
|
|
249
|
+
const appCfg1: AppConfig = {
|
|
250
|
+
scripts: {
|
|
251
|
+
start: 'python app.py'
|
|
252
|
+
},
|
|
253
|
+
models: {},
|
|
254
|
+
analytics: {
|
|
255
|
+
enable_cloud_publish: true
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const payload = {
|
|
260
|
+
[projectId1]: {
|
|
261
|
+
appConfig: JSON.stringify(appCfg1)
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
266
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
267
|
+
payload
|
|
268
|
+
});
|
|
269
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
270
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
271
|
+
projectId: projectId1,
|
|
272
|
+
newAppCfg: appCfg1
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('handle shadow delta with invalid app config', async () => {
|
|
277
|
+
const ogAppCfg1: AppConfig = {
|
|
278
|
+
scripts: {
|
|
279
|
+
start: 'python app.py'
|
|
280
|
+
},
|
|
281
|
+
models: {
|
|
282
|
+
'alwaysai/mobilenet_ssd': 2
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
287
|
+
const appCfg1 = {
|
|
288
|
+
scripts: {
|
|
289
|
+
start: 'python app.py'
|
|
290
|
+
},
|
|
291
|
+
models: {
|
|
292
|
+
'alwaysai/mobilenet_ssd': '3',
|
|
293
|
+
'alwaysai/yolo_v4': '5'
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const payload = {
|
|
298
|
+
[projectId1]: {
|
|
299
|
+
appConfig: JSON.stringify(appCfg1)
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
304
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
305
|
+
payload
|
|
306
|
+
});
|
|
307
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test.skip('publish app state', async () => {
|
|
311
|
+
// FIXME: For some reason publisher is not being called...
|
|
312
|
+
const testAppCfg: AppConfig = {
|
|
313
|
+
scripts: {
|
|
314
|
+
start: ''
|
|
315
|
+
},
|
|
316
|
+
models: {}
|
|
317
|
+
};
|
|
318
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(testAppCfg);
|
|
319
|
+
|
|
320
|
+
shadowHandler.publishAppState(projectId1);
|
|
321
|
+
expect(jest.mocked(readAppCfgFile)).toBeCalledWith({ projectId1 });
|
|
322
|
+
const packet = {
|
|
323
|
+
state: {
|
|
324
|
+
reported: {
|
|
325
|
+
[projectId1]: { appConfig: JSON.stringify(testAppCfg) }
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
clientToken: clientId
|
|
329
|
+
};
|
|
330
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
331
|
+
shadowHandler.shadowTopics.projects.update,
|
|
332
|
+
JSON.stringify(packet)
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('get shadow updates', async () => {
|
|
337
|
+
shadowHandler.getShadowUpdates();
|
|
338
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
339
|
+
shadowHandler.shadowTopics.projects.get,
|
|
340
|
+
JSON.stringify({
|
|
341
|
+
clientToken: clientId
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('delete project shadow', async () => {
|
|
347
|
+
shadowHandler.deleteProjectShadow(projectId1);
|
|
348
|
+
const packet = {
|
|
349
|
+
state: {
|
|
350
|
+
reported: {
|
|
351
|
+
[projectId1]: null
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
clientToken: clientId
|
|
355
|
+
};
|
|
356
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
357
|
+
shadowHandler.shadowTopics.projects.update,
|
|
358
|
+
JSON.stringify(packet)
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -7,7 +7,7 @@ import { logger } from '../util/logger';
|
|
|
7
7
|
import { Publisher } from './publisher';
|
|
8
8
|
import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
|
|
9
9
|
|
|
10
|
-
interface ShadowTopics {
|
|
10
|
+
export interface ShadowTopics {
|
|
11
11
|
projects: {
|
|
12
12
|
update: string;
|
|
13
13
|
get: string;
|
|
@@ -59,8 +59,9 @@ export class ShadowHandler {
|
|
|
59
59
|
if (projectShadow.appConfig) {
|
|
60
60
|
const newAppCfg = JSON.parse(projectShadow.appConfig);
|
|
61
61
|
if (!validateAppConfig(newAppCfg)) {
|
|
62
|
+
// FIXME: Raise an exception to be handled at higher layer
|
|
62
63
|
logger.error(
|
|
63
|
-
`Received invalid app config for ${projectId}
|
|
64
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
64
65
|
validateAppConfig.errors,
|
|
65
66
|
null,
|
|
66
67
|
2
|
|
@@ -78,6 +79,10 @@ export class ShadowHandler {
|
|
|
78
79
|
} else {
|
|
79
80
|
appConfigUpdates.push({ projectId, newAppCfg });
|
|
80
81
|
}
|
|
82
|
+
} else {
|
|
83
|
+
logger.warn(
|
|
84
|
+
`Ignoring shadow update for ${projectId} due to no app config`
|
|
85
|
+
);
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
return appConfigUpdates;
|
|
@@ -96,6 +101,16 @@ export class ShadowHandler {
|
|
|
96
101
|
const shadowName = topic.split('/')[5];
|
|
97
102
|
switch (topic) {
|
|
98
103
|
case this.shadowTopics.projects.updateDelta:
|
|
104
|
+
if (payload.clientToken === this.clientId) {
|
|
105
|
+
logger.debug(
|
|
106
|
+
`Ignoring message sent from self: ${JSON.stringify(
|
|
107
|
+
{ topic, payload },
|
|
108
|
+
null,
|
|
109
|
+
2
|
|
110
|
+
)}`
|
|
111
|
+
);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
99
114
|
return await this.handleNamedShadowUpdate({ delta: payload });
|
|
100
115
|
case this.shadowTopics.projects.getAccepted:
|
|
101
116
|
if (payload['delta']) {
|
|
@@ -143,12 +158,18 @@ export class ShadowHandler {
|
|
|
143
158
|
);
|
|
144
159
|
}
|
|
145
160
|
|
|
146
|
-
public deleteProjectShadow() {
|
|
161
|
+
public deleteProjectShadow(projectId: string) {
|
|
162
|
+
const packet = {
|
|
163
|
+
state: {
|
|
164
|
+
reported: {
|
|
165
|
+
[projectId]: null
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
clientToken: this.clientId
|
|
169
|
+
};
|
|
147
170
|
this.publisher.publish(
|
|
148
|
-
|
|
149
|
-
JSON.stringify(
|
|
150
|
-
clientToken: this.clientId
|
|
151
|
-
})
|
|
171
|
+
this.shadowTopics.projects.update,
|
|
172
|
+
JSON.stringify(packet)
|
|
152
173
|
);
|
|
153
174
|
}
|
|
154
175
|
}
|
package/src/environment.ts
CHANGED
|
@@ -10,6 +10,9 @@ export const ALWAYSAI_DEVICE_AGENT_MODE =
|
|
|
10
10
|
process.env.ALWAYSAI_DEVICE_AGENT_MODE;
|
|
11
11
|
export const ALWAYSAI_LOG_LEVEL = process.env.AAI_LOG_LEVEL;
|
|
12
12
|
export const ALWAYSAI_LOG_TO_CONSOLE = process.env.ALWAYSAI_LOG_TO_CONSOLE;
|
|
13
|
+
export const ALWAYSAI_ANALYTICS_PASSTHROUGH = parseBoolean(
|
|
14
|
+
process.env.ALWAYSAI_ANALYTICS_PASSTHROUGH
|
|
15
|
+
);
|
|
13
16
|
|
|
14
17
|
function parseOsPlatform(str: string | undefined): NodeJS.Platform {
|
|
15
18
|
switch (str) {
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import * as tempy from 'tempy';
|
|
2
2
|
import { AgentConfigFile } from './agent-config';
|
|
3
3
|
|
|
4
|
-
const mockAgentModeGetter = jest.fn().mockReturnValue(undefined);
|
|
5
|
-
jest.mock('../environment', () => ({
|
|
6
|
-
get ALWAYSAI_DEVICE_AGENT_MODE() {
|
|
7
|
-
return mockAgentModeGetter();
|
|
8
|
-
}
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
4
|
const configFile = AgentConfigFile(tempy.directory());
|
|
12
5
|
|
|
13
6
|
describe('Test Agent Config', () => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ConfigFileSchema,
|
|
3
|
+
ConfigFileSchemaReturnType
|
|
4
|
+
} from '@alwaysai/config-nodejs';
|
|
2
5
|
import Ajv, { JSONSchemaType } from 'ajv';
|
|
3
6
|
import { homedir } from 'os';
|
|
4
7
|
import { join } from 'path';
|
|
@@ -55,7 +58,26 @@ const ALWAYSAI_CONFIG_DIR = join(homedir(), '.config', 'alwaysai');
|
|
|
55
58
|
|
|
56
59
|
const AGENT_CONFIG_FILE_NAME = 'alwaysai.agent.json';
|
|
57
60
|
|
|
58
|
-
export
|
|
61
|
+
export interface AgentJsonFileReturnType
|
|
62
|
+
extends ConfigFileSchemaReturnType<AgentConfig> {
|
|
63
|
+
name: string;
|
|
64
|
+
getApps: () => Promise<InstalledAppConfig[]>;
|
|
65
|
+
getReadyApps;
|
|
66
|
+
getApp;
|
|
67
|
+
isAppPresent;
|
|
68
|
+
isAppReady;
|
|
69
|
+
removeApp;
|
|
70
|
+
setAppInstalling;
|
|
71
|
+
setAppInstalled;
|
|
72
|
+
setAppUninstalled;
|
|
73
|
+
getAppVersion;
|
|
74
|
+
setAppBackup;
|
|
75
|
+
getAppBackup;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function AgentConfigFile(
|
|
79
|
+
dir = ALWAYSAI_CONFIG_DIR
|
|
80
|
+
): AgentJsonFileReturnType {
|
|
59
81
|
const path = join(dir, AGENT_CONFIG_FILE_NAME);
|
|
60
82
|
const initialValue: AgentConfig = {
|
|
61
83
|
applications: []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { SystemId } from 'alwaysai/lib/constants';
|
|
1
2
|
import { DeviceConfigFile } from 'alwaysai/lib/core/device';
|
|
2
3
|
import { ALWAYSAI_SYSTEM_ID } from 'alwaysai/lib/environment';
|
|
3
|
-
import { SystemId } from 'alwaysai/lib/infrastructure';
|
|
4
4
|
|
|
5
5
|
export function getSystemId() {
|
|
6
6
|
if (ALWAYSAI_SYSTEM_ID) {
|
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeviceTokens,
|
|
3
|
-
writeOrValidateDeviceCfgFile
|
|
4
|
-
} from 'alwaysai/lib/components/device';
|
|
5
|
-
import { checkUserIsLoggedInComponent } from 'alwaysai/lib/components/user';
|
|
6
1
|
import { LOCAL_AAI_CFG_DIR } from 'alwaysai/lib/constants';
|
|
7
|
-
import { checkPaidPlan } from 'alwaysai/lib/core/project';
|
|
8
|
-
import { JsSpawner, writeTokens } from 'alwaysai/lib/util';
|
|
9
2
|
import { logger } from '../util/logger';
|
|
10
3
|
import { microServiceHttpClient } from '../util/http-client';
|
|
4
|
+
import { requireLoggedInAndPaidPlan } from '../util/require-logged-in-and-paid-plan';
|
|
5
|
+
import {
|
|
6
|
+
DeviceTokens,
|
|
7
|
+
writeOrValidateDeviceCfgFile,
|
|
8
|
+
writeTokens
|
|
9
|
+
} from 'alwaysai/lib/core/device';
|
|
10
|
+
import { JsSpawner } from 'alwaysai/lib/util';
|
|
11
11
|
|
|
12
12
|
// NOTE: This closely follows the flow of deviceCheckAndUpdateComponent in the CLI
|
|
13
13
|
export async function writeTokenAndDeviceCfg(props: { deviceUuid: string }) {
|
|
14
14
|
const { deviceUuid } = props;
|
|
15
|
-
await
|
|
16
|
-
if (!(await checkPaidPlan())) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`This action only supported for Enterprise alwaysAI accounts!`
|
|
19
|
-
);
|
|
20
|
-
}
|
|
15
|
+
await requireLoggedInAndPaidPlan();
|
|
21
16
|
const { accessToken, refreshToken, idToken } = await microServiceHttpClient(
|
|
22
17
|
'token-service',
|
|
23
18
|
'create-device-tokens',
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { JsSpawner } from 'alwaysai/lib/util';
|
|
2
|
+
import { logger } from '../util/logger';
|
|
3
|
+
import sleep from '../util/sleep';
|
|
4
|
+
|
|
5
|
+
export async function checkRabbitMQContainerRunning() {
|
|
6
|
+
const spawner = JsSpawner();
|
|
7
|
+
return await spawner.run({
|
|
8
|
+
exe: 'docker',
|
|
9
|
+
args: ['ps', '-q', '--filter', 'ancestor=rabbitmq']
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function setupRabbitMQContainer() {
|
|
14
|
+
const spawner = JsSpawner();
|
|
15
|
+
const rabbitMqContainerRunning = await checkRabbitMQContainerRunning();
|
|
16
|
+
logger.debug('Checking for running RabbitMQ container');
|
|
17
|
+
if (!rabbitMqContainerRunning) {
|
|
18
|
+
logger.debug('Setting up RabbitMQ container');
|
|
19
|
+
await spawner.run({
|
|
20
|
+
exe: 'docker',
|
|
21
|
+
args: [
|
|
22
|
+
'run',
|
|
23
|
+
'--rm',
|
|
24
|
+
'-p',
|
|
25
|
+
'5672:5672',
|
|
26
|
+
'-d',
|
|
27
|
+
'--hostname',
|
|
28
|
+
'my-rabbit',
|
|
29
|
+
'rabbitmq'
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
await sleep(8000);
|
|
33
|
+
while (!(await checkRabbitMQContainerRunning())) {
|
|
34
|
+
await sleep(5000);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function stopRabbitMQContainer() {
|
|
40
|
+
const spawner = JsSpawner();
|
|
41
|
+
const rabbitMqContainer = await spawner.run({
|
|
42
|
+
exe: 'docker',
|
|
43
|
+
args: ['ps', '-q', '--filter', 'ancestor=rabbitmq']
|
|
44
|
+
});
|
|
45
|
+
if (!rabbitMqContainer) {
|
|
46
|
+
logger.debug('No RabbitMQ container running');
|
|
47
|
+
} else {
|
|
48
|
+
await spawner.run({
|
|
49
|
+
exe: 'docker',
|
|
50
|
+
args: ['stop', `${rabbitMqContainer}`]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|