http-request-manager 18.15.34 → 18.16.1

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 (300) hide show
  1. package/fesm2022/http-request-manager.mjs +13688 -0
  2. package/fesm2022/http-request-manager.mjs.map +1 -0
  3. package/http-request-manager-18.16.0.tgz +0 -0
  4. package/package.json +15 -6
  5. package/types/http-request-manager.d.ts +3968 -0
  6. package/TEST_COVERAGE_SUMMARY.md +0 -458
  7. package/ng-package.json +0 -8
  8. package/src/docs/ADVANCED_WEBSOCKET.md +0 -633
  9. package/src/docs/ARCHITECTURE.md +0 -633
  10. package/src/docs/BATCH_REQUEST_README.md +0 -467
  11. package/src/docs/COMPLETE_API_REFERENCE.md +0 -1037
  12. package/src/docs/DATABASE_README.md +0 -1195
  13. package/src/docs/ENCRYPTION_README.md +0 -403
  14. package/src/docs/HTTP_MANAGER_README.md +0 -628
  15. package/src/docs/HTTP_SINGNALS_MANAGER_README.md +0 -654
  16. package/src/docs/HTTP_STATE_MANAGER_README.md +0 -1391
  17. package/src/docs/INTERCEPTOR_README.md +0 -549
  18. package/src/docs/LOCAL_STORAGE_README.md +0 -1056
  19. package/src/docs/LOCAL_STORAGE_SIGNALS_README.md +0 -338
  20. package/src/docs/LOGGER_README.md +0 -310
  21. package/src/docs/MESSAGE_TRACKER_README.md +0 -518
  22. package/src/docs/MESSAGE_TRACKER_SIGNALS_README.md +0 -563
  23. package/src/docs/MODELS_README.md +0 -1264
  24. package/src/docs/SIGNAL_SERVICES_README.md +0 -238
  25. package/src/docs/SQL_DIXIE_README.md +0 -574
  26. package/src/docs/STORE_STATE_MANAGER_README.md +0 -556
  27. package/src/docs/STORE_STATE_SIGNALS_README.md +0 -600
  28. package/src/docs/UPLOAD_REQUEST_README.md +0 -324
  29. package/src/docs/UTILS_README.md +0 -1604
  30. package/src/docs/WEBSOCKET_MESSAGE_SERVICE.md +0 -799
  31. package/src/docs/WEBSOCKET_SIGNALS_README.md +0 -641
  32. package/src/docs/WEBSOCKET_SINGLETON_REFACTORING.md +0 -201
  33. package/src/docs/WS_MANAGER_README.md +0 -613
  34. package/src/lib/http-request-manager.module.ts +0 -147
  35. package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.html +0 -116
  36. package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.scss +0 -0
  37. package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.ts +0 -255
  38. package/src/lib/http-request-services-demo/http-request-services-demo.component.html +0 -123
  39. package/src/lib/http-request-services-demo/http-request-services-demo.component.scss +0 -6
  40. package/src/lib/http-request-services-demo/http-request-services-demo.component.ts +0 -53
  41. package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.html +0 -195
  42. package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.scss +0 -17
  43. package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.ts +0 -208
  44. package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.html +0 -200
  45. package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.scss +0 -17
  46. package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.ts +0 -214
  47. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/download-file/download-file.component.html +0 -53
  48. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/download-file/download-file.component.scss +0 -60
  49. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/download-file/download-file.component.ts +0 -72
  50. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/file-download.module.ts +0 -28
  51. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/file-downloader.component.html +0 -10
  52. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/file-downloader.component.scss +0 -29
  53. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/file-downloader.component.ts +0 -100
  54. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/models/download-labels-model.ts +0 -22
  55. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/spinner/spinner.component.html +0 -8
  56. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/spinner/spinner.component.scss +0 -19
  57. package/src/lib/http-request-services-demo/request-manager-basic-demo/file-downloader/spinner/spinner.component.ts +0 -26
  58. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/app-session.model.ts +0 -30
  59. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/app.model.ts +0 -19
  60. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/get-sample.model.ts +0 -25
  61. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/sample-ai-prompt.ts +0 -19
  62. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/sample-client-details.ts +0 -24
  63. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/sample-client-info.ts +0 -30
  64. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/sample-client.model.ts +0 -49
  65. package/src/lib/http-request-services-demo/request-manager-basic-demo/models/sample-mapper-client-info.ts +0 -33
  66. package/src/lib/http-request-services-demo/request-manager-basic-demo/request-manager-basic-demo.component.html +0 -279
  67. package/src/lib/http-request-services-demo/request-manager-basic-demo/request-manager-basic-demo.component.scss +0 -24
  68. package/src/lib/http-request-services-demo/request-manager-basic-demo/request-manager-basic-demo.component.ts +0 -461
  69. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.html +0 -53
  70. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.scss +0 -60
  71. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.ts +0 -72
  72. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-download.module.ts +0 -28
  73. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.html +0 -10
  74. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.scss +0 -29
  75. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.ts +0 -100
  76. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/models/download-labels-model.ts +0 -22
  77. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.html +0 -8
  78. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.scss +0 -19
  79. package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.ts +0 -26
  80. package/src/lib/http-request-services-demo/request-manager-demo/models/app-session.model.ts +0 -30
  81. package/src/lib/http-request-services-demo/request-manager-demo/models/app.model.ts +0 -19
  82. package/src/lib/http-request-services-demo/request-manager-demo/models/get-sample.model.ts +0 -25
  83. package/src/lib/http-request-services-demo/request-manager-demo/models/sample-ai-prompt.ts +0 -19
  84. package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client-details.ts +0 -24
  85. package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client-info.ts +0 -30
  86. package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client.model.ts +0 -49
  87. package/src/lib/http-request-services-demo/request-manager-demo/models/sample-mapper-client-info.ts +0 -33
  88. package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.html +0 -622
  89. package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.scss +0 -106
  90. package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.ts +0 -687
  91. package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.html +0 -418
  92. package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.scss +0 -24
  93. package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.ts +0 -576
  94. package/src/lib/http-request-services-demo/request-manager-state-demo/services/state-manager-demo.service.ts +0 -89
  95. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/services/state-data-request.service.ts +0 -119
  96. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.css +0 -0
  97. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.html +0 -3
  98. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.ts +0 -16
  99. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.css +0 -0
  100. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.html +0 -3
  101. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.ts +0 -16
  102. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.css +0 -31
  103. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.html +0 -94
  104. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.scss +0 -41
  105. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.spec.ts +0 -203
  106. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.ts +0 -144
  107. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.css +0 -11
  108. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.html +0 -102
  109. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.spec.ts +0 -40
  110. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.ts +0 -230
  111. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.css +0 -30
  112. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.html +0 -172
  113. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.spec.ts +0 -31
  114. package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.ts +0 -239
  115. package/src/lib/http-request-services-demo/request-manager-ws-demo/models/oidc-client.model.ts +0 -31
  116. package/src/lib/http-request-services-demo/request-manager-ws-demo/models/user-data.model.ts +0 -32
  117. package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.css +0 -0
  118. package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.html +0 -84
  119. package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.ts +0 -40
  120. package/src/lib/http-request-services-demo/request-manager-ws-demo/services/index.ts +0 -3
  121. package/src/lib/http-request-services-demo/request-manager-ws-demo/services/jwt-token.service.ts +0 -62
  122. package/src/lib/http-request-services-demo/request-manager-ws-demo/services/message-service-demo.service.ts +0 -83
  123. package/src/lib/http-request-services-demo/request-manager-ws-demo/services/notification-service-demo.service.ts +0 -147
  124. package/src/lib/http-request-services-demo/request-manager-ws-demo/services/state-service-demo.service.ts +0 -168
  125. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.html +0 -53
  126. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.scss +0 -60
  127. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.ts +0 -72
  128. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-download.module.ts +0 -28
  129. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.html +0 -10
  130. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.scss +0 -29
  131. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.ts +0 -100
  132. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/models/download-labels-model.ts +0 -22
  133. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.html +0 -8
  134. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.scss +0 -19
  135. package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.ts +0 -26
  136. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/app-session.model.ts +0 -30
  137. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/app.model.ts +0 -19
  138. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/get-sample.model.ts +0 -25
  139. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-ai-prompt.ts +0 -19
  140. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client-details.ts +0 -24
  141. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client-info.ts +0 -30
  142. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client.model.ts +0 -49
  143. package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-mapper-client-info.ts +0 -33
  144. package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.html +0 -380
  145. package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.scss +0 -24
  146. package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.ts +0 -410
  147. package/src/lib/http-request-services-demo/store-state-manager-demo/models/settings.model.ts +0 -28
  148. package/src/lib/http-request-services-demo/store-state-manager-demo/services/settings-state.service.ts +0 -49
  149. package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.css +0 -0
  150. package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.html +0 -23
  151. package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.ts +0 -36
  152. package/src/lib/http-request-services-demo/store-state-signals-demo/store-state-signals-demo.component.ts +0 -161
  153. package/src/lib/http-request-services-demo/upload-demo/models/index.ts +0 -1
  154. package/src/lib/http-request-services-demo/upload-demo/models/upload-state.model.ts +0 -30
  155. package/src/lib/http-request-services-demo/upload-demo/upload-demo.component.html +0 -89
  156. package/src/lib/http-request-services-demo/upload-demo/upload-demo.component.scss +0 -160
  157. package/src/lib/http-request-services-demo/upload-demo/upload-demo.component.spec.ts +0 -101
  158. package/src/lib/http-request-services-demo/upload-demo/upload-demo.component.ts +0 -136
  159. package/src/lib/index.ts +0 -3
  160. package/src/lib/interceptors/credentials.interceptor.ts +0 -16
  161. package/src/lib/interceptors/index.ts +0 -6
  162. package/src/lib/interceptors/models/error-settings.model.ts +0 -22
  163. package/src/lib/interceptors/models/index.ts +0 -2
  164. package/src/lib/interceptors/proxy-debugger.interceptor.ts +0 -46
  165. package/src/lib/interceptors/request-error.interceptor.ts +0 -65
  166. package/src/lib/interceptors/request-header.interceptor.ts +0 -56
  167. package/src/lib/message-display/README.md +0 -509
  168. package/src/lib/message-display/index.ts +0 -4
  169. package/src/lib/message-display/models/action.model.ts +0 -27
  170. package/src/lib/message-display/models/communication-message.model.ts +0 -77
  171. package/src/lib/message-display/models/display-config.model.ts +0 -35
  172. package/src/lib/message-display/models/display-rule.interface.ts +0 -28
  173. package/src/lib/message-display/models/display-strategy.interface.ts +0 -8
  174. package/src/lib/message-display/models/index.ts +0 -6
  175. package/src/lib/message-display/models/slide.model.ts +0 -24
  176. package/src/lib/message-display/rules/default-display-rules.ts +0 -35
  177. package/src/lib/message-display/services/message-display-router.service.ts +0 -63
  178. package/src/lib/message-display/strategies/snackbar.strategy.ts +0 -46
  179. package/src/lib/models/batch-options.model.ts +0 -33
  180. package/src/lib/models/batch-progress.model.ts +0 -19
  181. package/src/lib/models/batch-request-state.model.ts +0 -40
  182. package/src/lib/models/batch-result.model.ts +0 -30
  183. package/src/lib/models/config-http-options.model.ts +0 -45
  184. package/src/lib/models/config-local-storage-options.model.ts +0 -27
  185. package/src/lib/models/config-options.model.ts +0 -27
  186. package/src/lib/models/config-token.model.ts +0 -9
  187. package/src/lib/models/data-type.enum.ts +0 -5
  188. package/src/lib/models/database-storage.model.ts +0 -24
  189. package/src/lib/models/index.ts +0 -16
  190. package/src/lib/models/retry-options.model.ts +0 -22
  191. package/src/lib/models/upload-validation-error.model.ts +0 -46
  192. package/src/lib/services/SQL-DixieJS service/dexie-query-executor.ts +0 -246
  193. package/src/lib/services/SQL-DixieJS service/dexie-sql.service.ts +0 -31
  194. package/src/lib/services/SQL-DixieJS service/index.ts +0 -4
  195. package/src/lib/services/SQL-DixieJS service/models/execution-plan.model.ts +0 -52
  196. package/src/lib/services/SQL-DixieJS service/models/index.ts +0 -3
  197. package/src/lib/services/SQL-DixieJS service/models/sql-errors.model.ts +0 -13
  198. package/src/lib/services/SQL-DixieJS service/models/sql-options.model.ts +0 -3
  199. package/src/lib/services/SQL-DixieJS service/query-planner.ts +0 -284
  200. package/src/lib/services/SQL-DixieJS service/schema-validator.ts +0 -217
  201. package/src/lib/services/SQL-DixieJS service/sql-parser.ts +0 -35
  202. package/src/lib/services/database-manager-service/database.manager.service.ts +0 -384
  203. package/src/lib/services/database-manager-service/db.storage.service.ts +0 -240
  204. package/src/lib/services/database-manager-service/index.ts +0 -4
  205. package/src/lib/services/database-manager-service/models/index.ts +0 -2
  206. package/src/lib/services/database-manager-service/models/table-schema.ts +0 -33
  207. package/src/lib/services/index.ts +0 -20
  208. package/src/lib/services/local-storage-manager-service/index.ts +0 -4
  209. package/src/lib/services/local-storage-manager-service/local-storage-manager.service.spec.ts +0 -71
  210. package/src/lib/services/local-storage-manager-service/local-storage-manager.service.ts +0 -567
  211. package/src/lib/services/local-storage-manager-service/local-storage-signals-manager.service.spec.ts +0 -67
  212. package/src/lib/services/local-storage-manager-service/local-storage-signals-manager.service.ts +0 -437
  213. package/src/lib/services/local-storage-manager-service/models/global-store-options.model.ts +0 -30
  214. package/src/lib/services/local-storage-manager-service/models/index.ts +0 -6
  215. package/src/lib/services/local-storage-manager-service/models/setting-options.model.ts +0 -35
  216. package/src/lib/services/local-storage-manager-service/models/storage-data.model.ts +0 -24
  217. package/src/lib/services/local-storage-manager-service/models/storage-option.model.ts +0 -32
  218. package/src/lib/services/local-storage-manager-service/models/storage-type.enum.ts +0 -5
  219. package/src/lib/services/request-manager-services/README.md +0 -282
  220. package/src/lib/services/request-manager-services/http-manager-signals.service.ts +0 -674
  221. package/src/lib/services/request-manager-services/http-manager.service.spec.ts +0 -353
  222. package/src/lib/services/request-manager-services/http-manager.service.ts +0 -727
  223. package/src/lib/services/request-manager-services/index.ts +0 -8
  224. package/src/lib/services/request-manager-services/request-signals.service.ts +0 -372
  225. package/src/lib/services/request-manager-services/request.service.ts +0 -435
  226. package/src/lib/services/request-manager-services/rxjs-operators/countdown.ts +0 -17
  227. package/src/lib/services/request-manager-services/rxjs-operators/delay-retry.ts +0 -16
  228. package/src/lib/services/request-manager-services/rxjs-operators/index.ts +0 -4
  229. package/src/lib/services/request-manager-services/rxjs-operators/request-polling.ts +0 -35
  230. package/src/lib/services/request-manager-services/rxjs-operators/request-streaming.ts +0 -468
  231. package/src/lib/services/request-manager-state-service/http-manager-state.store.spec.ts +0 -665
  232. package/src/lib/services/request-manager-state-service/http-manager-state.store.ts +0 -2395
  233. package/src/lib/services/request-manager-state-service/index.ts +0 -3
  234. package/src/lib/services/request-manager-state-service/models/api-request.model.ts +0 -86
  235. package/src/lib/services/request-manager-state-service/models/index.ts +0 -14
  236. package/src/lib/services/request-manager-state-service/models/operation-result.model.ts +0 -18
  237. package/src/lib/services/request-manager-state-service/models/parsing-result.model.ts +0 -21
  238. package/src/lib/services/request-manager-state-service/models/request-options.model.ts +0 -37
  239. package/src/lib/services/request-manager-state-service/models/stream-config.model.ts +0 -20
  240. package/src/lib/services/request-manager-state-service/models/stream-event-metadata.model.ts +0 -23
  241. package/src/lib/services/request-manager-state-service/models/stream-event.model.ts +0 -23
  242. package/src/lib/services/request-manager-state-service/models/stream-output.model.ts +0 -23
  243. package/src/lib/services/request-manager-state-service/models/stream-progress.model.ts +0 -24
  244. package/src/lib/services/request-manager-state-service/models/stream-type.enum.ts +0 -13
  245. package/src/lib/services/request-manager-state-service/models/ws-options.model.ts +0 -42
  246. package/src/lib/services/store-state-manager-service/index.ts +0 -4
  247. package/src/lib/services/store-state-manager-service/models/index.ts +0 -3
  248. package/src/lib/services/store-state-manager-service/models/state-operation-result.model.ts +0 -30
  249. package/src/lib/services/store-state-manager-service/models/state-storage-options.model.ts +0 -24
  250. package/src/lib/services/store-state-manager-service/store-state-manager-signals.service.ts +0 -169
  251. package/src/lib/services/store-state-manager-service/store-state-manager.service.ts +0 -153
  252. package/src/lib/services/utils/app.service.spec.ts +0 -25
  253. package/src/lib/services/utils/app.service.ts +0 -21
  254. package/src/lib/services/utils/encryption/README.md +0 -79
  255. package/src/lib/services/utils/encryption/asymmetrical-encryption.service.ts +0 -282
  256. package/src/lib/services/utils/encryption/encryption-test.service.ts +0 -39
  257. package/src/lib/services/utils/encryption/index.ts +0 -5
  258. package/src/lib/services/utils/encryption/random.ts +0 -81
  259. package/src/lib/services/utils/encryption/symmetrical-encryption.service.ts +0 -106
  260. package/src/lib/services/utils/headers.service.spec.ts +0 -80
  261. package/src/lib/services/utils/headers.service.ts +0 -18
  262. package/src/lib/services/utils/index.ts +0 -9
  263. package/src/lib/services/utils/logger.service.ts +0 -90
  264. package/src/lib/services/utils/models/index.ts +0 -4
  265. package/src/lib/services/utils/models/normalized-request-options.model.ts +0 -24
  266. package/src/lib/services/utils/models/path-tracker-state.model.ts +0 -20
  267. package/src/lib/services/utils/models/query-params-tracker-options.model.ts +0 -24
  268. package/src/lib/services/utils/models/query-tracker-state.model.ts +0 -23
  269. package/src/lib/services/utils/object-merger.service.spec.ts +0 -18
  270. package/src/lib/services/utils/object-merger.service.ts +0 -78
  271. package/src/lib/services/utils/path-query.service.spec.ts +0 -117
  272. package/src/lib/services/utils/path-query.service.ts +0 -69
  273. package/src/lib/services/utils/query-params-tracker.service.ts +0 -442
  274. package/src/lib/services/utils/random-color.utils.ts +0 -83
  275. package/src/lib/services/utils/utils.service.spec.ts +0 -165
  276. package/src/lib/services/utils/utils.service.ts +0 -192
  277. package/src/lib/services/ws-manager-service/index.ts +0 -13
  278. package/src/lib/services/ws-manager-service/message-tracker-signals.service.ts +0 -147
  279. package/src/lib/services/ws-manager-service/message-tracker.service.ts +0 -477
  280. package/src/lib/services/ws-manager-service/models/channel-info.model.ts +0 -29
  281. package/src/lib/services/ws-manager-service/models/channel-message-data.model.ts +0 -24
  282. package/src/lib/services/ws-manager-service/models/channel-message.model.ts +0 -36
  283. package/src/lib/services/ws-manager-service/models/channel-type.enum.ts +0 -6
  284. package/src/lib/services/ws-manager-service/models/communication-type.enum.ts +0 -5
  285. package/src/lib/services/ws-manager-service/models/index.ts +0 -10
  286. package/src/lib/services/ws-manager-service/models/notification-message.model.ts +0 -29
  287. package/src/lib/services/ws-manager-service/models/public-message.model.ts +0 -18
  288. package/src/lib/services/ws-manager-service/models/state-message.model.ts +0 -18
  289. package/src/lib/services/ws-manager-service/models/ws-user.model.ts +0 -38
  290. package/src/lib/services/ws-manager-service/services/index.ts +0 -4
  291. package/src/lib/services/ws-manager-service/services/websocket-message.service.ts +0 -129
  292. package/src/lib/services/ws-manager-service/services/websocket.service.ts +0 -434
  293. package/src/lib/services/ws-manager-service/websocket-service/index.ts +0 -1
  294. package/src/lib/services/ws-manager-service/websocket-service/websocket-manager.service.ts +0 -716
  295. package/src/lib/services/ws-manager-service/websocket-services-complete.spec.ts +0 -596
  296. package/src/lib/services/ws-manager-service/websocket-signals-manager.service.ts +0 -141
  297. package/src/public-api.ts +0 -19
  298. package/tsconfig.lib.json +0 -34
  299. package/tsconfig.lib.prod.json +0 -10
  300. package/tsconfig.spec.json +0 -14
