@qwickapps/server 1.3.1 → 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.
Files changed (395) hide show
  1. package/README.md +157 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +114 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/types.d.ts +19 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +15 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
  12. package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
  13. package/dist/plugins/auth/adapter-wrapper.js +166 -0
  14. package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
  15. package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
  16. package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
  17. package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
  18. package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
  19. package/dist/plugins/auth/config-store.d.ts +11 -0
  20. package/dist/plugins/auth/config-store.d.ts.map +1 -0
  21. package/dist/plugins/auth/config-store.js +232 -0
  22. package/dist/plugins/auth/config-store.js.map +1 -0
  23. package/dist/plugins/auth/config-store.test.d.ts +7 -0
  24. package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
  25. package/dist/plugins/auth/config-store.test.js +299 -0
  26. package/dist/plugins/auth/config-store.test.js.map +1 -0
  27. package/dist/plugins/auth/env-config.d.ts +51 -1
  28. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  29. package/dist/plugins/auth/env-config.js +640 -7
  30. package/dist/plugins/auth/env-config.js.map +1 -1
  31. package/dist/plugins/auth/index.d.ts +6 -2
  32. package/dist/plugins/auth/index.d.ts.map +1 -1
  33. package/dist/plugins/auth/index.js +5 -1
  34. package/dist/plugins/auth/index.js.map +1 -1
  35. package/dist/plugins/auth/types.d.ts +106 -0
  36. package/dist/plugins/auth/types.d.ts.map +1 -1
  37. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
  38. package/dist/plugins/bans/bans-plugin.js +12 -3
  39. package/dist/plugins/bans/bans-plugin.js.map +1 -1
  40. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
  41. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
  42. package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
  43. package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
  44. package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
  45. package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
  46. package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
  47. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
  48. package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
  49. package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
  50. package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
  51. package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
  52. package/dist/plugins/devices/adapters/index.d.ts +12 -0
  53. package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
  54. package/dist/plugins/devices/adapters/index.js +10 -0
  55. package/dist/plugins/devices/adapters/index.js.map +1 -0
  56. package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
  57. package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
  58. package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
  59. package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
  60. package/dist/plugins/devices/devices-plugin.d.ts +70 -0
  61. package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
  62. package/dist/plugins/devices/devices-plugin.js +453 -0
  63. package/dist/plugins/devices/devices-plugin.js.map +1 -0
  64. package/dist/plugins/devices/index.d.ts +18 -0
  65. package/dist/plugins/devices/index.d.ts.map +1 -0
  66. package/dist/plugins/devices/index.js +18 -0
  67. package/dist/plugins/devices/index.js.map +1 -0
  68. package/dist/plugins/devices/stores/index.d.ts +9 -0
  69. package/dist/plugins/devices/stores/index.d.ts.map +1 -0
  70. package/dist/plugins/devices/stores/index.js +9 -0
  71. package/dist/plugins/devices/stores/index.js.map +1 -0
  72. package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
  73. package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
  74. package/dist/plugins/devices/stores/postgres-store.js +199 -0
  75. package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
  76. package/dist/plugins/devices/token-utils.d.ts +100 -0
  77. package/dist/plugins/devices/token-utils.d.ts.map +1 -0
  78. package/dist/plugins/devices/token-utils.js +162 -0
  79. package/dist/plugins/devices/token-utils.js.map +1 -0
  80. package/dist/plugins/devices/types.d.ts +307 -0
  81. package/dist/plugins/devices/types.d.ts.map +1 -0
  82. package/dist/plugins/devices/types.js +10 -0
  83. package/dist/plugins/devices/types.js.map +1 -0
  84. package/dist/plugins/index.d.ts +18 -4
  85. package/dist/plugins/index.d.ts.map +1 -1
  86. package/dist/plugins/index.js +16 -2
  87. package/dist/plugins/index.js.map +1 -1
  88. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
  89. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
  90. package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
  91. package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
  92. package/dist/plugins/notifications/index.d.ts +71 -0
  93. package/dist/plugins/notifications/index.d.ts.map +1 -0
  94. package/dist/plugins/notifications/index.js +72 -0
  95. package/dist/plugins/notifications/index.js.map +1 -0
  96. package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
  97. package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
  98. package/dist/plugins/notifications/notifications-manager.js +610 -0
  99. package/dist/plugins/notifications/notifications-manager.js.map +1 -0
  100. package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
  101. package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
  102. package/dist/plugins/notifications/notifications-plugin.js +337 -0
  103. package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
  104. package/dist/plugins/notifications/types.d.ts +164 -0
  105. package/dist/plugins/notifications/types.d.ts.map +1 -0
  106. package/dist/plugins/notifications/types.js +9 -0
  107. package/dist/plugins/notifications/types.js.map +1 -0
  108. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
  109. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
  110. package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
  111. package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
  112. package/dist/plugins/parental/adapters/index.d.ts +8 -0
  113. package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
  114. package/dist/plugins/parental/adapters/index.js +7 -0
  115. package/dist/plugins/parental/adapters/index.js.map +1 -0
  116. package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
  117. package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
  118. package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
  119. package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
  120. package/dist/plugins/parental/index.d.ts +14 -0
  121. package/dist/plugins/parental/index.d.ts.map +1 -0
  122. package/dist/plugins/parental/index.js +15 -0
  123. package/dist/plugins/parental/index.js.map +1 -0
  124. package/dist/plugins/parental/parental-plugin.d.ts +88 -0
  125. package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
  126. package/dist/plugins/parental/parental-plugin.js +666 -0
  127. package/dist/plugins/parental/parental-plugin.js.map +1 -0
  128. package/dist/plugins/parental/stores/index.d.ts +7 -0
  129. package/dist/plugins/parental/stores/index.d.ts.map +1 -0
  130. package/dist/plugins/parental/stores/index.js +7 -0
  131. package/dist/plugins/parental/stores/index.js.map +1 -0
  132. package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
  133. package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
  134. package/dist/plugins/parental/stores/postgres-store.js +209 -0
  135. package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
  136. package/dist/plugins/parental/types.d.ts +154 -0
  137. package/dist/plugins/parental/types.d.ts.map +1 -0
  138. package/dist/plugins/parental/types.js +10 -0
  139. package/dist/plugins/parental/types.js.map +1 -0
  140. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
  141. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
  142. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
  143. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
  144. package/dist/plugins/profiles/index.d.ts +12 -0
  145. package/dist/plugins/profiles/index.d.ts.map +1 -0
  146. package/dist/plugins/profiles/index.js +13 -0
  147. package/dist/plugins/profiles/index.js.map +1 -0
  148. package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
  149. package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
  150. package/dist/plugins/profiles/profiles-plugin.js +481 -0
  151. package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
  152. package/dist/plugins/profiles/stores/index.d.ts +9 -0
  153. package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
  154. package/dist/plugins/profiles/stores/index.js +9 -0
  155. package/dist/plugins/profiles/stores/index.js.map +1 -0
  156. package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
  157. package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
  158. package/dist/plugins/profiles/stores/postgres-store.js +310 -0
  159. package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
  160. package/dist/plugins/profiles/types.d.ts +289 -0
  161. package/dist/plugins/profiles/types.d.ts.map +1 -0
  162. package/dist/plugins/profiles/types.js +10 -0
  163. package/dist/plugins/profiles/types.js.map +1 -0
  164. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
  165. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
  166. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
  167. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
  168. package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
  169. package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
  170. package/dist/plugins/rate-limit/cleanup.js +72 -0
  171. package/dist/plugins/rate-limit/cleanup.js.map +1 -0
  172. package/dist/plugins/rate-limit/env-config.d.ts +91 -0
  173. package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
  174. package/dist/plugins/rate-limit/env-config.js +318 -0
  175. package/dist/plugins/rate-limit/env-config.js.map +1 -0
  176. package/dist/plugins/rate-limit/index.d.ts +76 -0
  177. package/dist/plugins/rate-limit/index.d.ts.map +1 -0
  178. package/dist/plugins/rate-limit/index.js +79 -0
  179. package/dist/plugins/rate-limit/index.js.map +1 -0
  180. package/dist/plugins/rate-limit/middleware.d.ts +40 -0
  181. package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
  182. package/dist/plugins/rate-limit/middleware.js +169 -0
  183. package/dist/plugins/rate-limit/middleware.js.map +1 -0
  184. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
  185. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
  186. package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
  187. package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
  188. package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
  189. package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
  190. package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
  191. package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
  192. package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
  193. package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
  194. package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
  195. package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
  196. package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
  197. package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
  198. package/dist/plugins/rate-limit/stores/index.js +8 -0
  199. package/dist/plugins/rate-limit/stores/index.js.map +1 -0
  200. package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
  201. package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
  202. package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
  203. package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
  204. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
  205. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
  206. package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
  207. package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
  208. package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
  209. package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
  210. package/dist/plugins/rate-limit/strategies/index.js +27 -0
  211. package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
  212. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
  213. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
  214. package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
  215. package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
  216. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
  217. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
  218. package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
  219. package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
  220. package/dist/plugins/rate-limit/types.d.ts +265 -0
  221. package/dist/plugins/rate-limit/types.d.ts.map +1 -0
  222. package/dist/plugins/rate-limit/types.js +9 -0
  223. package/dist/plugins/rate-limit/types.js.map +1 -0
  224. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
  225. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
  226. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
  227. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
  228. package/dist/plugins/subscriptions/index.d.ts +12 -0
  229. package/dist/plugins/subscriptions/index.d.ts.map +1 -0
  230. package/dist/plugins/subscriptions/index.js +13 -0
  231. package/dist/plugins/subscriptions/index.js.map +1 -0
  232. package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
  233. package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
  234. package/dist/plugins/subscriptions/stores/index.js +9 -0
  235. package/dist/plugins/subscriptions/stores/index.js.map +1 -0
  236. package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
  237. package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
  238. package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
  239. package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
  240. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
  241. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
  242. package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
  243. package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
  244. package/dist/plugins/subscriptions/types.d.ts +308 -0
  245. package/dist/plugins/subscriptions/types.d.ts.map +1 -0
  246. package/dist/plugins/subscriptions/types.js +10 -0
  247. package/dist/plugins/subscriptions/types.js.map +1 -0
  248. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
  249. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
  250. package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
  251. package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
  252. package/dist/plugins/usage/index.d.ts +12 -0
  253. package/dist/plugins/usage/index.d.ts.map +1 -0
  254. package/dist/plugins/usage/index.js +13 -0
  255. package/dist/plugins/usage/index.js.map +1 -0
  256. package/dist/plugins/usage/stores/index.d.ts +9 -0
  257. package/dist/plugins/usage/stores/index.d.ts.map +1 -0
  258. package/dist/plugins/usage/stores/index.js +9 -0
  259. package/dist/plugins/usage/stores/index.js.map +1 -0
  260. package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
  261. package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
  262. package/dist/plugins/usage/stores/postgres-store.js +146 -0
  263. package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
  264. package/dist/plugins/usage/types.d.ts +195 -0
  265. package/dist/plugins/usage/types.d.ts.map +1 -0
  266. package/dist/plugins/usage/types.js +10 -0
  267. package/dist/plugins/usage/types.js.map +1 -0
  268. package/dist/plugins/usage/usage-plugin.d.ts +51 -0
  269. package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
  270. package/dist/plugins/usage/usage-plugin.js +412 -0
  271. package/dist/plugins/usage/usage-plugin.js.map +1 -0
  272. package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
  273. package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
  274. package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
  275. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
  276. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  277. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  278. package/dist/plugins/users/index.d.ts +2 -2
  279. package/dist/plugins/users/index.d.ts.map +1 -1
  280. package/dist/plugins/users/index.js +1 -1
  281. package/dist/plugins/users/index.js.map +1 -1
  282. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  283. package/dist/plugins/users/stores/postgres-store.js +76 -0
  284. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  285. package/dist/plugins/users/types.d.ts +74 -6
  286. package/dist/plugins/users/types.d.ts.map +1 -1
  287. package/dist/plugins/users/users-plugin.d.ts +15 -1
  288. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  289. package/dist/plugins/users/users-plugin.js +29 -0
  290. package/dist/plugins/users/users-plugin.js.map +1 -1
  291. package/dist-ui/assets/index-CynOqPkb.js +469 -0
  292. package/dist-ui/assets/{index-BY8OxNgO.js.map → index-CynOqPkb.js.map} +1 -1
  293. package/dist-ui/index.html +1 -1
  294. package/dist-ui-lib/api/controlPanelApi.d.ts +187 -0
  295. package/dist-ui-lib/components/StatCard.d.ts +16 -0
  296. package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
  297. package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
  298. package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
  299. package/dist-ui-lib/dashboard/widgets/index.d.ts +3 -0
  300. package/dist-ui-lib/index.js +3579 -2379
  301. package/dist-ui-lib/index.js.map +1 -1
  302. package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
  303. package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
  304. package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
  305. package/dist-ui-lib/utils/formatters.d.ts +19 -0
  306. package/package.json +1 -1
  307. package/src/core/control-panel.ts +128 -0
  308. package/src/core/types.ts +17 -0
  309. package/src/index.ts +216 -0
  310. package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
  311. package/src/plugins/auth/adapter-wrapper.ts +205 -0
  312. package/src/plugins/auth/config-store.test.ts +417 -0
  313. package/src/plugins/auth/config-store.ts +305 -0
  314. package/src/plugins/auth/env-config.ts +714 -7
  315. package/src/plugins/auth/index.ts +22 -1
  316. package/src/plugins/auth/types.ts +138 -0
  317. package/src/plugins/bans/bans-plugin.ts +15 -3
  318. package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
  319. package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
  320. package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
  321. package/src/plugins/devices/adapters/index.ts +13 -0
  322. package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
  323. package/src/plugins/devices/devices-plugin.ts +538 -0
  324. package/src/plugins/devices/index.ts +69 -0
  325. package/src/plugins/devices/stores/index.ts +9 -0
  326. package/src/plugins/devices/stores/postgres-store.ts +304 -0
  327. package/src/plugins/devices/token-utils.ts +213 -0
  328. package/src/plugins/devices/types.ts +351 -0
  329. package/src/plugins/index.ts +267 -0
  330. package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
  331. package/src/plugins/notifications/index.ts +91 -0
  332. package/src/plugins/notifications/notifications-manager.ts +773 -0
  333. package/src/plugins/notifications/notifications-plugin.ts +398 -0
  334. package/src/plugins/notifications/types.ts +207 -0
  335. package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
  336. package/src/plugins/parental/adapters/index.ts +8 -0
  337. package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
  338. package/src/plugins/parental/index.ts +55 -0
  339. package/src/plugins/parental/parental-plugin.ts +759 -0
  340. package/src/plugins/parental/stores/index.ts +7 -0
  341. package/src/plugins/parental/stores/postgres-store.ts +304 -0
  342. package/src/plugins/parental/types.ts +180 -0
  343. package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
  344. package/src/plugins/profiles/index.ts +49 -0
  345. package/src/plugins/profiles/profiles-plugin.ts +546 -0
  346. package/src/plugins/profiles/stores/index.ts +9 -0
  347. package/src/plugins/profiles/stores/postgres-store.ts +439 -0
  348. package/src/plugins/profiles/types.ts +338 -0
  349. package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
  350. package/src/plugins/rate-limit/cleanup.ts +117 -0
  351. package/src/plugins/rate-limit/env-config.ts +400 -0
  352. package/src/plugins/rate-limit/index.ts +128 -0
  353. package/src/plugins/rate-limit/middleware.ts +212 -0
  354. package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
  355. package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
  356. package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
  357. package/src/plugins/rate-limit/stores/index.ts +8 -0
  358. package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
  359. package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
  360. package/src/plugins/rate-limit/strategies/index.ts +30 -0
  361. package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
  362. package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
  363. package/src/plugins/rate-limit/types.ts +338 -0
  364. package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
  365. package/src/plugins/subscriptions/index.ts +51 -0
  366. package/src/plugins/subscriptions/stores/index.ts +9 -0
  367. package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
  368. package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
  369. package/src/plugins/subscriptions/types.ts +355 -0
  370. package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
  371. package/src/plugins/usage/index.ts +39 -0
  372. package/src/plugins/usage/stores/index.ts +9 -0
  373. package/src/plugins/usage/stores/postgres-store.ts +213 -0
  374. package/src/plugins/usage/types.ts +222 -0
  375. package/src/plugins/usage/usage-plugin.ts +484 -0
  376. package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
  377. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  378. package/src/plugins/users/index.ts +6 -0
  379. package/src/plugins/users/stores/postgres-store.ts +104 -0
  380. package/src/plugins/users/types.ts +82 -6
  381. package/src/plugins/users/users-plugin.ts +37 -0
  382. package/ui/src/App.tsx +36 -14
  383. package/ui/src/api/controlPanelApi.ts +329 -6
  384. package/ui/src/components/StatCard.tsx +58 -0
  385. package/ui/src/dashboard/builtInWidgets.tsx +7 -1
  386. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
  387. package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
  388. package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
  389. package/ui/src/dashboard/widgets/index.ts +3 -0
  390. package/ui/src/pages/AuthPage.tsx +986 -142
  391. package/ui/src/pages/IntegrationsPage.tsx +288 -0
  392. package/ui/src/pages/NotificationsPage.tsx +417 -0
  393. package/ui/src/pages/RateLimitPage.tsx +292 -0
  394. package/ui/src/utils/formatters.ts +33 -0
  395. package/dist-ui/assets/index-BY8OxNgO.js +0 -465
