@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,424 @@
1
+ import { ConsoleLogSink } from '../../shared/logging/Logger';
2
+ import { resolveConfigSync } from './ConfigLoader';
3
+ import { CONFIG_ENVELOPE_MAX_BYTES } from './ConfigDocumentValidator';
4
+ import type {
5
+ ConfigTier,
6
+ RawConfigDocument,
7
+ SignedConfigEnvelope,
8
+ TrustedKeySet,
9
+ VersionManagerConfigDocument,
10
+ } from '../../domain/models/ConfigDocument';
11
+ import {
12
+ BoundaryValidationException,
13
+ ConfigSizeLimitExceededException,
14
+ SchemaValidationException,
15
+ SignatureVerificationException,
16
+ UntrustedKeyIdException,
17
+ toVersionManagerException,
18
+ } from '../../domain/errors/VersionManagerException';
19
+ import type { VersionManagerException } from '../../domain/errors/VersionManagerException';
20
+ import type {
21
+ IConfigCache,
22
+ IConfigValidator,
23
+ IEnvironmentOverrideSource,
24
+ ILocalConfigSource,
25
+ IRemoteConfigSource,
26
+ ISignatureVerifier,
27
+ } from '../../domain/ports/IConfigSources';
28
+ import type { IClock } from '../../domain/ports/IClock';
29
+ import type {
30
+ ResolvedVersionManagerConfig,
31
+ VersionManagerOptions,
32
+ } from '../../domain/models/VersionManagerOptions';
33
+
34
+ export interface ConfigResolutionPipelineDeps {
35
+ readonly local: ILocalConfigSource;
36
+ readonly remote: IRemoteConfigSource;
37
+ readonly envOverrides: IEnvironmentOverrideSource;
38
+ readonly cache: IConfigCache;
39
+ readonly validator: IConfigValidator;
40
+ readonly signatureVerifier: ISignatureVerifier;
41
+ readonly clock: IClock;
42
+ /** §7.4 — default 5, configurable. */
43
+ readonly circuitBreakerThreshold?: number;
44
+ /** §7.4 cool-down window once the breaker trips. */
45
+ readonly circuitBreakerCooldownMs?: number;
46
+ readonly onError?: (error: VersionManagerException) => void;
47
+ }
48
+
49
+ export interface ConfigResolutionResult {
50
+ readonly config: ResolvedVersionManagerConfig;
51
+ readonly tier: ConfigTier;
52
+ }
53
+
54
+ /**
55
+ * Doc 03's ConfigResolutionPipeline — orchestrates §3.2 (schema -> boundary -> signature
56
+ * validation), §4.2 (per-field precedence merge, env > remote > local > default), §4.3
57
+ * (access path), §7.2 (cascading fallback), §7.3 (field vs whole-document fallback), and
58
+ * §7.4 (quarantine + circuit breaker). Lives in src/data/config/ per doc 03 §0.
59
+ */
60
+ export class ConfigResolutionPipeline {
61
+ private readonly quarantinedFingerprints = new Set<string>();
62
+ private consecutiveRemoteFailures = 0;
63
+ private circuitOpenUntil = 0;
64
+
65
+ constructor(private readonly deps: ConfigResolutionPipelineDeps) {}
66
+
67
+ async resolve(
68
+ defaults: VersionManagerOptions
69
+ ): Promise<ConfigResolutionResult> {
70
+ // Tier 4 — always present, validated synchronously at configure()-time already by
71
+ // ConfigLoader.resolveConfigSync (doc 02 §1.3 fail-fast); re-run here defensively so
72
+ // this pipeline is safe to call standalone (e.g. from tests).
73
+ const tier4 = resolveConfigSync(defaults);
74
+ const trustedKeys: TrustedKeySet = {
75
+ algorithm: tier4.security.signatureAlgorithm,
76
+ keys: new Set(tier4.security.trustedKeyIds),
77
+ };
78
+
79
+ const [envelopeTier1, envelopeTier3] = await Promise.all([
80
+ this.deps.envOverrides.read().catch(() => ({})),
81
+ this.readLocal(trustedKeys),
82
+ ]);
83
+
84
+ const remoteResult = await this.readRemote(defaults, trustedKeys);
85
+
86
+ // §7.2 whole-tier cascade: if the remote tier failed outright, prefer the last known
87
+ // valid *fully resolved* config over recomputing fresh from local+default, so a
88
+ // network hiccup doesn't regress an app back to weaker settings every single check.
89
+ if (remoteResult.status === 'failed') {
90
+ const cached = await this.deps.cache.getLastValid().catch(() => null);
91
+ if (cached) {
92
+ return {
93
+ config: cached,
94
+ tier: 'remote' /* cache reflects a prior remote win */,
95
+ };
96
+ }
97
+ }
98
+
99
+ const tier2 = remoteResult.status === 'ok' ? remoteResult.payload : null;
100
+ const tier3 = envelopeTier3;
101
+
102
+ const merged = mergeConfig(tier4, tier3, tier2, envelopeTier1);
103
+ const tier: ConfigTier = tier2
104
+ ? 'remote'
105
+ : tier3
106
+ ? 'local'
107
+ : envelopeTier1 && Object.keys(envelopeTier1).length > 0
108
+ ? 'env'
109
+ : 'default';
110
+
111
+ await this.deps.cache.setLastValid(merged).catch(() => {
112
+ // Cache-write failures never block resolution (§7.2's guarantee is about read
113
+ // fallback; a write failure here just means the *next* remote outage won't have a
114
+ // warm cache to fall back to — already surfaced via CacheWriteException elsewhere).
115
+ });
116
+
117
+ return { config: merged, tier };
118
+ }
119
+
120
+ private async readLocal(
121
+ trustedKeys: TrustedKeySet
122
+ ): Promise<VersionManagerConfigDocument | null> {
123
+ const raw = await this.deps.local.read().catch(() => null);
124
+ if (raw === null || raw === undefined) return null;
125
+
126
+ const verified = await this.validateAndVerify(raw, 'local', trustedKeys);
127
+ return verified;
128
+ }
129
+
130
+ private async readRemote(
131
+ defaults: VersionManagerOptions,
132
+ trustedKeys: TrustedKeySet
133
+ ): Promise<
134
+ | { status: 'ok'; payload: VersionManagerConfigDocument }
135
+ | { status: 'absent' }
136
+ | { status: 'failed' }
137
+ > {
138
+ const url = defaults.remoteConfigUrl;
139
+ if (!url) return { status: 'absent' };
140
+
141
+ if (this.deps.clock.now() < this.circuitOpenUntil) {
142
+ // §7.4 circuit breaker: skip attempting remote fetch entirely during the cooldown.
143
+ return { status: 'failed' };
144
+ }
145
+
146
+ let raw: RawConfigDocument;
147
+ try {
148
+ raw = await this.deps.remote.fetch(url, {
149
+ timeoutMs: defaults.fallback?.requestTimeoutMs ?? 8_000,
150
+ headers: defaults.configSources?.remoteFetchHeaders,
151
+ });
152
+ } catch (error) {
153
+ this.recordRemoteFailure();
154
+ this.report(toVersionManagerException(error));
155
+ return { status: 'failed' };
156
+ }
157
+
158
+ const fingerprint = fingerprintOf(raw);
159
+ if (this.quarantinedFingerprints.has(fingerprint)) {
160
+ // §7.4: same bad bytes seen again within the session — fail silently (no
161
+ // re-validation, no re-logging) but still counts as a circuit-breaker failure.
162
+ this.recordRemoteFailure();
163
+ return { status: 'failed' };
164
+ }
165
+
166
+ const verified = await this.validateAndVerify(
167
+ raw,
168
+ 'remote',
169
+ trustedKeys,
170
+ fingerprint
171
+ );
172
+ if (verified === null) {
173
+ this.recordRemoteFailure();
174
+ return { status: 'failed' };
175
+ }
176
+
177
+ this.consecutiveRemoteFailures = 0;
178
+ this.circuitOpenUntil = 0;
179
+ return { status: 'ok', payload: verified };
180
+ }
181
+
182
+ private recordRemoteFailure(): void {
183
+ this.consecutiveRemoteFailures++;
184
+ const threshold = this.deps.circuitBreakerThreshold ?? 5;
185
+ if (this.consecutiveRemoteFailures >= threshold) {
186
+ const cooldown = this.deps.circuitBreakerCooldownMs ?? 5 * 60_000;
187
+ this.circuitOpenUntil = this.deps.clock.now() + cooldown;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * §3.2 sequence: schema -> boundary -> canonicalize -> verify signature. Returns the
193
+ * validated payload (with any boundary-failed fields stripped per §7.3's field-level
194
+ * fallback) or null if the whole document must be rejected (schema failure or
195
+ * signature failure — §7.3's whole-document fallback).
196
+ */
197
+ private async validateAndVerify(
198
+ raw: RawConfigDocument,
199
+ tier: 'local' | 'remote',
200
+ trustedKeys: TrustedKeySet,
201
+ fingerprint?: string
202
+ ): Promise<VersionManagerConfigDocument | null> {
203
+ const sizeCheck =
204
+ typeof raw === 'string' ? raw.length : JSON.stringify(raw).length;
205
+ if (sizeCheck > CONFIG_ENVELOPE_MAX_BYTES) {
206
+ this.report(
207
+ new ConfigSizeLimitExceededException({
208
+ message: `${tier} config envelope exceeds ${CONFIG_ENVELOPE_MAX_BYTES} bytes`,
209
+ })
210
+ );
211
+ return null;
212
+ }
213
+
214
+ const schemaResult = this.deps.validator.validateSchema(raw);
215
+ if (!schemaResult.valid) {
216
+ this.report(
217
+ new SchemaValidationException({
218
+ message: `${tier} config document failed schema validation: ${schemaResult.errors.join('; ')}`,
219
+ metadata: { tier, errors: schemaResult.errors },
220
+ })
221
+ );
222
+ return null; // whole-document fallback (§7.3)
223
+ }
224
+
225
+ const envelope = (
226
+ typeof raw === 'string' ? JSON.parse(raw) : raw
227
+ ) as SignedConfigEnvelope;
228
+
229
+ // Signature verification — required for remote always, required for local unless
230
+ // algorithm is "none" in a dev build (enforced inside SignatureVerifier, §3.1).
231
+ if (
232
+ envelope.signature.algorithm !== 'none' &&
233
+ !trustedKeys.keys.has(envelope.signature.keyId)
234
+ ) {
235
+ this.report(
236
+ new UntrustedKeyIdException({
237
+ message: `${tier} config signature.keyId "${envelope.signature.keyId}" is not in security.trustedKeyIds`,
238
+ metadata: { tier },
239
+ })
240
+ );
241
+ if (fingerprint) this.quarantinedFingerprints.add(fingerprint);
242
+ return null;
243
+ }
244
+
245
+ const sigResult = await this.deps.signatureVerifier.verify(
246
+ envelope,
247
+ trustedKeys
248
+ );
249
+ if (!sigResult.valid) {
250
+ this.report(
251
+ new SignatureVerificationException({
252
+ message: `${tier} config signature verification failed: ${sigResult.reason ?? 'unknown reason'}`,
253
+ metadata: { tier },
254
+ })
255
+ );
256
+ if (fingerprint) this.quarantinedFingerprints.add(fingerprint);
257
+ return null; // whole-document fallback (§7.3) — an untrusted signature taints every field
258
+ }
259
+
260
+ // §7.3 field-level fallback: only the offending fields are dropped, not the whole doc.
261
+ const boundaryResult = this.deps.validator.validateBoundaries(raw);
262
+ if (!boundaryResult.valid) {
263
+ this.report(
264
+ new BoundaryValidationException({
265
+ message: `${tier} config document failed boundary validation on: ${Array.from(boundaryResult.fieldErrors.keys()).join(', ')}`,
266
+ metadata: {
267
+ tier,
268
+ fields: Array.from(boundaryResult.fieldErrors.entries()),
269
+ },
270
+ })
271
+ );
272
+ return stripFields(envelope.payload, boundaryResult.fieldErrors);
273
+ }
274
+
275
+ return envelope.payload;
276
+ }
277
+
278
+ private report(error: VersionManagerException): void {
279
+ if (this.deps.onError) {
280
+ this.deps.onError(error);
281
+ } else {
282
+ new ConsoleLogSink().write({
283
+ level: 'error',
284
+ message: error.message,
285
+ timestamp: this.deps.clock.now(),
286
+ metadata: { code: error.code },
287
+ });
288
+ }
289
+ }
290
+ }
291
+
292
+ function fingerprintOf(raw: RawConfigDocument): string {
293
+ const str = typeof raw === 'string' ? raw : JSON.stringify(raw);
294
+ // Cheap non-cryptographic fingerprint — this is a quarantine dedupe key, not a
295
+ // security boundary (signature verification is what actually protects integrity).
296
+ let hash = 0;
297
+ for (let i = 0; i < str.length; i++) {
298
+ hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
299
+ }
300
+ return `${str.length}:${hash}`;
301
+ }
302
+
303
+ function stripFields(
304
+ payload: VersionManagerConfigDocument,
305
+ fieldErrors: ReadonlyMap<string, string>
306
+ ): VersionManagerConfigDocument {
307
+ const clone: Record<string, unknown> = JSON.parse(JSON.stringify(payload));
308
+ for (const path of fieldErrors.keys()) {
309
+ const segments = path.split('.');
310
+ let cursor: Record<string, unknown> = clone;
311
+ for (let i = 0; i < segments.length - 1; i++) {
312
+ const next = cursor[segments[i] as string];
313
+ if (typeof next !== 'object' || next === null) break;
314
+ cursor = next as Record<string, unknown>;
315
+ }
316
+ delete cursor[segments[segments.length - 1] as string];
317
+ }
318
+ return clone as VersionManagerConfigDocument;
319
+ }
320
+
321
+ function firstDefined<T>(...values: (T | undefined | null)[]): T | undefined {
322
+ for (const v of values) {
323
+ if (v !== undefined && v !== null) return v;
324
+ }
325
+ return undefined;
326
+ }
327
+
328
+ /**
329
+ * Doc 03 §4.2 precedence matrix — per-field deep merge, honoring which tiers each field
330
+ * group is eligible to appear in (not every field accepts every tier; e.g. env never
331
+ * overrides cache.storage or stores.*, and security.* is tier-4-only).
332
+ */
333
+ function mergeConfig(
334
+ tier4: ResolvedVersionManagerConfig,
335
+ tier3: VersionManagerConfigDocument | null,
336
+ tier2: VersionManagerConfigDocument | null,
337
+ tier1: Partial<VersionManagerConfigDocument>
338
+ ): ResolvedVersionManagerConfig {
339
+ return {
340
+ appVersion:
341
+ firstDefined(tier1.localVersionIdentifier, tier4.appVersion) ?? null,
342
+ logging: tier4.logging,
343
+ cache: {
344
+ ttlMs:
345
+ firstDefined(
346
+ tier1.cache?.ttlMs,
347
+ tier2?.cache?.ttlMs,
348
+ tier3?.cache?.ttlMs,
349
+ tier4.cache.ttlMs
350
+ ) ?? 21_600_000,
351
+ storage:
352
+ firstDefined(
353
+ tier2?.cache?.storage,
354
+ tier3?.cache?.storage,
355
+ tier4.cache.storage
356
+ ) ?? 'persistent',
357
+ bustOnManualCheck: tier4.cache.bustOnManualCheck,
358
+ },
359
+ fallback: {
360
+ onNetworkError: tier4.fallback.onNetworkError,
361
+ defaultConfig: tier4.fallback.defaultConfig,
362
+ requestTimeoutMs:
363
+ firstDefined(
364
+ tier1.network?.requestTimeoutMs,
365
+ tier2?.network?.requestTimeoutMs,
366
+ tier3?.network?.requestTimeoutMs,
367
+ tier4.fallback.requestTimeoutMs
368
+ ) ?? 8_000,
369
+ retry: {
370
+ maxAttempts:
371
+ firstDefined(
372
+ tier1.network?.retry?.maxAttempts,
373
+ tier2?.network?.retry?.maxAttempts,
374
+ tier3?.network?.retry?.maxAttempts,
375
+ tier4.fallback.retry.maxAttempts
376
+ ) ?? 3,
377
+ backoff:
378
+ firstDefined(
379
+ tier2?.network?.retry?.backoff,
380
+ tier3?.network?.retry?.backoff,
381
+ tier4.fallback.retry.backoff
382
+ ) ?? 'exponential-jitter',
383
+ baseDelayMs:
384
+ firstDefined(
385
+ tier1.network?.retry?.baseDelayMs,
386
+ tier2?.network?.retry?.baseDelayMs,
387
+ tier3?.network?.retry?.baseDelayMs,
388
+ tier4.fallback.retry.baseDelayMs
389
+ ) ?? 500,
390
+ },
391
+ },
392
+ stores: firstDefined(tier2?.stores, tier3?.stores) ?? tier4.stores,
393
+ policy: {
394
+ forceUpdateBelow:
395
+ firstDefined(
396
+ tier1.alerts?.forceUpdateBelow,
397
+ tier2?.alerts?.forceUpdateBelow,
398
+ tier3?.alerts?.forceUpdateBelow,
399
+ tier4.policy.forceUpdateBelow ?? undefined
400
+ ) ?? null,
401
+ reminderIntervalMs:
402
+ firstDefined(
403
+ tier1.alerts?.reminderDelayMs,
404
+ tier2?.alerts?.reminderDelayMs,
405
+ tier3?.alerts?.reminderDelayMs,
406
+ tier4.policy.reminderIntervalMs
407
+ ) ?? 259_200_000,
408
+ rolloutPercentage: tier4.policy.rolloutPercentage,
409
+ },
410
+ strictReadiness: tier4.strictReadiness,
411
+ remoteConfigUrl:
412
+ firstDefined(tier1.remoteConfigUrl, tier4.remoteConfigUrl ?? undefined) ??
413
+ null,
414
+ // security.* is tier-4-only, never overridden by a loaded document (§4.2).
415
+ security: tier4.security,
416
+ schemaVersion:
417
+ firstDefined(
418
+ tier1.schemaVersion,
419
+ tier2?.schemaVersion,
420
+ tier3?.schemaVersion,
421
+ tier4.schemaVersion
422
+ ) ?? '1.0',
423
+ };
424
+ }
@@ -0,0 +1,79 @@
1
+ import { parseVersion } from '../../domain/engines/semver/SemVerEngine';
2
+ import { InvalidConfigException } from '../../domain/errors/VersionManagerException';
3
+ import type { VersionManagerOptions } from '../../domain/models/VersionManagerOptions';
4
+
5
+ /**
6
+ * Boundary validation (Prompt 3 §2.2) for the "default fallback" config tier — the
7
+ * VersionManagerOptions object passed to configure(). Full JSON-Schema structural
8
+ * validation and signature verification for local/remote config documents (Prompt 3
9
+ * §2.1, §3) are scoped to the not-yet-built config-document pipeline; see
10
+ * docs/architecture/03-configuration-system.md.
11
+ */
12
+ export function validateOptions(options: VersionManagerOptions): void {
13
+ if (!options.stores || Object.keys(options.stores).length === 0) {
14
+ throw new InvalidConfigException({
15
+ message:
16
+ 'VersionManagerOptions.stores must configure at least one store (ios/android/huawei/amazon/custom)',
17
+ });
18
+ }
19
+
20
+ if (options.appVersion !== undefined) {
21
+ try {
22
+ parseVersion(options.appVersion);
23
+ } catch (error) {
24
+ throw new InvalidConfigException({
25
+ message: `options.appVersion "${options.appVersion}" is not a valid SemVer string`,
26
+ cause: error,
27
+ });
28
+ }
29
+ }
30
+
31
+ if (options.policy?.forceUpdateBelow !== undefined) {
32
+ try {
33
+ parseVersion(options.policy.forceUpdateBelow);
34
+ } catch (error) {
35
+ throw new InvalidConfigException({
36
+ message: `options.policy.forceUpdateBelow "${options.policy.forceUpdateBelow}" is not a valid SemVer string`,
37
+ cause: error,
38
+ });
39
+ }
40
+ }
41
+
42
+ const timeoutMs = options.fallback?.requestTimeoutMs;
43
+ if (timeoutMs !== undefined && timeoutMs <= 0) {
44
+ throw new InvalidConfigException({
45
+ message: 'options.fallback.requestTimeoutMs must be greater than 0',
46
+ });
47
+ }
48
+
49
+ const ttlMs = options.cache?.ttlMs;
50
+ if (ttlMs !== undefined && ttlMs < 0) {
51
+ throw new InvalidConfigException({
52
+ message: 'options.cache.ttlMs must be >= 0',
53
+ });
54
+ }
55
+
56
+ const maxAttempts = options.fallback?.retry?.maxAttempts;
57
+ if (maxAttempts !== undefined && (maxAttempts < 0 || maxAttempts > 10)) {
58
+ throw new InvalidConfigException({
59
+ message: 'options.fallback.retry.maxAttempts must be between 0 and 10',
60
+ });
61
+ }
62
+
63
+ const baseDelayMs = options.fallback?.retry?.baseDelayMs;
64
+ if (baseDelayMs !== undefined && baseDelayMs <= 0) {
65
+ throw new InvalidConfigException({
66
+ message: 'options.fallback.retry.baseDelayMs must be greater than 0',
67
+ });
68
+ }
69
+
70
+ const rolloutPercentage = options.policy?.rolloutPercentage;
71
+ if (
72
+ rolloutPercentage !== undefined &&
73
+ (rolloutPercentage < 0 || rolloutPercentage > 100)
74
+ ) {
75
+ throw new InvalidConfigException({
76
+ message: 'options.policy.rolloutPercentage must be between 0 and 100',
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,90 @@
1
+ import { canonicalize, utf8ToBase64 } from './canonicalize';
2
+ import type {
3
+ SignatureVerificationResult,
4
+ SignedConfigEnvelope,
5
+ TrustedKeySet,
6
+ } from '../../domain/models/ConfigDocument';
7
+ import type { ICryptoProvider } from '../../domain/ports/IPlatformBridge';
8
+ import type { ISignatureVerifier } from '../../domain/ports/IConfigSources';
9
+
10
+ declare const __DEV__: boolean | undefined;
11
+
12
+ function isDev(): boolean {
13
+ return typeof __DEV__ !== 'undefined' && __DEV__ === true;
14
+ }
15
+
16
+ /**
17
+ * Doc 03 §3.2/§3.4 — verification calls are implemented using OS-provided cryptography
18
+ * exclusively (iOS CryptoKit/Security.framework, Android java.security), reached through
19
+ * IPlatformBridge.crypto (ICryptoProvider) so this class never itself hand-rolls
20
+ * cryptographic primitives (Prompt 1 §9.3's zero-external-dependency constraint extends
21
+ * to "don't implement crypto in JS either" — only OS APIs are trusted for this).
22
+ */
23
+ export class SignatureVerifier implements ISignatureVerifier {
24
+ constructor(private readonly crypto?: ICryptoProvider) {}
25
+
26
+ async verify(
27
+ envelope: SignedConfigEnvelope,
28
+ trustedKeys: TrustedKeySet
29
+ ): Promise<SignatureVerificationResult> {
30
+ const { algorithm, keyId, value } = envelope.signature;
31
+
32
+ // §3.1: algorithm "none" is permitted only for the local bundled document in
33
+ // development builds — rejected for any remote fetch and for release builds,
34
+ // enforced here regardless of what security.signatureAlgorithm claims (a
35
+ // compromised document cannot downgrade its own algorithm to "none" and be
36
+ // believed). The pipeline is responsible for only calling verify() with a "none"
37
+ // envelope on the local-dev path in the first place; this is a defense-in-depth
38
+ // second check.
39
+ if (algorithm === 'none') {
40
+ if (!isDev()) {
41
+ return {
42
+ valid: false,
43
+ reason:
44
+ 'signature.algorithm "none" is rejected outside development builds',
45
+ };
46
+ }
47
+ return { valid: true };
48
+ }
49
+
50
+ if (trustedKeys.algorithm !== algorithm) {
51
+ return {
52
+ valid: false,
53
+ reason: `Envelope algorithm "${algorithm}" does not match configured security.signatureAlgorithm "${trustedKeys.algorithm}"`,
54
+ };
55
+ }
56
+
57
+ if (!trustedKeys.keys.has(keyId)) {
58
+ return {
59
+ valid: false,
60
+ reason: `keyId "${keyId}" is not present in security.trustedKeyIds`,
61
+ };
62
+ }
63
+
64
+ if (!this.crypto) {
65
+ return {
66
+ valid: false,
67
+ reason:
68
+ 'No ICryptoProvider available on the active IPlatformBridge — cannot verify signatures on this target',
69
+ };
70
+ }
71
+
72
+ const canonicalPayload = canonicalize(envelope.payload);
73
+ const messageBase64 = utf8ToBase64(canonicalPayload);
74
+
75
+ try {
76
+ const valid =
77
+ algorithm === 'ed25519'
78
+ ? await this.crypto.verifyEd25519(keyId, messageBase64, value)
79
+ : await this.crypto.verifyHmacSha256(keyId, messageBase64, value);
80
+ return valid
81
+ ? { valid: true }
82
+ : { valid: false, reason: 'Signature mismatch' };
83
+ } catch (error) {
84
+ return {
85
+ valid: false,
86
+ reason: `Native signature verification threw: ${String(error)}`,
87
+ };
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Hand-rolled canonicalizer (Prompt 1 §9.3 zero-dependency constraint), implementing the
3
+ * same principles as RFC 8785 (JSON Canonicalization Scheme) that doc 03 §3.3 calls for:
4
+ * recursive key sorting by UTF-16 code unit order, fixed number formatting, no
5
+ * insignificant whitespace. Signatures are computed/verified over this canonical byte
6
+ * form so that whitespace/key-order differences in transit never invalidate a
7
+ * legitimately signed document.
8
+ */
9
+
10
+ type JsonValue =
11
+ null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
12
+
13
+ function canonicalizeValue(value: JsonValue): string {
14
+ if (value === null) return 'null';
15
+
16
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
17
+
18
+ if (typeof value === 'number') return canonicalizeNumber(value);
19
+
20
+ if (typeof value === 'string') return JSON.stringify(value);
21
+
22
+ if (Array.isArray(value)) {
23
+ return `[${value.map(canonicalizeValue).join(',')}]`;
24
+ }
25
+
26
+ // Recursive key sorting, UTF-16 code unit order (default JS string `<` comparison).
27
+ const keys = Object.keys(value).sort();
28
+ const members = keys.map(
29
+ (key) =>
30
+ `${JSON.stringify(key)}:${canonicalizeValue(value[key] as JsonValue)}`
31
+ );
32
+ return `{${members.join(',')}}`;
33
+ }
34
+
35
+ /**
36
+ * Fixed number formatting per RFC 8785 §3.2.2.3 (simplified): integers render without a
37
+ * decimal point/exponent; this codebase's config schema (doc 03 §6) only ever contains
38
+ * integers and no fractional/exponential numeric fields, so the full ECMA-262
39
+ * Number::toString algorithm RFC 8785 mandates is unnecessary here.
40
+ */
41
+ function canonicalizeNumber(value: number): string {
42
+ if (!Number.isFinite(value)) {
43
+ throw new Error('Cannot canonicalize a non-finite number');
44
+ }
45
+ if (Number.isInteger(value)) return String(value);
46
+ return String(value);
47
+ }
48
+
49
+ /** Canonical byte sequence (as a UTF-8 string) for the given JSON-compatible value. */
50
+ export function canonicalize(value: unknown): string {
51
+ return canonicalizeValue(value as JsonValue);
52
+ }
53
+
54
+ /** Base64 encoding of the UTF-8 bytes of a string, without relying on Node's Buffer. */
55
+ export function utf8ToBase64(input: string): string {
56
+ if (typeof btoa === 'function' && typeof TextEncoder !== 'undefined') {
57
+ const bytes = new TextEncoder().encode(input);
58
+ let binary = '';
59
+ for (let i = 0; i < bytes.length; i++) {
60
+ binary += String.fromCharCode(bytes[i] as number);
61
+ }
62
+ return btoa(binary);
63
+ }
64
+ // React Native's Hermes engine provides global Buffer via a polyfill in most RN
65
+ // setups, but not guaranteed — fall back to a minimal hand-rolled base64 encoder.
66
+ return manualUtf8ToBase64(input);
67
+ }
68
+
69
+ const BASE64_CHARS =
70
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
71
+
72
+ function manualUtf8ToBase64(input: string): string {
73
+ const bytes: number[] = [];
74
+ for (let i = 0; i < input.length; i++) {
75
+ let code = input.codePointAt(i) as number;
76
+ if (code > 0xffff) i++; // consumed a surrogate pair
77
+ if (code < 0x80) {
78
+ bytes.push(code);
79
+ } else if (code < 0x800) {
80
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
81
+ } else if (code < 0x10000) {
82
+ bytes.push(
83
+ 0xe0 | (code >> 12),
84
+ 0x80 | ((code >> 6) & 0x3f),
85
+ 0x80 | (code & 0x3f)
86
+ );
87
+ } else {
88
+ bytes.push(
89
+ 0xf0 | (code >> 18),
90
+ 0x80 | ((code >> 12) & 0x3f),
91
+ 0x80 | ((code >> 6) & 0x3f),
92
+ 0x80 | (code & 0x3f)
93
+ );
94
+ }
95
+ }
96
+
97
+ let output = '';
98
+ for (let i = 0; i < bytes.length; i += 3) {
99
+ const b0 = bytes[i] as number;
100
+ const b1 = bytes[i + 1];
101
+ const b2 = bytes[i + 2];
102
+ output += BASE64_CHARS[b0 >> 2];
103
+ output += BASE64_CHARS[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)];
104
+ output +=
105
+ b1 === undefined
106
+ ? '='
107
+ : BASE64_CHARS[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)];
108
+ output += b2 === undefined ? '=' : BASE64_CHARS[b2 & 0x3f];
109
+ }
110
+ return output;
111
+ }