@alwaysai/device-agent 1.4.0 → 2.0.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.
Files changed (313) hide show
  1. package/lib/application-control/config.d.ts.map +1 -1
  2. package/lib/application-control/config.js +10 -5
  3. package/lib/application-control/config.js.map +1 -1
  4. package/lib/application-control/environment-variables.d.ts +1 -5
  5. package/lib/application-control/environment-variables.d.ts.map +1 -1
  6. package/lib/application-control/environment-variables.js +9 -26
  7. package/lib/application-control/environment-variables.js.map +1 -1
  8. package/lib/application-control/environment-variables.test.js +27 -7
  9. package/lib/application-control/environment-variables.test.js.map +1 -1
  10. package/lib/application-control/index.d.ts +4 -4
  11. package/lib/application-control/index.d.ts.map +1 -1
  12. package/lib/application-control/index.js +1 -4
  13. package/lib/application-control/index.js.map +1 -1
  14. package/lib/application-control/install.d.ts.map +1 -1
  15. package/lib/application-control/install.js +9 -7
  16. package/lib/application-control/install.js.map +1 -1
  17. package/lib/application-control/models.d.ts +5 -11
  18. package/lib/application-control/models.d.ts.map +1 -1
  19. package/lib/application-control/models.js +27 -64
  20. package/lib/application-control/models.js.map +1 -1
  21. package/lib/application-control/status.d.ts.map +1 -1
  22. package/lib/application-control/status.js +10 -12
  23. package/lib/application-control/status.js.map +1 -1
  24. package/lib/application-control/utils.d.ts +0 -4
  25. package/lib/application-control/utils.d.ts.map +1 -1
  26. package/lib/application-control/utils.js +3 -26
  27. package/lib/application-control/utils.js.map +1 -1
  28. package/lib/cloud-connection/bootstrap-provision.js +3 -2
  29. package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
  30. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +11 -16
  31. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  32. package/lib/cloud-connection/device-agent-cloud-connection.js +295 -246
  33. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  34. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  35. package/lib/cloud-connection/device-agent.js +11 -9
  36. package/lib/cloud-connection/device-agent.js.map +1 -1
  37. package/lib/cloud-connection/live-updates-handler.d.ts +18 -27
  38. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  39. package/lib/cloud-connection/live-updates-handler.js +58 -170
  40. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  41. package/lib/cloud-connection/live-updates-handler.test.js +76 -54
  42. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  43. package/lib/cloud-connection/passthrough-handler.d.ts +9 -4
  44. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  45. package/lib/cloud-connection/passthrough-handler.js +95 -62
  46. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  47. package/lib/cloud-connection/shadow-handler.d.ts +5 -1
  48. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  49. package/lib/cloud-connection/shadow-handler.js +63 -31
  50. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  51. package/lib/cloud-connection/shadow-handler.test.js +45 -57
  52. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  53. package/lib/cloud-connection/shadow.d.ts.map +1 -1
  54. package/lib/cloud-connection/shadow.js +2 -1
  55. package/lib/cloud-connection/shadow.js.map +1 -1
  56. package/lib/cloud-connection/transaction-manager.d.ts +7 -2
  57. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  58. package/lib/cloud-connection/transaction-manager.js +29 -29
  59. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  60. package/lib/cloud-connection/transaction-manager.test.js +105 -3
  61. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  62. package/lib/device-control/device-control.d.ts +14 -6
  63. package/lib/device-control/device-control.d.ts.map +1 -1
  64. package/lib/device-control/device-control.js +172 -72
  65. package/lib/device-control/device-control.js.map +1 -1
  66. package/lib/docker/docker-compose.d.ts +14 -0
  67. package/lib/docker/docker-compose.d.ts.map +1 -0
  68. package/lib/docker/docker-compose.js +57 -0
  69. package/lib/docker/docker-compose.js.map +1 -0
  70. package/lib/index.js +2 -5
  71. package/lib/index.js.map +1 -1
  72. package/lib/infrastructure/agent-config.d.ts +46 -14
  73. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  74. package/lib/infrastructure/agent-config.js +36 -21
  75. package/lib/infrastructure/agent-config.js.map +1 -1
  76. package/lib/infrastructure/agent-config.test.js +6 -1
  77. package/lib/infrastructure/agent-config.test.js.map +1 -1
  78. package/lib/infrastructure/config-check-utility.d.ts +6 -0
  79. package/lib/infrastructure/config-check-utility.d.ts.map +1 -0
  80. package/lib/infrastructure/config-check-utility.js +67 -0
  81. package/lib/infrastructure/config-check-utility.js.map +1 -0
  82. package/lib/infrastructure/config-check-utility.test.d.ts +2 -0
  83. package/lib/infrastructure/config-check-utility.test.d.ts.map +1 -0
  84. package/lib/infrastructure/config-check-utility.test.js +109 -0
  85. package/lib/infrastructure/config-check-utility.test.js.map +1 -0
  86. package/lib/infrastructure/device-certificate.d.ts +10 -0
  87. package/lib/infrastructure/device-certificate.d.ts.map +1 -0
  88. package/lib/infrastructure/device-certificate.js +47 -0
  89. package/lib/infrastructure/device-certificate.js.map +1 -0
  90. package/lib/infrastructure/device-certificate.test.d.ts +2 -0
  91. package/lib/infrastructure/device-certificate.test.d.ts.map +1 -0
  92. package/lib/infrastructure/device-certificate.test.js +24 -0
  93. package/lib/infrastructure/device-certificate.test.js.map +1 -0
  94. package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts +2 -0
  95. package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts.map +1 -0
  96. package/lib/infrastructure/legacy-migration/legacy-file.test.js +61 -0
  97. package/lib/infrastructure/legacy-migration/legacy-file.test.js.map +1 -0
  98. package/lib/infrastructure/legacy-migration/legacy-files.d.ts +75 -0
  99. package/lib/infrastructure/legacy-migration/legacy-files.d.ts.map +1 -0
  100. package/lib/infrastructure/legacy-migration/legacy-files.js +75 -0
  101. package/lib/infrastructure/legacy-migration/legacy-files.js.map +1 -0
  102. package/lib/infrastructure/legacy-migration/legacy-migration.d.ts +6 -0
  103. package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -0
  104. package/lib/infrastructure/legacy-migration/legacy-migration.js +149 -0
  105. package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -0
  106. package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts +2 -0
  107. package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts.map +1 -0
  108. package/lib/infrastructure/legacy-migration/legacy-migration.test.js +226 -0
  109. package/lib/infrastructure/legacy-migration/legacy-migration.test.js.map +1 -0
  110. package/lib/infrastructure/require-files-present-ready.test.d.ts +2 -0
  111. package/lib/infrastructure/require-files-present-ready.test.d.ts.map +1 -0
  112. package/lib/infrastructure/require-files-present-ready.test.js +44 -0
  113. package/lib/infrastructure/require-files-present-ready.test.js.map +1 -0
  114. package/lib/infrastructure/required-config-checks.d.ts +2 -0
  115. package/lib/infrastructure/required-config-checks.d.ts.map +1 -0
  116. package/lib/infrastructure/required-config-checks.js +30 -0
  117. package/lib/infrastructure/required-config-checks.js.map +1 -0
  118. package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -1
  119. package/lib/infrastructure/tokens-and-device-cfg.js +11 -8
  120. package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -1
  121. package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -1
  122. package/lib/local-connection/rabbitmq-connection.js +21 -21
  123. package/lib/local-connection/rabbitmq-connection.js.map +1 -1
  124. package/lib/secure-tunneling/secure-tunneling.d.ts +15 -23
  125. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
  126. package/lib/secure-tunneling/secure-tunneling.js +52 -47
  127. package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
  128. package/lib/secure-tunneling/secure-tunneling.test.js +29 -31
  129. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
  130. package/lib/subcommands/app/analytics.d.ts.map +1 -1
  131. package/lib/subcommands/app/analytics.js +1 -2
  132. package/lib/subcommands/app/analytics.js.map +1 -1
  133. package/lib/subcommands/app/env-vars.d.ts +4 -0
  134. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  135. package/lib/subcommands/app/env-vars.js +52 -6
  136. package/lib/subcommands/app/env-vars.js.map +1 -1
  137. package/lib/subcommands/app/index.d.ts.map +1 -1
  138. package/lib/subcommands/app/index.js +1 -3
  139. package/lib/subcommands/app/index.js.map +1 -1
  140. package/lib/subcommands/app/models.d.ts +0 -11
  141. package/lib/subcommands/app/models.d.ts.map +1 -1
  142. package/lib/subcommands/app/models.js +2 -58
  143. package/lib/subcommands/app/models.js.map +1 -1
  144. package/lib/subcommands/app/shadow.d.ts.map +1 -1
  145. package/lib/subcommands/app/shadow.js +6 -5
  146. package/lib/subcommands/app/shadow.js.map +1 -1
  147. package/lib/subcommands/app/version.d.ts.map +1 -1
  148. package/lib/subcommands/app/version.js +2 -4
  149. package/lib/subcommands/app/version.js.map +1 -1
  150. package/lib/subcommands/config.d.ts +2 -0
  151. package/lib/subcommands/config.d.ts.map +1 -0
  152. package/lib/subcommands/config.js +39 -0
  153. package/lib/subcommands/config.js.map +1 -0
  154. package/lib/subcommands/device/clean.d.ts +1 -1
  155. package/lib/subcommands/device/clean.d.ts.map +1 -1
  156. package/lib/subcommands/device/clean.js +25 -15
  157. package/lib/subcommands/device/clean.js.map +1 -1
  158. package/lib/subcommands/device/get-info.d.ts +2 -0
  159. package/lib/subcommands/device/get-info.d.ts.map +1 -0
  160. package/lib/subcommands/device/get-info.js +36 -0
  161. package/lib/subcommands/device/get-info.js.map +1 -0
  162. package/lib/subcommands/device/index.d.ts.map +1 -1
  163. package/lib/subcommands/device/index.js +13 -2
  164. package/lib/subcommands/device/index.js.map +1 -1
  165. package/lib/subcommands/device/init.d.ts +5 -0
  166. package/lib/subcommands/device/init.d.ts.map +1 -0
  167. package/lib/subcommands/device/{device.js → init.js} +10 -49
  168. package/lib/subcommands/device/init.js.map +1 -0
  169. package/lib/subcommands/device/migrate.d.ts +2 -0
  170. package/lib/subcommands/device/migrate.d.ts.map +1 -0
  171. package/lib/subcommands/device/migrate.js +24 -0
  172. package/lib/subcommands/device/migrate.js.map +1 -0
  173. package/lib/subcommands/device/refresh.d.ts +2 -0
  174. package/lib/subcommands/device/refresh.d.ts.map +1 -0
  175. package/lib/subcommands/device/refresh.js +25 -0
  176. package/lib/subcommands/device/refresh.js.map +1 -0
  177. package/lib/subcommands/device/restart.d.ts +2 -0
  178. package/lib/subcommands/device/restart.d.ts.map +1 -0
  179. package/lib/subcommands/device/restart.js +14 -0
  180. package/lib/subcommands/device/restart.js.map +1 -0
  181. package/lib/subcommands/index.d.ts +1 -1
  182. package/lib/subcommands/index.d.ts.map +1 -1
  183. package/lib/subcommands/index.js +3 -1
  184. package/lib/subcommands/index.js.map +1 -1
  185. package/lib/subcommands/rabbitmq-connection.d.ts +1 -1
  186. package/lib/subcommands/rabbitmq-connection.d.ts.map +1 -1
  187. package/lib/util/aai-error.d.ts +12 -0
  188. package/lib/util/aai-error.d.ts.map +1 -0
  189. package/lib/util/aai-error.js +11 -0
  190. package/lib/util/aai-error.js.map +1 -0
  191. package/lib/util/aws-regions.d.ts +2 -0
  192. package/lib/util/aws-regions.d.ts.map +1 -0
  193. package/lib/util/{cloud-mode-ready.js → aws-regions.js} +2 -20
  194. package/lib/util/aws-regions.js.map +1 -0
  195. package/lib/util/check-for-updates.d.ts +3 -0
  196. package/lib/util/check-for-updates.d.ts.map +1 -0
  197. package/lib/util/check-for-updates.js +46 -0
  198. package/lib/util/check-for-updates.js.map +1 -0
  199. package/lib/util/clean-certs.d.ts.map +1 -1
  200. package/lib/util/clean-certs.js +5 -4
  201. package/lib/util/clean-certs.js.map +1 -1
  202. package/lib/util/directories.d.ts +4 -18
  203. package/lib/util/directories.d.ts.map +1 -1
  204. package/lib/util/directories.js +18 -32
  205. package/lib/util/directories.js.map +1 -1
  206. package/lib/util/file.d.ts +11 -0
  207. package/lib/util/file.d.ts.map +1 -0
  208. package/lib/util/file.js +127 -0
  209. package/lib/util/file.js.map +1 -0
  210. package/lib/util/file.test.d.ts +2 -0
  211. package/lib/util/file.test.d.ts.map +1 -0
  212. package/lib/util/file.test.js +87 -0
  213. package/lib/util/file.test.js.map +1 -0
  214. package/lib/util/get-device-id.d.ts.map +1 -1
  215. package/lib/util/get-device-id.js +7 -1
  216. package/lib/util/get-device-id.js.map +1 -1
  217. package/lib/util/http-client.js +3 -3
  218. package/lib/util/http-client.js.map +1 -1
  219. package/package.json +22 -19
  220. package/readme.md +15 -35
  221. package/src/application-control/config.ts +10 -13
  222. package/src/application-control/environment-variables.test.ts +28 -7
  223. package/src/application-control/environment-variables.ts +13 -40
  224. package/src/application-control/index.ts +3 -16
  225. package/src/application-control/install.ts +16 -10
  226. package/src/application-control/models.ts +40 -98
  227. package/src/application-control/status.ts +9 -7
  228. package/src/application-control/utils.ts +1 -29
  229. package/src/cloud-connection/bootstrap-provision.ts +7 -7
  230. package/src/cloud-connection/device-agent-cloud-connection.ts +647 -509
  231. package/src/cloud-connection/device-agent.ts +16 -7
  232. package/src/cloud-connection/live-updates-handler.test.ts +137 -64
  233. package/src/cloud-connection/live-updates-handler.ts +103 -234
  234. package/src/cloud-connection/passthrough-handler.ts +134 -75
  235. package/src/cloud-connection/shadow-handler.test.ts +45 -57
  236. package/src/cloud-connection/shadow-handler.ts +114 -56
  237. package/src/cloud-connection/shadow.ts +4 -1
  238. package/src/cloud-connection/transaction-manager.test.ts +127 -3
  239. package/src/cloud-connection/transaction-manager.ts +68 -39
  240. package/src/device-control/device-control.ts +179 -72
  241. package/src/docker/docker-compose.ts +61 -0
  242. package/src/index.ts +2 -6
  243. package/src/infrastructure/agent-config.test.ts +9 -2
  244. package/src/infrastructure/agent-config.ts +45 -46
  245. package/src/infrastructure/config-check-utility.test.ts +154 -0
  246. package/src/infrastructure/config-check-utility.ts +77 -0
  247. package/src/infrastructure/device-certificate.test.ts +40 -0
  248. package/src/infrastructure/device-certificate.ts +58 -0
  249. package/src/infrastructure/legacy-migration/legacy-file.test.ts +88 -0
  250. package/src/infrastructure/legacy-migration/legacy-files.ts +101 -0
  251. package/src/infrastructure/legacy-migration/legacy-migration.test.ts +396 -0
  252. package/src/infrastructure/legacy-migration/legacy-migration.ts +229 -0
  253. package/src/infrastructure/require-files-present-ready.test.ts +53 -0
  254. package/src/infrastructure/required-config-checks.ts +33 -0
  255. package/src/infrastructure/tokens-and-device-cfg.ts +12 -10
  256. package/src/local-connection/rabbitmq-connection.ts +28 -23
  257. package/src/secure-tunneling/secure-tunneling.test.ts +37 -39
  258. package/src/secure-tunneling/secure-tunneling.ts +74 -69
  259. package/src/subcommands/app/analytics.ts +2 -4
  260. package/src/subcommands/app/env-vars.ts +72 -9
  261. package/src/subcommands/app/index.ts +3 -11
  262. package/src/subcommands/app/models.ts +5 -81
  263. package/src/subcommands/app/shadow.ts +6 -5
  264. package/src/subcommands/app/version.ts +3 -4
  265. package/src/subcommands/config.ts +42 -0
  266. package/src/subcommands/device/clean.ts +32 -18
  267. package/src/subcommands/device/get-info.ts +49 -0
  268. package/src/subcommands/device/index.ts +13 -2
  269. package/src/subcommands/device/{device.ts → init.ts} +11 -69
  270. package/src/subcommands/device/migrate.ts +20 -0
  271. package/src/subcommands/device/refresh.ts +23 -0
  272. package/src/subcommands/device/restart.ts +11 -0
  273. package/src/subcommands/index.ts +3 -1
  274. package/src/util/aai-error.ts +20 -0
  275. package/src/util/{cloud-mode-ready.ts → aws-regions.ts} +0 -24
  276. package/src/util/check-for-updates.ts +53 -0
  277. package/src/util/clean-certs.ts +8 -4
  278. package/src/util/directories.ts +23 -67
  279. package/src/util/file.test.ts +90 -0
  280. package/src/util/file.ts +156 -0
  281. package/src/util/get-device-id.ts +7 -7
  282. package/src/util/http-client.ts +2 -2
  283. package/lib/docker/docker-compose-cmd.d.ts +0 -5
  284. package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
  285. package/lib/docker/docker-compose-cmd.js +0 -16
  286. package/lib/docker/docker-compose-cmd.js.map +0 -1
  287. package/lib/subcommands/device/device.d.ts +0 -7
  288. package/lib/subcommands/device/device.d.ts.map +0 -1
  289. package/lib/subcommands/device/device.js.map +0 -1
  290. package/lib/util/cloud-mode-ready.d.ts +0 -3
  291. package/lib/util/cloud-mode-ready.d.ts.map +0 -1
  292. package/lib/util/cloud-mode-ready.js.map +0 -1
  293. package/lib/util/download-file.d.ts +0 -6
  294. package/lib/util/download-file.d.ts.map +0 -1
  295. package/lib/util/download-file.js +0 -25
  296. package/lib/util/download-file.js.map +0 -1
  297. package/lib/util/fetch-with-timeout.d.ts +0 -4
  298. package/lib/util/fetch-with-timeout.d.ts.map +0 -1
  299. package/lib/util/fetch-with-timeout.js +0 -30
  300. package/lib/util/fetch-with-timeout.js.map +0 -1
  301. package/lib/util/parsing.d.ts +0 -2
  302. package/lib/util/parsing.d.ts.map +0 -1
  303. package/lib/util/parsing.js +0 -17
  304. package/lib/util/parsing.js.map +0 -1
  305. package/lib/util/safe-rimraf.d.ts +0 -2
  306. package/lib/util/safe-rimraf.d.ts.map +0 -1
  307. package/lib/util/safe-rimraf.js +0 -16
  308. package/lib/util/safe-rimraf.js.map +0 -1
  309. package/src/docker/docker-compose-cmd.ts +0 -15
  310. package/src/util/download-file.ts +0 -25
  311. package/src/util/fetch-with-timeout.ts +0 -35
  312. package/src/util/parsing.ts +0 -11
  313. package/src/util/safe-rimraf.ts +0 -14