@@ -1,2395 +0,0 @@
1
- import { inject, Inject, Injectable, InjectionToken } from '@angular/core';
2
- import { ComponentStore } from '@ngrx/component-store';
3
-
4
- import { BehaviorSubject, Observable, of, Subject, merge, Subscription } from 'rxjs';
5
- import { tap, switchMap, concatMap, scan, delay, take, map, distinctUntilChanged, filter, catchError, finalize } from 'rxjs/operators';
6
- import { DatabaseStorage } from '../../models/database-storage.model';
7
- import { ApiRequest, RequestOptions, WSOptions, OperationResultModel } from './models';
8
- import { WSUser } from '../ws-manager-service/models';
9
- import { HTTPManagerService, DataType } from '../request-manager-services';
10
- import { DatabaseManagerService } from '../database-manager-service';
11
- import { TableSchemaDef } from '../database-manager-service/models/table-schema';
12
- import { LocalStorageManagerService, StorageType } from '../local-storage-manager-service';
13
- import { SettingOptions } from '../local-storage-manager-service/models/setting-options.model';
14
- import { UtilsService } from '../..';
15
- import { ChannelMessage } from '../ws-manager-service/models/channel-message.model';
16
- import { WebSocketManagerService } from '../ws-manager-service/websocket-service';
17
- import { LoggerService } from '../../services/utils/logger.service';
18
- // import { QueryParamsTrackerService } from '../utils/query-params-tracker.service';
19
-
20
- const API_OPTS = new InjectionToken<ApiRequest>('API_OPTS');
21
-
22
- /**
23
- * Channel type enum for different communication purposes
24
- * - STATE: Private channels for state synchronization (SYS- prefix)
25
- * - MESSAGE: Public messaging/communication channels (PUB- prefix)
26
- * - NOTIFICATION: Notification channels with DB persistence (MES- prefix)
27
- */
28
- export enum ChannelType {
29
- STATE = 'SYS',
30
- MESSAGE = 'PUB',
31
- NOTIFICATION = 'MES'
32
- }
33
-
34
- /**
35
- * Utility function to create prefixed channel name
36
- * @param channelType - The type of channel
37
- * @param channelName - The base channel name
38
- * @returns Prefixed channel name (e.g., 'SYS-USERS123')
39
- */
40
- export function createChannelName(channelType: ChannelType, channelName: string): string {
41
- return `${channelType}-${channelName}`;
42
- }
43
-
44
- export interface APIStateManagerData<T> {
45
- data: T[]
46
- dataObject: T | null
47
- }
48
-
49
- const defaultState: APIStateManagerData<any> = {
50
- data: [],
51
- dataObject: null,
52
- };
53
-
54
- @Injectable()
55
- export class HTTPManagerStateService<T extends { id: number|string }> extends ComponentStore<APIStateManagerData<T>> {
56
-
57
- httpManagerService = inject(HTTPManagerService)
58
- dbManagerService = inject(DatabaseManagerService)
59
- localStorageManagerService = inject(LocalStorageManagerService)
60
- utils = inject(UtilsService)
61
- logger = inject(LoggerService)
62
- // private queryParamsTrackerService = inject(QueryParamsTrackerService)
63
-
64
- error$ = this.httpManagerService.error$
65
- isPending$ = this.httpManagerService.isPending$.pipe(delay(1))
66
-
67
- private operationSuccess = new BehaviorSubject<OperationResultModel | null>(null)
68
- operationSuccess$ = this.operationSuccess.asObservable()
69
-
70
- // PAGINATION
71
- private page = new BehaviorSubject<number>(0)
72
- page$ = this.page.asObservable()
73
-
74
- private totalPages = new BehaviorSubject<number>(0)
75
- totalPages$ = this.totalPages.asObservable()
76
-
77
- private percentage = new BehaviorSubject<number>(0)
78
- percentage$ = this.percentage.asObservable()
79
-
80
- private hasDatabase = false
81
-
82
- streamedResponse: any[] = []
83
-
84
- // WS
85
- private maxRetries: number
86
- private retryDelay: number
87
- private shouldRetry = true
88
-
89
- // Track connection status subscription to prevent duplicates
90
- private connectionStatusSubscription?: Subscription
91
-
92
- private databaseOptions?: DatabaseStorage
93
-
94
- private readonly volatileHeaders = new Set([
95
- 'authorization',
96
- 'x-request-id',
97
- 'x-correlation-id',
98
- 'x-trace-id',
99
- 'x-amzn-trace-id',
100
- 'date',
101
- 'if-none-match'
102
- ])
103
-
104
- private requestSignatureCache: Record<string, { GET?: string; STREAM?: string }> = {}
105
- private inFlightRequestSignatures = new Set<string>()
106
- private _requestCachePaths = new Map<string, string[]>()
107
-
108
- private wsRetryAttempts = new BehaviorSubject<number>(0)
109
- wsRetryAttempts$ = this.wsRetryAttempts.asObservable()
110
-
111
- private wsNextRetry: BehaviorSubject<number>
112
- wsNextRetry$: Observable<number>
113
-
114
-
115
- private messages = new BehaviorSubject<any[]>([])
116
- messages$ = this.messages.asObservable()
117
-
118
- private userListByChannel = new BehaviorSubject<Map<string, any[]>>(new Map())
119
- userListByChannel$ = this.userListByChannel.asObservable()
120
-
121
- // Convenience observable that returns users for a specific channel
122
- getUsersForChannel$(channel: string) {
123
- return this.userListByChannel$.pipe(
124
- map(channelMap => channelMap.get(channel) || [])
125
- )
126
- }
127
-
128
- // Returns all unique users across all channels
129
- private userList = new BehaviorSubject<any[]>([])
130
- userList$ = this.userList.asObservable()
131
-
132
- private user = new BehaviorSubject<WSUser|null>(null)
133
- user$ = this.user.asObservable()
134
-
135
- private channels = new BehaviorSubject<string[]|null>(null)
136
- channels$ = this.channels.asObservable()
137
-
138
- // In-memory notification channels (from server)
139
- private notificationChannels = new BehaviorSubject<string[]>([])
140
- notificationChannels$ = this.notificationChannels.asObservable()
141
-
142
- // Today's notification channels (from database - channels with data for today)
143
- private todaysNotificationChannels = new BehaviorSubject<string[]>([])
144
- todaysNotificationChannels$ = this.todaysNotificationChannels.asObservable()
145
-
146
- // Notification messages (MES- channels)
147
- private notificationMessages = new BehaviorSubject<any[]>([])
148
- notificationMessages$ = this.notificationMessages.asObservable()
149
-
150
- private latestNotification = new Subject<any>()
151
- latestNotification$ = this.latestNotification.asObservable()
152
-
153
- private communicationMessages = new BehaviorSubject<any[]>([])
154
- communicationMessages$ = this.communicationMessages.asObservable()
155
-
156
- private latestCommunicationMessages = new BehaviorSubject<any|null>(null)
157
- latestCommunicationMessages$ = this.latestCommunicationMessages.asObservable()
158
-
159
- private userAction = new BehaviorSubject<any|null>(null)
160
- userAction$ = this.userAction.asObservable()
161
-
162
- // Message queue for WebSocket communication (processed when connection is established)
163
- private static wsCommunicationQueue: Array<{method: string, path?: string|number[]}> = [];
164
-
165
- // Store our own sessionId for filtering out own messages
166
- private ownSessionId: string | null = null;
167
-
168
- wsOptions = WSOptions.adapt()
169
-
170
- // Expose raw WS connection status directly to UI (from singleton WebSocketManagerService)
171
- public connectionStatus$: Observable<boolean> = this.httpManagerService.connectionStatus$
172
- public wsRetryCount$: Observable<number> = this.httpManagerService.retryCount$
173
- public wsMaxRetries$: Observable<number> = this.httpManagerService.maxRetries$
174
-
175
- constructor(
176
- @Inject(API_OPTS) private apiOptions = ApiRequest.adapt(),
177
- @Inject("dataType") private dataType: DataType | undefined,
178
- database?: DatabaseStorage
179
- ) {
180
-
181
- super(defaultState);
182
-
183
- try {
184
- this.databaseOptions = database
185
- this.hasDatabase = (database?.table) ? true : false
186
- this.maxRetries = this.apiOptions.ws?.retry?.times || 3
187
- this.retryDelay = (this.apiOptions.ws?.retry?.delay && this.apiOptions.ws.retry.delay * 1000) || 5 * 1000
188
-
189
- // Start next retry countdown at 0 to avoid showing 5000 pre-connection
190
- this.wsNextRetry = new BehaviorSubject<number>(0)
191
- this.wsNextRetry$ = this.wsNextRetry.asObservable()
192
-
193
- this.setApiRequestOptions(apiOptions, dataType, database)
194
-
195
- if (this.databaseOptions && this.databaseOptions.table) {
196
-
197
- this.localStorageManagerService.createStore({
198
- name: this.databaseOptions.table,
199
- data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn)} },
200
- options: SettingOptions.adapt({
201
- storage: StorageType.GLOBAL,
202
- encrypted: false,
203
- })
204
- })
205
-
206
- // Use initDBStorageAsync directly - the effect initDBStorage requires subscription
207
- this.initDBStorageAsync().subscribe({
208
- next: () => console.log('[Constructor] Database storage initialized'),
209
- error: (err: any) => console.error('[Constructor] Database storage initialization failed:', err)
210
- })
211
-
212
- }
213
- } catch (error) {
214
- this.logger.error('HTTPManagerStateService', 'Error initializing', error);
215
- // Initialize with safe defaults
216
- this.databaseOptions = undefined;
217
- this.maxRetries = 3;
218
- this.retryDelay = 5000;
219
- this.wsNextRetry = new BehaviorSubject<number>(0);
220
- this.wsNextRetry$ = this.wsNextRetry.asObservable();
221
- }
222
-
223
- }
224
-
225
- /**
226
- * Add appropriate prefix to a channel name if not already present
227
- */
228
- private prefixChannel(channel: string, type: ChannelType): string {
229
- const prefix = `${type}-`;
230
- if (channel.startsWith(prefix)) {
231
- return channel;
232
- }
233
- // Remove any other known prefix before adding the correct one
234
- const cleanChannel = this.stripChannelPrefix(channel);
235
- return `${type}-${cleanChannel}`;
236
- }
237
-
238
- /**
239
- * Remove any known prefix from a channel name
240
- */
241
- private stripChannelPrefix(channel: string): string {
242
- for (const type of Object.values(ChannelType)) {
243
- const prefix = `${type}-`;
244
- if (channel.startsWith(prefix)) {
245
- return channel.slice(prefix.length);
246
- }
247
- }
248
- return channel;
249
- }
250
-
251
- /**
252
- * Get the base channel name without prefix (for display/user reference)
253
- */
254
- getBaseChannelName(channel: string): string {
255
- return this.stripChannelPrefix(channel);
256
- }
257
-
258
- setApiRequestOptions(apiOptions?: ApiRequest, dataType?: DataType, database?: DatabaseStorage) {
259
-
260
- this.logger.debug('StateStore', '🔧 setApiRequestOptions called', {
261
- hasWs: !!apiOptions?.ws,
262
- wsId: apiOptions?.ws?.id,
263
- wsServer: apiOptions?.ws?.wsServer,
264
- path: apiOptions?.path
265
- });
266
-
267
- this.apiOptions = ApiRequest.adapt(apiOptions)
268
- this.dataType = (dataType) ? dataType : DataType.ARRAY
269
-
270
- // Only update database options if a database parameter is explicitly provided
271
- if (database !== undefined) {
272
- console.log('[setApiRequestOptions] Database config:', {
273
- database,
274
- hasTable: !!database?.table,
275
- tableValue: database?.table
276
- })
277
- this.hasDatabase = (database?.table) ? true : false
278
- const adapted = DatabaseStorage.adapt(database)
279
- console.log('[setApiRequestOptions] DatabaseStorage.adapt result:', adapted)
280
- this.databaseOptions = (this.hasDatabase) ? adapted : undefined
281
-
282
- // Trigger database table creation if table is configured
283
- if (this.hasDatabase && this.databaseOptions?.table) {
284
- this.localStorageManagerService.createStore({
285
- name: this.databaseOptions.table,
286
- data: {
287
- ...this.databaseOptions,
288
- expires: this.utils.expires(this.databaseOptions.expiresIn),
289
- },
290
- options: SettingOptions.adapt({
291
- storage: StorageType.GLOBAL,
292
- encrypted: false,
293
- })
294
- })
295
-
296
- console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table)
297
- // Use initDBStorageAsync directly instead of the effect - effects need subscription to run
298
- this.initDBStorageAsync().subscribe({
299
- next: () => console.log('[setApiRequestOptions] Database storage initialized successfully'),
300
- error: (err: any) => console.error('[setApiRequestOptions] Database storage initialization failed:', err)
301
- })
302
-
303
- // Pre-populate _requestCachePaths so clearRequestCacheMetadata works after browser refresh
304
- // (before any GET request has run to populate it via saveRequestCacheMetadata)
305
- this._requestCachePaths.set(
306
- this.databaseOptions.table,
307
- this.resolvePath().filter(p => typeof p === 'string' || typeof p === 'number').map(String)
308
- )
309
- }
310
- }
311
-
312
- if(this.apiOptions.ws && this.apiOptions.ws.id !== '') {
313
-
314
- // Auto-prefix channel ID for private state manager channels
315
- // This ensures state manager channels are separate from user-defined channels
316
- this.apiOptions.ws.id = this.prefixChannel(this.apiOptions.ws.id, ChannelType.STATE);
317
- this.logger.debug('StateStore', `🔒 Private state channel configured`, { channelId: this.apiOptions.ws.id });
318
-
319
- // Store our own sessionId for filtering incoming messages
320
- this.ownSessionId = sessionStorage.getItem('WSID') || null;
321
- this.logger.debug('StateStore', `🆔 Stored own sessionId for message filtering`, { sessionId: this.ownSessionId });
322
- this.logger.debug('StateStore', `🔍 WebSocket configuration`, {
323
- channelId: this.apiOptions.ws.id,
324
- wsServer: this.apiOptions.ws.wsServer,
325
- sessionId: this.ownSessionId,
326
- path: this.apiOptions.path
327
- });
328
-
329
- // Initialize WebSocket connection if not already connected
330
- this.logger.debug('StateStore', '🔌 Checking WebSocket connection status...');
331
- this.logger.debug('StateStore', '🔌 Is connected?', { connected: WebSocketManagerService.isConnected() });
332
-
333
- if (!WebSocketManagerService.isConnected()) {
334
- this.logger.debug('StateStore', '🔌 WebSocket not connected, will initialize via effect');
335
- // initWS is an effect that triggers when setApiRequestOptions is called with ws config
336
- // The effect is already triggered by calling setApiRequestOptions above
337
- }
338
-
339
- if (this.apiOptions.ws?.retry) {
340
- this.maxRetries = this.apiOptions.ws.retry.times || 3
341
- this.retryDelay = (this.apiOptions.ws.retry.delay && this.apiOptions.ws.retry.delay * 1000) || 5 * 1000
342
- this.wsNextRetry.next(this.retryDelay)
343
- }
344
-
345
- // Validate wsServer before attempting connection
346
- if (!this.apiOptions.ws.wsServer || this.apiOptions.ws.wsServer === '') {
347
- this.logger.error('StateStore', 'WSOptions invalid: wsServer is missing or empty');
348
- return;
349
- }
350
-
351
- // Clean up previous subscription to prevent duplicate handlers
352
- if (this.connectionStatusSubscription) {
353
- this.connectionStatusSubscription.unsubscribe();
354
- this.connectionStatusSubscription = undefined;
355
- }
356
-
357
- // Setup connection status monitoring (internal subscription to drive retry counters)
358
- this.connectionStatusSubscription = this.setupConnectionStatus().subscribe();
359
-
360
- // Make initial connection attempt
361
- this.logger.debug('StateStore', '🔄 Initial WebSocket connection attempt...');
362
- this.httpManagerService.connect(this.apiOptions.ws as WSOptions, this.apiOptions.ws.jwtToken || '');
363
-
364
- // Initialize WS effect to handle messages
365
- this.initWS(this.apiOptions.ws as WSOptions);
366
-
367
- } else {
368
- this.logger.warn('StateStore', 'WSOptions invalid Id: empty');
369
- }
370
- }
371
-
372
- // WebSocket
373
- private setupConnectionStatus(): Observable<boolean> {
374
- return this.httpManagerService.connectionStatus$.pipe(
375
- distinctUntilChanged(),
376
- tap(status => {
377
- if (status === true) {
378
- this.logger.debug('StateStore', '🟢 WebSocket connection is open')
379
- } else {
380
- this.logger.debug('StateStore', '🔴 WebSocket connection is closed')
381
- }
382
- }),
383
- map(status => {
384
-
385
- if (status === true) {
386
- this.shouldRetry = true
387
- this.wsRetryAttempts.next(0)
388
- this.wsNextRetry.next(0)
389
- return true
390
- }
391
-
392
- // Retry orchestration is centralized in WebSocketManagerService.connect/onclose
393
- // and uses ws.retry from request options. Keep this stream as status/state only.
394
- if (!this.shouldRetry) {
395
- this.wsRetryAttempts.next(0)
396
- this.wsNextRetry.next(0)
397
- return false
398
- }
399
-
400
- const nextAttempt = Math.min(this.wsRetryAttempts.value + 1, this.maxRetries)
401
- this.wsRetryAttempts.next(nextAttempt)
402
- this.wsNextRetry.next(nextAttempt >= this.maxRetries ? 0 : this.retryDelay / 1000)
403
- return false
404
- })
405
- );
406
- }
407
-
408
- // WebSocket
409
- readonly initWS = this.effect<WSOptions>((wsOptions$) =>
410
- wsOptions$.pipe(
411
- switchMap((wsOptions) =>
412
- merge(
413
- this.httpManagerService.connectionStatus$.pipe(
414
- tap((isConnected: any) => {
415
- if (isConnected) {
416
- // Subscribe to our private SYS- state channel so we receive stateMangerMessage broadcasts
417
- // This is the critical subscription that enables multi-client state syncing
418
- const stateChannel = wsOptions?.id || this.apiOptions.ws?.id;
419
- if (stateChannel) {
420
- console.log(`🔒 [STATE STORE] Subscribing to state channel: ${stateChannel}`);
421
- this.httpManagerService.subscribeToChannel(stateChannel);
422
- }
423
-
424
- // Process queued wsCommunication calls when connection becomes true
425
- if (HTTPManagerStateService.wsCommunicationQueue.length > 0) {
426
- console.log(`🔄 Processing ${HTTPManagerStateService.wsCommunicationQueue.length} queued WS messages`);
427
- while (HTTPManagerStateService.wsCommunicationQueue.length > 0) {
428
- const queued = HTTPManagerStateService.wsCommunicationQueue.shift()!;
429
- this.sendWsCommunication(queued.method, queued.path);
430
- }
431
- }
432
- }
433
- })
434
- ),
435
- this.httpManagerService.messages$.pipe(
436
- tap((message: any) => {
437
- if (!message) return
438
-
439
- // CRITICAL DEBUG: Log EVERY message received
440
- console.log('🔍 [STATE STORE] Received WebSocket message:', {
441
- type: message.type,
442
- channel: message.channel,
443
- sessionId: message.data?.sessionId?.id || message.sessionId?.id,
444
- content: message.content || message.data?.content,
445
- timestamp: new Date().toISOString()
446
- });
447
-
448
- // Add message to messages array
449
- const currentMessages = this.messages.value
450
- this.messages.next([...currentMessages, message])
451
-
452
- console.log('Received:', message)
453
-
454
- // Debug: Log all message types
455
- this.logger.debug('StateStore', '📨 Message type', { type: message.type })
456
-
457
- if (message.error === 'JWT_INVALID' || message.error === 'AUTH_BLOCKED') {
458
- this.shouldRetry = false
459
- this.httpManagerService.disconnect()
460
- }
461
-
462
- if (message.type === 'success') {
463
- if(message.data.id !== this.user.value?.id) {
464
- const user = WSUser.adapt(message.data)
465
- this.user.next(user)
466
- }
467
- }
468
-
469
- switch (message.type) {
470
- case 'channelsList':
471
-
472
- this.logger.debug('StateStore', '💬 Channels', { channels: message.channels })
473
- this.logger.debug('StateStore', '🔍 channelsList received, checking connection status...')
474
- this.logger.debug('StateStore', '🔍 WebSocket connected', { connected: WebSocketManagerService.isConnected() })
475
-
476
- // Extract channel names from metadata objects (new format) or use strings directly (old format)
477
- const channelNames = message.channels.map((c: any) => {
478
- // Handle new format: {name: string, canSubscribe: boolean}
479
- if (typeof c === 'object' && c.name) {
480
- return c.name;
481
- }
482
- // Handle old format: string
483
- return c;
484
- });
485
-
486
- // Auto-subscribe to all channels from the list
487
- if (channelNames && channelNames.length > 0) {
488
- this.logger.debug('StateStore', '📥 Auto-subscribing to channels', { count: channelNames.length })
489
- this.subscribeToChannels(channelNames)
490
- } else {
491
- this.logger.warn('StateStore', '⚠️ No channels to subscribe to')
492
- }
493
-
494
- this.channels.next(channelNames)
495
-
496
- break;
497
-
498
- case 'success':
499
- // Success messages - check for subscription confirmation
500
- this.logger.info('StateStore', `✅ Success`, { message: message.message })
501
- if (message.message?.includes('Subscribed to channel:')) {
502
- // Extract channel name from message: "Subscribed to channel: PUB-chat"
503
- const channelName = message.message.split('Subscribed to channel:')[1]?.trim()
504
- this.logger.debug('StateStore', `✅ Subscription confirmed for channel`, { channel: channelName })
505
- WebSocketManagerService.addSubscribedChannel(channelName)
506
- }
507
- break;
508
-
509
- case 'subscribed':
510
- this.logger.debug('StateStore', `✅ Subscription confirmed`, { channel: message.channel })
511
- // Track as subscribed now that server confirmed
512
- WebSocketManagerService.addSubscribedChannel(message.channel);
513
- break;
514
-
515
- case 'unsubscribed':
516
- this.logger.debug('StateStore', `🔓 Unsubscription confirmed`, { channel: message.channel })
517
- break;
518
-
519
- case 'info':
520
- // Already subscribed or other info messages
521
- this.logger.info('StateStore', `ℹ️ Info`, { message: message.message })
522
- // If it's an "Already subscribed" message, treat it as subscription confirmation
523
- if (message.message?.includes('Already subscribed to channel:')) {
524
- // Extract channel name from message: "Already subscribed to channel: PUB-chat"
525
- const channelName = message.message.split('Already subscribed to channel:')[1]?.trim()
526
- console.log(`✅ Treating info as subscription confirmation for channel: ${channelName}`)
527
- WebSocketManagerService.addSubscribedChannel(channelName)
528
- }
529
- break;
530
-
531
- case 'statemanagerMessage':
532
- case 'stateMangerMessage':
533
- // CRITICAL DEBUG: Log channel comparison
534
- console.log('🔍 [STATE STORE] stateMangerMessage received:', {
535
- messageChannel: message.channel,
536
- ourChannel: this.apiOptions.ws?.id,
537
- channelsMatch: message.channel === this.apiOptions.ws?.id,
538
- senderSessionId: message.data.sessionId?.id,
539
- ourSessionId: this.ownSessionId
540
- });
541
-
542
- // Compare sender's session ID with our own sessionId
543
- // message.data.sessionId is an object with 'id' property from server
544
- const stateManagerSenderId = message.data.sessionId?.id || message.data.sessionId;
545
- console.log('🔍 State Manager: Sender sessionId:', stateManagerSenderId, 'Own sessionId:', this.ownSessionId);
546
- if(stateManagerSenderId !== this.ownSessionId) {
547
- console.log('💬 State Manager Message:', message.data)
548
- console.log('📥 Fetching record with path:', message.data.content.path, 'method:', message.data.content.method)
549
- this.userAction.next(message.data)
550
- this.fetchRecord(RequestOptions.adapt({ path: message.data.content.path }), message.data.content.method)
551
- } else {
552
- console.log('⏭️ Skipping own message (sessionId match)');
553
- }
554
- break;
555
-
556
- case 'channelMessage':
557
- // Handle channel-based messages (from sendChannelMessage)
558
- // Structure: { type: 'channelMessage', messageId, channel, sessionId, content, timestamp }
559
- // Skip messages from self
560
- const senderSessionId = message.sessionId?.id || message.sessionId;
561
- if (senderSessionId === this.ownSessionId) {
562
- console.log('🔇 Skipping message from self (sessionId match)');
563
- break;
564
- }
565
-
566
- console.log('💬 Channel Message received:', message);
567
-
568
- // Determine which channels this message was sent to
569
- const messageChannels = message.channel ? [message.channel] : [];
570
-
571
- // Construct expected channel path (without env prefix)
572
- const myPath = this.apiOptions.path || [];
573
- const myPathString = myPath.join('/');
574
-
575
- // Check if any of the message channels CONTAIN our path
576
- const isWsUpdateChannel = messageChannels.some((ch: string) => {
577
- // Strip SYS- prefix if present for comparison
578
- const cleanChannel = ch.replace('SYS-', '');
579
- // Check if channel contains our path (or starts with it)
580
- const matches = cleanChannel === myPathString ||
581
- cleanChannel.startsWith(myPathString + '/') ||
582
- cleanChannel.includes(myPathString);
583
- console.log(`🔍 Channel check: ${ch} contains ${myPathString}? ${matches}`);
584
- return matches;
585
- });
586
-
587
- // If it's the expected channel, trigger fetchRecord like stateManagerMessage
588
- if (isWsUpdateChannel && message.content?.path) {
589
- console.log('🔍 Message received on expected channel (path match):', myPathString);
590
- console.log('📄 Content:', message.content);
591
-
592
- console.log('📥 Fetching record for channel:', myPathString);
593
- const path = message.content.path;
594
- const method = message.content.method || 'UPDATE';
595
- this.userAction.next({ sessionId: message.sessionId, content: message.content });
596
- this.fetchRecord(RequestOptions.adapt({ path }), method);
597
-
598
- } else if (message.content) {
599
- // Handle message content directly
600
- console.log('📄 Processing message content:', message.content);
601
- this.appendMessages(ChannelMessage.adapt({
602
- sessionId: message.sessionId,
603
- content: message.content,
604
- }));
605
-
606
- } else {
607
- console.log('⚠️ Message does not contain data.content.path, skipping fetchRecord');
608
- }
609
-
610
- // Keep existing functionality for backward compatibility
611
- if (message.data?.content && !isWsUpdateChannel) {
612
- this.appendMessages(ChannelMessage.adapt({
613
- sessionId: message.data.sessionId,
614
- content: message.data.content,
615
- }));
616
- }
617
- break;
618
-
619
- case 'usersInChannel':
620
- console.log(`👥 Users in channel "${message.channel}":`, message.data.users)
621
-
622
- // Update channel-specific user list
623
- const currentMap = new Map(this.userListByChannel.value)
624
- currentMap.set(message.channel, message.data.users)
625
- this.userListByChannel.next(currentMap)
626
-
627
- // Update legacy userList with unique users across all channels
628
- const allUsers = Array.from(currentMap.values()).flat()
629
- const uniqueUsers = allUsers.filter((user, index, self) =>
630
- index === self.findIndex(u => u.id === user.id)
631
- )
632
- this.userList.next(uniqueUsers)
633
- break;
634
-
635
- case 'notificationChannelsList':
636
- console.log('📢 Notification Channels (in-memory):', message.channels)
637
- // Extract channel names from metadata objects (new format) or use strings directly (old format)
638
- const notifyChannelNames = message.channels.map((c: any) => {
639
- if (typeof c === 'object' && c.name) {
640
- return c.name;
641
- }
642
- return c;
643
- });
644
- this.notificationChannels.next(notifyChannelNames || [])
645
- break;
646
-
647
- case 'todaysNotificationChannelsList':
648
- console.log('📢 Today\'s Notification Channels (from DB):', message.channels)
649
- // Extract channel names from metadata objects (new format) or use strings directly (old format)
650
- const todaysNotifyChannelNames = message.channels.map((c: any) => {
651
- if (typeof c === 'object' && c.name) {
652
- return c.name;
653
- }
654
- return c;
655
- });
656
- this.todaysNotificationChannels.next(todaysNotifyChannelNames || [])
657
- break;
658
-
659
- case 'notificationSubscribed':
660
- console.log(`📢 Notification subscription confirmed: ${message.channel}`, message.notifications)
661
- // Set historical notifications from subscription
662
- if (message.notifications && Array.isArray(message.notifications)) {
663
- this.notificationMessages.next(message.notifications)
664
- }
665
- break;
666
-
667
- case 'notificationUnsubscribed':
668
- console.log(`📢 Notification unsubscription confirmed: ${message.channel}`)
669
- break;
670
-
671
- case 'notification':
672
- console.log('📢 Notification received:', message)
673
- // Add to notifications array
674
- const currentNotifications = this.notificationMessages.value
675
- this.notificationMessages.next([...currentNotifications, message])
676
- // Emit as latest notification
677
- this.latestNotification.next(message)
678
- break;
679
-
680
- default:
681
- // Messages are already added at the beginning of the tap
682
- break
683
- }
684
- })
685
- )
686
- )
687
- )
688
- )
689
- );
690
-
691
- appendMessages(message: any) {
692
- const currentMessages = this.communicationMessages.value
693
- this.communicationMessages.next([...currentMessages, message])
694
- this.latestMessage()
695
- }
696
-
697
- latestMessage() {
698
- const messages = this.communicationMessages.value
699
- const latestMessage = messages[messages.length -1]
700
- this.latestCommunicationMessages.next(latestMessage)
701
- }
702
-
703
- clearMessages() {
704
- this.communicationMessages.next([])
705
- }
706
-
707
- get ApiRequestOptions() {
708
- return this.apiOptions
709
- }
710
-
711
- readonly initDBStorage = this.effect<void>((trigger$) =>
712
- trigger$.pipe(
713
- tap(() => {
714
- console.log('[initDBStorage effect] Triggered, checking conditions:', {
715
- dataType: this.dataType,
716
- isARRAY: this.dataType === DataType.ARRAY,
717
- hasAdapter: !!this.apiOptions?.adapter,
718
- hasTable: !!this.databaseOptions?.table,
719
- tableValue: this.databaseOptions?.table
720
- })
721
- if (this.dataType !== DataType.ARRAY) console.warn('Database storage requires dataType to be ARRAY')
722
- if (!this.apiOptions.adapter) console.warn('Database storage adapter missing, using minimal or inferred schema')
723
- if (this.databaseOptions && this.databaseOptions?.table === '') console.warn('Database storage requires a table name')
724
- }),
725
- filter(() => {
726
- const shouldProceed = this.dataType === DataType.ARRAY && !!this.databaseOptions?.table
727
- console.log('[initDBStorage effect] Filter result:', shouldProceed)
728
- return shouldProceed
729
- }),
730
- switchMap(() => {
731
- const schema = this.buildSchemaFromAdapter()
732
-
733
- const tableDef = TableSchemaDef.adapt({
734
- table: this.databaseOptions?.table,
735
- schema: schema
736
- });
737
-
738
- return this.dbManagerService.createDatabaseTable(tableDef)
739
- })
740
- )
741
- )
742
-
743
- initializeState(data: any) {
744
- this.setData$(data)
745
- }
746
-
747
- // --------------------------------------------------------------------------------------------------
748
- // SELECTORS
749
- readonly data$ = this.select(({ data, dataObject }) => {
750
- const isArray =( this.dataType === DataType.ARRAY) ? true : false
751
- return (isArray) ? data : dataObject
752
- })
753
-
754
- readonly selectRecord$ = (id: number) => this.select(
755
- this.data$,
756
- (data) => {
757
- if (this.dataType === DataType.ARRAY && Array.isArray(data)) {
758
- return data.find(item => item.id === id) as T | null
759
- } else {
760
- return (data as T).id === id ? data : null
761
- }
762
- }
763
- )
764
-
765
- // --------------------------------------------------------------------------------------------------
766
-
767
- // UPDATERS
768
- private readonly setData$ = this.updater((state: APIStateManagerData<T>, data: T | T[] | any) => {
769
-
770
- if (!data) return state;
771
-
772
- if (this.dataType === DataType.ARRAY) {
773
-
774
- const dataArray = Array.isArray(data) ? data : [data]
775
-
776
- const stateDataSample = (state.data.length > 0) ? Object.keys(state.data[0]) : []
777
- const newDataSample = (dataArray.length > 0) ? Object.keys(dataArray[0]) : []
778
-
779
- const isSame = (state.data.length === 0) ? false : stateDataSample.every((value, index) => value === newDataSample[index])
780
- const updatedData = (!isSame && dataArray.length !== 0) ? this.updateArrayState([], dataArray) : this.updateArrayState(state.data, dataArray)
781
-
782
- return { ...state, data: updatedData, dataObject: null } as APIStateManagerData<T>;
783
-
784
- } else {
785
- const dataObject = this.isEmpty(data) ? null : data;
786
- return { ...state, data: [], dataObject: dataObject } as APIStateManagerData<T>;
787
- }
788
-
789
- })
790
-
791
- private updateArrayState(currentData: any[], newData: any[]): any[] {
792
-
793
- // For non-streaming requests (GET), REPLACE data entirely
794
- // For streaming requests, MERGE with existing data incrementally
795
- if (this.streamedResponse.length === 0) {
796
- // GET request: return new data as-is (replace)
797
- return newData
798
- }
799
-
800
- // Streaming: merge with existing data
801
- const filterCurrentData = () => {
802
- const ids = this.streamedResponse.map((obj: any) => obj.id)
803
- return currentData.filter(obj => (obj.id) ? ids.includes(obj.id) : obj)
804
- }
805
-
806
- const filteredCurrentData = (this.httpManagerService.isPending.value)
807
- ? currentData
808
- : filterCurrentData()
809
-
810
- const updatedData = filteredCurrentData.map(item => {
811
- const newItem = newData.find(newItem => {
812
- const hasId = (newItem?.id && item?.id) ? true : false
813
- return (hasId) ? newItem.id === item.id : JSON.stringify(newItem) === JSON.stringify(item)
814
- })
815
- return (newItem) ? { ...item, ...newItem } : item
816
- })
817
-
818
- const addedData = newData.filter(newItem => {
819
- return !filteredCurrentData.some(item => {
820
- const hasId = (newItem?.id && item?.id) ? true : false
821
- return (hasId) ? item.id === newItem.id : JSON.stringify(newItem) === JSON.stringify(item)
822
- })
823
- })
824
-
825
- return [...updatedData, ...addedData]
826
-
827
- }
828
-
829
- private readonly addData$ = this.updater((state: APIStateManagerData<T>, data: T) => {
830
-
831
- if (this.dataType === DataType.ARRAY) {
832
- const exists = state.data.some(item => item.id === data.id);
833
-
834
- if (exists) {
835
- const updatedData = state.data.map(item =>
836
- item.id === data.id ? data : item
837
- );
838
-
839
- return { ...state, data: updatedData };
840
- } else {
841
- const newState = [...state.data, data];
842
- return { ...state, data: newState };
843
- }
844
- } else {
845
- return { ...state, dataObject: data };
846
- }
847
-
848
- })
849
-
850
- private readonly deleteData$ = this.updater((state: APIStateManagerData<T>, data: T & { id: number }) => {
851
- if (this.dataType === DataType.ARRAY) {
852
- const newState = state.data.filter(item => item.id !== data.id)
853
- return { ...state, ...{ data: newState } }
854
- } else {
855
- return { ...state, ...{ dataObject: null } }
856
- }
857
- })
858
-
859
- private readonly updateData$ = this.updater((state: APIStateManagerData<T>, data: T) => {
860
-
861
- if (this.dataType === DataType.ARRAY) {
862
-
863
- const objIndex = state.data.findIndex(item => item.id === data.id)
864
-
865
- if (objIndex > -1) {
866
- const newState = [...state.data]
867
- newState[objIndex] = data
868
- return { ...state, ...{ data: newState } }
869
- }
870
-
871
- return state
872
-
873
- } else {
874
- return { ...state, ...{ dataObject: data } }
875
- }
876
-
877
- })
878
-
879
- // --------------------------------------------------------------------------------------------------
880
- // EFFECTS
881
- readonly clearRecords = this.effect(data =>
882
- data.pipe(
883
-
884
- tap(() => {
885
-
886
- if (this.dataType === DataType.ARRAY) {
887
- this.setData$([])
888
- } else {
889
- this.setData$({})
890
- }
891
-
892
- }),
893
- concatMap(() => {
894
- if (this.hasDatabase && this.databaseOptions?.table) {
895
-
896
- this.clearRequestCacheMetadata(this.databaseOptions.table)
897
-
898
- return this.dbManagerService.clearTable(this.databaseOptions.table);
899
-
900
- }
901
- return of(null);
902
- })
903
- ))
904
-
905
- // --------------------------------------------------------------------------------------------------
906
- // CRUD OPERATIONS
907
-
908
- // FETCH RECORDS
909
- readonly fetchRecords = (options?: RequestOptions) =>
910
- this.effect<any>(() =>
911
- of(RequestOptions.adapt(options)).pipe(
912
- switchMap(() => {
913
-
914
- this.streamedResponse = []
915
- const requestOptions = this.updateRequestOptions(options?.headers)
916
- const effectiveParams = this.getEffectiveParams(options?.path)
917
- const requestSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams)
918
-
919
- const fetchFromAPI = () => {
920
-
921
- if (this.hasDatabase && this.databaseOptions?.table) {
922
- this.setCachedRequestSignature(this.databaseOptions.table, 'GET', requestSignature)
923
-
924
- if (!this.tryBeginInFlightRequest(requestSignature)) {
925
- const currentStateData = this.get()?.data
926
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
927
- return of(currentStateData)
928
- }
929
-
930
- if (
931
- currentStateData &&
932
- !Array.isArray(currentStateData) &&
933
- Object.keys(currentStateData).length > 0
934
- ) {
935
- return of(currentStateData)
936
- }
937
-
938
- return of(this.dataType === DataType.ARRAY ? [] : {})
939
- }
940
- }
941
-
942
- return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(
943
- tap((data: any) => {
944
- // Extract array from paginated response if needed
945
- const arrayData = (data?.results && Array.isArray(data.results)) ? data.results : data
946
- data = (!arrayData) ? (this.dataType === DataType.ARRAY) ? [] : {} : arrayData
947
- this.setData$(data)
948
- }),
949
- concatMap((data: any) => {
950
- // Extract array from paginated response for database storage
951
- const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data
952
- if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
953
- const tableName = this.databaseOptions.table
954
-
955
- const schema = this.buildSchemaFromSample(dbData[0]);
956
- const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
957
- const schemaSignature = this.buildSchemaSignature(schema)
958
-
959
- // Always ensure table exists immediately before writing to avoid stale schema/store races.
960
- return this.localStorageManagerService.store$(tableName).pipe(
961
- take(1),
962
- switchMap((storeData: any) => {
963
- this.localStorageManagerService.updateStore({
964
- name: tableName,
965
- data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions!.expiresIn) }
966
- })
967
-
968
- const ensureTable$ = this.dbManagerService.createDatabaseTable(tableDef)
969
-
970
- return ensureTable$.pipe(
971
- switchMap((created) => {
972
- if (!created) {
973
- console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
974
- return of(data);
975
- }
976
- return this.dbManagerService.createTableRecords(tableName, dbData).pipe(
977
- tap(() => this.saveRequestCacheMetadata(tableName, 'GET', requestSignature, schemaSignature, options))
978
- );
979
- })
980
- )
981
- }),
982
- catchError((error) => {
983
- console.error('[DB STORAGE] Failed to ensure table and write records:', { table: tableName, schema, error });
984
- return of(data);
985
- })
986
- );
987
- }
988
- return of(data);
989
- }),
990
- finalize(() => {
991
- if (this.hasDatabase && this.databaseOptions?.table) {
992
- this.endInFlightRequest(requestSignature)
993
- }
994
- })
995
- );
996
-
997
- }
998
-
999
- // console.log('[DB STORAGE] Checking database storage:', {
1000
- // hasDatabase: this.hasDatabase,
1001
- // table: this.databaseOptions?.table,
1002
- // databaseOptions: this.databaseOptions
1003
- // })
1004
-
1005
- if (this.hasDatabase && this.databaseOptions?.table) {
1006
-
1007
- return this.dbManagerService.databaseExists().pipe(
1008
- switchMap((dbExists: boolean) => {
1009
-
1010
- if (!dbExists) {
1011
- const initObs: Observable<any> = this.initDBStorageAsync();
1012
- return initObs.pipe(
1013
- switchMap(() => fetchFromAPI()),
1014
- catchError((error) => {
1015
- console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
1016
- return fetchFromAPI();
1017
- })
1018
- );
1019
- }
1020
-
1021
- return this.dbManagerService.hasDatabaseTable(this.databaseOptions!.table).pipe(
1022
- switchMap((tableExists: boolean): Observable<any> => {
1023
-
1024
- if (!tableExists) {
1025
- const initObs: Observable<any> = this.initDBStorageAsync();
1026
- return initObs.pipe(
1027
- switchMap(() => fetchFromAPI()),
1028
- catchError((error) => {
1029
- console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
1030
- return fetchFromAPI();
1031
- })
1032
- );
1033
- }
1034
-
1035
- return this.localStorageManagerService.store$(this.databaseOptions!.table).pipe(
1036
- take(1),
1037
- switchMap((storeData: any) => {
1038
- // console.log('[CacheDebug] storeData for table:', this.databaseOptions!.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires })
1039
-
1040
- const forceRefresh = !!(options as any)?.forceRefresh
1041
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData)
1042
-
1043
- const expires = storeData?.expires || 0;
1044
- const hasExpired = expires > 0 && this.utils.hasExpired(expires);
1045
-
1046
- if (forceRefresh) {
1047
- return fetchFromAPI();
1048
- }
1049
-
1050
- if (hasExpired) {
1051
- return this.dbManagerService.clearTable(this.databaseOptions!.table).pipe(
1052
- switchMap(() => fetchFromAPI())
1053
- );
1054
- }
1055
-
1056
- const expectedSchema = this.buildSchemaFromAdapter()
1057
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema)
1058
- // console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions!.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature })
1059
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
1060
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions!.table, schema: expectedSchema })
1061
- return this.dbManagerService.clearTable(this.databaseOptions!.table).pipe(
1062
- switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)),
1063
- switchMap(() => fetchFromAPI())
1064
- )
1065
- }
1066
-
1067
- return this.checkTrackerAllowsRequest(
1068
- this.databaseOptions!.table,
1069
- 'GET',
1070
- this.resolvePath(effectiveParams),
1071
- options,
1072
- storeData
1073
- ).pipe(
1074
- switchMap((trackerAllowsRequest: boolean) => {
1075
- if (trackerAllowsRequest) {
1076
- const normalizedPath = this.trackerNormalizePath(this.resolvePath(effectiveParams));
1077
-
1078
- if (!normalizedPath.hasQuery) {
1079
- return this.dbManagerService.getTableRecords(this.databaseOptions!.table).pipe(
1080
- switchMap((dbData: any) => {
1081
- if (Array.isArray(dbData) && dbData.length > 0) {
1082
- this.setData$(dbData);
1083
- // Save cache metadata for both GET and STREAM request types so
1084
- // tracker/check logic can detect cached responses regardless
1085
- // of whether a subsequent call is a normal GET or a streaming GET.
1086
- const getSignature = requestSignature;
1087
- const streamRequestOptions = { ...(requestOptions || {}), stream: true } as any;
1088
- const streamSignature = this.buildRequestSignature('STREAM', streamRequestOptions, effectiveParams);
1089
- this.saveRequestCacheMetadata(
1090
- this.databaseOptions!.table,
1091
- 'GET',
1092
- getSignature,
1093
- storedSchemaSignature ?? expectedSchemaSignature ?? undefined,
1094
- options
1095
- );
1096
- this.saveRequestCacheMetadata(
1097
- this.databaseOptions!.table,
1098
- 'STREAM',
1099
- streamSignature,
1100
- storedSchemaSignature ?? expectedSchemaSignature ?? undefined,
1101
- options
1102
- );
1103
- return of({ data: dbData, fromCache: true });
1104
- }
1105
- return fetchFromAPI();
1106
- }),
1107
- catchError((error) => {
1108
- const tableName = this.databaseOptions!.table;
1109
- console.warn('[DB STORAGE] fallback cache read failed, falling back to API:', { table: tableName, error });
1110
- return fetchFromAPI();
1111
- })
1112
- );
1113
- }
1114
-
1115
- return fetchFromAPI();
1116
- }
1117
-
1118
- return this.dbManagerService.getTableRecords(this.databaseOptions!.table).pipe(
1119
- switchMap((dbData: any) => {
1120
-
1121
- if (Array.isArray(dbData) && dbData.length > 0) {
1122
- this.setData$(dbData);
1123
- this.saveRequestCacheMetadata(
1124
- this.databaseOptions!.table,
1125
- 'GET',
1126
- requestSignature,
1127
- storedSchemaSignature ?? expectedSchemaSignature ?? undefined,
1128
- options
1129
- );
1130
- return of(dbData);
1131
- }
1132
-
1133
- return fetchFromAPI();
1134
-
1135
- })
1136
- ,
1137
- catchError((error) => {
1138
- const tableName = this.databaseOptions!.table;
1139
- console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
1140
- const schema = this.buildSchemaFromAdapter();
1141
- const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
1142
- return this.dbManagerService.createDatabaseTable(tableDef).pipe(
1143
- switchMap(() => fetchFromAPI()),
1144
- catchError((recreateError) => {
1145
- console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
1146
- return fetchFromAPI();
1147
- })
1148
- );
1149
- })
1150
- );
1151
- })
1152
- );
1153
- })
1154
- );
1155
-
1156
- })
1157
- );
1158
- })
1159
- );
1160
-
1161
- }
1162
-
1163
- return fetchFromAPI();
1164
-
1165
- })
1166
- )
1167
- )
1168
-
1169
- private initDBStorageAsync(): Observable<any> {
1170
-
1171
- console.log('[initDBStorageAsync] Starting initialization:', {
1172
- dataType: this.dataType,
1173
- hasAdapter: !!this.apiOptions?.adapter,
1174
- table: this.databaseOptions?.table,
1175
- databaseOptions: this.databaseOptions
1176
- })
1177
-
1178
- if (this.dataType !== DataType.ARRAY) {
1179
- console.warn('Database storage requires dataType to be ARRAY');
1180
- return of(null);
1181
- }
1182
-
1183
- if (!this.apiOptions.adapter) {
1184
- console.warn('Database storage adapter missing, using minimal or inferred schema');
1185
- }
1186
-
1187
- if (!this.databaseOptions?.table) {
1188
- console.warn('Database storage requires a table name');
1189
- return of(null);
1190
- }
1191
-
1192
- const schema = this.buildSchemaFromAdapter();
1193
-
1194
- const tableDef = TableSchemaDef.adapt({
1195
- table: this.databaseOptions?.table,
1196
- schema: schema
1197
- });
1198
-
1199
- return this.dbManagerService.createDatabaseTable(tableDef).pipe(
1200
- tap((created) => {
1201
- if (created && this.databaseOptions?.table) {
1202
- this.saveSchemaSignature(this.databaseOptions.table, this.buildSchemaSignature(schema))
1203
- }
1204
- })
1205
- );
1206
-
1207
- }
1208
-
1209
- private buildSchemaFromAdapter(): string {
1210
- const sampleData = this.apiOptions.adapter?.({}) || {};
1211
- return this.buildSchemaFromSample(sampleData);
1212
- }
1213
-
1214
- private buildSchemaFromSample(sampleData: any): string {
1215
- const schemaKeys = Object.keys(sampleData || {}).filter(key => sampleData[key] !== undefined);
1216
- let schema = '++id';
1217
-
1218
- if (schemaKeys.length > 0) {
1219
- const otherKeys = schemaKeys.filter((k) => k !== 'id').sort();
1220
- if (otherKeys.length > 0) {
1221
- schema += ', ' + otherKeys.join(', ');
1222
- }
1223
- }
1224
-
1225
- return schema;
1226
- }
1227
-
1228
- private persistStreamDataToDb(payload: any, streamOptions?: RequestOptions): Observable<any> {
1229
- if (!this.hasDatabase || !this.databaseOptions?.table) {
1230
- return of(payload);
1231
- }
1232
-
1233
- const dbData = (payload?.results && Array.isArray(payload.results))
1234
- ? payload.results
1235
- : Array.isArray(payload)
1236
- ? payload
1237
- : payload
1238
- ? [payload]
1239
- : [];
1240
-
1241
- if (dbData.length === 0) {
1242
- return of(payload);
1243
- }
1244
-
1245
- const tableName = this.databaseOptions.table;
1246
- const requestOptions = this.updateRequestOptions(streamOptions?.headers)
1247
- requestOptions.stream = true
1248
- const effectiveParams = this.getEffectiveParams(streamOptions?.path)
1249
- const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams)
1250
-
1251
- const schema = this.buildSchemaFromSample(dbData[0]);
1252
- const schemaSignature = this.buildSchemaSignature(schema)
1253
- const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
1254
-
1255
- return this.localStorageManagerService.store$(tableName).pipe(
1256
- take(1),
1257
- switchMap((storeData: any) => {
1258
- this.localStorageManagerService.updateStore({
1259
- name: tableName,
1260
- data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions!.expiresIn) }
1261
- });
1262
-
1263
- const ensureTable$ = this.dbManagerService.createDatabaseTable(tableDef)
1264
-
1265
- return ensureTable$.pipe(
1266
- switchMap((created) => {
1267
- if (!created) {
1268
- console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
1269
- return of(payload);
1270
- }
1271
- return this.dbManagerService.createTableRecords(tableName, dbData).pipe(
1272
- tap(() => this.saveRequestCacheMetadata(tableName, 'STREAM', requestSignature, schemaSignature, streamOptions)),
1273
- map(() => payload)
1274
- );
1275
- })
1276
- )
1277
- }),
1278
- map(() => payload),
1279
- catchError((error) => {
1280
- console.error('[DB STORAGE] Failed to persist streaming payload:', { table: tableName, schema, error });
1281
- return of(payload);
1282
- })
1283
- );
1284
- }
1285
-
1286
- // FETCH RECORD
1287
- readonly fetchRecord = (options: RequestOptions, method: string) =>
1288
- this.effect<any>(() =>
1289
- of(RequestOptions.adapt(options)).pipe(
1290
- tap(() => console.log('🔄 fetchRecord effect triggered with path:', options?.path, 'method:', method)),
1291
- switchMap((options) => {
1292
-
1293
- this.streamedResponse = []
1294
-
1295
- // Temporarily update apiOptions.path with the path from the WebSocket message
1296
- // This ensures the request goes to the correct endpoint
1297
- const originalPath = this.apiOptions.path;
1298
- if (options?.path && Array.isArray(options.path)) {
1299
- this.apiOptions.path = options.path;
1300
- console.log('🔧 Temporarily set apiOptions.path to:', options.path);
1301
- }
1302
-
1303
- const requestOptions = this.updateRequestOptions(options?.headers)
1304
-
1305
- console.log('🌐 Making GET request to path:', this.apiOptions.path)
1306
- return this.httpManagerService.getRequest(requestOptions)
1307
- .pipe(
1308
- tap((data: any) => {
1309
- console.log('📦 fetchRecord received data:', data)
1310
-
1311
- // Restore original path after request completes
1312
- this.apiOptions.path = originalPath;
1313
- console.log('🔧 Restored apiOptions.path to:', originalPath);
1314
-
1315
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data
1316
-
1317
- const id = (options as any).path?.length ? options.path[options.path.length -1] : null
1318
-
1319
- if(method === 'DELETE') {
1320
- console.log('🗑️ Deleting record with id:', id)
1321
- this.deleteData$({ id } as T & { id: number })
1322
- }
1323
- if(method === 'UPDATE') {
1324
- console.log('✏️ Updating record:', data)
1325
- this.updateData$(data)
1326
- }
1327
- if(method === 'CREATE') {
1328
- console.log('➕ Adding record:', data)
1329
- this.addData$(data)
1330
- }
1331
-
1332
- }),
1333
- concatMap((data: any) => {
1334
- if (this.hasDatabase && this.databaseOptions?.table) {
1335
- const id = (options as any).path?.length ? options.path[options.path.length -1] : null
1336
-
1337
- if(method === 'DELETE' && id) return this.dbManagerService.deleteTableRecord(this.databaseOptions.table, id)
1338
- if(method === 'UPDATE' && data) return this.dbManagerService.updateTableRecord(this.databaseOptions.table, data)
1339
- if(method === 'CREATE' && data) {
1340
- // Validate that data has a valid id before saving to IndexedDB
1341
- if (data && (data.id !== undefined && data.id !== null && data.id !== '')) {
1342
- console.log('💾 Saving to IndexedDB:', { table: this.databaseOptions.table, id: data.id, data });
1343
- return this.dbManagerService.createTableRecord(this.databaseOptions.table, data);
1344
- } else {
1345
- console.warn('⚠️ Skipping IndexedDB save: data.id is invalid', data);
1346
- }
1347
- }
1348
- }
1349
- return of(data)
1350
- })
1351
- )
1352
-
1353
- })
1354
- )
1355
- )
1356
-
1357
- // CREATE RECORD
1358
- readonly createRecord = (data: any|null, options?: RequestOptions) =>
1359
- this.effect<any>(() =>
1360
- of(data).pipe(
1361
- switchMap((data: any) => {
1362
-
1363
- this.streamedResponse = []
1364
- const requestOptions = this.updateRequestOptions(options?.headers)
1365
-
1366
- return this.httpManagerService.postRequest(data, requestOptions, options?.path)
1367
- .pipe(
1368
- tap((data: any) => {
1369
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data
1370
- this.addData$(data)
1371
- this.operationSuccess.next(OperationResultModel.adapt({ success: true, operation: 'CREATE' }))
1372
- // Always call wsCommunication - it will queue if not connected
1373
- this.wsCommunication('CREATE', [...options?.path || [], data.id])
1374
- }),
1375
- concatMap((data: any) => {
1376
- if (this.hasDatabase && this.databaseOptions?.table && data?.id) {
1377
- return this.dbManagerService.createTableRecord(this.databaseOptions.table, data);
1378
- }
1379
- return of(data);
1380
- })
1381
- )
1382
-
1383
- })
1384
- )
1385
- )
1386
-
1387
- // UPDATE RECORD
1388
- readonly updateRecord = (data: any|null, options?: RequestOptions) =>
1389
- this.effect<any>(() =>
1390
- of(data).pipe(
1391
- concatMap((data: any) => {
1392
-
1393
- this.streamedResponse = []
1394
- const requestOptions = this.updateRequestOptions(options?.headers)
1395
-
1396
- return this.httpManagerService.putRequest(data, requestOptions, options?.path)
1397
- .pipe(
1398
- tap((data: any) => {
1399
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data
1400
- this.updateData$(data)
1401
- this.operationSuccess.next(OperationResultModel.adapt({ success: true, operation: 'UPDATE' }))
1402
- // Always call wsCommunication - it will queue if not connected
1403
- this.wsCommunication('UPDATE', [...options?.path || []])
1404
- }),
1405
- concatMap((data: any) => {
1406
- if (this.hasDatabase && this.databaseOptions?.table && data?.id) {
1407
- return this.dbManagerService.updateTableRecord(this.databaseOptions.table, data);
1408
- }
1409
- return of(data);
1410
- })
1411
- )
1412
-
1413
- })
1414
- )
1415
- )
1416
-
1417
- // DELETE RECORD
1418
- readonly deleteRecord = (options?: RequestOptions) =>
1419
- this.effect<any>(() =>
1420
- of(options).pipe(
1421
- concatMap((data: any) => {
1422
-
1423
- this.streamedResponse = []
1424
- const requestOptions = this.updateRequestOptions(options?.headers)
1425
-
1426
- return this.httpManagerService.deleteRequest(requestOptions, options?.path)
1427
- .pipe(
1428
- tap((data: any) => {
1429
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data
1430
- this.deleteData$(data)
1431
- this.operationSuccess.next(OperationResultModel.adapt({ success: true, operation: 'DELETE' }))
1432
- // Always call wsCommunication - it will queue if not connected
1433
- this.wsCommunication('DELETE', [...options?.path || []])
1434
- }),
1435
- concatMap((data: any) => {
1436
- if (this.hasDatabase && this.databaseOptions?.table && data?.id) {
1437
- return this.dbManagerService.deleteTableRecord(this.databaseOptions.table, data.id);
1438
- }
1439
- return of(data);
1440
- })
1441
- )
1442
-
1443
- })
1444
- )
1445
- )
1446
-
1447
- // --------------------------------------------------------------------------------------------------
1448
- // FETCH STREAM
1449
- readonly createStream = (data: any|null, options?: RequestOptions) =>
1450
- this.effect<any>(() =>
1451
- of(data).pipe(
1452
- tap(() => this.httpManagerService.isPending.next(true)),
1453
- switchMap((data: any) => {
1454
-
1455
- const requestOptions = this.updateRequestOptions(options?.headers)
1456
-
1457
- return this.httpManagerService.postRequest(data, requestOptions, options?.path)
1458
- .pipe(
1459
- tap((res: any) => {
1460
- if(res.length > 0) this.setData$(res)
1461
- this.streamedResponse = res
1462
- }),
1463
- concatMap((res: any) => this.persistStreamDataToDb(res, options)),
1464
- scan((acc, res: any) => {
1465
-
1466
- const previous = acc.current
1467
- const current = res
1468
- return { previous, current };
1469
-
1470
- }, { previous: null, current: null }),
1471
- tap(({ previous, current }) => {
1472
-
1473
- if(previous && JSON.stringify(previous) === JSON.stringify(current)) {
1474
- this.httpManagerService.isPending.next(false)
1475
- this.setData$([])
1476
- } else {
1477
- this.httpManagerService.isPending.next(true)
1478
- }
1479
-
1480
- })
1481
- )
1482
-
1483
- })
1484
- )
1485
-
1486
- )
1487
-
1488
- readonly fetchStream = (options?: RequestOptions) =>
1489
- this.effect<any>(() =>
1490
- of(options).pipe(
1491
- tap(() => {
1492
- // console.log('[DEBUG] fetchStream called')
1493
- this.streamedResponse = []
1494
- this.httpManagerService.isPending.next(true)
1495
- }),
1496
- switchMap((options: any) => {
1497
-
1498
- const requestOptions = this.updateRequestOptions(options?.headers)
1499
- requestOptions.stream = true
1500
- const effectiveParams = this.getEffectiveParams(options?.path)
1501
-
1502
- // console.log('[DEBUG] Making streaming request:', requestOptions)
1503
-
1504
- if (this.hasDatabase && this.databaseOptions?.table) {
1505
- const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams)
1506
- const fetchStreamFromAPI = () => {
1507
- if (!this.tryBeginInFlightRequest(requestSignature)) {
1508
- const currentStateData = this.get()?.data
1509
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
1510
- return of({ data: currentStateData, fromCache: true })
1511
- }
1512
-
1513
- if (
1514
- currentStateData &&
1515
- !Array.isArray(currentStateData) &&
1516
- Object.keys(currentStateData).length > 0
1517
- ) {
1518
- return of({ data: currentStateData, fromCache: true })
1519
- }
1520
-
1521
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true })
1522
- }
1523
-
1524
- this.setCachedRequestSignature(this.databaseOptions!.table, 'STREAM', requestSignature)
1525
- return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(
1526
- map((apiData: any) => ({ data: apiData, fromCache: false })),
1527
- finalize(() => this.endInFlightRequest(requestSignature))
1528
- )
1529
- }
1530
-
1531
- return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(
1532
- take(1),
1533
- switchMap((storeData: any) => {
1534
- const forceRefresh = !!(options as any)?.forceRefresh
1535
- const expires = storeData?.expires || 0
1536
- const hasExpired = expires > 0 && this.utils.hasExpired(expires)
1537
-
1538
- if (forceRefresh) {
1539
- return fetchStreamFromAPI()
1540
- }
1541
-
1542
- if (hasExpired) {
1543
- return this.dbManagerService.clearTable(this.databaseOptions!.table).pipe(
1544
- switchMap(() => fetchStreamFromAPI())
1545
- )
1546
- }
1547
-
1548
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData)
1549
- const expectedSchema = this.buildSchemaFromAdapter()
1550
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema)
1551
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
1552
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions!.table, schema: expectedSchema })
1553
- return this.dbManagerService.clearTable(this.databaseOptions!.table).pipe(
1554
- switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)),
1555
- switchMap(() => fetchStreamFromAPI())
1556
- )
1557
- }
1558
-
1559
- return this.checkTrackerAllowsRequest(
1560
- this.databaseOptions!.table,
1561
- 'STREAM',
1562
- this.resolvePath(effectiveParams),
1563
- options,
1564
- storeData
1565
- ).pipe(
1566
- switchMap((trackerAllowsRequest: boolean) => {
1567
- if (!trackerAllowsRequest) {
1568
- return this.dbManagerService.getTableRecords(this.databaseOptions!.table).pipe(
1569
- switchMap((dbData: any) => {
1570
- if (Array.isArray(dbData) && dbData.length > 0) {
1571
- return of({ data: dbData, fromCache: true })
1572
- }
1573
-
1574
- const currentStateData = this.get()?.data
1575
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
1576
- return of({ data: currentStateData, fromCache: true })
1577
- }
1578
-
1579
- if (
1580
- currentStateData &&
1581
- !Array.isArray(currentStateData) &&
1582
- Object.keys(currentStateData).length > 0
1583
- ) {
1584
- return of({ data: currentStateData, fromCache: true })
1585
- }
1586
-
1587
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true })
1588
- })
1589
- )
1590
- }
1591
-
1592
- return fetchStreamFromAPI()
1593
- })
1594
- )
1595
- })
1596
- ).pipe(
1597
- tap((packet: any) => {
1598
- const res = packet?.data
1599
- // console.log('[DEBUG] Streaming response received:', res)
1600
-
1601
- if (res && res.length > 0) {
1602
- // console.log('[DEBUG] Updating state with streaming data:', res)
1603
- this.setData$(res)
1604
- this.streamedResponse = [...this.streamedResponse, ...res]
1605
- }
1606
- }),
1607
- concatMap((packet: any) => {
1608
- if (packet?.fromCache) {
1609
- return of(packet?.data)
1610
- }
1611
- return this.persistStreamDataToDb(packet?.data, options)
1612
- }),
1613
- map((res: any) => {
1614
- // console.log('[DEBUG] Returning data to subscribers:', res)
1615
- return res
1616
- }),
1617
- catchError((error) => {
1618
- console.error('[DEBUG] Streaming error:', error)
1619
- return of([])
1620
- }),
1621
- finalize(() => this.httpManagerService.isPending.next(false))
1622
- )
1623
- }
1624
-
1625
- // If no database is configured, always call the API (do not check tracker)
1626
- return this.httpManagerService.getRequest(requestOptions, effectiveParams)
1627
- .pipe(
1628
- tap((res: any) => {
1629
- console.log('[DEBUG] Streaming response received:', res)
1630
-
1631
- // Always update state with streaming data
1632
- if (res && res.length > 0) {
1633
- // console.log('[DEBUG] Updating state with streaming data:', res)
1634
- this.setData$(res)
1635
- this.streamedResponse = [...this.streamedResponse, ...res]
1636
- } else {
1637
- // console.log('[DEBUG] No streaming data or empty array:', res)
1638
- }
1639
- }),
1640
- concatMap((res: any) => this.persistStreamDataToDb(res, options)),
1641
- map((res: any) => {
1642
- // console.log('[DEBUG] Returning data to subscribers:', res)
1643
- return res // Return the data so subscribers can receive it
1644
- }),
1645
- catchError((error) => {
1646
- // console.error('[DEBUG] Streaming error:', error)
1647
- return of([])
1648
- }),
1649
- finalize(() => this.httpManagerService.isPending.next(false))
1650
- )
1651
-
1652
- }),
1653
- )
1654
- )
1655
-
1656
- // WEBSOCKET COMMUNICATION (STATE MANAGER)
1657
- private wsCommunication(method: string, path?: string|number[]) {
1658
- if (!this.apiOptions.ws) {
1659
- console.warn('wsCommunication called but no WebSocket options configured');
1660
- return;
1661
- }
1662
-
1663
- // If connected, send immediately (check singleton WebSocketManagerService)
1664
- if (WebSocketManagerService.isConnected()) {
1665
- this.sendWsCommunication(method, path);
1666
- } else {
1667
- // Queue the message to be sent when connection is established
1668
- console.log(`⏳ Queuing WS message (not connected): ${method} ${path ? JSON.stringify(path) : ''}`);
1669
- HTTPManagerStateService.wsCommunicationQueue.push({ method, path });
1670
- }
1671
- }
1672
-
1673
- /**
1674
- * Actually send the WebSocket message (called when connected or from queue)
1675
- */
1676
- private sendWsCommunication(method: string, path?: string|number[]) {
1677
- if (this.apiOptions.ws) {
1678
- const wsServer = this.apiOptions.ws.id;
1679
-
1680
- // Guard: Don't send if channel is empty
1681
- if (!wsServer || wsServer === '') {
1682
- console.error('❌ Cannot send WS message: Channel ID is empty!');
1683
- return;
1684
- }
1685
-
1686
- // DEBUG: Log what we're sending
1687
- console.log('🔍 [DEBUG] sendWsCommunication called:', {
1688
- wsServer,
1689
- wsServerType: typeof wsServer,
1690
- wsServerEmpty: wsServer === '' || wsServer === null || wsServer === undefined,
1691
- method,
1692
- path,
1693
- user: this.apiOptions.ws.user,
1694
- fullPayload: { method, path, user: this.apiOptions.ws.user }
1695
- });
1696
-
1697
- this.httpManagerService.sendMessageInChannel(wsServer, { method, path, user: this.apiOptions.ws.user });
1698
- } else {
1699
- console.error('❌ [DEBUG] sendWsCommunication: apiOptions.ws is undefined!');
1700
- }
1701
- }
1702
-
1703
- /**
1704
- * Send a message to channel(s)
1705
- * @param message - The message content
1706
- * @param channels - Optional array of channel names (passed as-is, caller should include prefix)
1707
- * Use 'allChannels' to broadcast to all
1708
- */
1709
- wsMessaging(message: ChannelMessage, channels?: string[]) {
1710
-
1711
- const user = this.user.value
1712
- const messageInfo = ChannelMessage.adapt({ ...message, fromUser: user })
1713
-
1714
- console.log('📤 wsMessaging called with channels:', channels);
1715
-
1716
- if (WebSocketManagerService.isConnected() && this.apiOptions.ws) {
1717
-
1718
- // If specific channels provided, send to each channel
1719
- // Channels are passed as-is - caller is responsible for including the correct prefix
1720
- if (channels && channels.length > 0) {
1721
- console.log(`📤 Sending to ${channels.length} channel(s):`, channels);
1722
-
1723
- // Send single message with all channels (more efficient)
1724
- const user = this.user.value;
1725
- const messageData = {
1726
- sessionId: user,
1727
- content: messageInfo
1728
- };
1729
-
1730
- this.httpManagerService.sendChannelMessageToChannels(channels, messageData);
1731
- } else {
1732
- // Fallback to the primary WS channel (already prefixed with SYS-)
1733
- const wsChannel = this.apiOptions.ws.id;
1734
- console.log(`📤 Sending to state channel:`, wsChannel);
1735
- this.httpManagerService.sendChannelMessage(wsChannel, messageInfo);
1736
- }
1737
-
1738
- }
1739
-
1740
- }
1741
-
1742
- /**
1743
- * Subscribe to a messaging channel
1744
- * @param channel - Base channel name (MES- prefix added automatically)
1745
- */
1746
- subscribeToMessageChannel(channel: string) {
1747
- if (WebSocketManagerService.isConnected()) {
1748
- const prefixedChannel = this.prefixChannel(channel, ChannelType.MESSAGE);
1749
- this.httpManagerService.subscribeToChannel(prefixedChannel);
1750
- console.log(`💬 Subscribed to message channel: ${prefixedChannel}`);
1751
- } else {
1752
- console.warn('Cannot subscribe to message channel: WebSocket not connected.');
1753
- }
1754
- }
1755
-
1756
- /**
1757
- * Unsubscribe from a messaging channel
1758
- * @param channel - Base channel name (MES- prefix added automatically)
1759
- */
1760
- unsubscribeFromMessageChannel(channel: string) {
1761
- if (WebSocketManagerService.isConnected()) {
1762
- const prefixedChannel = this.prefixChannel(channel, ChannelType.MESSAGE);
1763
- this.httpManagerService.unsubscribeFromChannel(prefixedChannel);
1764
- console.log(`💬 Unsubscribed from message channel: ${prefixedChannel}`);
1765
- } else {
1766
- console.warn('Cannot unsubscribe from message channel: WebSocket not connected.');
1767
- }
1768
- }
1769
-
1770
- // --------------------------------------------------------------------------------------------------
1771
- // CHANNEL MANAGEMENT (Raw channels - no automatic prefix)
1772
-
1773
- /**
1774
- * Subscribe to a single channel (no automatic prefix)
1775
- * Use subscribeToMessageChannel() for MES- prefixed channels
1776
- */
1777
- subscribeToChannel(channel: string) {
1778
- if (WebSocketManagerService.isConnected()) {
1779
- // Get current user data to send with subscription
1780
- const currentUser = this.user.value;
1781
- console.log('👤 Subscribing with user:', currentUser)
1782
- this.httpManagerService.subscribeToChannel(channel, currentUser);
1783
- } else {
1784
- console.warn('Cannot subscribe: WebSocket not connected.');
1785
- }
1786
- }
1787
-
1788
- /**
1789
- * Subscribe to multiple channels at once
1790
- */
1791
- subscribeToChannels(channels: string[]) {
1792
- if (WebSocketManagerService.isConnected()) {
1793
- // Get current user data to send with subscription
1794
- const currentUser = this.user.value;
1795
- console.log('👤 Subscribing to', channels.length, 'channels with user:', currentUser)
1796
- this.httpManagerService.subscribeToChannels(channels, currentUser);
1797
- } else {
1798
- console.warn('Cannot subscribe: WebSocket not connected.');
1799
- }
1800
- }
1801
-
1802
- /**
1803
- * Unsubscribe from a channel
1804
- */
1805
- unsubscribeFromChannel(channel: string) {
1806
- if (WebSocketManagerService.isConnected()) {
1807
- this.httpManagerService.unsubscribeFromChannel(channel);
1808
- } else {
1809
- console.warn('Cannot unsubscribe: WebSocket not connected.');
1810
- }
1811
- }
1812
-
1813
- /**
1814
- * Get observable of currently subscribed channels
1815
- */
1816
- get subscribedChannels$(): Observable<Set<string>> {
1817
- return this.httpManagerService.subscribedChannels$;
1818
- }
1819
-
1820
- /**
1821
- * Get current subscribed channels synchronously
1822
- */
1823
- getSubscribedChannels(): Set<string> {
1824
- return this.httpManagerService.getSubscribedChannels();
1825
- }
1826
-
1827
- /**
1828
- * Create a new channel on the server
1829
- */
1830
- createChannel(channel: string) {
1831
- if (WebSocketManagerService.isConnected()) {
1832
- this.httpManagerService.createChannel(channel);
1833
- } else {
1834
- console.warn('Cannot create channel: WebSocket not connected.');
1835
- }
1836
- }
1837
-
1838
- /**
1839
- * Delete a channel from the server
1840
- */
1841
- deleteChannel(channel: string) {
1842
- if (WebSocketManagerService.isConnected()) {
1843
- this.httpManagerService.deleteChannel(channel);
1844
- } else {
1845
- console.warn('Cannot delete channel: WebSocket not connected.');
1846
- }
1847
- }
1848
-
1849
- /**
1850
- * Request list of all channels from server
1851
- */
1852
- getAllChannels() {
1853
- if (WebSocketManagerService.isConnected()) {
1854
- this.httpManagerService.getAllChannels();
1855
- } else {
1856
- console.warn('Cannot get channels: WebSocket not connected.');
1857
- }
1858
- }
1859
-
1860
- /**
1861
- * Get users in a specific channel
1862
- */
1863
- getUsersInChannel(channel: string) {
1864
- if (WebSocketManagerService.isConnected()) {
1865
- this.httpManagerService.getUsersInChannel(channel);
1866
- } else {
1867
- console.warn('Cannot get users: WebSocket not connected.');
1868
- }
1869
- }
1870
-
1871
- // --------------------------------------------------------------------------------------------------
1872
- // NOTIFICATION CHANNELS (MES- prefix - managed automatically)
1873
-
1874
- /**
1875
- * Create a notification channel on the server
1876
- * @param channel - Base channel name (MES- prefix added automatically)
1877
- */
1878
- createNotificationChannel(channel: string) {
1879
- if (WebSocketManagerService.isConnected()) {
1880
- const prefixedChannel = this.prefixChannel(channel, ChannelType.NOTIFICATION);
1881
- this.httpManagerService.createNotificationChannel(prefixedChannel);
1882
- console.log(`📢 Creating notification channel: ${prefixedChannel}`);
1883
- } else {
1884
- console.warn('Cannot create notification channel: WebSocket not connected.');
1885
- }
1886
- }
1887
-
1888
- /**
1889
- * Request list of all notification channels from server (in-memory)
1890
- */
1891
- getNotificationChannels() {
1892
- if (WebSocketManagerService.isConnected()) {
1893
- this.httpManagerService.getNotificationChannels();
1894
- } else {
1895
- console.warn('Cannot get notification channels: WebSocket not connected.');
1896
- }
1897
- }
1898
-
1899
- /**
1900
- * Request list of today's notification channels from database
1901
- * Returns unique channels that have notifications posted today
1902
- */
1903
- getTodaysNotificationChannels() {
1904
- if (WebSocketManagerService.isConnected()) {
1905
- this.httpManagerService.getTodaysNotificationChannels();
1906
- } else {
1907
- console.warn('Cannot get today\'s notification channels: WebSocket not connected.');
1908
- }
1909
- }
1910
-
1911
- /**
1912
- * Subscribe to a notification channel with optional date filters
1913
- * @param channel - Base channel name (MES- prefix added automatically)
1914
- */
1915
- subscribeToNotificationChannel(channel: string, options?: { startEpoch?: number; endEpoch?: number }, user?: any) {
1916
- if (WebSocketManagerService.isConnected()) {
1917
- const prefixedChannel = this.prefixChannel(channel, ChannelType.NOTIFICATION);
1918
- this.httpManagerService.subscribeToNotificationChannel(prefixedChannel, options, user);
1919
- console.log(`📢 Subscribing to notification channel: ${prefixedChannel}`);
1920
- } else {
1921
- console.warn('Cannot subscribe to notification channel: WebSocket not connected.');
1922
- }
1923
- }
1924
-
1925
- /**
1926
- * Unsubscribe from a notification channel
1927
- * @param channel - Base channel name (MES- prefix added automatically)
1928
- */
1929
- unsubscribeFromNotificationChannel(channel: string) {
1930
- if (WebSocketManagerService.isConnected()) {
1931
- const prefixedChannel = this.prefixChannel(channel, ChannelType.NOTIFICATION);
1932
- this.httpManagerService.unsubscribeFromNotificationChannel(prefixedChannel);
1933
- console.log(`📢 Unsubscribing from notification channel: ${prefixedChannel}`);
1934
- } else {
1935
- console.warn('Cannot unsubscribe from notification channel: WebSocket not connected.');
1936
- }
1937
- }
1938
-
1939
- /**
1940
- * Send a notification to a channel
1941
- * @param channel - Base channel name (MES- prefix added automatically)
1942
- */
1943
- sendNotification(channel: string, content: any) {
1944
- if (WebSocketManagerService.isConnected()) {
1945
- const prefixedChannel = this.prefixChannel(channel, ChannelType.NOTIFICATION);
1946
- this.httpManagerService.sendNotification(prefixedChannel, content);
1947
- console.log(`📢 Sending notification to channel: ${prefixedChannel}`);
1948
- } else {
1949
- console.warn('Cannot send notification: WebSocket not connected.');
1950
- }
1951
- }
1952
-
1953
- // --------------------------------------------------------------------------------------------------
1954
- // MISC
1955
-
1956
- /**
1957
- * Clear/flush all records from the database table
1958
- * Does not clear localStorage metadata (expires info)
1959
- * Does not re-fetch from API - leaves state empty for manual refresh
1960
- */
1961
- clearDatabase() {
1962
-
1963
- if (!this.hasDatabase || !this.databaseOptions?.table) return;
1964
-
1965
- const tableName = this.databaseOptions.table;
1966
-
1967
- // Reset localStorage tracking SYNCHRONOUSLY before the async DB clear so any
1968
- // concurrent request that fires during clearTable() sees a clean store immediately
1969
- // and is not blocked by stale queryParams from the previous session.
1970
- this.localStorageManagerService.updateStore({
1971
- name: tableName,
1972
- data: { ...this.databaseOptions, expires: 0 }
1973
- });
1974
- this._requestCachePaths.delete(tableName);
1975
-
1976
- this.dbManagerService.clearTable(tableName).subscribe({
1977
- next: () => {
1978
- if (this.dataType === DataType.ARRAY) {
1979
- this.setData$([]);
1980
- } else {
1981
- this.setData$({});
1982
- }
1983
- },
1984
- error: (err) => {
1985
- console.error(`❌ Error clearing table ${tableName}:`, err);
1986
- }
1987
- });
1988
- }
1989
-
1990
- private isEmpty(obj: any) {
1991
- return Object.keys(obj).length === 0
1992
- }
1993
-
1994
- private updateRequestOptions(headers?: any): ApiRequest {
1995
-
1996
- const options = ApiRequest.adapt({ ...this.apiOptions })
1997
-
1998
- options.headers = (headers)
1999
- ? { ...options.headers, ...headers }
2000
- : { ...options.headers }
2001
-
2002
- return options
2003
- }
2004
-
2005
- private normalizeObject(value: any): any {
2006
- if (Array.isArray(value)) {
2007
- return value.map((item) => this.normalizeObject(item))
2008
- }
2009
-
2010
- if (value && typeof value === 'object') {
2011
- return Object.keys(value)
2012
- .sort()
2013
- .reduce((acc: any, key: string) => {
2014
- acc[key] = this.normalizeObject(value[key])
2015
- return acc
2016
- }, {})
2017
- }
2018
-
2019
- return value
2020
- }
2021
-
2022
- private filterHeaders(headers?: any): Record<string, any> {
2023
- const source = headers || {}
2024
- return Object.keys(source).reduce((acc: Record<string, any>, key: string) => {
2025
- if (!this.volatileHeaders.has(key.toLowerCase())) {
2026
- acc[key] = source[key]
2027
- }
2028
- return acc
2029
- }, {})
2030
- }
2031
-
2032
- private resolvePath(params?: any[]): any[] {
2033
- const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : []
2034
- const effective = this.getEffectiveParams(params)
2035
- return effective ? [...basePath, ...effective] : [...basePath]
2036
- }
2037
-
2038
- private getEffectiveParams(params?: any[]): any[] | undefined {
2039
- if (!Array.isArray(params) || params.length === 0) {
2040
- return undefined
2041
- }
2042
-
2043
- const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : []
2044
-
2045
- if (basePath.length !== params.length) {
2046
- return params
2047
- }
2048
-
2049
- const normalizePart = (value: any): string => {
2050
- if (value && typeof value === 'object') {
2051
- return JSON.stringify(this.normalizeObject(value))
2052
- }
2053
- return String(value)
2054
- }
2055
-
2056
- const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]))
2057
- return samePath ? undefined : params
2058
- }
2059
-
2060
- private buildRequestSignature(method: 'GET'|'STREAM', requestOptions: ApiRequest, params?: any[]): string {
2061
- const signaturePayload = {
2062
- method,
2063
- server: requestOptions.server,
2064
- path: this.resolvePath(params),
2065
- headers: this.filterHeaders(requestOptions.headers),
2066
- stream: !!requestOptions.stream,
2067
- streamType: requestOptions.streamType || null
2068
- }
2069
-
2070
- return JSON.stringify(this.normalizeObject(signaturePayload))
2071
- }
2072
-
2073
- private buildSchemaSignature(schema: string): string {
2074
- return JSON.stringify(this.normalizeObject(schema.split(',').map((part) => part.trim()).filter(Boolean)))
2075
- }
2076
-
2077
- private setCachedRequestSignature(tableName: string, type: 'GET'|'STREAM', signature: string): void {
2078
- const existing = this.requestSignatureCache[tableName] || {}
2079
- this.requestSignatureCache[tableName] = {
2080
- ...existing,
2081
- [type]: signature,
2082
- }
2083
- }
2084
-
2085
- private tryBeginInFlightRequest(signature: string): boolean {
2086
- if (this.inFlightRequestSignatures.has(signature)) {
2087
- return false
2088
- }
2089
- this.inFlightRequestSignatures.add(signature)
2090
- return true
2091
- }
2092
-
2093
- private endInFlightRequest(signature: string): void {
2094
- this.inFlightRequestSignatures.delete(signature)
2095
- }
2096
-
2097
- private getRequestCacheMetadata(storeData: any, type: 'GET'|'STREAM'): any {
2098
- return storeData?.requestCache?.[type] || null
2099
- }
2100
-
2101
- private getStoredSchemaSignature(storeData: any): string | null {
2102
- return storeData?.schemaSignature || null
2103
- }
2104
-
2105
- private saveSchemaSignature(tableName: string, schemaSignature: string) {
2106
- this.localStorageManagerService.store$(tableName).pipe(
2107
- take(1),
2108
- tap((storeData: any) => {
2109
- this.localStorageManagerService.updateStore({
2110
- name: tableName,
2111
- data: {
2112
- ...(storeData || {}),
2113
- schemaSignature,
2114
- }
2115
- })
2116
- })
2117
- ).subscribe()
2118
- }
2119
-
2120
- private saveRequestCacheMetadata(tableName: string, type: 'GET'|'STREAM', signature: string, schemaSignature?: string, options?: RequestOptions) {
2121
- this.setCachedRequestSignature(tableName, type, signature)
2122
- this._requestCachePaths.set(tableName, this.resolvePath(options?.path).filter(p => typeof p === 'string' || typeof p === 'number').map(String))
2123
-
2124
- this.localStorageManagerService.store$(tableName).pipe(
2125
- take(1),
2126
- tap((storeData: any) => {
2127
- const currentCache = storeData?.requestCache || {}
2128
- const currentEntry = currentCache[type] || {}
2129
- this.localStorageManagerService.updateStore({
2130
- name: tableName,
2131
- data: {
2132
- ...(storeData || {}),
2133
- requestCache: {
2134
- ...currentCache,
2135
- [type]: {
2136
- ...currentEntry,
2137
- signature,
2138
- savedAt: Date.now(),
2139
- path: this.resolvePath(options?.path),
2140
- headers: this.filterHeaders(options?.headers),
2141
- queryParams: currentEntry.queryParams,
2142
- queryParamsExpires: currentEntry.queryParamsExpires,
2143
- }
2144
- }
2145
- }
2146
- })
2147
- })
2148
- ).subscribe()
2149
- }
2150
-
2151
- private clearRequestCacheMetadata(tableName: string) {
2152
- this.localStorageManagerService.store$(tableName).pipe(
2153
- take(1),
2154
- tap((storeData: any) => {
2155
- let pathArray = this._requestCachePaths.get(tableName)
2156
- if (!Array.isArray(pathArray) || pathArray.length === 0) {
2157
- const requestCache = storeData?.requestCache
2158
- if (requestCache) {
2159
- const cached = requestCache['GET'] || requestCache['STREAM']
2160
- if (Array.isArray(cached?.path) && cached.path.length > 0) {
2161
- pathArray = cached.path.filter((p: any) => typeof p === 'string' || typeof p === 'number').map(String)
2162
- }
2163
- }
2164
- }
2165
- if (Array.isArray(pathArray) && pathArray.length > 0) {
2166
- // this.queryParamsTrackerService.clearTrackingForPath(pathArray.join('/'))
2167
- this._requestCachePaths.delete(tableName)
2168
- }
2169
- if (!storeData) return
2170
- const updated = { ...(storeData || {}) }
2171
- delete updated.requestCache
2172
- delete updated.tracker
2173
- this.localStorageManagerService.updateStore({
2174
- name: tableName,
2175
- data: updated
2176
- })
2177
- })
2178
- ).subscribe()
2179
- }
2180
-
2181
- private trackerNormalizePath(path: any[]): { pathKey: string; query: Record<string, string>; hasQuery: boolean } {
2182
- const pathSegments: string[] = []
2183
- const query: Record<string, string> = {}
2184
- path.forEach((segment) => {
2185
- if (segment && typeof segment === 'object' && !Array.isArray(segment)) {
2186
- Object.keys(segment).forEach((key) => {
2187
- query[this.trackerNormalizeParamKey(key)] = this.trackerNormalizeParamValue(segment[key])
2188
- })
2189
- } else {
2190
- const parsed = this.trackerParsePathSegment(String(segment))
2191
- pathSegments.push(parsed.pathSegment)
2192
- Object.keys(parsed.queryObject).forEach((key) => {
2193
- query[this.trackerNormalizeParamKey(key)] = this.trackerNormalizeParamValue(parsed.queryObject[key])
2194
- })
2195
- }
2196
- })
2197
- return {
2198
- pathKey: this.trackerNormalizePathSegments(pathSegments),
2199
- query,
2200
- hasQuery: Object.keys(query).length > 0,
2201
- }
2202
- }
2203
-
2204
- private trackerParsePathSegment(rawSegment: string): { pathSegment: string; queryObject: Record<string, any> } {
2205
- const [pathPart, ...queryParts] = String(rawSegment).split('?')
2206
- const queryString = queryParts.join('?')
2207
- const queryObject: Record<string, any> = {}
2208
- if (queryString) {
2209
- queryString.split('&').forEach((pair) => {
2210
- if (!pair) return
2211
- const [rawKey, ...rawValueParts] = pair.split('=')
2212
- const key = this.trackerSafeDecode(rawKey).trim()
2213
- const value = this.trackerSafeDecode(rawValueParts.join('=')).trim()
2214
- if (!key) return
2215
- queryObject[key] = value
2216
- })
2217
- }
2218
- return { pathSegment: pathPart, queryObject }
2219
- }
2220
-
2221
- private trackerSafeDecode(value: string | undefined): string {
2222
- if (typeof value === 'undefined') return ''
2223
- try { return decodeURIComponent(value) } catch { return String(value) }
2224
- }
2225
-
2226
- private trackerNormalizePathSegments(pathSegments: string[]): string {
2227
- return pathSegments
2228
- .map((segment) => String(segment).trim())
2229
- .filter((segment) => segment.length > 0)
2230
- .join('/')
2231
- .replace(/([^:]\/+)\/+/g, '$1')
2232
- .replace(/^\//, '')
2233
- .replace(/\/$/, '')
2234
- }
2235
-
2236
- private trackerNormalizeParamKey(key: string): string {
2237
- return String(key).trim().toLowerCase()
2238
- }
2239
-
2240
- private trackerNormalizeParamValue(value: any): string {
2241
- if (Array.isArray(value)) {
2242
- return value.map((item) => this.trackerNormalizeParamValue(item)).join(',')
2243
- }
2244
- if (Object.prototype.toString.call(value) === '[object Object]') {
2245
- return JSON.stringify(
2246
- Object.keys(value).sort().reduce((acc: any, k) => {
2247
- acc[this.trackerNormalizeParamKey(k)] = this.trackerNormalizeParamValue(value[k])
2248
- return acc
2249
- }, {})
2250
- )
2251
- }
2252
- return String(value).trim().toLowerCase()
2253
- }
2254
-
2255
- private trackerFilterQuery(query: Record<string, string>, ignoreQueryParams?: string[]): Record<string, string> {
2256
- if (!Array.isArray(ignoreQueryParams) || ignoreQueryParams.length === 0) return { ...query }
2257
- const normalizedIgnore = ignoreQueryParams.map(p => this.trackerNormalizeParamKey(p))
2258
- const result: Record<string, string> = {}
2259
- Object.keys(query).forEach((key) => {
2260
- if (!normalizedIgnore.includes(key)) {
2261
- result[key] = query[key]
2262
- }
2263
- })
2264
- return result
2265
- }
2266
-
2267
- private trackerBuildExpiryEpoch(expireIn?: string | number): number | null {
2268
- if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') return null
2269
- if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
2270
- return Math.floor(Date.now() / 1000) + Math.floor(expireIn)
2271
- }
2272
- const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '')
2273
- const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/)
2274
- if (!parsed) return null
2275
- const toSeconds: Record<string, number> = { y: 31556926, w: 604800, d: 86400, hr: 3600, h: 3600, mn: 60, min: 60, m: 60, s: 1 }
2276
- const seconds = toSeconds[parsed[2]]
2277
- if (!seconds) return null
2278
- return Math.floor(Date.now() / 1000) + Number(parsed[1]) * seconds
2279
- }
2280
-
2281
- private checkTrackerAllowsRequest(tableName: string, type: 'GET'|'STREAM', path: any[], options: RequestOptions | undefined, storeData: any): Observable<boolean> {
2282
- const normalized = this.trackerNormalizePath(path)
2283
- const ignoreQueryParams = Array.isArray(options?.ignoreQueryParams) ? options!.ignoreQueryParams : []
2284
-
2285
- if (!normalized.hasQuery) {
2286
- const meta = this.getRequestCacheMetadata(storeData, type)
2287
- if (!meta) {
2288
- // No prior cache entry — record that we're tracking this request
2289
- this.setCachedRequestSignature(tableName, type, '')
2290
- this.localStorageManagerService.store$(tableName).pipe(
2291
- take(1),
2292
- tap((s: any) => {
2293
- const currentCache = s?.requestCache || {}
2294
- this.localStorageManagerService.updateStore({
2295
- name: tableName,
2296
- data: {
2297
- ...(s || {}),
2298
- requestCache: {
2299
- ...currentCache,
2300
- [type]: {
2301
- ...(currentCache[type] || {}),
2302
- active: true,
2303
- }
2304
- }
2305
- }
2306
- })
2307
- })
2308
- ).subscribe()
2309
- }
2310
- return of(!meta)
2311
- }
2312
-
2313
- const filtered = this.trackerFilterQuery(normalized.query, ignoreQueryParams)
2314
- const keys = Object.keys(filtered)
2315
-
2316
- if (keys.length === 0) {
2317
- // All query params were filtered out — check if we have any prior entry
2318
- const meta = this.getRequestCacheMetadata(storeData, type)
2319
- if (!meta) {
2320
- this.setCachedRequestSignature(tableName, type, '')
2321
- this.localStorageManagerService.store$(tableName).pipe(
2322
- take(1),
2323
- tap((s: any) => {
2324
- const currentCache = s?.requestCache || {}
2325
- this.localStorageManagerService.updateStore({
2326
- name: tableName,
2327
- data: {
2328
- ...(s || {}),
2329
- requestCache: {
2330
- ...currentCache,
2331
- [type]: {
2332
- ...(currentCache[type] || {}),
2333
- active: true,
2334
- }
2335
- }
2336
- }
2337
- })
2338
- })
2339
- ).subscribe()
2340
- }
2341
- return of(!meta)
2342
- }
2343
-
2344
- const meta = this.getRequestCacheMetadata(storeData, type) || {}
2345
- const now = Math.floor(Date.now() / 1000)
2346
- let queryParams: Record<string, string[]> = { ...(meta.queryParams || {}) }
2347
- let queryParamsExpires: number | null = meta.queryParamsExpires ?? null
2348
-
2349
- if (queryParamsExpires !== null && queryParamsExpires <= now) {
2350
- queryParams = {}
2351
- queryParamsExpires = null
2352
- }
2353
-
2354
- let accepted = false
2355
- keys.forEach(key => {
2356
- const value = filtered[key]
2357
- const consumed = queryParams[key] || []
2358
- if (!consumed.includes(value)) {
2359
- queryParams[key] = [...consumed, value]
2360
- accepted = true
2361
- }
2362
- })
2363
-
2364
- if (accepted) {
2365
- const newExpiry = this.trackerBuildExpiryEpoch(options?.queryParamsExpiresIn)
2366
- if (newExpiry !== null) {
2367
- queryParamsExpires = newExpiry
2368
- }
2369
- this.localStorageManagerService.store$(tableName).pipe(
2370
- take(1),
2371
- tap((latestStoreData: any) => {
2372
- const currentCache = latestStoreData?.requestCache || {}
2373
- const currentEntry = currentCache[type] || {}
2374
- this.localStorageManagerService.updateStore({
2375
- name: tableName,
2376
- data: {
2377
- ...(latestStoreData || {}),
2378
- requestCache: {
2379
- ...currentCache,
2380
- [type]: {
2381
- ...currentEntry,
2382
- queryParams,
2383
- queryParamsExpires,
2384
- }
2385
- }
2386
- }
2387
- })
2388
- })
2389
- ).subscribe()
2390
- }
2391
-
2392
- return of(accepted)
2393
- }
2394
-
2395
- }