@rs-native-kit/version-check 0.1.2

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 (371) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +256 -0
  3. package/VersionCheck.podspec +20 -0
  4. package/android/build.gradle +56 -0
  5. package/android/src/main/AndroidManifest.xml +9 -0
  6. package/android/src/main/java/com/rsnativekit/versioncheck/SecureStorageCipher.kt +57 -0
  7. package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckHeadlessTaskService.kt +31 -0
  8. package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckModule.kt +215 -0
  9. package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckPackage.kt +31 -0
  10. package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckWorker.kt +29 -0
  11. package/ios/PlatformBridge/Ed25519Verifier.swift +19 -0
  12. package/ios/VersionCheck.h +5 -0
  13. package/ios/VersionCheck.mm +278 -0
  14. package/lib/module/data/cache/MemoryCacheStore.js +30 -0
  15. package/lib/module/data/cache/MemoryCacheStore.js.map +1 -0
  16. package/lib/module/data/cache/PersistentCacheStore.js +56 -0
  17. package/lib/module/data/cache/PersistentCacheStore.js.map +1 -0
  18. package/lib/module/data/config/ConfigCache.js +29 -0
  19. package/lib/module/data/config/ConfigCache.js.map +1 -0
  20. package/lib/module/data/config/ConfigDocumentValidator.js +185 -0
  21. package/lib/module/data/config/ConfigDocumentValidator.js.map +1 -0
  22. package/lib/module/data/config/ConfigLoader.js +82 -0
  23. package/lib/module/data/config/ConfigLoader.js.map +1 -0
  24. package/lib/module/data/config/ConfigProvider.js +58 -0
  25. package/lib/module/data/config/ConfigProvider.js.map +1 -0
  26. package/lib/module/data/config/ConfigResolutionPipeline.js +268 -0
  27. package/lib/module/data/config/ConfigResolutionPipeline.js.map +1 -0
  28. package/lib/module/data/config/ConfigValidator.js +69 -0
  29. package/lib/module/data/config/ConfigValidator.js.map +1 -0
  30. package/lib/module/data/config/SignatureVerifier.js +80 -0
  31. package/lib/module/data/config/SignatureVerifier.js.map +1 -0
  32. package/lib/module/data/config/canonicalize.js +88 -0
  33. package/lib/module/data/config/canonicalize.js.map +1 -0
  34. package/lib/module/data/config/sources/EnvironmentOverrideSource.js +20 -0
  35. package/lib/module/data/config/sources/EnvironmentOverrideSource.js.map +1 -0
  36. package/lib/module/data/config/sources/LocalConfigSource.js +22 -0
  37. package/lib/module/data/config/sources/LocalConfigSource.js.map +1 -0
  38. package/lib/module/data/config/sources/RemoteConfigSource.js +38 -0
  39. package/lib/module/data/config/sources/RemoteConfigSource.js.map +1 -0
  40. package/lib/module/data/providers/amazon/AmazonAppstoreProvider.js +13 -0
  41. package/lib/module/data/providers/amazon/AmazonAppstoreProvider.js.map +1 -0
  42. package/lib/module/data/providers/apple/AppleStoreProvider.js +67 -0
  43. package/lib/module/data/providers/apple/AppleStoreProvider.js.map +1 -0
  44. package/lib/module/data/providers/custom-api/CustomApiProvider.js +51 -0
  45. package/lib/module/data/providers/custom-api/CustomApiProvider.js.map +1 -0
  46. package/lib/module/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.js +13 -0
  47. package/lib/module/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.js.map +1 -0
  48. package/lib/module/data/providers/google-play/GooglePlayProvider.js +62 -0
  49. package/lib/module/data/providers/google-play/GooglePlayProvider.js.map +1 -0
  50. package/lib/module/data/providers/huawei/HuaweiAppGalleryProvider.js +18 -0
  51. package/lib/module/data/providers/huawei/HuaweiAppGalleryProvider.js.map +1 -0
  52. package/lib/module/data/repositories/VersionRepositoryImpl.js +69 -0
  53. package/lib/module/data/repositories/VersionRepositoryImpl.js.map +1 -0
  54. package/lib/module/di/container.js +80 -0
  55. package/lib/module/di/container.js.map +1 -0
  56. package/lib/module/domain/IVersionManagerCore.js +4 -0
  57. package/lib/module/domain/IVersionManagerCore.js.map +1 -0
  58. package/lib/module/domain/VersionManagerCore.js +276 -0
  59. package/lib/module/domain/VersionManagerCore.js.map +1 -0
  60. package/lib/module/domain/engines/decision/DecisionEngine.js +40 -0
  61. package/lib/module/domain/engines/decision/DecisionEngine.js.map +1 -0
  62. package/lib/module/domain/engines/policy/IUpdatePolicyEngine.js +4 -0
  63. package/lib/module/domain/engines/policy/IUpdatePolicyEngine.js.map +1 -0
  64. package/lib/module/domain/engines/policy/PolicyEngine.js +42 -0
  65. package/lib/module/domain/engines/policy/PolicyEngine.js.map +1 -0
  66. package/lib/module/domain/engines/semver/IVersionComparator.js +4 -0
  67. package/lib/module/domain/engines/semver/IVersionComparator.js.map +1 -0
  68. package/lib/module/domain/engines/semver/SemVerEngine.js +176 -0
  69. package/lib/module/domain/engines/semver/SemVerEngine.js.map +1 -0
  70. package/lib/module/domain/errors/VersionManagerException.js +104 -0
  71. package/lib/module/domain/errors/VersionManagerException.js.map +1 -0
  72. package/lib/module/domain/models/ActionPlan.js +9 -0
  73. package/lib/module/domain/models/ActionPlan.js.map +1 -0
  74. package/lib/module/domain/models/ConfigDocument.js +4 -0
  75. package/lib/module/domain/models/ConfigDocument.js.map +1 -0
  76. package/lib/module/domain/models/Events.js +4 -0
  77. package/lib/module/domain/models/Events.js.map +1 -0
  78. package/lib/module/domain/models/LifecycleState.js +29 -0
  79. package/lib/module/domain/models/LifecycleState.js.map +1 -0
  80. package/lib/module/domain/models/ParsedVersion.js +2 -0
  81. package/lib/module/domain/models/ParsedVersion.js.map +1 -0
  82. package/lib/module/domain/models/PlatformId.js +2 -0
  83. package/lib/module/domain/models/PlatformId.js.map +1 -0
  84. package/lib/module/domain/models/PublicApiOptions.js +2 -0
  85. package/lib/module/domain/models/PublicApiOptions.js.map +1 -0
  86. package/lib/module/domain/models/RemoteVersionInfo.js +4 -0
  87. package/lib/module/domain/models/RemoteVersionInfo.js.map +1 -0
  88. package/lib/module/domain/models/StoreLookup.js +2 -0
  89. package/lib/module/domain/models/StoreLookup.js.map +1 -0
  90. package/lib/module/domain/models/Unsubscribe.js +2 -0
  91. package/lib/module/domain/models/Unsubscribe.js.map +1 -0
  92. package/lib/module/domain/models/UpdateInfo.js +4 -0
  93. package/lib/module/domain/models/UpdateInfo.js.map +1 -0
  94. package/lib/module/domain/models/VersionManagerOptions.js +4 -0
  95. package/lib/module/domain/models/VersionManagerOptions.js.map +1 -0
  96. package/lib/module/domain/ports/ICacheStore.js +2 -0
  97. package/lib/module/domain/ports/ICacheStore.js.map +1 -0
  98. package/lib/module/domain/ports/IClock.js +2 -0
  99. package/lib/module/domain/ports/IClock.js.map +1 -0
  100. package/lib/module/domain/ports/IConfigProvider.js +4 -0
  101. package/lib/module/domain/ports/IConfigProvider.js.map +1 -0
  102. package/lib/module/domain/ports/IConfigSources.js +4 -0
  103. package/lib/module/domain/ports/IConfigSources.js.map +1 -0
  104. package/lib/module/domain/ports/IPlatformBridge.js +4 -0
  105. package/lib/module/domain/ports/IPlatformBridge.js.map +1 -0
  106. package/lib/module/domain/ports/IStoreProvider.js +4 -0
  107. package/lib/module/domain/ports/IStoreProvider.js.map +1 -0
  108. package/lib/module/domain/ports/IVersionRepository.js +4 -0
  109. package/lib/module/domain/ports/IVersionRepository.js.map +1 -0
  110. package/lib/module/domain/statemachine/LifecycleStateMachine.js +55 -0
  111. package/lib/module/domain/statemachine/LifecycleStateMachine.js.map +1 -0
  112. package/lib/module/domain/usecases/CheckForUpdatesUseCase.js +31 -0
  113. package/lib/module/domain/usecases/CheckForUpdatesUseCase.js.map +1 -0
  114. package/lib/module/domain/usecases/ForceTriggerUpdateUseCase.js +20 -0
  115. package/lib/module/domain/usecases/ForceTriggerUpdateUseCase.js.map +1 -0
  116. package/lib/module/domain/usecases/ResetIgnoredVersionsUseCase.js +24 -0
  117. package/lib/module/domain/usecases/ResetIgnoredVersionsUseCase.js.map +1 -0
  118. package/lib/module/index.js +10 -0
  119. package/lib/module/index.js.map +1 -0
  120. package/lib/module/package.json +1 -0
  121. package/lib/module/platform/contracts/IPlatformBridge.js +2 -0
  122. package/lib/module/platform/contracts/IPlatformBridge.js.map +1 -0
  123. package/lib/module/platform/createPlatformBridge.js +17 -0
  124. package/lib/module/platform/createPlatformBridge.js.map +1 -0
  125. package/lib/module/platform/createPlatformBridge.native.js +9 -0
  126. package/lib/module/platform/createPlatformBridge.native.js.map +1 -0
  127. package/lib/module/platform/native/NativePlatformBridge.js +143 -0
  128. package/lib/module/platform/native/NativePlatformBridge.js.map +1 -0
  129. package/lib/module/platform/native/NativeVersionCheck.js +33 -0
  130. package/lib/module/platform/native/NativeVersionCheck.js.map +1 -0
  131. package/lib/module/platform/web/WebPlatformBridge.js +228 -0
  132. package/lib/module/platform/web/WebPlatformBridge.js.map +1 -0
  133. package/lib/module/presentation/components/ForceUpdateScreen.js +68 -0
  134. package/lib/module/presentation/components/ForceUpdateScreen.js.map +1 -0
  135. package/lib/module/presentation/components/OptionalUpdateBanner.js +69 -0
  136. package/lib/module/presentation/components/OptionalUpdateBanner.js.map +1 -0
  137. package/lib/module/presentation/components/SoftUpdateDialog.js +101 -0
  138. package/lib/module/presentation/components/SoftUpdateDialog.js.map +1 -0
  139. package/lib/module/presentation/hooks/VersionManagerProvider.js +18 -0
  140. package/lib/module/presentation/hooks/VersionManagerProvider.js.map +1 -0
  141. package/lib/module/presentation/hooks/context.js +5 -0
  142. package/lib/module/presentation/hooks/context.js.map +1 -0
  143. package/lib/module/presentation/hooks/useUpdateState.js +26 -0
  144. package/lib/module/presentation/hooks/useUpdateState.js.map +1 -0
  145. package/lib/module/presentation/hooks/useVersionManager.js +33 -0
  146. package/lib/module/presentation/hooks/useVersionManager.js.map +1 -0
  147. package/lib/module/presentation/state/VersionManagerStore.js +47 -0
  148. package/lib/module/presentation/state/VersionManagerStore.js.map +1 -0
  149. package/lib/module/publicApi.js +114 -0
  150. package/lib/module/publicApi.js.map +1 -0
  151. package/lib/module/shared/eventbus/EventBus.js +34 -0
  152. package/lib/module/shared/eventbus/EventBus.js.map +1 -0
  153. package/lib/module/shared/logging/Logger.js +56 -0
  154. package/lib/module/shared/logging/Logger.js.map +1 -0
  155. package/lib/module/ui.js +13 -0
  156. package/lib/module/ui.js.map +1 -0
  157. package/lib/typescript/package.json +1 -0
  158. package/lib/typescript/src/data/cache/MemoryCacheStore.d.ts +12 -0
  159. package/lib/typescript/src/data/cache/MemoryCacheStore.d.ts.map +1 -0
  160. package/lib/typescript/src/data/cache/PersistentCacheStore.d.ts +14 -0
  161. package/lib/typescript/src/data/cache/PersistentCacheStore.d.ts.map +1 -0
  162. package/lib/typescript/src/data/config/ConfigCache.d.ts +15 -0
  163. package/lib/typescript/src/data/config/ConfigCache.d.ts.map +1 -0
  164. package/lib/typescript/src/data/config/ConfigDocumentValidator.d.ts +20 -0
  165. package/lib/typescript/src/data/config/ConfigDocumentValidator.d.ts.map +1 -0
  166. package/lib/typescript/src/data/config/ConfigLoader.d.ts +28 -0
  167. package/lib/typescript/src/data/config/ConfigLoader.d.ts.map +1 -0
  168. package/lib/typescript/src/data/config/ConfigProvider.d.ts +30 -0
  169. package/lib/typescript/src/data/config/ConfigProvider.d.ts.map +1 -0
  170. package/lib/typescript/src/data/config/ConfigResolutionPipeline.d.ts +49 -0
  171. package/lib/typescript/src/data/config/ConfigResolutionPipeline.d.ts.map +1 -0
  172. package/lib/typescript/src/data/config/ConfigValidator.d.ts +10 -0
  173. package/lib/typescript/src/data/config/ConfigValidator.d.ts.map +1 -0
  174. package/lib/typescript/src/data/config/SignatureVerifier.d.ts +16 -0
  175. package/lib/typescript/src/data/config/SignatureVerifier.d.ts.map +1 -0
  176. package/lib/typescript/src/data/config/canonicalize.d.ts +13 -0
  177. package/lib/typescript/src/data/config/canonicalize.d.ts.map +1 -0
  178. package/lib/typescript/src/data/config/sources/EnvironmentOverrideSource.d.ts +17 -0
  179. package/lib/typescript/src/data/config/sources/EnvironmentOverrideSource.d.ts.map +1 -0
  180. package/lib/typescript/src/data/config/sources/LocalConfigSource.d.ts +19 -0
  181. package/lib/typescript/src/data/config/sources/LocalConfigSource.d.ts.map +1 -0
  182. package/lib/typescript/src/data/config/sources/RemoteConfigSource.d.ts +10 -0
  183. package/lib/typescript/src/data/config/sources/RemoteConfigSource.d.ts.map +1 -0
  184. package/lib/typescript/src/data/providers/amazon/AmazonAppstoreProvider.d.ts +9 -0
  185. package/lib/typescript/src/data/providers/amazon/AmazonAppstoreProvider.d.ts.map +1 -0
  186. package/lib/typescript/src/data/providers/apple/AppleStoreProvider.d.ts +19 -0
  187. package/lib/typescript/src/data/providers/apple/AppleStoreProvider.d.ts.map +1 -0
  188. package/lib/typescript/src/data/providers/custom-api/CustomApiProvider.d.ts +14 -0
  189. package/lib/typescript/src/data/providers/custom-api/CustomApiProvider.d.ts.map +1 -0
  190. package/lib/typescript/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.d.ts +9 -0
  191. package/lib/typescript/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.d.ts.map +1 -0
  192. package/lib/typescript/src/data/providers/google-play/GooglePlayProvider.d.ts +22 -0
  193. package/lib/typescript/src/data/providers/google-play/GooglePlayProvider.d.ts.map +1 -0
  194. package/lib/typescript/src/data/providers/huawei/HuaweiAppGalleryProvider.d.ts +14 -0
  195. package/lib/typescript/src/data/providers/huawei/HuaweiAppGalleryProvider.d.ts.map +1 -0
  196. package/lib/typescript/src/data/repositories/VersionRepositoryImpl.d.ts +23 -0
  197. package/lib/typescript/src/data/repositories/VersionRepositoryImpl.d.ts.map +1 -0
  198. package/lib/typescript/src/di/container.d.ts +12 -0
  199. package/lib/typescript/src/di/container.d.ts.map +1 -0
  200. package/lib/typescript/src/domain/IVersionManagerCore.d.ts +30 -0
  201. package/lib/typescript/src/domain/IVersionManagerCore.d.ts.map +1 -0
  202. package/lib/typescript/src/domain/VersionManagerCore.d.ts +94 -0
  203. package/lib/typescript/src/domain/VersionManagerCore.d.ts.map +1 -0
  204. package/lib/typescript/src/domain/engines/decision/DecisionEngine.d.ts +16 -0
  205. package/lib/typescript/src/domain/engines/decision/DecisionEngine.d.ts.map +1 -0
  206. package/lib/typescript/src/domain/engines/policy/IUpdatePolicyEngine.d.ts +17 -0
  207. package/lib/typescript/src/domain/engines/policy/IUpdatePolicyEngine.d.ts.map +1 -0
  208. package/lib/typescript/src/domain/engines/policy/PolicyEngine.d.ts +16 -0
  209. package/lib/typescript/src/domain/engines/policy/PolicyEngine.d.ts.map +1 -0
  210. package/lib/typescript/src/domain/engines/semver/IVersionComparator.d.ts +7 -0
  211. package/lib/typescript/src/domain/engines/semver/IVersionComparator.d.ts.map +1 -0
  212. package/lib/typescript/src/domain/engines/semver/SemVerEngine.d.ts +11 -0
  213. package/lib/typescript/src/domain/engines/semver/SemVerEngine.d.ts.map +1 -0
  214. package/lib/typescript/src/domain/errors/VersionManagerException.d.ts +97 -0
  215. package/lib/typescript/src/domain/errors/VersionManagerException.d.ts.map +1 -0
  216. package/lib/typescript/src/domain/models/ActionPlan.d.ts +15 -0
  217. package/lib/typescript/src/domain/models/ActionPlan.d.ts.map +1 -0
  218. package/lib/typescript/src/domain/models/ConfigDocument.d.ts +86 -0
  219. package/lib/typescript/src/domain/models/ConfigDocument.d.ts.map +1 -0
  220. package/lib/typescript/src/domain/models/Events.d.ts +39 -0
  221. package/lib/typescript/src/domain/models/Events.d.ts.map +1 -0
  222. package/lib/typescript/src/domain/models/LifecycleState.d.ts +18 -0
  223. package/lib/typescript/src/domain/models/LifecycleState.d.ts.map +1 -0
  224. package/lib/typescript/src/domain/models/ParsedVersion.d.ts +9 -0
  225. package/lib/typescript/src/domain/models/ParsedVersion.d.ts.map +1 -0
  226. package/lib/typescript/src/domain/models/PlatformId.d.ts +3 -0
  227. package/lib/typescript/src/domain/models/PlatformId.d.ts.map +1 -0
  228. package/lib/typescript/src/domain/models/PublicApiOptions.d.ts +13 -0
  229. package/lib/typescript/src/domain/models/PublicApiOptions.d.ts.map +1 -0
  230. package/lib/typescript/src/domain/models/RemoteVersionInfo.d.ts +21 -0
  231. package/lib/typescript/src/domain/models/RemoteVersionInfo.d.ts.map +1 -0
  232. package/lib/typescript/src/domain/models/StoreLookup.d.ts +12 -0
  233. package/lib/typescript/src/domain/models/StoreLookup.d.ts.map +1 -0
  234. package/lib/typescript/src/domain/models/Unsubscribe.d.ts +2 -0
  235. package/lib/typescript/src/domain/models/Unsubscribe.d.ts.map +1 -0
  236. package/lib/typescript/src/domain/models/UpdateInfo.d.ts +11 -0
  237. package/lib/typescript/src/domain/models/UpdateInfo.d.ts.map +1 -0
  238. package/lib/typescript/src/domain/models/VersionManagerOptions.d.ts +149 -0
  239. package/lib/typescript/src/domain/models/VersionManagerOptions.d.ts.map +1 -0
  240. package/lib/typescript/src/domain/ports/ICacheStore.d.ts +6 -0
  241. package/lib/typescript/src/domain/ports/ICacheStore.d.ts.map +1 -0
  242. package/lib/typescript/src/domain/ports/IClock.d.ts +4 -0
  243. package/lib/typescript/src/domain/ports/IClock.d.ts.map +1 -0
  244. package/lib/typescript/src/domain/ports/IConfigProvider.d.ts +11 -0
  245. package/lib/typescript/src/domain/ports/IConfigProvider.d.ts.map +1 -0
  246. package/lib/typescript/src/domain/ports/IConfigSources.d.ts +25 -0
  247. package/lib/typescript/src/domain/ports/IConfigSources.d.ts.map +1 -0
  248. package/lib/typescript/src/domain/ports/IPlatformBridge.d.ts +77 -0
  249. package/lib/typescript/src/domain/ports/IPlatformBridge.d.ts.map +1 -0
  250. package/lib/typescript/src/domain/ports/IStoreProvider.d.ts +7 -0
  251. package/lib/typescript/src/domain/ports/IStoreProvider.d.ts.map +1 -0
  252. package/lib/typescript/src/domain/ports/IVersionRepository.d.ts +8 -0
  253. package/lib/typescript/src/domain/ports/IVersionRepository.d.ts.map +1 -0
  254. package/lib/typescript/src/domain/statemachine/LifecycleStateMachine.d.ts +25 -0
  255. package/lib/typescript/src/domain/statemachine/LifecycleStateMachine.d.ts.map +1 -0
  256. package/lib/typescript/src/domain/usecases/CheckForUpdatesUseCase.d.ts +30 -0
  257. package/lib/typescript/src/domain/usecases/CheckForUpdatesUseCase.d.ts.map +1 -0
  258. package/lib/typescript/src/domain/usecases/ForceTriggerUpdateUseCase.d.ts +17 -0
  259. package/lib/typescript/src/domain/usecases/ForceTriggerUpdateUseCase.d.ts.map +1 -0
  260. package/lib/typescript/src/domain/usecases/ResetIgnoredVersionsUseCase.d.ts +7 -0
  261. package/lib/typescript/src/domain/usecases/ResetIgnoredVersionsUseCase.d.ts.map +1 -0
  262. package/lib/typescript/src/index.d.ts +17 -0
  263. package/lib/typescript/src/index.d.ts.map +1 -0
  264. package/lib/typescript/src/platform/contracts/IPlatformBridge.d.ts +2 -0
  265. package/lib/typescript/src/platform/contracts/IPlatformBridge.d.ts.map +1 -0
  266. package/lib/typescript/src/platform/createPlatformBridge.d.ts +12 -0
  267. package/lib/typescript/src/platform/createPlatformBridge.d.ts.map +1 -0
  268. package/lib/typescript/src/platform/createPlatformBridge.native.d.ts +4 -0
  269. package/lib/typescript/src/platform/createPlatformBridge.native.d.ts.map +1 -0
  270. package/lib/typescript/src/platform/native/NativePlatformBridge.d.ts +17 -0
  271. package/lib/typescript/src/platform/native/NativePlatformBridge.d.ts.map +1 -0
  272. package/lib/typescript/src/platform/native/NativeVersionCheck.d.ts +45 -0
  273. package/lib/typescript/src/platform/native/NativeVersionCheck.d.ts.map +1 -0
  274. package/lib/typescript/src/platform/web/WebPlatformBridge.d.ts +17 -0
  275. package/lib/typescript/src/platform/web/WebPlatformBridge.d.ts.map +1 -0
  276. package/lib/typescript/src/presentation/components/ForceUpdateScreen.d.ts +12 -0
  277. package/lib/typescript/src/presentation/components/ForceUpdateScreen.d.ts.map +1 -0
  278. package/lib/typescript/src/presentation/components/OptionalUpdateBanner.d.ts +12 -0
  279. package/lib/typescript/src/presentation/components/OptionalUpdateBanner.d.ts.map +1 -0
  280. package/lib/typescript/src/presentation/components/SoftUpdateDialog.d.ts +14 -0
  281. package/lib/typescript/src/presentation/components/SoftUpdateDialog.d.ts.map +1 -0
  282. package/lib/typescript/src/presentation/hooks/VersionManagerProvider.d.ts +9 -0
  283. package/lib/typescript/src/presentation/hooks/VersionManagerProvider.d.ts.map +1 -0
  284. package/lib/typescript/src/presentation/hooks/context.d.ts +3 -0
  285. package/lib/typescript/src/presentation/hooks/context.d.ts.map +1 -0
  286. package/lib/typescript/src/presentation/hooks/useUpdateState.d.ts +17 -0
  287. package/lib/typescript/src/presentation/hooks/useUpdateState.d.ts.map +1 -0
  288. package/lib/typescript/src/presentation/hooks/useVersionManager.d.ts +22 -0
  289. package/lib/typescript/src/presentation/hooks/useVersionManager.d.ts.map +1 -0
  290. package/lib/typescript/src/presentation/state/VersionManagerStore.d.ts +28 -0
  291. package/lib/typescript/src/presentation/state/VersionManagerStore.d.ts.map +1 -0
  292. package/lib/typescript/src/publicApi.d.ts +24 -0
  293. package/lib/typescript/src/publicApi.d.ts.map +1 -0
  294. package/lib/typescript/src/shared/eventbus/EventBus.d.ts +21 -0
  295. package/lib/typescript/src/shared/eventbus/EventBus.d.ts.map +1 -0
  296. package/lib/typescript/src/shared/logging/Logger.d.ts +17 -0
  297. package/lib/typescript/src/shared/logging/Logger.d.ts.map +1 -0
  298. package/lib/typescript/src/ui.d.ts +15 -0
  299. package/lib/typescript/src/ui.d.ts.map +1 -0
  300. package/package.json +192 -0
  301. package/src/data/cache/MemoryCacheStore.ts +36 -0
  302. package/src/data/cache/PersistentCacheStore.ts +63 -0
  303. package/src/data/config/ConfigCache.ts +30 -0
  304. package/src/data/config/ConfigDocumentValidator.ts +309 -0
  305. package/src/data/config/ConfigLoader.ts +105 -0
  306. package/src/data/config/ConfigProvider.ts +84 -0
  307. package/src/data/config/ConfigResolutionPipeline.ts +424 -0
  308. package/src/data/config/ConfigValidator.ts +79 -0
  309. package/src/data/config/SignatureVerifier.ts +90 -0
  310. package/src/data/config/canonicalize.ts +111 -0
  311. package/src/data/config/sources/EnvironmentOverrideSource.ts +22 -0
  312. package/src/data/config/sources/LocalConfigSource.ts +24 -0
  313. package/src/data/config/sources/RemoteConfigSource.ts +41 -0
  314. package/src/data/providers/amazon/AmazonAppstoreProvider.ts +16 -0
  315. package/src/data/providers/apple/AppleStoreProvider.ts +94 -0
  316. package/src/data/providers/custom-api/CustomApiProvider.ts +75 -0
  317. package/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.ts +16 -0
  318. package/src/data/providers/google-play/GooglePlayProvider.ts +75 -0
  319. package/src/data/providers/huawei/HuaweiAppGalleryProvider.ts +21 -0
  320. package/src/data/repositories/VersionRepositoryImpl.ts +90 -0
  321. package/src/di/container.ts +128 -0
  322. package/src/domain/IVersionManagerCore.ts +54 -0
  323. package/src/domain/VersionManagerCore.ts +425 -0
  324. package/src/domain/engines/decision/DecisionEngine.ts +47 -0
  325. package/src/domain/engines/policy/IUpdatePolicyEngine.ts +18 -0
  326. package/src/domain/engines/policy/PolicyEngine.ts +60 -0
  327. package/src/domain/engines/semver/IVersionComparator.ts +7 -0
  328. package/src/domain/engines/semver/SemVerEngine.ts +200 -0
  329. package/src/domain/errors/VersionManagerException.ts +166 -0
  330. package/src/domain/models/ActionPlan.ts +17 -0
  331. package/src/domain/models/ConfigDocument.ts +97 -0
  332. package/src/domain/models/Events.ts +48 -0
  333. package/src/domain/models/LifecycleState.ts +50 -0
  334. package/src/domain/models/ParsedVersion.ts +8 -0
  335. package/src/domain/models/PlatformId.ts +9 -0
  336. package/src/domain/models/PublicApiOptions.ts +14 -0
  337. package/src/domain/models/RemoteVersionInfo.ts +23 -0
  338. package/src/domain/models/StoreLookup.ts +12 -0
  339. package/src/domain/models/Unsubscribe.ts +1 -0
  340. package/src/domain/models/UpdateInfo.ts +11 -0
  341. package/src/domain/models/VersionManagerOptions.ts +150 -0
  342. package/src/domain/ports/ICacheStore.ts +5 -0
  343. package/src/domain/ports/IClock.ts +3 -0
  344. package/src/domain/ports/IConfigProvider.ts +18 -0
  345. package/src/domain/ports/IConfigSources.ts +44 -0
  346. package/src/domain/ports/IPlatformBridge.ts +97 -0
  347. package/src/domain/ports/IStoreProvider.ts +12 -0
  348. package/src/domain/ports/IVersionRepository.ts +14 -0
  349. package/src/domain/statemachine/LifecycleStateMachine.ts +76 -0
  350. package/src/domain/usecases/CheckForUpdatesUseCase.ts +63 -0
  351. package/src/domain/usecases/ForceTriggerUpdateUseCase.ts +35 -0
  352. package/src/domain/usecases/ResetIgnoredVersionsUseCase.ts +26 -0
  353. package/src/index.tsx +84 -0
  354. package/src/platform/contracts/IPlatformBridge.ts +14 -0
  355. package/src/platform/createPlatformBridge.native.ts +7 -0
  356. package/src/platform/createPlatformBridge.ts +15 -0
  357. package/src/platform/native/NativePlatformBridge.ts +179 -0
  358. package/src/platform/native/NativeVersionCheck.ts +64 -0
  359. package/src/platform/web/WebPlatformBridge.ts +347 -0
  360. package/src/presentation/components/ForceUpdateScreen.tsx +64 -0
  361. package/src/presentation/components/OptionalUpdateBanner.tsx +53 -0
  362. package/src/presentation/components/SoftUpdateDialog.tsx +84 -0
  363. package/src/presentation/hooks/VersionManagerProvider.tsx +23 -0
  364. package/src/presentation/hooks/context.ts +6 -0
  365. package/src/presentation/hooks/useUpdateState.ts +50 -0
  366. package/src/presentation/hooks/useVersionManager.ts +68 -0
  367. package/src/presentation/state/VersionManagerStore.ts +72 -0
  368. package/src/publicApi.ts +126 -0
  369. package/src/shared/eventbus/EventBus.ts +56 -0
  370. package/src/shared/logging/Logger.ts +70 -0
  371. package/src/ui.tsx +19 -0