@@ -0,0 +1,637 @@
1
+ /**
2
+ * NotificationsManager Unit Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import type { Response } from 'express';
7
+ import type { NotificationsPluginConfig } from '../types.js';
8
+ import type { Logger } from '../../../core/types.js';
9
+
10
+ // Mock pg module
11
+ vi.mock('pg', () => {
12
+ const mockClient = {
13
+ connect: vi.fn().mockResolvedValue(undefined),
14
+ query: vi.fn().mockResolvedValue({ rows: [] }),
15
+ end: vi.fn().mockResolvedValue(undefined),
16
+ on: vi.fn(),
17
+ removeAllListeners: vi.fn(),
18
+ };
19
+
20
+ return {
21
+ default: {
22
+ Client: vi.fn(() => mockClient),
23
+ },
24
+ Client: vi.fn(() => mockClient),
25
+ };
26
+ });
27
+
28
+ // Import after mocking
29
+ import {
30
+ NotificationsManager,
31
+ setNotificationsManager,
32
+ getNotificationsManager,
33
+ hasNotificationsManager,
34
+ broadcastToDevice,
35
+ broadcastToUser,
36
+ broadcastToAll,
37
+ } from '../notifications-manager.js';
38
+
39
+ // Helper to create mock logger
40
+ function createMockLogger(): Logger {
41
+ return {
42
+ debug: vi.fn(),
43
+ info: vi.fn(),
44
+ warn: vi.fn(),
45
+ error: vi.fn(),
46
+ };
47
+ }
48
+
49
+ // Helper to create mock response
50
+ function createMockResponse(): Response {
51
+ const res = {
52
+ write: vi.fn().mockReturnValue(true),
53
+ end: vi.fn(),
54
+ on: vi.fn(),
55
+ setHeader: vi.fn(),
56
+ flushHeaders: vi.fn(),
57
+ } as unknown as Response;
58
+ return res;
59
+ }
60
+
61
+ // Default test config
62
+ const defaultConfig: NotificationsPluginConfig = {
63
+ channels: ['test_channel'],
64
+ heartbeat: { interval: 60000 },
65
+ reconnect: { maxAttempts: 3, baseDelay: 100, maxDelay: 1000 },
66
+ };
67
+
68
+ describe('NotificationsManager', () => {
69
+ let manager: NotificationsManager;
70
+ let logger: Logger;
71
+
72
+ beforeEach(() => {
73
+ vi.useFakeTimers();
74
+ logger = createMockLogger();
75
+ manager = new NotificationsManager(
76
+ 'postgresql://localhost/test',
77
+ ['test_channel'],
78
+ defaultConfig,
79
+ logger
80
+ );
81
+ });
82
+
83
+ afterEach(async () => {
84
+ vi.useRealTimers();
85
+ setNotificationsManager(null);
86
+ try {
87
+ await manager.shutdown();
88
+ } catch {
89
+ // Ignore shutdown errors in tests
90
+ }
91
+ });
92
+
93
+ describe('Client Registration', () => {
94
+ it('should register a client with device_id', () => {
95
+ const res = createMockResponse();
96
+ const clientId = 'client-1';
97
+ const deviceId = 'device-123';
98
+
99
+ manager.registerClient(clientId, deviceId, undefined, res);
100
+
101
+ const stats = manager.getStats();
102
+ expect(stats.currentConnections).toBe(1);
103
+ expect(stats.totalConnections).toBe(1);
104
+ expect(stats.clientsByType.device).toBe(1);
105
+ });
106
+
107
+ it('should register a client with user_id', () => {
108
+ const res = createMockResponse();
109
+ const clientId = 'client-1';
110
+ const userId = 'user-456';
111
+
112
+ manager.registerClient(clientId, undefined, userId, res);
113
+
114
+ const stats = manager.getStats();
115
+ expect(stats.currentConnections).toBe(1);
116
+ expect(stats.clientsByType.user).toBe(1);
117
+ });
118
+
119
+ it('should register a client with both device_id and user_id', () => {
120
+ const res = createMockResponse();
121
+ const clientId = 'client-1';
122
+
123
+ manager.registerClient(clientId, 'device-123', 'user-456', res);
124
+
125
+ const stats = manager.getStats();
126
+ expect(stats.currentConnections).toBe(1);
127
+ // Should count as device since device_id takes precedence
128
+ expect(stats.clientsByType.device).toBe(1);
129
+ });
130
+
131
+ it('should send connected event on registration', () => {
132
+ const res = createMockResponse();
133
+
134
+ manager.registerClient('client-1', 'device-123', undefined, res);
135
+
136
+ expect(res.write).toHaveBeenCalledWith('event: connected\n');
137
+ expect(res.write).toHaveBeenCalledWith(expect.stringContaining('data: '));
138
+ });
139
+
140
+ it('should handle multiple client registrations', () => {
141
+ const res1 = createMockResponse();
142
+ const res2 = createMockResponse();
143
+ const res3 = createMockResponse();
144
+
145
+ manager.registerClient('client-1', 'device-1', undefined, res1);
146
+ manager.registerClient('client-2', 'device-2', undefined, res2);
147
+ manager.registerClient('client-3', undefined, 'user-1', res3);
148
+
149
+ const stats = manager.getStats();
150
+ expect(stats.currentConnections).toBe(3);
151
+ expect(stats.clientsByType.device).toBe(2);
152
+ expect(stats.clientsByType.user).toBe(1);
153
+ });
154
+ });
155
+
156
+ describe('Event Broadcasting', () => {
157
+ beforeEach(() => {
158
+ const res1 = createMockResponse();
159
+ const res2 = createMockResponse();
160
+ const res3 = createMockResponse();
161
+
162
+ manager.registerClient('client-1', 'device-1', undefined, res1);
163
+ manager.registerClient('client-2', 'device-2', undefined, res2);
164
+ manager.registerClient('client-3', undefined, 'user-1', res3);
165
+ });
166
+
167
+ it('should broadcast to specific device', () => {
168
+ const count = manager.broadcastToDevice('device-1', 'test', { data: 'hello' });
169
+
170
+ expect(count).toBe(1);
171
+ });
172
+
173
+ it('should broadcast to all devices for a user', () => {
174
+ const count = manager.broadcastToUser('user-1', 'test', { data: 'hello' });
175
+
176
+ expect(count).toBe(1);
177
+ });
178
+
179
+ it('should broadcast to all clients', () => {
180
+ const count = manager.broadcastToAll('test', { data: 'hello' });
181
+
182
+ expect(count).toBe(3);
183
+ });
184
+
185
+ it('should return 0 when no clients match device', () => {
186
+ const count = manager.broadcastToDevice('unknown-device', 'test', {});
187
+
188
+ expect(count).toBe(0);
189
+ });
190
+
191
+ it('should return 0 when no clients match user', () => {
192
+ const count = manager.broadcastToUser('unknown-user', 'test', {});
193
+
194
+ expect(count).toBe(0);
195
+ });
196
+ });
197
+
198
+ describe('Statistics', () => {
199
+ it('should track total connections', () => {
200
+ const res1 = createMockResponse();
201
+ const res2 = createMockResponse();
202
+
203
+ manager.registerClient('client-1', 'device-1', undefined, res1);
204
+ manager.registerClient('client-2', 'device-2', undefined, res2);
205
+
206
+ const stats = manager.getStats();
207
+ expect(stats.totalConnections).toBe(2);
208
+ expect(stats.currentConnections).toBe(2);
209
+ });
210
+
211
+ it('should provide connection health', () => {
212
+ const health = manager.getConnectionHealth();
213
+
214
+ expect(health).toHaveProperty('isConnected');
215
+ expect(health).toHaveProperty('isHealthy');
216
+ expect(health).toHaveProperty('channelCount');
217
+ expect(health).toHaveProperty('isReconnecting');
218
+ expect(health).toHaveProperty('reconnectAttempts');
219
+ expect(health.channelCount).toBe(1);
220
+ });
221
+ });
222
+
223
+ describe('Heartbeat', () => {
224
+ it('should start heartbeat when first client connects', () => {
225
+ const res = createMockResponse();
226
+
227
+ manager.registerClient('client-1', 'device-1', undefined, res);
228
+
229
+ // Advance time to trigger heartbeat
230
+ vi.advanceTimersByTime(60001);
231
+
232
+ // Heartbeat should have been sent
233
+ expect(res.write).toHaveBeenCalledWith('event: heartbeat\n');
234
+ });
235
+
236
+ it('should include server status in heartbeat', () => {
237
+ const res = createMockResponse();
238
+
239
+ manager.registerClient('client-1', 'device-1', undefined, res);
240
+
241
+ // Clear previous calls
242
+ (res.write as ReturnType<typeof vi.fn>).mockClear();
243
+
244
+ // Advance time to trigger heartbeat
245
+ vi.advanceTimersByTime(60001);
246
+
247
+ // Find the data call
248
+ const dataCalls = (res.write as ReturnType<typeof vi.fn>).mock.calls.filter(
249
+ (call: unknown[]) => call[0]?.toString().startsWith('data:')
250
+ );
251
+ expect(dataCalls.length).toBeGreaterThan(0);
252
+
253
+ const dataStr = dataCalls[0][0] as string;
254
+ const data = JSON.parse(dataStr.replace('data: ', '').trim());
255
+ expect(data.payload).toHaveProperty('timestamp');
256
+ expect(data.payload).toHaveProperty('server');
257
+ expect(data.payload.server).toHaveProperty('status');
258
+ });
259
+ });
260
+ });
261
+
262
+ describe('Singleton and Helpers', () => {
263
+ let manager: NotificationsManager;
264
+ let logger: Logger;
265
+
266
+ beforeEach(() => {
267
+ logger = createMockLogger();
268
+ manager = new NotificationsManager(
269
+ 'postgresql://localhost/test',
270
+ ['test_channel'],
271
+ defaultConfig,
272
+ logger
273
+ );
274
+ setNotificationsManager(manager);
275
+ });
276
+
277
+ afterEach(() => {
278
+ setNotificationsManager(null);
279
+ });
280
+
281
+ describe('getNotificationsManager', () => {
282
+ it('should return manager when set', () => {
283
+ expect(getNotificationsManager()).toBe(manager);
284
+ });
285
+
286
+ it('should throw when not set', () => {
287
+ setNotificationsManager(null);
288
+ expect(() => getNotificationsManager()).toThrow('NotificationsManager not initialized');
289
+ });
290
+ });
291
+
292
+ describe('hasNotificationsManager', () => {
293
+ it('should return true when set', () => {
294
+ expect(hasNotificationsManager()).toBe(true);
295
+ });
296
+
297
+ it('should return false when not set', () => {
298
+ setNotificationsManager(null);
299
+ expect(hasNotificationsManager()).toBe(false);
300
+ });
301
+ });
302
+
303
+ describe('broadcastToDevice helper', () => {
304
+ it('should delegate to manager', () => {
305
+ const res = createMockResponse();
306
+ manager.registerClient('client-1', 'device-1', undefined, res);
307
+
308
+ const count = broadcastToDevice('device-1', 'test', { data: 'hello' });
309
+
310
+ expect(count).toBe(1);
311
+ });
312
+ });
313
+
314
+ describe('broadcastToUser helper', () => {
315
+ it('should delegate to manager', () => {
316
+ const res = createMockResponse();
317
+ manager.registerClient('client-1', undefined, 'user-1', res);
318
+
319
+ const count = broadcastToUser('user-1', 'test', { data: 'hello' });
320
+
321
+ expect(count).toBe(1);
322
+ });
323
+ });
324
+
325
+ describe('broadcastToAll helper', () => {
326
+ it('should delegate to manager', () => {
327
+ const res1 = createMockResponse();
328
+ const res2 = createMockResponse();
329
+ manager.registerClient('client-1', 'device-1', undefined, res1);
330
+ manager.registerClient('client-2', undefined, 'user-1', res2);
331
+
332
+ const count = broadcastToAll('test', { data: 'hello' });
333
+
334
+ expect(count).toBe(2);
335
+ });
336
+ });
337
+ });
338
+
339
+ describe('NotificationsManager - Additional Coverage', () => {
340
+ let manager: NotificationsManager;
341
+ let logger: Logger;
342
+
343
+ beforeEach(() => {
344
+ vi.useFakeTimers();
345
+ logger = createMockLogger();
346
+ manager = new NotificationsManager(
347
+ 'postgresql://localhost/test',
348
+ ['test_channel'],
349
+ defaultConfig,
350
+ logger
351
+ );
352
+ });
353
+
354
+ afterEach(async () => {
355
+ vi.useRealTimers();
356
+ setNotificationsManager(null);
357
+ try {
358
+ await manager.shutdown();
359
+ } catch {
360
+ // Ignore shutdown errors in tests
361
+ }
362
+ });
363
+
364
+ describe('Client Disconnect Cleanup', () => {
365
+ it('should remove client when response closes', () => {
366
+ const res = createMockResponse();
367
+ let closeHandler: (() => void) | undefined;
368
+
369
+ // Capture the close handler
370
+ (res.on as ReturnType<typeof vi.fn>).mockImplementation((event: string, handler: () => void) => {
371
+ if (event === 'close') {
372
+ closeHandler = handler;
373
+ }
374
+ });
375
+
376
+ manager.registerClient('client-1', 'device-1', undefined, res);
377
+ expect(manager.getStats().currentConnections).toBe(1);
378
+
379
+ // Simulate disconnect
380
+ closeHandler?.();
381
+ expect(manager.getStats().currentConnections).toBe(0);
382
+ });
383
+
384
+ it('should stop heartbeat when last client disconnects', () => {
385
+ const res = createMockResponse();
386
+ let closeHandler: (() => void) | undefined;
387
+
388
+ (res.on as ReturnType<typeof vi.fn>).mockImplementation((event: string, handler: () => void) => {
389
+ if (event === 'close') {
390
+ closeHandler = handler;
391
+ }
392
+ });
393
+
394
+ manager.registerClient('client-1', 'device-1', undefined, res);
395
+
396
+ // Heartbeat should be running
397
+ vi.advanceTimersByTime(60001);
398
+ expect(res.write).toHaveBeenCalledWith('event: heartbeat\n');
399
+
400
+ // Clear and disconnect
401
+ (res.write as ReturnType<typeof vi.fn>).mockClear();
402
+ closeHandler?.();
403
+
404
+ // Advance time - no more heartbeats
405
+ vi.advanceTimersByTime(60001);
406
+ expect(res.write).not.toHaveBeenCalled();
407
+ });
408
+ });
409
+
410
+ describe('Max Client Capacity', () => {
411
+ it('should reject clients when at capacity', () => {
412
+ // Create a manager with very low capacity for testing
413
+ const lowCapacityConfig: NotificationsPluginConfig = {
414
+ channels: ['test_channel'],
415
+ };
416
+ const testManager = new NotificationsManager(
417
+ 'postgresql://localhost/test',
418
+ ['test_channel'],
419
+ lowCapacityConfig,
420
+ logger
421
+ );
422
+
423
+ // Register clients up to default max (we can't easily test 10000)
424
+ // Instead test the return value behavior
425
+ const res1 = createMockResponse();
426
+ const result = testManager.registerClient('client-1', 'device-1', undefined, res1);
427
+ expect(result).toBe(true);
428
+ });
429
+
430
+ it('should return true when registration succeeds', () => {
431
+ const res = createMockResponse();
432
+ const result = manager.registerClient('client-1', 'device-1', undefined, res);
433
+
434
+ expect(result).toBe(true);
435
+ expect(manager.getStats().currentConnections).toBe(1);
436
+ });
437
+ });
438
+
439
+ describe('Connection Health', () => {
440
+ it('should report unhealthy when not initialized', () => {
441
+ const health = manager.getConnectionHealth();
442
+
443
+ expect(health.isConnected).toBe(false);
444
+ });
445
+
446
+ it('should track channel count', () => {
447
+ const health = manager.getConnectionHealth();
448
+
449
+ expect(health.channelCount).toBe(1);
450
+ });
451
+
452
+ it('should track reconnection state', () => {
453
+ const health = manager.getConnectionHealth();
454
+
455
+ expect(health.isReconnecting).toBe(false);
456
+ expect(health.reconnectAttempts).toBe(0);
457
+ });
458
+ });
459
+
460
+ describe('Shutdown', () => {
461
+ it('should close all client connections on shutdown', async () => {
462
+ const res1 = createMockResponse();
463
+ const res2 = createMockResponse();
464
+
465
+ manager.registerClient('client-1', 'device-1', undefined, res1);
466
+ manager.registerClient('client-2', 'device-2', undefined, res2);
467
+
468
+ await manager.shutdown();
469
+
470
+ expect(res1.end).toHaveBeenCalled();
471
+ expect(res2.end).toHaveBeenCalled();
472
+ });
473
+
474
+ it('should clear all clients on shutdown', async () => {
475
+ const res = createMockResponse();
476
+ manager.registerClient('client-1', 'device-1', undefined, res);
477
+
478
+ await manager.shutdown();
479
+
480
+ // Stats should still show historical data but current should be 0
481
+ // (shutdown clears the map)
482
+ });
483
+ });
484
+
485
+ describe('Event Send Error Handling', () => {
486
+ it('should handle write errors gracefully', () => {
487
+ const res = createMockResponse();
488
+ (res.write as ReturnType<typeof vi.fn>).mockImplementation(() => {
489
+ throw new Error('Connection reset');
490
+ });
491
+
492
+ manager.registerClient('client-1', 'device-1', undefined, res);
493
+
494
+ // Should not throw
495
+ expect(() => {
496
+ manager.broadcastToDevice('device-1', 'test', { data: 'hello' });
497
+ }).not.toThrow();
498
+ });
499
+ });
500
+
501
+ describe('getClients()', () => {
502
+ it('should return empty array when no clients (UT-001)', () => {
503
+ const clients = manager.getClients();
504
+
505
+ expect(clients).toEqual([]);
506
+ });
507
+
508
+ it('should return client info with correct shape (UT-002)', () => {
509
+ const res = createMockResponse();
510
+ manager.registerClient('client-1', 'device-123', 'user-456', res);
511
+
512
+ const clients = manager.getClients();
513
+
514
+ expect(clients).toHaveLength(1);
515
+ expect(clients[0]).toHaveProperty('id', 'client-1');
516
+ expect(clients[0]).toHaveProperty('deviceId', 'device-123');
517
+ expect(clients[0]).toHaveProperty('userId', 'user-456');
518
+ expect(clients[0]).toHaveProperty('connectedAt');
519
+ expect(clients[0]).toHaveProperty('durationMs');
520
+ expect(typeof clients[0].connectedAt).toBe('string');
521
+ expect(typeof clients[0].durationMs).toBe('number');
522
+ });
523
+
524
+ it('should return multiple clients (UT-003)', () => {
525
+ const res1 = createMockResponse();
526
+ const res2 = createMockResponse();
527
+ const res3 = createMockResponse();
528
+
529
+ manager.registerClient('client-1', 'device-1', undefined, res1);
530
+ manager.registerClient('client-2', 'device-2', undefined, res2);
531
+ manager.registerClient('client-3', undefined, 'user-1', res3);
532
+
533
+ const clients = manager.getClients();
534
+
535
+ expect(clients).toHaveLength(3);
536
+ expect(clients.map(c => c.id).sort()).toEqual(['client-1', 'client-2', 'client-3']);
537
+ });
538
+
539
+ it('should calculate durationMs correctly (UT-004)', () => {
540
+ const res = createMockResponse();
541
+ manager.registerClient('client-1', 'device-1', undefined, res);
542
+
543
+ // Advance time by 5 minutes
544
+ vi.advanceTimersByTime(5 * 60 * 1000);
545
+
546
+ const clients = manager.getClients();
547
+
548
+ expect(clients[0].durationMs).toBeGreaterThanOrEqual(5 * 60 * 1000);
549
+ // Allow some tolerance for test execution time
550
+ expect(clients[0].durationMs).toBeLessThan(5 * 60 * 1000 + 1000);
551
+ });
552
+ });
553
+
554
+ describe('disconnectClient()', () => {
555
+ it('should return false for unknown client (UT-005)', () => {
556
+ const result = manager.disconnectClient('unknown-client-id');
557
+
558
+ expect(result).toBe(false);
559
+ });
560
+
561
+ it('should return true for valid client (UT-006)', () => {
562
+ const res = createMockResponse();
563
+ manager.registerClient('client-1', 'device-1', undefined, res);
564
+
565
+ const result = manager.disconnectClient('client-1');
566
+
567
+ expect(result).toBe(true);
568
+ });
569
+
570
+ it('should remove client after disconnect - client not in getClients() (UT-007)', () => {
571
+ const res = createMockResponse();
572
+ let closeHandler: (() => void) | undefined;
573
+
574
+ // Capture the close handler
575
+ (res.on as ReturnType<typeof vi.fn>).mockImplementation((event: string, handler: () => void) => {
576
+ if (event === 'close') {
577
+ closeHandler = handler;
578
+ }
579
+ });
580
+
581
+ manager.registerClient('client-1', 'device-1', undefined, res);
582
+ expect(manager.getClients()).toHaveLength(1);
583
+
584
+ manager.disconnectClient('client-1');
585
+
586
+ // Simulate the close event that would be triggered by response.end()
587
+ closeHandler?.();
588
+
589
+ expect(manager.getClients()).toHaveLength(0);
590
+ });
591
+
592
+ it('should send disconnected event before closing (UT-008)', () => {
593
+ const res = createMockResponse();
594
+ manager.registerClient('client-1', 'device-1', undefined, res);
595
+
596
+ // Clear previous write calls from registration
597
+ (res.write as ReturnType<typeof vi.fn>).mockClear();
598
+
599
+ manager.disconnectClient('client-1');
600
+
601
+ // Should have sent disconnected event
602
+ expect(res.write).toHaveBeenCalledWith('event: disconnected\n');
603
+ // Should have sent data with reason
604
+ const dataCalls = (res.write as ReturnType<typeof vi.fn>).mock.calls.filter(
605
+ (call: unknown[]) => call[0]?.toString().startsWith('data:')
606
+ );
607
+ expect(dataCalls.length).toBeGreaterThan(0);
608
+ const dataStr = dataCalls[0][0] as string;
609
+ const data = JSON.parse(dataStr.replace('data: ', '').trim());
610
+ expect(data.payload).toHaveProperty('reason');
611
+ expect(data.payload.reason).toContain('administrator');
612
+ });
613
+
614
+ it('should call response.end() on disconnect', () => {
615
+ const res = createMockResponse();
616
+ manager.registerClient('client-1', 'device-1', undefined, res);
617
+
618
+ manager.disconnectClient('client-1');
619
+
620
+ expect(res.end).toHaveBeenCalled();
621
+ });
622
+
623
+ it('should accept disconnectedBy parameter for audit logging (UT-009)', () => {
624
+ const res = createMockResponse();
625
+ manager.registerClient('client-1', 'device-1', undefined, res);
626
+
627
+ const result = manager.disconnectClient('client-1', {
628
+ userId: 'admin-123',
629
+ email: 'admin@example.com',
630
+ ip: '192.168.1.1',
631
+ });
632
+
633
+ expect(result).toBe(true);
634
+ expect(res.end).toHaveBeenCalled();
635
+ });
636
+ });
637
+ });