@@ -1,16 +1,20 @@
1
1
  // eslint-disable-next-line
2
2
  const awsIot = require('aws-iot-device-sdk');
3
+ import { getTargetHardwareUuid } from 'alwaysai/lib/core/app';
3
4
  import {
5
+ DEVICE_CERTIFICATE_FILE_NAME,
6
+ DEVICE_CERTIFICATE_ID_FILE_NAME,
4
7
  DEVICE_PRIVATE_KEY_FILE_NAME,
8
+ LOCAL_CERT_AND_KEY_DIR
9
+ } from 'alwaysai/lib/infrastructure';
10
+ import { JsSpawner } from 'alwaysai/lib/util';
11
+ import {
5
12
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
6
- DEVICE_ID_FILE_NAME,
7
- DEVICE_CERTIFICATE_FILE_NAME
8
- } from '../util/directories';
13
+ getBootstrapCertificateDirectoryPath
14
+ } from '../infrastructure/device-certificate';
9
15
  import { getDeviceUuid } from '../util/get-device-id';
10
- import { JsSpawner } from 'alwaysai/lib/util';
11
16
  import { logger } from '../util/logger';
12
- import { getTargetHardwareUuid } from 'alwaysai/lib/core/app';
13
- import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/paths';
17
+
14
18
  // eslint-disable-next-line