@@ -0,0 +1,309 @@
1
+ import { parseVersion } from '../../domain/engines/semver/SemVerEngine';
2
+ import type {
3
+ BoundaryValidationResult,
4
+ RawConfigDocument,
5
+ SchemaValidationResult,
6
+ } from '../../domain/models/ConfigDocument';
7
+ import type { IConfigValidator } from '../../domain/ports/IConfigSources';
8
+
9
+ /** Doc 03 §2.2 — serialized document size cap, enforced before parsing even begins. */
10
+ export const CONFIG_ENVELOPE_MAX_BYTES = 64 * 1024;
11
+
12
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
13
+ const HTTPS_URL_PATTERN = /^https:\/\//;
14
+ const APP_STORE_ID_PATTERN = /^[0-9]{6,12}$/;
15
+ const ANDROID_PACKAGE_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
16
+ const AMAZON_ASIN_PATTERN = /^[A-Z0-9]{10}$/;
17
+ const HUAWEI_APP_ID_PATTERN = /^[0-9]{6,12}$/;
18
+ const SCHEMA_VERSION_PATTERN = /^\d+\.\d+$/;
19
+
20
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
21
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
22
+ }
23
+
24
+ /**
25
+ * Hand-rolled, zero-dependency structural validator (Prompt 1 §9.3 — no ajv) that walks
26
+ * a SignedConfigEnvelope against the JSON Schema in doc 03 §6: required-field presence,
27
+ * type checks, enum membership, and additionalProperties:false at every level (unknown
28
+ * fields are rejected, not silently ignored — §2.1's downgrade-attack rationale).
29
+ */
30
+ export class ConfigDocumentValidator implements IConfigValidator {
31
+ validateSchema(doc: RawConfigDocument): SchemaValidationResult {
32
+ const errors: string[] = [];
33
+
34
+ if (typeof doc === 'string' && doc.length > CONFIG_ENVELOPE_MAX_BYTES) {
35
+ // Cheap byte-length check that short-circuits pathological payloads (§2.2, VM-1007
36
+ // is raised by the caller — this method only reports the schema-shape verdict).
37
+ errors.push(
38
+ `Envelope exceeds ${CONFIG_ENVELOPE_MAX_BYTES} bytes before parsing`
39
+ );
40
+ return { valid: false, errors };
41
+ }
42
+
43
+ let envelope: unknown = doc;
44
+ if (typeof doc === 'string') {
45
+ try {
46
+ envelope = JSON.parse(doc);
47
+ } catch (error) {
48
+ return {
49
+ valid: false,
50
+ errors: [`Envelope is not valid JSON: ${String(error)}`],
51
+ };
52
+ }
53
+ }
54
+
55
+ if (!isPlainObject(envelope)) {
56
+ return { valid: false, errors: ['Envelope must be a JSON object'] };
57
+ }
58
+
59
+ this.checkAdditionalProperties(
60
+ envelope,
61
+ ['schemaVersion', 'payload', 'signature'],
62
+ 'envelope',
63
+ errors
64
+ );
65
+ this.requireField(envelope, 'schemaVersion', 'string', errors, 'envelope');
66
+ this.requireField(envelope, 'payload', 'object', errors, 'envelope');
67
+ this.requireField(envelope, 'signature', 'object', errors, 'envelope');
68
+
69
+ if (
70
+ typeof envelope.schemaVersion === 'string' &&
71
+ !SCHEMA_VERSION_PATTERN.test(envelope.schemaVersion)
72
+ ) {
73
+ errors.push('envelope.schemaVersion must match ^\\d+\\.\\d+$');
74
+ }
75
+
76
+ if (isPlainObject(envelope.signature)) {
77
+ const sig = envelope.signature;
78
+ this.checkAdditionalProperties(
79
+ sig,
80
+ ['algorithm', 'keyId', 'value', 'signedAt'],
81
+ 'signature',
82
+ errors
83
+ );
84
+ this.requireField(sig, 'algorithm', 'string', errors, 'signature');
85
+ this.requireField(sig, 'keyId', 'string', errors, 'signature');
86
+ this.requireField(sig, 'value', 'string', errors, 'signature');
87
+ this.requireField(sig, 'signedAt', 'string', errors, 'signature');
88
+ if (
89
+ typeof sig.algorithm === 'string' &&
90
+ !['ed25519', 'hmac-sha256', 'none'].includes(sig.algorithm)
91
+ ) {
92
+ errors.push(
93
+ 'signature.algorithm must be one of ed25519 | hmac-sha256 | none'
94
+ );
95
+ }
96
+ }
97
+
98
+ if (isPlainObject(envelope.payload)) {
99
+ this.validatePayloadShape(envelope.payload, errors);
100
+ }
101
+
102
+ return { valid: errors.length === 0, errors };
103
+ }
104
+
105
+ validateBoundaries(doc: RawConfigDocument): BoundaryValidationResult {
106
+ const fieldErrors = new Map<string, string>();
107
+ const envelope =
108
+ typeof doc === 'string'
109
+ ? safeParse(doc)
110
+ : (doc as Record<string, unknown>);
111
+ const payload = isPlainObject(envelope)
112
+ ? (envelope.payload as Record<string, unknown> | undefined)
113
+ : undefined;
114
+
115
+ if (!isPlainObject(payload)) {
116
+ return { valid: true, fieldErrors };
117
+ }
118
+
119
+ const network = payload.network as Record<string, unknown> | undefined;
120
+ if (isPlainObject(network)) {
121
+ const timeout = network.requestTimeoutMs;
122
+ if (typeof timeout === 'number' && timeout <= 0) {
123
+ fieldErrors.set('network.requestTimeoutMs', 'must be > 0');
124
+ }
125
+ const retry = network.retry as Record<string, unknown> | undefined;
126
+ if (isPlainObject(retry)) {
127
+ const maxAttempts = retry.maxAttempts;
128
+ if (
129
+ typeof maxAttempts === 'number' &&
130
+ (maxAttempts < 0 || maxAttempts > 10)
131
+ ) {
132
+ fieldErrors.set(
133
+ 'network.retry.maxAttempts',
134
+ 'must be between 0 and 10'
135
+ );
136
+ }
137
+ const baseDelayMs = retry.baseDelayMs;
138
+ if (typeof baseDelayMs === 'number' && baseDelayMs <= 0) {
139
+ fieldErrors.set('network.retry.baseDelayMs', 'must be > 0');
140
+ }
141
+ }
142
+ }
143
+
144
+ const cache = payload.cache as Record<string, unknown> | undefined;
145
+ if (
146
+ isPlainObject(cache) &&
147
+ typeof cache.ttlMs === 'number' &&
148
+ cache.ttlMs < 0
149
+ ) {
150
+ fieldErrors.set('cache.ttlMs', 'must be >= 0');
151
+ }
152
+
153
+ if (
154
+ typeof payload.localVersionIdentifier === 'string' &&
155
+ !this.isValidSemVer(payload.localVersionIdentifier)
156
+ ) {
157
+ fieldErrors.set('localVersionIdentifier', 'not a valid SemVer string');
158
+ }
159
+
160
+ const alerts = payload.alerts as Record<string, unknown> | undefined;
161
+ if (
162
+ isPlainObject(alerts) &&
163
+ typeof alerts.forceUpdateBelow === 'string' &&
164
+ !this.isValidSemVer(alerts.forceUpdateBelow)
165
+ ) {
166
+ fieldErrors.set('alerts.forceUpdateBelow', 'not a valid SemVer string');
167
+ }
168
+
169
+ if (
170
+ typeof payload.remoteConfigUrl === 'string' &&
171
+ !HTTPS_URL_PATTERN.test(payload.remoteConfigUrl)
172
+ ) {
173
+ fieldErrors.set('remoteConfigUrl', 'must use https://');
174
+ }
175
+
176
+ const stores = payload.stores as Record<string, unknown> | undefined;
177
+ if (isPlainObject(stores)) {
178
+ this.checkStoreShape(
179
+ stores,
180
+ 'ios',
181
+ 'appStoreId',
182
+ APP_STORE_ID_PATTERN,
183
+ fieldErrors
184
+ );
185
+ this.checkStoreShape(
186
+ stores,
187
+ 'android',
188
+ 'packageName',
189
+ ANDROID_PACKAGE_PATTERN,
190
+ fieldErrors
191
+ );
192
+ this.checkStoreShape(
193
+ stores,
194
+ 'huawei',
195
+ 'appId',
196
+ HUAWEI_APP_ID_PATTERN,
197
+ fieldErrors
198
+ );
199
+ this.checkStoreShape(
200
+ stores,
201
+ 'amazon',
202
+ 'asin',
203
+ AMAZON_ASIN_PATTERN,
204
+ fieldErrors
205
+ );
206
+ const custom = stores.custom as Record<string, unknown> | undefined;
207
+ if (
208
+ isPlainObject(custom) &&
209
+ typeof custom.url === 'string' &&
210
+ !HTTPS_URL_PATTERN.test(custom.url)
211
+ ) {
212
+ fieldErrors.set('stores.custom.url', 'must use https://');
213
+ }
214
+ }
215
+
216
+ return { valid: fieldErrors.size === 0, fieldErrors };
217
+ }
218
+
219
+ private isValidSemVer(v: string): boolean {
220
+ if (!SEMVER_PATTERN.test(v)) return false;
221
+ try {
222
+ parseVersion(v);
223
+ return true;
224
+ } catch {
225
+ return false;
226
+ }
227
+ }
228
+
229
+ private checkStoreShape(
230
+ stores: Record<string, unknown>,
231
+ key: string,
232
+ field: string,
233
+ pattern: RegExp,
234
+ fieldErrors: Map<string, string>
235
+ ): void {
236
+ const entry = stores[key] as Record<string, unknown> | undefined;
237
+ if (isPlainObject(entry) && typeof entry[field] === 'string') {
238
+ const value = entry[field] as string;
239
+ if (!pattern.test(value)) {
240
+ fieldErrors.set(
241
+ `stores.${key}.${field}`,
242
+ `"${value}" has invalid shape`
243
+ );
244
+ }
245
+ }
246
+ }
247
+
248
+ private validatePayloadShape(
249
+ payload: Record<string, unknown>,
250
+ errors: string[]
251
+ ): void {
252
+ this.checkAdditionalProperties(
253
+ payload,
254
+ [
255
+ 'localVersionIdentifier',
256
+ 'remoteConfigUrl',
257
+ 'cache',
258
+ 'network',
259
+ 'alerts',
260
+ 'stores',
261
+ 'security',
262
+ 'schemaVersion',
263
+ ],
264
+ 'payload',
265
+ errors
266
+ );
267
+ }
268
+
269
+ private requireField(
270
+ obj: Record<string, unknown>,
271
+ field: string,
272
+ type: 'string' | 'object',
273
+ errors: string[],
274
+ context: string
275
+ ): void {
276
+ const value = obj[field];
277
+ if (value === undefined) {
278
+ errors.push(`${context}.${field} is required`);
279
+ return;
280
+ }
281
+ if (type === 'string' && typeof value !== 'string') {
282
+ errors.push(`${context}.${field} must be a string`);
283
+ }
284
+ if (type === 'object' && !isPlainObject(value)) {
285
+ errors.push(`${context}.${field} must be an object`);
286
+ }
287
+ }
288
+
289
+ private checkAdditionalProperties(
290
+ obj: Record<string, unknown>,
291
+ allowed: string[],
292
+ context: string,
293
+ errors: string[]
294
+ ): void {
295
+ for (const key of Object.keys(obj)) {
296
+ if (!allowed.includes(key)) {
297
+ errors.push(`${context}.${key} is not a recognized field`);
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ function safeParse(input: string): unknown {
304
+ try {
305
+ return JSON.parse(input);
306
+ } catch {
307
+ return null;
308
+ }
309
+ }
@@ -0,0 +1,105 @@
1
+ import type { ConfigLoadRequest } from '../../domain/models/ConfigDocument';
2
+ import type { IConfigProvider } from '../../domain/ports/IConfigProvider';
3
+ import type { Unsubscribe } from '../../domain/models/Unsubscribe';
4
+ import type {
5
+ ResolvedVersionManagerConfig,
6
+ VersionManagerOptions,
7
+ } from '../../domain/models/VersionManagerOptions';
8
+ import { ConsoleLogSink } from '../../shared/logging/Logger';
9
+ import type { ConfigResolutionPipeline } from './ConfigResolutionPipeline';
10
+ import { validateOptions } from './ConfigValidator';
11
+
12
+ /**
13
+ * Synchronous default-merge (doc 03 §4.2 tier 4 only). Kept as a plain function, not
14
+ * just a method on ConfigLoader, so the DI composition root (src/di/container.ts) can
15
+ * validate + resolve synchronously at configure()-time (doc 02 §1.3's fail-fast
16
+ * requirement) without awaiting a Promise, and so ConfigResolutionPipeline can call it
17
+ * as its always-valid tier-4 floor (doc 03 §7.2's termination guarantee).
18
+ */
19
+ export function resolveConfigSync(
20
+ defaults: VersionManagerOptions
21
+ ): ResolvedVersionManagerConfig {
22
+ validateOptions(defaults);
23
+
24
+ return {
25
+ appVersion: defaults.appVersion ?? null,
26
+ logging: {
27
+ level: defaults.logging?.level ?? 'warn',
28
+ sink: defaults.logging?.sink ?? new ConsoleLogSink(),
29
+ },
30
+ cache: {
31
+ ttlMs: defaults.cache?.ttlMs ?? 21_600_000,
32
+ storage: defaults.cache?.storage ?? 'persistent',
33
+ bustOnManualCheck: defaults.cache?.bustOnManualCheck ?? true,
34
+ },
35
+ fallback: {
36
+ onNetworkError: defaults.fallback?.onNetworkError ?? 'useCache',
37
+ defaultConfig: defaults.fallback?.defaultConfig ?? null,
38
+ requestTimeoutMs: defaults.fallback?.requestTimeoutMs ?? 8_000,
39
+ retry: {
40
+ maxAttempts: defaults.fallback?.retry?.maxAttempts ?? 3,
41
+ backoff: defaults.fallback?.retry?.backoff ?? 'exponential-jitter',
42
+ baseDelayMs: defaults.fallback?.retry?.baseDelayMs ?? 500,
43
+ },
44
+ },
45
+ stores: defaults.stores,
46
+ policy: {
47
+ forceUpdateBelow: defaults.policy?.forceUpdateBelow ?? null,
48
+ reminderIntervalMs: defaults.policy?.reminderIntervalMs ?? 259_200_000,
49
+ rolloutPercentage: defaults.policy?.rolloutPercentage ?? 100,
50
+ },
51
+ strictReadiness: defaults.strictReadiness ?? false,
52
+ remoteConfigUrl: defaults.remoteConfigUrl ?? null,
53
+ security: {
54
+ signatureAlgorithm: defaults.security?.signatureAlgorithm ?? 'none',
55
+ trustedKeyIds: defaults.security?.trustedKeyIds ?? [],
56
+ },
57
+ schemaVersion: '1.0',
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Doc 03 §0's IConfigProvider implementation — thin wrapper around
63
+ * ConfigResolutionPipeline that adds change-detection so subscribe() only fires when a
64
+ * later resolution actually differs from the last one (§4.4 hot-update contract).
65
+ */
66
+ export class ConfigLoader implements IConfigProvider {
67
+ private readonly listeners = new Set<
68
+ (config: ResolvedVersionManagerConfig) => void
69
+ >();
70
+ private lastResolved: ResolvedVersionManagerConfig | null = null;
71
+
72
+ constructor(private readonly pipeline?: ConfigResolutionPipeline) {}
73
+
74
+ async resolve(
75
+ defaults: VersionManagerOptions
76
+ ): Promise<ResolvedVersionManagerConfig> {
77
+ const config = this.pipeline
78
+ ? (await this.pipeline.resolve(defaults)).config
79
+ : resolveConfigSync(defaults);
80
+
81
+ const changed =
82
+ this.lastResolved !== null &&
83
+ JSON.stringify(this.lastResolved) !== JSON.stringify(config);
84
+ this.lastResolved = config;
85
+ if (changed) {
86
+ for (const listener of Array.from(this.listeners)) listener(config);
87
+ }
88
+ return config;
89
+ }
90
+
91
+ async load(
92
+ request: ConfigLoadRequest
93
+ ): Promise<ResolvedVersionManagerConfig> {
94
+ return this.resolve(request.defaults);
95
+ }
96
+
97
+ subscribe(
98
+ listener: (config: ResolvedVersionManagerConfig) => void
99
+ ): Unsubscribe {
100
+ this.listeners.add(listener);
101
+ return () => {
102
+ this.listeners.delete(listener);
103
+ };
104
+ }
105
+ }
@@ -0,0 +1,84 @@
1
+ import { ConfigResolutionPipeline } from './ConfigResolutionPipeline';
2
+ import type { ConfigResolutionPipelineDeps } from './ConfigResolutionPipeline';
3
+ import type { ConfigLoadRequest } from '../../domain/models/ConfigDocument';
4
+ import type { IConfigProvider } from '../../domain/ports/IConfigProvider';
5
+ import type { Unsubscribe } from '../../domain/ports/IPlatformBridge';
6
+ import type {
7
+ ResolvedVersionManagerConfig,
8
+ VersionManagerOptions,
9
+ } from '../../domain/models/VersionManagerOptions';
10
+
11
+ export interface ConfigProviderDeps extends ConfigResolutionPipelineDeps {
12
+ /** Background remote-refresh cadence for subscribe() (doc 03 §4.4). 0/undefined disables polling — resolve() still works. */
13
+ readonly refreshIntervalMs?: number;
14
+ }
15
+
16
+ /**
17
+ * IConfigProvider adapter around ConfigResolutionPipeline — resolve()/load() delegate
18
+ * straight through, and subscribe() adds the doc 03 §4.4 "hot-update" behavior the raw
19
+ * pipeline doesn't provide on its own: a background poll that re-runs resolution and
20
+ * notifies listeners only when the resolved config actually changed.
21
+ */
22
+ export class ConfigProvider implements IConfigProvider {
23
+ private readonly pipeline: ConfigResolutionPipeline;
24
+ private readonly refreshIntervalMs: number;
25
+ private readonly listeners = new Set<
26
+ (config: ResolvedVersionManagerConfig) => void
27
+ >();
28
+ private timer: ReturnType<typeof setInterval> | null = null;
29
+ private lastConfig: ResolvedVersionManagerConfig | null = null;
30
+ private lastOptions: VersionManagerOptions | null = null;
31
+
32
+ constructor(deps: ConfigProviderDeps) {
33
+ this.pipeline = new ConfigResolutionPipeline(deps);
34
+ this.refreshIntervalMs = deps.refreshIntervalMs ?? 0;
35
+ }
36
+
37
+ async resolve(
38
+ defaults: VersionManagerOptions
39
+ ): Promise<ResolvedVersionManagerConfig> {
40
+ this.lastOptions = defaults;
41
+ const { config } = await this.pipeline.resolve(defaults);
42
+ this.lastConfig = config;
43
+ return config;
44
+ }
45
+
46
+ load(request: ConfigLoadRequest): Promise<ResolvedVersionManagerConfig> {
47
+ return this.resolve(request.defaults);
48
+ }
49
+
50
+ subscribe(
51
+ listener: (config: ResolvedVersionManagerConfig) => void
52
+ ): Unsubscribe {
53
+ this.listeners.add(listener);
54
+ this.ensurePolling();
55
+ return () => {
56
+ this.listeners.delete(listener);
57
+ if (this.listeners.size === 0 && this.timer) {
58
+ clearInterval(this.timer);
59
+ this.timer = null;
60
+ }
61
+ };
62
+ }
63
+
64
+ private ensurePolling(): void {
65
+ if (this.timer || this.refreshIntervalMs <= 0 || !this.lastOptions) return;
66
+ this.timer = setInterval(() => {
67
+ void this.refresh();
68
+ }, this.refreshIntervalMs);
69
+ }
70
+
71
+ private async refresh(): Promise<void> {
72
+ if (!this.lastOptions) return;
73
+ // A background poll tick must never surface as an unhandled rejection — the
74
+ // pipeline already reports validation/network failures via its own onError hook.
75
+ const result = await this.pipeline
76
+ .resolve(this.lastOptions)
77
+ .catch(() => null);
78
+ if (!result) return;
79
+ if (JSON.stringify(result.config) === JSON.stringify(this.lastConfig))
80
+ return;
81
+ this.lastConfig = result.config;
82
+ for (const listener of this.listeners) listener(result.config);
83
+ }
84
+ }