@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,425 @@
1
+ import { CheckForUpdatesUseCase } from './usecases/CheckForUpdatesUseCase';
2
+ import { ForceTriggerUpdateUseCase } from './usecases/ForceTriggerUpdateUseCase';
3
+ import { ResetIgnoredVersionsUseCase } from './usecases/ResetIgnoredVersionsUseCase';
4
+ import type { DecisionEngine } from './engines/decision/DecisionEngine';
5
+ import type { IUpdatePolicyEngine } from './engines/policy/IUpdatePolicyEngine';
6
+ import type { IVersionComparator } from './engines/semver/IVersionComparator';
7
+ import { ActionType } from './models/ActionPlan';
8
+ import type { ActionPlan } from './models/ActionPlan';
9
+ import type {
10
+ StateChangedEvent,
11
+ UpdateDetectedEvent,
12
+ UserActionEvent,
13
+ VersionManagerErrorEvent,
14
+ VersionManagerEventMap,
15
+ } from './models/Events';
16
+ import { LifecycleState } from './models/LifecycleState';
17
+ import type {
18
+ CheckForUpdatesOptions,
19
+ ForceTriggerOptions,
20
+ ResetIgnoredVersionsOptions,
21
+ } from './models/PublicApiOptions';
22
+ import type { RemoteVersionInfo } from './models/RemoteVersionInfo';
23
+ import type { ResolvedVersionManagerConfig } from './models/VersionManagerOptions';
24
+ import type { UpdateInfo } from './models/UpdateInfo';
25
+ import type { Unsubscribe } from './models/Unsubscribe';
26
+ import {
27
+ NotReadyException,
28
+ isConfigTierException,
29
+ toVersionManagerException,
30
+ } from './errors/VersionManagerException';
31
+ import type { VersionManagerException } from './errors/VersionManagerException';
32
+ import type { IVersionManagerCore } from './IVersionManagerCore';
33
+ import type { IClock } from './ports/IClock';
34
+ import type { IConfigProvider } from './ports/IConfigProvider';
35
+ import type { IPlatformBridge } from './ports/IPlatformBridge';
36
+ import type { IVersionRepository } from './ports/IVersionRepository';
37
+ import type { IEventBus } from '../shared/eventbus/EventBus';
38
+ import {
39
+ InvalidTransitionError,
40
+ LifecycleStateMachine,
41
+ } from './statemachine/LifecycleStateMachine';
42
+ import type { ILifecycleStateMachine } from './statemachine/LifecycleStateMachine';
43
+ import type { VersionManagerOptions } from './models/VersionManagerOptions';
44
+
45
+ export interface VersionManagerCoreDeps {
46
+ /** Tier-4-only synchronous resolution (doc 02 §1.3 fail-fast) — used to bootstrap
47
+ * before configProvider's full async pipeline (doc 03) resolves, and as the
48
+ * always-available fallback floor. */
49
+ readonly config: ResolvedVersionManagerConfig;
50
+ readonly rawOptions: VersionManagerOptions;
51
+ readonly platformBridge: IPlatformBridge;
52
+ readonly repository: IVersionRepository;
53
+ readonly comparator: IVersionComparator;
54
+ readonly policyEngine: IUpdatePolicyEngine;
55
+ readonly decisionEngine: DecisionEngine;
56
+ readonly eventBus: IEventBus<VersionManagerEventMap>;
57
+ readonly clock: IClock;
58
+ readonly stateMachine?: ILifecycleStateMachine;
59
+ /** Doc 03 full local/remote/env resolution pipeline — optional so tests/simple
60
+ * wiring can omit it and run on tier-4-only config, same as before this pass. */
61
+ readonly configProvider?: IConfigProvider;
62
+ }
63
+
64
+ /**
65
+ * The concrete IVersionManagerCore. Composed entirely from ports and other domain
66
+ * constructs (Prompt 1 §1.3) — every concrete adapter (repository impl, platform
67
+ * bridge impl) is injected by src/di/container.ts, never imported here directly.
68
+ */
69
+ export class VersionManagerCore implements IVersionManagerCore {
70
+ private readonly deps: VersionManagerCoreDeps;
71
+ private readonly stateMachine: ILifecycleStateMachine;
72
+ private readonly checkUseCase: CheckForUpdatesUseCase;
73
+ private readonly resetUseCase: ResetIgnoredVersionsUseCase;
74
+ private readonly forceUseCase: ForceTriggerUpdateUseCase;
75
+
76
+ private readonly readyPromise: Promise<void>;
77
+ /**
78
+ * Mutable, doc 03 §4.4 "hot-update" target — starts at the tier-4-only sync
79
+ * resolution and is replaced (not merged in place — a fresh immutable object each
80
+ * time) once configProvider's full pipeline resolves, and again on every subsequent
81
+ * subscribe() notification. In-flight operations keep whatever reference they already
82
+ * captured; only the *next* operation observes an update, matching §4.4's guarantee.
83
+ */
84
+ private currentConfig: ResolvedVersionManagerConfig;
85
+ private lastActionPlan: ActionPlan | null = null;
86
+ private lastCheckedAt: number | null = null;
87
+ private inFlightCheck: Promise<ActionPlan> | null = null;
88
+ /**
89
+ * Non-persisted rollout bucket (0-99), stable for the process lifetime only.
90
+ * A device-stable bucket derived from a persisted installation id is Prompt 18
91
+ * scope — this is a documented simplification, not the final design.
92
+ */
93
+ private rolloutBucket: number | null = null;
94
+
95
+ constructor(deps: VersionManagerCoreDeps) {
96
+ this.deps = deps;
97
+ this.currentConfig = deps.config;
98
+ this.stateMachine = deps.stateMachine ?? new LifecycleStateMachine();
99
+
100
+ this.checkUseCase = new CheckForUpdatesUseCase({
101
+ repository: deps.repository,
102
+ comparator: deps.comparator,
103
+ policyEngine: deps.policyEngine,
104
+ decisionEngine: deps.decisionEngine,
105
+ clock: deps.clock,
106
+ });
107
+ this.resetUseCase = new ResetIgnoredVersionsUseCase(deps.repository);
108
+ this.forceUseCase = new ForceTriggerUpdateUseCase(
109
+ deps.repository,
110
+ deps.decisionEngine
111
+ );
112
+
113
+ this.readyPromise = this.initialize();
114
+ }
115
+
116
+ private async initialize(): Promise<void> {
117
+ this.tryTransition(LifecycleState.CONFIG_LOADING);
118
+
119
+ // Doc 03 §4 full precedence resolution (env > remote > local > default) — runs
120
+ // during CONFIG_LOADING per doc 02 §7's sequence diagram. A failure here never
121
+ // fails initialization (doc 02 §7's "Initialization only ever hard-fails on VM-1xxx
122
+ // config codes, which reject before an instance is ever returned" — this is
123
+ // *post*-construction, so it degrades to the already-valid tier-4 config instead).
124
+ if (this.deps.configProvider) {
125
+ try {
126
+ this.currentConfig = await this.deps.configProvider.resolve(
127
+ this.deps.rawOptions
128
+ );
129
+ } catch (error) {
130
+ this.publishError(toVersionManagerException(error), 'config');
131
+ }
132
+ this.deps.configProvider.subscribe((config) => {
133
+ // §4.4 hot-update: only the *next* operation observes this — in-flight work
134
+ // already captured the previous currentConfig reference by value.
135
+ this.currentConfig = config;
136
+ });
137
+ }
138
+
139
+ try {
140
+ // Warm start: prime/validate the cache read path. The first checkForUpdates()
141
+ // call is what actually turns cached data into a decision (it needs the
142
+ // current app version, resolved asynchronously from the platform bridge).
143
+ await this.deps.repository.getCachedVersionInfo();
144
+ } catch (error) {
145
+ this.publishError(toVersionManagerException(error), 'config');
146
+ }
147
+ this.tryTransition(LifecycleState.IDLE);
148
+ }
149
+
150
+ ready(): Promise<void> {
151
+ return this.readyPromise;
152
+ }
153
+
154
+ async checkForUpdates(
155
+ options: CheckForUpdatesOptions = {}
156
+ ): Promise<ActionPlan> {
157
+ if (
158
+ this.currentConfig.strictReadiness &&
159
+ this.stateMachine.current === LifecycleState.CONFIG_LOADING
160
+ ) {
161
+ throw new NotReadyException({
162
+ message: 'checkForUpdates() called before ready() resolved',
163
+ });
164
+ }
165
+ await this.readyPromise;
166
+
167
+ if (this.inFlightCheck) {
168
+ return this.inFlightCheck;
169
+ }
170
+
171
+ const promise = this.runCheck(options).finally(() => {
172
+ this.inFlightCheck = null;
173
+ });
174
+ this.inFlightCheck = promise;
175
+ return promise;
176
+ }
177
+
178
+ private async runCheck(options: CheckForUpdatesOptions): Promise<ActionPlan> {
179
+ this.tryTransition(LifecycleState.VERSION_CHECKING);
180
+
181
+ const [currentVersion, bundleId] = await Promise.all([
182
+ this.resolveCurrentVersion(),
183
+ this.deps.platformBridge.appInfo.getBundleId(),
184
+ ]);
185
+
186
+ try {
187
+ const plan = await this.checkUseCase.execute({
188
+ currentVersion,
189
+ bundleId,
190
+ platform: this.deps.platformBridge.platform,
191
+ bypassCache:
192
+ options.bypassCache ?? this.currentConfig.cache.bustOnManualCheck,
193
+ timeoutMs:
194
+ options.timeoutMs ?? this.currentConfig.fallback.requestTimeoutMs,
195
+ forceUpdateBelow: this.currentConfig.policy.forceUpdateBelow,
196
+ rolloutPercentage: this.currentConfig.policy.rolloutPercentage,
197
+ rolloutBucket: this.getRolloutBucket(),
198
+ });
199
+ this.applyPlan(plan, currentVersion);
200
+ return plan;
201
+ } catch (error) {
202
+ const vmError = toVersionManagerException(error);
203
+ if (isConfigTierException(vmError)) {
204
+ this.tryTransition(LifecycleState.IDLE);
205
+ throw vmError;
206
+ }
207
+ return this.runFallback(vmError, currentVersion);
208
+ }
209
+ }
210
+
211
+ private async runFallback(
212
+ error: VersionManagerException,
213
+ currentVersion: string
214
+ ): Promise<ActionPlan> {
215
+ this.publishError(error, 'check');
216
+
217
+ let remote: RemoteVersionInfo | null = null;
218
+ const strategy = this.currentConfig.fallback.onNetworkError;
219
+ if (strategy === 'useCache') {
220
+ remote = await this.deps.repository
221
+ .getCachedVersionInfo()
222
+ .catch(() => null);
223
+ } else if (strategy === 'useDefaultConfig') {
224
+ remote = this.currentConfig.fallback.defaultConfig;
225
+ }
226
+
227
+ const plan: ActionPlan = remote
228
+ ? this.deps.decisionEngine.buildActionPlan(
229
+ ActionType.NO_ACTION,
230
+ currentVersion,
231
+ remote
232
+ )
233
+ : {
234
+ type: ActionType.NO_ACTION,
235
+ updateInfo: null,
236
+ reason: 'fallback',
237
+ decidedAt: this.deps.clock.now(),
238
+ };
239
+
240
+ this.applyPlan(plan, currentVersion);
241
+ return plan;
242
+ }
243
+
244
+ private applyPlan(plan: ActionPlan, currentVersion: string): void {
245
+ this.lastActionPlan = plan;
246
+ this.lastCheckedAt = this.deps.clock.now();
247
+ this.tryTransition(LifecycleState.DECIDING);
248
+
249
+ const targetState = this.stateForActionType(plan.type);
250
+ this.tryTransition(targetState);
251
+
252
+ if (plan.updateInfo) {
253
+ const event: UpdateDetectedEvent = {
254
+ updateInfo: plan.updateInfo,
255
+ actionPlan: plan,
256
+ timestamp: this.deps.clock.now(),
257
+ };
258
+ this.deps.eventBus.publish('updateDetected', event);
259
+ } else {
260
+ this.deps.eventBus.publish('updateNotAvailable', {
261
+ currentVersion,
262
+ checkedAt: this.deps.clock.now(),
263
+ });
264
+ }
265
+ }
266
+
267
+ private stateForActionType(type: ActionType): LifecycleState {
268
+ switch (type) {
269
+ case ActionType.FORCE_UPDATE:
270
+ return LifecycleState.FORCE_UPDATE_DISPLAYED;
271
+ case ActionType.SOFT_UPDATE:
272
+ case ActionType.OPTIONAL_REMINDER:
273
+ return LifecycleState.SOFT_UPDATE_DISPLAYED;
274
+ default:
275
+ return LifecycleState.IDLE;
276
+ }
277
+ }
278
+
279
+ async forceTriggerUpdate(_options: ForceTriggerOptions = {}): Promise<void> {
280
+ await this.readyPromise;
281
+ const [currentVersion, bundleId] = await Promise.all([
282
+ this.resolveCurrentVersion(),
283
+ this.deps.platformBridge.appInfo.getBundleId(),
284
+ ]);
285
+
286
+ try {
287
+ const plan = await this.forceUseCase.execute({
288
+ currentVersion,
289
+ bundleId,
290
+ platform: this.deps.platformBridge.platform,
291
+ timeoutMs: this.currentConfig.fallback.requestTimeoutMs,
292
+ });
293
+ this.applyPlan(plan, currentVersion);
294
+ } catch (error) {
295
+ this.publishError(toVersionManagerException(error), 'check');
296
+ }
297
+ }
298
+
299
+ async resetIgnoredVersions(
300
+ options: ResetIgnoredVersionsOptions = {}
301
+ ): Promise<void> {
302
+ await this.resetUseCase.execute(options.versions);
303
+ }
304
+
305
+ getCurrentState(): LifecycleState {
306
+ return this.stateMachine.current;
307
+ }
308
+
309
+ getUpdateInfo(): UpdateInfo | null {
310
+ return this.lastActionPlan?.updateInfo ?? null;
311
+ }
312
+
313
+ isUpdateAvailable(): boolean {
314
+ return (
315
+ this.lastActionPlan !== null &&
316
+ this.lastActionPlan.type !== ActionType.NO_ACTION
317
+ );
318
+ }
319
+
320
+ getActionPlan(): ActionPlan | null {
321
+ return this.lastActionPlan;
322
+ }
323
+
324
+ getLastCheckedAt(): number | null {
325
+ return this.lastCheckedAt;
326
+ }
327
+
328
+ on<K extends keyof VersionManagerEventMap>(
329
+ event: K,
330
+ handler: (payload: VersionManagerEventMap[K]) => void
331
+ ): Unsubscribe {
332
+ return this.deps.eventBus.subscribe(event, handler);
333
+ }
334
+
335
+ once<K extends keyof VersionManagerEventMap>(
336
+ event: K,
337
+ handler: (payload: VersionManagerEventMap[K]) => void
338
+ ): Unsubscribe {
339
+ const unsubscribe = this.deps.eventBus.subscribe(event, (payload) => {
340
+ unsubscribe();
341
+ handler(payload);
342
+ });
343
+ return unsubscribe;
344
+ }
345
+
346
+ off<K extends keyof VersionManagerEventMap>(
347
+ event: K,
348
+ handler: (payload: VersionManagerEventMap[K]) => void
349
+ ): void {
350
+ // Removes a handler registered via on(event, handler) by reference, in addition to
351
+ // (not instead of) the Unsubscribe closure returned from on()/once() — both styles
352
+ // are safe to mix (Prompt 2 §4.2). Handlers wrapped internally by once() are not
353
+ // reachable here since once() never hands the wrapped function back to the caller;
354
+ // its own Unsubscribe closure is the only way to remove a once() handler early.
355
+ this.deps.eventBus.off(event, handler);
356
+ }
357
+
358
+ onUpdateDetected(handler: (e: UpdateDetectedEvent) => void): Unsubscribe {
359
+ return this.on('updateDetected', handler);
360
+ }
361
+
362
+ onUserAction(handler: (e: UserActionEvent) => void): Unsubscribe {
363
+ return this.on('userAction', handler);
364
+ }
365
+
366
+ onError(handler: (e: VersionManagerErrorEvent) => void): Unsubscribe {
367
+ return this.on('error', handler);
368
+ }
369
+
370
+ onStateChanged(handler: (e: StateChangedEvent) => void): Unsubscribe {
371
+ return this.on('stateChanged', handler);
372
+ }
373
+
374
+ dispose(): void {
375
+ this.tryTransition(LifecycleState.TERMINATED);
376
+ }
377
+
378
+ private async resolveCurrentVersion(): Promise<string> {
379
+ return (
380
+ this.currentConfig.appVersion ??
381
+ this.deps.platformBridge.appInfo.getCurrentVersion()
382
+ );
383
+ }
384
+
385
+ private getRolloutBucket(): number {
386
+ if (this.rolloutBucket === null) {
387
+ this.rolloutBucket = Math.floor(Math.random() * 100);
388
+ }
389
+ return this.rolloutBucket;
390
+ }
391
+
392
+ private publishError(
393
+ error: VersionManagerException,
394
+ phase: VersionManagerErrorEvent['phase']
395
+ ): void {
396
+ this.deps.eventBus.publish('error', {
397
+ error,
398
+ phase,
399
+ timestamp: this.deps.clock.now(),
400
+ });
401
+ }
402
+
403
+ private tryTransition(to: LifecycleState): void {
404
+ const from = this.stateMachine.current;
405
+ if (from === to) return;
406
+ try {
407
+ this.stateMachine.transition(to);
408
+ } catch (error) {
409
+ if (error instanceof InvalidTransitionError) {
410
+ // Some manual-trigger call paths (e.g. checkForUpdates() while a dialog from a
411
+ // previous check is still displayed) do not have a modeled edge in the Prompt 9
412
+ // transition table yet. Rather than throw out of a public API method for an
413
+ // internal invariant, this is logged and the state machine is left untouched.
414
+ return;
415
+ }
416
+ throw error;
417
+ }
418
+ const event: StateChangedEvent = {
419
+ from,
420
+ to,
421
+ timestamp: this.deps.clock.now(),
422
+ };
423
+ this.deps.eventBus.publish('stateChanged', event);
424
+ }
425
+ }
@@ -0,0 +1,47 @@
1
+ import type { IClock } from '../../ports/IClock';
2
+ import { ActionType } from '../../models/ActionPlan';
3
+ import type { ActionPlan } from '../../models/ActionPlan';
4
+ import type { RemoteVersionInfo } from '../../models/RemoteVersionInfo';
5
+ import type { UpdateInfo } from '../../models/UpdateInfo';
6
+
7
+ /**
8
+ * Assembles the final ActionPlan (with a fully-populated UpdateInfo, when relevant)
9
+ * from a PolicyEngine classification. Kept separate from PolicyEngine per the Prompt 7
10
+ * Decision Engine split in docs/architecture/01 §5.3 — this is the orchestration step,
11
+ * not the rule evaluation step.
12
+ */
13
+ export class DecisionEngine {
14
+ constructor(private readonly clock: IClock) {}
15
+
16
+ buildActionPlan(
17
+ actionType: ActionType,
18
+ currentVersion: string,
19
+ remote: RemoteVersionInfo | null
20
+ ): ActionPlan {
21
+ if (actionType === ActionType.NO_ACTION || remote === null) {
22
+ return {
23
+ type: ActionType.NO_ACTION,
24
+ updateInfo: null,
25
+ reason: 'up_to_date_or_suppressed',
26
+ decidedAt: this.clock.now(),
27
+ };
28
+ }
29
+
30
+ const updateInfo: UpdateInfo = {
31
+ currentVersion,
32
+ latestVersion: remote.latestVersion,
33
+ storeUrl: remote.storeUrl,
34
+ releaseNotes: remote.releaseNotes,
35
+ isForceUpdate: actionType === ActionType.FORCE_UPDATE,
36
+ provider: remote.provider,
37
+ fetchedAt: remote.fetchedAt,
38
+ };
39
+
40
+ return {
41
+ type: actionType,
42
+ updateInfo,
43
+ reason: actionType.toLowerCase(),
44
+ decidedAt: this.clock.now(),
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ import type { ActionType } from '../../models/ActionPlan';
2
+ import type { ParsedVersion } from '../../models/ParsedVersion';
3
+ import type { UserVersionDecision } from '../../models/RemoteVersionInfo';
4
+
5
+ export interface PolicyEvaluationContext {
6
+ readonly currentVersion: ParsedVersion;
7
+ readonly remoteVersion: ParsedVersion;
8
+ readonly forceUpdateBelow: ParsedVersion | null;
9
+ readonly userDecision: UserVersionDecision | null;
10
+ readonly now: number;
11
+ readonly rolloutPercentage: number;
12
+ /** Precomputed 0-99 bucket for this device (Prompt 18 rollout hashing — deferred, see PolicyEngine). */
13
+ readonly rolloutBucket: number;
14
+ }
15
+
16
+ export interface IUpdatePolicyEngine {
17
+ evaluate(context: PolicyEvaluationContext): ActionType;
18
+ }
@@ -0,0 +1,60 @@
1
+ import { ActionType } from '../../models/ActionPlan';
2
+ import type { IVersionComparator } from '../semver/IVersionComparator';
3
+ import type {
4
+ IUpdatePolicyEngine,
5
+ PolicyEvaluationContext,
6
+ } from './IUpdatePolicyEngine';
7
+
8
+ /**
9
+ * Minimal rule evaluator: threshold-based force-update classification, ignore-list and
10
+ * remind-later suppression, and rollout-percentage gating. This intentionally covers a
11
+ * subset of the full Rule DSL described for Prompt 6 (conflict resolution across
12
+ * security/force/grace-period/optional tiers) — that richer precedence table is future
13
+ * scope; this engine implements the linear precedence documented inline below.
14
+ */
15
+ export class PolicyEngine implements IUpdatePolicyEngine {
16
+ constructor(private readonly comparator: IVersionComparator) {}
17
+
18
+ evaluate(context: PolicyEvaluationContext): ActionType {
19
+ const cmp = this.comparator.compare(
20
+ context.currentVersion,
21
+ context.remoteVersion
22
+ );
23
+ if (cmp >= 0) {
24
+ return ActionType.NO_ACTION;
25
+ }
26
+
27
+ if (
28
+ context.userDecision?.ignoredVersions.includes(context.remoteVersion.raw)
29
+ ) {
30
+ return ActionType.NO_ACTION;
31
+ }
32
+
33
+ const remindMeLaterUntil = context.userDecision?.remindMeLaterUntil ?? null;
34
+ if (remindMeLaterUntil !== null && context.now < remindMeLaterUntil) {
35
+ return ActionType.NO_ACTION;
36
+ }
37
+
38
+ if (
39
+ context.forceUpdateBelow &&
40
+ this.comparator.compare(
41
+ context.currentVersion,
42
+ context.forceUpdateBelow
43
+ ) < 0
44
+ ) {
45
+ return ActionType.FORCE_UPDATE;
46
+ }
47
+
48
+ if (context.rolloutBucket >= context.rolloutPercentage) {
49
+ return ActionType.NO_ACTION;
50
+ }
51
+
52
+ // A previously-expired "remind me later" snooze downgrades to a less intrusive
53
+ // banner on the next detection rather than repeating the modal dialog.
54
+ if (remindMeLaterUntil !== null) {
55
+ return ActionType.OPTIONAL_REMINDER;
56
+ }
57
+
58
+ return ActionType.SOFT_UPDATE;
59
+ }
60
+ }
@@ -0,0 +1,7 @@
1
+ import type { ParsedVersion } from '../../models/ParsedVersion';
2
+
3
+ export interface IVersionComparator {
4
+ parse(raw: string): ParsedVersion;
5
+ compare(a: ParsedVersion, b: ParsedVersion): -1 | 0 | 1;
6
+ satisfies(version: ParsedVersion, range: string): boolean;
7
+ }