15
19
  const process = require('process');
16
20
 
@@ -85,7 +89,10 @@ export class BootstrapAgent extends DeviceAgent {
85
89
 
86
90
  await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME, privateKey);
87
91
 
88
- await certSpawner.writeFile(DEVICE_ID_FILE_NAME, certificateId);
92
+ await certSpawner.writeFile(
93
+ DEVICE_CERTIFICATE_ID_FILE_NAME,
94
+ certificateId
95
+ );
89
96
 
90
97
  await certSpawner.writeFile(
91
98
  CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
@@ -111,6 +118,8 @@ export class BootstrapAgent extends DeviceAgent {
111
118
  }
112
119
  case '$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted': {
113
120
  logger.info('Device agent provisioning: success');
121
+ const spawner = JsSpawner();
122
+ await spawner.rimraf(getBootstrapCertificateDirectoryPath());
114
123
  process.exit();
115
124
  }
116
125
  }
@@ -1,89 +1,162 @@
1
- import { LiveUpdatesHandler } from './live-updates-handler';
2
- import { Publisher } from './publisher';
3
-
4
- global.setTimeout = jest.fn() as unknown as typeof setTimeout;
1
+ import {
2
+ DEFAULT_INTERVALS_MS,
3
+ LiveUpdatesHandler
4
+ } from './live-updates-handler';
5
+ import { keyMirrors } from '@alwaysai/device-agent-schemas';
6
+ import { randomUUID } from 'crypto';
7
+ import { PassThrough } from 'stream';
8
+
9
+ jest.mock('../util/sleep');
10
+ jest.mock('./messages');
11
+ jest.useFakeTimers();
5
12
 
