@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,179 @@
1
+ import { AppState, Platform } from 'react-native';
2
+ import {
3
+ OfflineException,
4
+ RequestTimeoutException,
5
+ } from '../../domain/errors/VersionManagerException';
6
+ import type { PlatformId } from '../../domain/models/PlatformId';
7
+ import type {
8
+ BackgroundTaskDescriptor,
9
+ HttpRequest,
10
+ HttpResponse,
11
+ IAppInfoProvider,
12
+ IAppLifecycleObserver,
13
+ IBackgroundScheduler,
14
+ ICryptoProvider,
15
+ IDeviceInfoProvider,
16
+ IHttpClient,
17
+ IKeyValueStorage,
18
+ IPlatformBridge,
19
+ ISecureStorage,
20
+ Unsubscribe,
21
+ } from '../../domain/ports/IPlatformBridge';
22
+ import NativeVersionCheck from './NativeVersionCheck';
23
+
24
+ class RnHttpClient implements IHttpClient {
25
+ async request(req: HttpRequest): Promise<HttpResponse> {
26
+ const controller = new AbortController();
27
+ const timeout = setTimeout(() => controller.abort(), req.timeoutMs);
28
+ try {
29
+ const response = await fetch(req.url, {
30
+ method: req.method,
31
+ headers: req.headers,
32
+ body: req.body,
33
+ signal: controller.signal,
34
+ });
35
+ const headers: Record<string, string> = {};
36
+ response.headers.forEach((value, key) => {
37
+ headers[key] = value;
38
+ });
39
+ const body = await response.text();
40
+ return { status: response.status, headers, body };
41
+ } catch (error) {
42
+ if (error instanceof Error && error.name === 'AbortError') {
43
+ throw new RequestTimeoutException({
44
+ message: `Request to "${req.url}" timed out after ${req.timeoutMs}ms`,
45
+ cause: error,
46
+ });
47
+ }
48
+ throw new OfflineException({
49
+ message: `Request to "${req.url}" failed`,
50
+ cause: error,
51
+ });
52
+ } finally {
53
+ clearTimeout(timeout);
54
+ }
55
+ }
56
+ }
57
+
58
+ class RnKeyValueStorage implements IKeyValueStorage {
59
+ async get(key: string): Promise<string | null> {
60
+ return NativeVersionCheck().storageGet(key);
61
+ }
62
+ async set(key: string, value: string): Promise<void> {
63
+ await NativeVersionCheck().storageSet(key, value);
64
+ }
65
+ async remove(key: string): Promise<void> {
66
+ await NativeVersionCheck().storageRemove(key);
67
+ }
68
+ }
69
+
70
+ class RnSecureStorage implements ISecureStorage {
71
+ async getItem(key: string): Promise<string | null> {
72
+ return NativeVersionCheck().secureStorageGet(key);
73
+ }
74
+ async setItem(key: string, value: string): Promise<void> {
75
+ await NativeVersionCheck().secureStorageSet(key, value);
76
+ }
77
+ async removeItem(key: string): Promise<void> {
78
+ await NativeVersionCheck().secureStorageRemove(key);
79
+ }
80
+ }
81
+
82
+ class RnAppInfoProvider implements IAppInfoProvider {
83
+ async getCurrentVersion(): Promise<string> {
84
+ return NativeVersionCheck().getCurrentAppVersion();
85
+ }
86
+ async getBuildNumber(): Promise<string> {
87
+ return NativeVersionCheck().getBuildNumber();
88
+ }
89
+ async getBundleId(): Promise<string> {
90
+ return NativeVersionCheck().getBundleId();
91
+ }
92
+ }
93
+
94
+ class RnDeviceInfoProvider implements IDeviceInfoProvider {
95
+ async getOsVersion(): Promise<string> {
96
+ return NativeVersionCheck().getOsVersion();
97
+ }
98
+ async getDeviceModel(): Promise<string> {
99
+ return NativeVersionCheck().getDeviceModel();
100
+ }
101
+ async getLocale(): Promise<string> {
102
+ return NativeVersionCheck().getLocale();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * True OS-scheduled background execution (BGTaskScheduler/WorkManager, Prompt 26) is
108
+ * not implemented in this pass. schedule()/cancel() log a warning rather than silently
109
+ * pretending to work — see the equivalent, identically-scoped note on WebBackgroundScheduler.
110
+ */
111
+ class RnBackgroundScheduler implements IBackgroundScheduler {
112
+ async schedule(task: BackgroundTaskDescriptor): Promise<void> {
113
+ console.warn(
114
+ `[VersionManager] Background scheduling ("${task.taskId}") is not implemented on ${Platform.OS} yet (Prompt 26).`
115
+ );
116
+ }
117
+ async cancel(_taskId: string): Promise<void> {
118
+ // no-op — nothing was ever scheduled, see schedule() above.
119
+ }
120
+ }
121
+
122
+ class RnAppLifecycleObserver implements IAppLifecycleObserver {
123
+ onForeground(cb: () => void): Unsubscribe {
124
+ const sub = AppState.addEventListener('change', (state) => {
125
+ if (state === 'active') cb();
126
+ });
127
+ return () => sub.remove();
128
+ }
129
+
130
+ onBackground(cb: () => void): Unsubscribe {
131
+ const sub = AppState.addEventListener('change', (state) => {
132
+ if (state === 'background') cb();
133
+ });
134
+ return () => sub.remove();
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Doc 03 §3.4 — delegates to the native TurboModule, which resolves `keyId` to
140
+ * build-time-embedded key material (iOS CommonCrypto/CryptoKit, Android
141
+ * java.security/javax.crypto) and never exposes key material to JS.
142
+ */
143
+ class RnCryptoProvider implements ICryptoProvider {
144
+ async verifyEd25519(
145
+ keyId: string,
146
+ messageBase64: string,
147
+ signatureBase64: string
148
+ ): Promise<boolean> {
149
+ return NativeVersionCheck().verifyEd25519(
150
+ keyId,
151
+ messageBase64,
152
+ signatureBase64
153
+ );
154
+ }
155
+ async verifyHmacSha256(
156
+ keyId: string,
157
+ messageBase64: string,
158
+ macBase64: string
159
+ ): Promise<boolean> {
160
+ return NativeVersionCheck().verifyHmacSha256(
161
+ keyId,
162
+ messageBase64,
163
+ macBase64
164
+ );
165
+ }
166
+ }
167
+
168
+ export class NativePlatformBridge implements IPlatformBridge {
169
+ readonly platform: PlatformId = Platform.OS === 'ios' ? 'ios' : 'android';
170
+ readonly http: IHttpClient = new RnHttpClient();
171
+ readonly storage: IKeyValueStorage = new RnKeyValueStorage();
172
+ readonly secureStorage: ISecureStorage = new RnSecureStorage();
173
+ readonly appInfo: IAppInfoProvider = new RnAppInfoProvider();
174
+ readonly deviceInfo: IDeviceInfoProvider = new RnDeviceInfoProvider();
175
+ readonly scheduler: IBackgroundScheduler = new RnBackgroundScheduler();
176
+ readonly clock = { now: (): number => Date.now() };
177
+ readonly lifecycle: IAppLifecycleObserver = new RnAppLifecycleObserver();
178
+ readonly crypto: ICryptoProvider = new RnCryptoProvider();
179
+ }
@@ -0,0 +1,64 @@
1
+ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+
3
+ /**
4
+ * TurboModule Codegen spec. Deliberately small: HTTP (global `fetch`), the clock
5
+ * (`Date.now()`), and app-lifecycle (`AppState`) are already available in pure JS via
6
+ * React Native itself and do not need a custom native surface (Prompt 1 §4.1 revised
7
+ * during implementation — the design doc's per-target table listed a native `http`
8
+ * bridge conceptually, but RN's own Networking module already satisfies IHttpClient
9
+ * without new native code). What genuinely requires native code — reading the app's
10
+ * own version/bundle id and persisting key-value pairs via UserDefaults/Keychain or
11
+ * SharedPreferences/Keystore — is exactly what's declared here.
12
+ */
13
+ export interface Spec extends TurboModule {
14
+ getCurrentAppVersion(): Promise<string>;
15
+ getBuildNumber(): Promise<string>;
16
+ getBundleId(): Promise<string>;
17
+ getOsVersion(): Promise<string>;
18
+ getDeviceModel(): Promise<string>;
19
+ getLocale(): Promise<string>;
20
+
21
+ storageGet(key: string): Promise<string | null>;
22
+ storageSet(key: string, value: string): Promise<void>;
23
+ storageRemove(key: string): Promise<void>;
24
+
25
+ secureStorageGet(key: string): Promise<string | null>;
26
+ secureStorageSet(key: string, value: string): Promise<void>;
27
+ secureStorageRemove(key: string): Promise<void>;
28
+
29
+ /**
30
+ * Doc 03 §3.4 — signature verification over OS-provided cryptography. `keyId` is
31
+ * resolved to build-time-embedded key material natively (Info.plist/BuildConfig);
32
+ * only the keyId, not the key itself, crosses this bridge. Android implements both
33
+ * via java.security/javax.crypto; iOS implements HMAC-SHA256 via CommonCrypto and
34
+ * Ed25519 via a small CryptoKit-backed Swift wrapper (ios/PlatformBridge/Ed25519Verifier.swift).
35
+ */
36
+ verifyEd25519(
37
+ keyId: string,
38
+ messageBase64: string,
39
+ signatureBase64: string
40
+ ): Promise<boolean>;
41
+ verifyHmacSha256(
42
+ keyId: string,
43
+ messageBase64: string,
44
+ macBase64: string
45
+ ): Promise<boolean>;
46
+ }
47
+
48
+ let cachedModule: Spec | null = null;
49
+
50
+ /**
51
+ * Lazily resolved rather than the usual top-level
52
+ * `TurboModuleRegistry.getEnforcing<Spec>(...)` pattern, so importing this module (or
53
+ * anything that transitively imports it, e.g. src/di/container.ts) never throws in
54
+ * environments where the native module isn't registered — Jest's simulated native
55
+ * platform resolution, or a caller who supplies VersionManagerOptions.platformBridge
56
+ * and therefore never actually needs it. The registry lookup only happens the first
57
+ * time a method is actually called.
58
+ */
59
+ export default function getNativeVersionCheck(): Spec {
60
+ if (!cachedModule) {
61
+ cachedModule = TurboModuleRegistry.getEnforcing<Spec>('VersionCheck');
62
+ }
63
+ return cachedModule;
64
+ }
@@ -0,0 +1,347 @@
1
+ import {
2
+ OfflineException,
3
+ RequestTimeoutException,
4
+ } from '../../domain/errors/VersionManagerException';
5
+ import type { PlatformId } from '../../domain/models/PlatformId';
6
+ import type {
7
+ HttpRequest,
8
+ HttpResponse,
9
+ IAppInfoProvider,
10
+ IAppLifecycleObserver,
11
+ IBackgroundScheduler,
12
+ ICryptoProvider,
13
+ IDeviceInfoProvider,
14
+ IHttpClient,
15
+ IKeyValueStorage,
16
+ IPlatformBridge,
17
+ ISecureStorage,
18
+ Unsubscribe,
19
+ BackgroundTaskDescriptor,
20
+ } from '../../domain/ports/IPlatformBridge';
21
+
22
+ /**
23
+ * This project's tsconfig deliberately omits the "DOM" lib (it collides with React
24
+ * Native's own global fetch/console typings — Prompt 1 constraint). The small subset
25
+ * of browser globals this bridge needs is declared locally instead of pulling in the
26
+ * full lib.dom.d.ts surface, and accessed through a single typed cast of globalThis.
27
+ */
28
+ interface WebFetchResponseLike {
29
+ readonly status: number;
30
+ readonly headers: { forEach(cb: (value: string, key: string) => void): void };
31
+ text(): Promise<string>;
32
+ }
33
+ type WebFetchInit = {
34
+ method?: string;
35
+ headers?: Record<string, string>;
36
+ body?: string;
37
+ signal?: unknown;
38
+ };
39
+ type WebFetchLike = (
40
+ url: string,
41
+ init?: WebFetchInit
42
+ ) => Promise<WebFetchResponseLike>;
43
+
44
+ interface WebStorageLike {
45
+ getItem(key: string): string | null;
46
+ setItem(key: string, value: string): void;
47
+ removeItem(key: string): void;
48
+ }
49
+
50
+ interface WebNavigatorLike {
51
+ readonly language: string;
52
+ readonly userAgent: string;
53
+ }
54
+
55
+ interface WebDocumentLike {
56
+ readonly visibilityState: string;
57
+ addEventListener(type: string, cb: () => void): void;
58
+ removeEventListener(type: string, cb: () => void): void;
59
+ }
60
+
61
+ interface WebAbortControllerLike {
62
+ readonly signal: unknown;
63
+ abort(): void;
64
+ }
65
+
66
+ interface WebSubtleCryptoLike {
67
+ importKey(
68
+ format: string,
69
+ keyData: unknown,
70
+ algorithm: unknown,
71
+ extractable: boolean,
72
+ keyUsages: string[]
73
+ ): Promise<unknown>;
74
+ verify(
75
+ algorithm: unknown,
76
+ key: unknown,
77
+ signature: unknown,
78
+ data: unknown
79
+ ): Promise<boolean>;
80
+ sign(algorithm: unknown, key: unknown, data: unknown): Promise<unknown>;
81
+ }
82
+
83
+ interface WebGlobals {
84
+ fetch: WebFetchLike;
85
+ localStorage: WebStorageLike;
86
+ navigator: WebNavigatorLike;
87
+ document: WebDocumentLike;
88
+ AbortController: new () => WebAbortControllerLike;
89
+ crypto?: { subtle: WebSubtleCryptoLike };
90
+ window?: {
91
+ addEventListener(type: string, cb: () => void): void;
92
+ removeEventListener(type: string, cb: () => void): void;
93
+ };
94
+ /**
95
+ * Doc 03 §3.4's "embedded at build time" tier, for Web: since there is no OS
96
+ * keychain/BuildConfig equivalent in a browser, the Web-target convention is a
97
+ * build-time `define` (Vite/webpack) injecting this global with keyId -> base64 key
98
+ * material — analogous to how §5.1 treats "Cache-Control: immutable" as the Web
99
+ * equivalent of native mmap. Absent this global, ed25519/hmac-sha256 verification on
100
+ * Web fails closed (returns false) rather than silently trusting an unverifiable doc.
101
+ */
102
+ __VM_TRUSTED_KEYS__?: Record<string, string>;
103
+ }
104
+
105
+ const webGlobal = globalThis as unknown as WebGlobals;
106
+
107
+ function base64ToBytes(base64: string): Uint8Array {
108
+ if (typeof atob === 'function') {
109
+ const binary = atob(base64);
110
+ const bytes = new Uint8Array(binary.length);
111
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
112
+ return bytes;
113
+ }
114
+ throw new Error('No base64 decoder available in this environment');
115
+ }
116
+
117
+ /**
118
+ * Doc 03 §3.4 crypto port, Web target — uses browser-native SubtleCrypto exclusively
119
+ * (Prompt 1 §9.3: no third-party crypto library). HMAC-SHA256 has broad browser
120
+ * support; Ed25519 support via WebCrypto is newer (Chrome 129+/Safari 17+) and this
121
+ * method throws a clear, caught error on unsupported browsers rather than silently
122
+ * failing — SignatureVerifier surfaces that as a normal SignatureVerificationException.
123
+ */
124
+ class WebCryptoProvider implements ICryptoProvider {
125
+ async verifyEd25519(
126
+ keyId: string,
127
+ messageBase64: string,
128
+ signatureBase64: string
129
+ ): Promise<boolean> {
130
+ const subtle = webGlobal.crypto?.subtle;
131
+ const keyMaterial = webGlobal.__VM_TRUSTED_KEYS__?.[keyId];
132
+ if (!subtle || !keyMaterial) return false;
133
+ const key = await subtle.importKey(
134
+ 'raw',
135
+ base64ToBytes(keyMaterial),
136
+ { name: 'Ed25519' },
137
+ false,
138
+ ['verify']
139
+ );
140
+ return subtle.verify(
141
+ { name: 'Ed25519' },
142
+ key,
143
+ base64ToBytes(signatureBase64),
144
+ base64ToBytes(messageBase64)
145
+ );
146
+ }
147
+
148
+ async verifyHmacSha256(
149
+ keyId: string,
150
+ messageBase64: string,
151
+ macBase64: string
152
+ ): Promise<boolean> {
153
+ const subtle = webGlobal.crypto?.subtle;
154
+ const keyMaterial = webGlobal.__VM_TRUSTED_KEYS__?.[keyId];
155
+ if (!subtle || !keyMaterial) return false;
156
+ const key = await subtle.importKey(
157
+ 'raw',
158
+ base64ToBytes(keyMaterial),
159
+ { name: 'HMAC', hash: 'SHA-256' },
160
+ false,
161
+ ['sign']
162
+ );
163
+ const computed = await subtle.sign(
164
+ { name: 'HMAC' },
165
+ key,
166
+ base64ToBytes(messageBase64)
167
+ );
168
+ const computedBytes = new Uint8Array(computed as ArrayBuffer);
169
+ const expectedBytes = base64ToBytes(macBase64);
170
+ if (computedBytes.length !== expectedBytes.length) return false;
171
+ let diff = 0;
172
+ for (let i = 0; i < computedBytes.length; i++) {
173
+ diff |= (computedBytes[i] as number) ^ (expectedBytes[i] as number);
174
+ }
175
+ return diff === 0;
176
+ }
177
+ }
178
+
179
+ class WebHttpClient implements IHttpClient {
180
+ async request(req: HttpRequest): Promise<HttpResponse> {
181
+ const controller = new webGlobal.AbortController();
182
+ const timeout = setTimeout(() => controller.abort(), req.timeoutMs);
183
+ try {
184
+ const response = await webGlobal.fetch(req.url, {
185
+ method: req.method,
186
+ headers: req.headers as Record<string, string> | undefined,
187
+ body: req.body,
188
+ signal: controller.signal,
189
+ });
190
+ const headers: Record<string, string> = {};
191
+ response.headers.forEach((value, key) => {
192
+ headers[key] = value;
193
+ });
194
+ const body = await response.text();
195
+ return { status: response.status, headers, body };
196
+ } catch (error) {
197
+ if (error instanceof Error && error.name === 'AbortError') {
198
+ throw new RequestTimeoutException({
199
+ message: `Request to "${req.url}" timed out after ${req.timeoutMs}ms`,
200
+ cause: error,
201
+ });
202
+ }
203
+ throw new OfflineException({
204
+ message: `Request to "${req.url}" failed`,
205
+ cause: error,
206
+ });
207
+ } finally {
208
+ clearTimeout(timeout);
209
+ }
210
+ }
211
+ }
212
+
213
+ class WebKeyValueStorage implements IKeyValueStorage {
214
+ async get(key: string): Promise<string | null> {
215
+ return webGlobal.localStorage.getItem(key);
216
+ }
217
+ async set(key: string, value: string): Promise<void> {
218
+ webGlobal.localStorage.setItem(key, value);
219
+ }
220
+ async remove(key: string): Promise<void> {
221
+ webGlobal.localStorage.removeItem(key);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Backed by localStorage under a distinct key prefix. This is functionally complete
227
+ * (satisfies ISecureStorage) but is NOT encrypted at rest — true SubtleCrypto-based
228
+ * encryption-at-rest for the Web target (Prompt 1 §4.1's documented design) is a
229
+ * follow-up hardening item, not implemented in this pass.
230
+ */
231
+ class WebSecureStorage implements ISecureStorage {
232
+ private static readonly PREFIX = 'vm_secure_';
233
+ async getItem(key: string): Promise<string | null> {
234
+ return webGlobal.localStorage.getItem(WebSecureStorage.PREFIX + key);
235
+ }
236
+ async setItem(key: string, value: string): Promise<void> {
237
+ webGlobal.localStorage.setItem(WebSecureStorage.PREFIX + key, value);
238
+ }
239
+ async removeItem(key: string): Promise<void> {
240
+ webGlobal.localStorage.removeItem(WebSecureStorage.PREFIX + key);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * The Web target has no OS bundle to read a version/build number from. These should be
246
+ * injected at build time (e.g. a Vite `define`); absent that, callers are expected to
247
+ * supply `VersionManagerOptions.appVersion` explicitly, which Core prefers over this
248
+ * bridge (see VersionManagerCore.resolveCurrentVersion()).
249
+ */
250
+ class WebAppInfoProvider implements IAppInfoProvider {
251
+ async getCurrentVersion(): Promise<string> {
252
+ return '0.0.0';
253
+ }
254
+ async getBuildNumber(): Promise<string> {
255
+ return '0';
256
+ }
257
+ async getBundleId(): Promise<string> {
258
+ return 'web';
259
+ }
260
+ }
261
+
262
+ class WebDeviceInfoProvider implements IDeviceInfoProvider {
263
+ async getOsVersion(): Promise<string> {
264
+ return webGlobal.navigator.userAgent;
265
+ }
266
+ async getDeviceModel(): Promise<string> {
267
+ return 'web';
268
+ }
269
+ async getLocale(): Promise<string> {
270
+ return webGlobal.navigator.language;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * True Service Worker `periodicSync` requires a registered service worker the host app
276
+ * controls (feature-detected, narrow browser support) and is out of this pass's scope —
277
+ * see the class-level fallback doc 01 §4.1 explicitly names: "visibility-change
278
+ * fallback." This implements exactly that fallback for real: a `setInterval` gated so
279
+ * the callback only actually fires while the tab is visible (`document.visibilityState
280
+ * === 'visible'`), which is the best a page can do without its own registered SW.
281
+ * `minIntervalMs` is honored as a floor, not a guarantee — like all browser timers, it
282
+ * is throttled/paused while backgrounded, which is the whole reason for the visibility
283
+ * gate (avoid firing a burst of missed intervals the instant the tab regains focus).
284
+ */
285
+ class WebBackgroundScheduler implements IBackgroundScheduler {
286
+ private readonly timers = new Map<string, ReturnType<typeof setInterval>>();
287
+ private readonly callbacks = new Map<string, () => void>();
288
+
289
+ async schedule(task: BackgroundTaskDescriptor): Promise<void> {
290
+ this.cancelInternal(task.taskId);
291
+ const timer = setInterval(() => {
292
+ if (webGlobal.document.visibilityState !== 'visible') return;
293
+ this.callbacks.get(task.taskId)?.();
294
+ }, task.minIntervalMs);
295
+ this.timers.set(task.taskId, timer);
296
+ }
297
+
298
+ async cancel(taskId: string): Promise<void> {
299
+ this.cancelInternal(taskId);
300
+ }
301
+
302
+ /** Registers the function a scheduled task actually invokes when it fires (wired by src/di/container.ts, not part of IBackgroundScheduler's public port shape). */
303
+ setCallback(taskId: string, cb: () => void): void {
304
+ this.callbacks.set(taskId, cb);
305
+ }
306
+
307
+ private cancelInternal(taskId: string): void {
308
+ const existing = this.timers.get(taskId);
309
+ if (existing !== undefined) {
310
+ clearInterval(existing);
311
+ this.timers.delete(taskId);
312
+ }
313
+ }
314
+ }
315
+
316
+ class WebAppLifecycleObserver implements IAppLifecycleObserver {
317
+ onForeground(cb: () => void): Unsubscribe {
318
+ const handler = (): void => {
319
+ if (webGlobal.document.visibilityState === 'visible') cb();
320
+ };
321
+ webGlobal.document.addEventListener('visibilitychange', handler);
322
+ return () =>
323
+ webGlobal.document.removeEventListener('visibilitychange', handler);
324
+ }
325
+
326
+ onBackground(cb: () => void): Unsubscribe {
327
+ const handler = (): void => {
328
+ if (webGlobal.document.visibilityState === 'hidden') cb();
329
+ };
330
+ webGlobal.document.addEventListener('visibilitychange', handler);
331
+ return () =>
332
+ webGlobal.document.removeEventListener('visibilitychange', handler);
333
+ }
334
+ }
335
+
336
+ export class WebPlatformBridge implements IPlatformBridge {
337
+ readonly platform: PlatformId = 'web';
338
+ readonly http: IHttpClient = new WebHttpClient();
339
+ readonly storage: IKeyValueStorage = new WebKeyValueStorage();
340
+ readonly secureStorage: ISecureStorage = new WebSecureStorage();
341
+ readonly appInfo: IAppInfoProvider = new WebAppInfoProvider();
342
+ readonly deviceInfo: IDeviceInfoProvider = new WebDeviceInfoProvider();
343
+ readonly scheduler: IBackgroundScheduler = new WebBackgroundScheduler();
344
+ readonly clock = { now: (): number => Date.now() };
345
+ readonly lifecycle: IAppLifecycleObserver = new WebAppLifecycleObserver();
346
+ readonly crypto: ICryptoProvider = new WebCryptoProvider();
347
+ }
@@ -0,0 +1,64 @@
1
+ import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
2
+ import type { ReactElement } from 'react';
3
+ import type { UpdateInfo } from '../../domain/models/UpdateInfo';
4
+
5
+ export interface ForceUpdateScreenProps {
6
+ updateInfo: UpdateInfo;
7
+ onUpdatePress: () => void;
8
+ title?: string;
9
+ message?: string;
10
+ updateButtonLabel?: string;
11
+ }
12
+
13
+ /** Full-screen, non-dismissible lockout (Prompt 1 §5.3; UI copy detail is Prompt 19). */
14
+ export function ForceUpdateScreen({
15
+ updateInfo,
16
+ onUpdatePress,
17
+ title = 'Update Required',
18
+ message = `Version ${updateInfo.latestVersion} is available. You must update to continue.`,
19
+ updateButtonLabel = 'Update Now',
20
+ }: ForceUpdateScreenProps): ReactElement {
21
+ return (
22
+ <Modal
23
+ visible
24
+ animationType="fade"
25
+ transparent={false}
26
+ onRequestClose={() => undefined}
27
+ >
28
+ <View style={styles.container}>
29
+ <Text style={styles.title}>{title}</Text>
30
+ <Text style={styles.message}>{message}</Text>
31
+ <Pressable
32
+ style={styles.button}
33
+ onPress={onUpdatePress}
34
+ accessibilityRole="button"
35
+ >
36
+ <Text style={styles.buttonLabel}>{updateButtonLabel}</Text>
37
+ </Pressable>
38
+ </View>
39
+ </Modal>
40
+ );
41
+ }
42
+
43
+ const styles = StyleSheet.create({
44
+ container: {
45
+ flex: 1,
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ padding: 24,
49
+ },
50
+ title: {
51
+ fontSize: 20,
52
+ fontWeight: '700',
53
+ marginBottom: 12,
54
+ textAlign: 'center',
55
+ },
56
+ message: { fontSize: 15, textAlign: 'center', marginBottom: 24 },
57
+ button: {
58
+ paddingVertical: 12,
59
+ paddingHorizontal: 24,
60
+ borderRadius: 8,
61
+ backgroundColor: '#2563eb',
62
+ },
63
+ buttonLabel: { color: '#ffffff', fontSize: 16, fontWeight: '600' },
64
+ });