6
13
  // https://github.com/facebook/react-native/issues/35701
7
14
  Object.defineProperty(global, 'performance', {
8
15
  writable: true
9
16
  });
10
17
 
11
- const testTrueToggles = {
12
- deviceStats: true,
13
- appState: true
14
- };
15
-
16
- const testFalseToggles = {
17
- deviceStats: false,
18
- appState: false
19
- };
20
-
21
- const mockClient = jest.fn();
22
- const clientId = 'test-client';
23
- const emptyTxId = '';
24
-
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);
18
+ const mockTransactionId = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d';
19
+ const mockProjectId = '12345678-abcd-abcd-abcd-0123456789ab';
31
20
 
32
21
  describe('Test Live Updates Handler', () => {
33
22
  let liveUpdatesHandler: LiveUpdatesHandler;
34
- let publisher: Publisher;
35
23
 
36
24
  beforeEach(() => {
37
- publisher = new Publisher(mockClient, clientId);
38
- liveUpdatesHandler = new LiveUpdatesHandler(publisher, clientId);
25
+ liveUpdatesHandler = new LiveUpdatesHandler();
39
26
  jest.clearAllMocks();
40
27
  });
41
28
 
42
- test('ignore subsequent enables', async () => {
43
- void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
44
- // called twice, once for device stats, once for app state
45
- expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
46
- // restartLiveUpdatesTimeout is always called once when handleToggles is called
47
- expect(jest.mocked(setTimeout)).toBeCalledTimes(1);
29
+ test('enable interval', async () => {
30
+ const mockPublishingFn = jest.fn();
31
+ const mockClearInterval = jest.spyOn(global, 'clearInterval');
32
+ const mockSetInterval = jest.spyOn(global, 'setInterval');
48
33
 
49
- // Second call -> should not call startPublishingLiveUpdates should not be called
50
- jest.clearAllMocks();
51
- void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
52
- expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
53
- expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
34
+ await liveUpdatesHandler.enable(
35
+ keyMirrors.toClientMessageType.device_stats,
36
+ mockPublishingFn
37
+ );
38
+
39
+ jest.advanceTimersByTime(DEFAULT_INTERVALS_MS);
40
+
41
+ expect(mockPublishingFn).toBeCalledTimes(2);
42
+ expect(mockClearInterval).toBeCalledWith(undefined);
43
+ expect(mockSetInterval).toBeCalledTimes(1);
44
+ });
45
+
46
+ test('subsequent enable resets interval with no duplicates', async () => {
47
+ const mockPublishingFn = jest.fn();
48
+ const mockSetInterval = jest.spyOn(global, 'setInterval');
49
+ const mockClearInterval = jest.spyOn(global, 'clearInterval');
50
+
51
+ await liveUpdatesHandler.enable(
52
+ keyMirrors.toClientMessageType.device_stats,
53
+ mockPublishingFn,
54
+ mockTransactionId
55
+ );
56
+ await liveUpdatesHandler.enable(
57
+ keyMirrors.toClientMessageType.device_stats,
58
+ mockPublishingFn,
59
+ mockTransactionId
60
+ );
61
+
62
+ jest.advanceTimersByTime(DEFAULT_INTERVALS_MS);
63
+
64
+ expect(mockClearInterval.mock.calls[0][0]).toEqual(undefined);
65
+ expect(mockClearInterval.mock.calls[1][0]).toEqual(
66
+ mockSetInterval.mock.results[0].value
67
+ );
68
+ expect(mockSetInterval).toBeCalledTimes(2);
69
+ expect(mockPublishingFn).toBeCalledTimes(3);
54
70
  });
55
71
 
56
72
  test('test disable live updates', async () => {
57
- // Test calling handleToggles one time, enabling it
58
- void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
59
- expect(mockStartPublishingLiveUpdates).toBeCalledTimes(2);
60
- expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
61
- expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
62
- expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
63
- expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
64
-
65
- // Check to see that attributes are properly set to false when disabled
66
- jest.clearAllMocks();
67
- void liveUpdatesHandler.handleToggles(testFalseToggles, emptyTxId);
68
- expect(mockStartPublishingLiveUpdates).toBeCalledTimes(0);
69
- expect(jest.mocked(setTimeout)).toHaveBeenCalledTimes(1);
70
- expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
71
- expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
72
- expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
73
+ const mockSetInterval = jest.spyOn(global, 'setInterval');
74
+ const mockClearInterval = jest.spyOn(global, 'clearInterval');
75
+
76
+ await liveUpdatesHandler.enable(
77
+ keyMirrors.toClientMessageType.device_stats,
78
+ async () => undefined,
79
+ mockTransactionId
80
+ );
81
+ liveUpdatesHandler.disable(
82
+ keyMirrors.toClientMessageType.device_stats,
83
+ mockTransactionId
84
+ );
85
+
86
+ expect(mockClearInterval).toHaveBeenLastCalledWith(
87
+ mockSetInterval.mock.results[0].value
88
+ );
73
89
  });
74
90
 
75
- test('timeout turns off live updates', async () => {
76
- jest.useFakeTimers({ legacyFakeTimers: true });
91
+ test('kill all timeout turns off all live updates', async () => {
92
+ const mockPublishingFn = jest.fn();
93
+ const mockSetInterval = jest.spyOn(global, 'setInterval');
94
+ const mockClearInterval = jest.spyOn(global, 'clearInterval');
95
+ jest.spyOn(global, 'setTimeout');
96
+
97
+ const numLivingIntervals = 5;
98
+ for (let i = 0; i < numLivingIntervals; i++) {
99
+ await liveUpdatesHandler.enable(
100
+ keyMirrors.toClientMessageType.status_response,
101
+ mockPublishingFn,
102
+ randomUUID()
103
+ );
104
+ }
105
+
106
+ mockSetInterval.mock.results.forEach((result) =>
107
+ clearInterval(result.value)
108
+ );
109
+ jest.runAllTimers();
77
110
 
78
- void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
79
- expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
80
- expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
81
- expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
111
+ expect(mockPublishingFn).toBeCalledTimes(numLivingIntervals);
112
+ expect(mockSetInterval).toBeCalledTimes(numLivingIntervals);
113
+ for (let i = 0; i < numLivingIntervals; i++) {
114
+ expect(mockClearInterval).toHaveBeenCalledWith(
115
+ mockSetInterval.mock.results[i].value
116
+ );
117
+ }
118
+ });
82
119
 
83
- jest.runAllTimers();
120
+ test('startStream streams data to publish', async () => {
121
+ const mockStream = new PassThrough();
122
+ const mockPublishingFn = jest
123
+ .fn()
124
+ .mockImplementation((logChunk: string) => undefined);
125
+
126
+ await liveUpdatesHandler.startStream(
127
+ mockProjectId,
128
+ async () => mockStream,
129
+ mockPublishingFn
130
+ );
131
+
132
+ const data = 'Data for stream';
133
+ mockStream.emit('data', data);
134
+
135
+ expect(mockPublishingFn).toBeCalledWith(data);
136
+
137
+ mockStream.end();
138
+ mockStream.destroy();
139
+ });
84
140
 
85
- expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
86
- expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
87
- expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
141
+ test('failure does not disable interval', async () => {
142
+ const mockPublishingFn = jest
143
+ .fn()
144
+ .mockImplementationOnce(() => undefined)
145
+ .mockImplementation(() => {
146
+ throw new Error('Manual throw for unit test');
147
+ });
148
+ jest.spyOn(global, 'setInterval');
149
+ const mockClearInterval = jest.spyOn(global, 'clearInterval');
150
+
151
+ await liveUpdatesHandler.enable(
152
+ keyMirrors.toClientMessageType.device_stats,
153
+ mockPublishingFn,
154
+ mockTransactionId
155
+ );
156
+
157
+ jest.advanceTimersByTime(DEFAULT_INTERVALS_MS);
158
+
159
+ expect(mockClearInterval).toBeCalledTimes(1);
160
+ expect(mockPublishingFn).toBeCalledTimes(2);
88
161
  });
89
162
  });
@@ -1,284 +1,153 @@
1
- import {
2
- AppLogsPayload,
3
- keyMirrors,
4
- LiveStateUpdatesTogglePayload,
5
- ToClientMessage,
6
- buildAppLogsMessage,
7
- buildAppStateMessage,
8
- buildDeviceStatsMessage,
9
- StatusResponsePayload,
10
- buildToClientStatusResponseMessage,
11
- ToClientMessageTypeValue
12
- } from '@alwaysai/device-agent-schemas';
13
- import { getAppLogs } from '../application-control';
1
+ import { ToClientMessageTypeValue } from '@alwaysai/device-agent-schemas';
14
2
  import { logger } from '../util/logger';
15
3
  import sleep from '../util/sleep';
16
- import { Publisher } from './publisher';
17
- import { getAppStatePayload, getDeviceStatsPayload } from './messages';
18
4
  import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
19
- import { read } from 'fs';
5
+ import { stringifyError } from 'alwaysai/lib/util';
20
6
 
21
- const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
7
+ const KILL_ALL_TIMEOUT_MS = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
22
8
  ? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
23
- : 60000;
9
+ : 80000;
10
+
11
+ export const DEFAULT_INTERVALS_MS = 5000;
12
+
13
+ interface IntervalOptions {
14
+ ms?: number;
15
+ }
24
16
 
25
17
  export class LiveUpdatesHandler {
26
- private publisher: Publisher;
27
- private clientId: string;
18
+ private killAllTimeout: ReturnType<typeof setTimeout>;
19
+ private livingIntervals: Record<string, ReturnType<typeof setInterval>> = {};
20
+ private livingStreams = new Set<string>();
28
21
 
29
- private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
30
- private liveUpdatesAlive = {
31
- [keyMirrors.toClientMessageType.device_stats]: false,
32
- [keyMirrors.toClientMessageType.app_state]: false,
33
- [keyMirrors.toClientMessageType.app_logs]: false,
34
- [keyMirrors.toClientMessageType.status_response]: false
35
- };
36
- private liveUpdatesSleepIntervals = {
37
- [keyMirrors.toClientMessageType.device_stats]: 5000,
38
- [keyMirrors.toClientMessageType.app_state]: 5000,
39
- [keyMirrors.toClientMessageType.app_logs]: 5000,
40
- [keyMirrors.toClientMessageType.status_response]: 5000
41
- };
22
+ constructor() {
23
+ logger.debug(
24
+ `Toggle live updates timeout set to ${KILL_ALL_TIMEOUT_MS} ms`
25
+ );
26
+ }
42
27
 
43
- private appLogStreams = new Set<string>();
44
- private transactionStatuses = new Set<string>();
28
+ public async enable(
29
+ intervalType: ToClientMessageTypeValue,
30
+ publishingFn: () => Promise<void>,
31
+ transactionId?: string,
32
+ options?: IntervalOptions
33
+ ) {
34
+ this.restartKillAllTimeout();
45
35
 
46
- private async getAppLogsWithRetry(
47
- projectId: string
48
- ): Promise<NodeJS.ReadableStream | null> {
49
- // Retry starting logs until it starts successfully or is terminated
50
- while (this.appLogStreams.has(projectId)) {
51
- try {
52
- return await getAppLogs({
53
- projectId,
54
- args: ['--tail', '100', '--no-log-prefix']
55
- });
56
- } catch (e) {
57
- logger.info(
58
- `Failed to start app logs, retrying in 1 second. Error: ${e}`
59
- );
60
- await sleep(1000);
61
- }
62
- }
63
- // Case where logs were disabled prior to connecting
64
- return null;
36
+ const key = this.generateIntervalKey(intervalType, transactionId);
37
+
38
+ clearInterval(this.livingIntervals[key]);
39
+
40
+ // initial publish
41
+ await publishingFn();
42
+
43
+ this.livingIntervals[key] = setInterval(
44
+ async () => {
45
+ try {
46
+ await publishingFn();
47
+ } catch (e) {
48
+ logger.error(
49
+ `Error getting live updates: ${JSON.stringify(
50
+ e
51
+ )}: intervalKey: ${key}. Continuing...`
52
+ );
53
+ }
54
+ },
55
+ options?.ms ? options.ms : DEFAULT_INTERVALS_MS
56
+ );
57
+ }
58
+
59
+ public disable(
60
+ intervalType: ToClientMessageTypeValue,
61
+ transactionId?: string
62
+ ) {
63
+ const key = this.generateIntervalKey(intervalType, transactionId);
64
+ clearInterval(this.livingIntervals[key]);
65
+ delete this.livingIntervals[key];
65
66
  }
66
67
 
67
- private async startAppLogStream(projectId: string, txId: string) {
68
+ public async startStream(
69
+ projectId: string,
70
+ streamGetter: () => Promise<NodeJS.ReadableStream | null>,
71
+ publishingFn: (logChunk: string) => void
72
+ ) {
68
73
  logger.info(`Starting log stream for ${projectId}`);
69
74
 
70
- this.appLogStreams.add(projectId);
75
+ this.livingStreams.add(projectId);
71
76
 
72
- const readable = await this.getAppLogsWithRetry(projectId);
77
+ const readable = await this.getStreamWithRetry(projectId, streamGetter);
73
78
 
74
79
  if (readable === null) {
75
80
  logger.info(
76
- `App log stream terminated for project ${projectId} prior to starting`
81
+ `Stream terminated prior to starting. Readable stream is null. ProjectId: ${projectId}`
77
82
  );
78
83
  return;
79
84
  }
80
85
 
81
86
  readable.on('data', async (chunk: Buffer) => {
82
- if (!this.appLogStreams.has(projectId)) {
87
+ if (!this.livingStreams.has(projectId)) {
83
88
  // why doesn't typescript know about this function?
84
89
  // @ts-ignore
85
90
  readable.destroy();
86
- logger.info(`App log stream terminated for project ${projectId}`);
91
+ logger.info(`Stream terminated. ProjectId: ${projectId}`);
87
92
  return;
88
93
  }
89
- const logStr = chunk.toString();
90
- const payload: AppLogsPayload = {
91
- projectId,
92
- logChunk: logStr
93
- };
94
- const message = buildAppLogsMessage(this.clientId, payload, txId);
95
- this.publisher.publishToClient(message, logger.silly);
94
+
95
+ publishingFn(chunk.toString());
96
96
  });
97
97
 
98
98
  readable.on('error', (error) => {
99
- logger.error(
100
- `App log stream terminated for project ${projectId}: ${error}`
101
- );
99
+ logger.error(`Stream terminated. ProjectId: ${projectId} - ${error}`);
102
100
  });
103
101
 
104
102
  readable.on('finished', () => {
105
- logger.info(`App logs finished piping for project ${projectId}`);
103
+ logger.info(`Strean complete. ProjectId: ${projectId}`);
106
104
  });
107
105
  }
108
106
 
109
- private continuePublishing(
110
- flag: ToClientMessageTypeValue,
111
- txId?: string
112
- ): boolean {
113
- switch (flag) {
114
- case keyMirrors.toClientMessageType.device_stats:
115
- case keyMirrors.toClientMessageType.app_state:
116
- return this.liveUpdatesAlive[flag];
117
- case keyMirrors.toClientMessageType.status_response: {
118
- if (!txId) {
119
- throw new Error(`Transaction ID not provided to continuePublishing!`);
120
- }
121
- return this.transactionStatuses.has(txId);
122
- }
123
- default:
124
- logger.error(`Unrecognized publishable flag ${flag}`);
125
- return false;
126
- }
127
- }
128
-
129
- private getLiveUpdatesInterval(flag: ToClientMessageTypeValue): number {
130
- const exists = this.liveUpdatesSleepIntervals[flag];
131
- if (exists) {
132
- return exists;
133
- }
134
- logger.error(`Unrecognized live updates flag ${flag}`);
135
- return -1;
136
- }
137
-
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
- private restartLiveUpdatesTimeout() {
148
- clearTimeout(this.liveUpdatesTimeout);
149
- this.liveUpdatesTimeout = setTimeout(() => {
150
- this.setLiveUpdates({
151
- deviceStats: false,
152
- appState: false
153
- });
154
- this.appLogStreams.clear();
155
- }, LIVE_UPDATES_TIMEOUT);
156
- }
157
-
158
- private async startPublishingLiveUpdates(
159
- messageType: ToClientMessageTypeValue,
160
- getMessage: () => Promise<ToClientMessage>,
161
- txId: string
162
- ) {
163
- logger.info(`Turned on live updates for ${messageType}`);
164
- try {
165
- while (this.continuePublishing(messageType, txId)) {
166
- const message = await getMessage();
167
- this.publisher.publishToClient(message, logger.silly);
168
-
169
- await sleep(this.getLiveUpdatesInterval(messageType));
170
- }
171
- } catch (e) {
172
- logger.error(
173
- `Error publishing live updates for ${messageType}: ${e.message}`
174
- );
175
- }
176
- logger.info(`Turned off live updates for ${messageType}`);
107
+ public stopStream(streamId: string) {
108
+ this.livingStreams.delete(streamId);
177
109
  }
178
110
 
179
111
  /*=================================================================
180
- Public interface
112
+ Private interface
181
113
  =================================================================*/
182
114
 
183
- constructor(publisher: Publisher, clientId: string) {
184
- this.publisher = publisher;
185
- this.clientId = clientId;
186
- logger.debug(
187
- `Toggle live updates timeout set to ${LIVE_UPDATES_TIMEOUT} ms`
188
- );
189
- }
190
-
191
- public getDeviceStatsLiveUpdates() {
192
- return this.liveUpdatesAlive.device_stats;
193
- }
194
-
195
- public getAppStateLiveUpdates() {
196
- return this.liveUpdatesAlive.app_state;
197
- }
198
-
199
- public getAppLogsLiveUpdates() {
200
- return this.liveUpdatesAlive.app_logs;
201
- }
202
-
203
- public async enableTransactionStatus(props: { txId: string }) {
204
- const { txId } = props;
205
- this.liveUpdatesAlive.status_response = true;
206
- if (!this.transactionStatuses.has(txId)) {
207
- this.transactionStatuses.add(txId);
208
- // Don't wait for this call to finish since it loops until disabled
209
- void this.startPublishingLiveUpdates(
210
- keyMirrors.toClientMessageType.status_response,
211
- async () => {
212
- const payload: StatusResponsePayload = {
213
- status: keyMirrors.statusResponse.in_progress
214
- };
215
- return buildToClientStatusResponseMessage(
216
- this.clientId,
217
- payload,
218
- txId
219
- );
220
- },
221
- txId
222
- );
115
+ private async getStreamWithRetry(
116
+ streamId: string,
117
+ streamGetter: () => Promise<NodeJS.ReadableStream | null>
118
+ ): Promise<NodeJS.ReadableStream | null> {
119
+ // Retry starting logs until it starts successfully or is terminated
120
+ while (this.livingStreams.has(streamId)) {
121
+ try {
122
+ return await streamGetter();
123
+ } catch (e) {
124
+ logger.info(
125
+ `Failed to start app logs, retrying in 1 second.\n${stringifyError(
126
+ e
127
+ )}`
128
+ );
129
+ await sleep(1000);
130
+ }
223
131
  }
132
+ // Case where logs were disabled prior to connecting
133
+ return null;
224
134
  }
225
135
 
226
- public async disableTransactionStatus(props: { txId: string }) {
227
- const { txId } = props;
228
- this.transactionStatuses.delete(txId);
229
-
230
- if (this.transactionStatuses.size === 0) {
231
- this.liveUpdatesAlive.status_response = false;
232
- }
136
+ private restartKillAllTimeout() {
137
+ clearTimeout(this.killAllTimeout);
138
+ this.killAllTimeout = setTimeout(() => {
139
+ Object.values(this.livingIntervals).forEach((interval) =>
140
+ clearInterval(interval)
141
+ );
142
+ this.livingIntervals = {};
143
+ this.livingStreams.clear();
144
+ }, KILL_ALL_TIMEOUT_MS);
233
145
  }
234
146
 
235
- public async handleToggles(
236
- toggles: LiveStateUpdatesTogglePayload,
237
- txId: string
147
+ private generateIntervalKey(
148
+ intervalType: ToClientMessageTypeValue,
149
+ transactionId?: string
238
150
  ) {
239
- const { deviceStats, appState, appLogs } = toggles;
240
- this.restartLiveUpdatesTimeout();
241
-
242
- if (deviceStats !== undefined) {
243
- const currentDeviceStats = this.getDeviceStatsLiveUpdates();
244
- this.liveUpdatesAlive.device_stats = deviceStats;
245
- if (deviceStats && currentDeviceStats !== true) {
246
- // Don't wait for this call to finish since it loops until disabled
247
- void this.startPublishingLiveUpdates(
248
- keyMirrors.toClientMessageType.device_stats,
249
- async () => {
250
- const payload = await getDeviceStatsPayload();
251
- return buildDeviceStatsMessage(this.clientId, payload, txId);
252
- },
253
- txId
254
- );
255
- }
256
- }
257
-
258
- if (appState !== undefined) {
259
- const currentAppState = this.getAppStateLiveUpdates();
260
- this.liveUpdatesAlive.app_state = appState;
261
- if (appState && currentAppState !== true) {
262
- // Don't wait for this call to finish since it loops until disabled
263
- void this.startPublishingLiveUpdates(
264
- keyMirrors.toClientMessageType.app_state,
265
- async () => {
266
- const payload = await getAppStatePayload();
267
- return buildAppStateMessage(this.clientId, payload, txId);
268
- },
269
- txId
270
- );
271
- }
272
- }
273
-
274
- if (appLogs !== undefined) {
275
- const currentAppLogs = this.getAppLogsLiveUpdates();
276
- if (appLogs.toggle && currentAppLogs !== true) {
277
- // Don't wait for this call to finish since it loops until disabled
278
- void this.startAppLogStream(appLogs.projectId, txId);
279
- } else {
280
- this.appLogStreams.delete(appLogs.projectId);
281
- }
282
- }
151
+ return intervalType + `${transactionId ? ':' + transactionId : ''}`;
283
152
  }
284
153
  }