@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
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rajesh Shinde
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ <div align="center">
2
+
3
+ # 📦 @rs-native-kit/version-check
4
+
5
+ **Drop-in version-check & force/soft-update orchestration for React Native**
6
+
7
+ Detect when your users are behind, decide what to do about it, and ship the UI to tell them — all from one config call.
8
+
9
+ [![CI](https://github.com/rajssinde/rs-native-kit-version-check/actions/workflows/ci.yml/badge.svg)](https://github.com/rajssinde/rs-native-kit-version-check/actions/workflows/ci.yml)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white)](#)
12
+ [![New Architecture](https://img.shields.io/badge/React%20Native-New%20Architecture-61DAFB?logo=react&logoColor=white)](#requirements)
13
+ [![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-lightgrey)](#requirements)
14
+ [![Zero deps](https://img.shields.io/badge/runtime%20deps-zero-success)](#)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## Why this exists
21
+
22
+ Shipping an update means nothing if half your users are stuck on a version from three releases ago. **`version-check`** answers three questions for you on every app launch:
23
+
24
+ 1. **Is the user out of date?** — compares the running app version against what your store (or your own API) says is current.
25
+ 2. **How out of date, and does it matter?** — runs that comparison through a policy engine (hard floor, reminder cadence, staged rollout %) to decide what actually needs to happen.
26
+ 3. **What should the UI do about it?** — hands back a typed `ActionPlan` (`FORCE_UPDATE`, `SOFT_UPDATE`, `OPTIONAL_REMINDER`, `NO_ACTION`) that you render with the included components, your own UI, or ignore entirely if you just want the raw decision.
27
+
28
+ ## At a glance
29
+
30
+ | | |
31
+ |---|---|
32
+ | 📦 Runtime dependencies | **0** — nothing pulled in at install time |
33
+ | 🏬 Store providers | Apple App Store, Google Play, Custom API (Huawei/Amazon/Firebase planned) |
34
+ | ⏱️ Default check cache | 6h TTL, persisted across app restarts |
35
+ | 🔁 Default reminder cadence | Every 3 days after "Later" |
36
+ | 🎯 Staged rollout | 0–100% device-bucketed rollout out of the box |
37
+ | 🧩 Setup | One `configure()` call, or `<VersionManagerProvider>` + a hook |
38
+ | 🖼️ Prebuilt UI | 3 ready-made components, or go fully headless |
39
+ | 🧪 Test coverage | Domain/config logic covered by unit tests, no device required |
40
+ | 🌐 Platforms | iOS, Android, Web (`react-native-web`) |
41
+
42
+ ## Features
43
+
44
+ - 🏬 **Multi-store version lookup** — App Store, Google Play, or your own Custom API out of the box.
45
+ - 🧠 **Policy engine** — SemVer-aware force-update floor, reminder throttling, and percentage-based staged rollout.
46
+ - 🔐 **Signed remote config** — optionally hot-load update policy from a URL or bundled JSON, verified with Ed25519/HMAC via native OS crypto (never hand-rolled in JS).
47
+ - ⚛️ **React hooks + prebuilt UI** — `useVersionManager`, `<ForceUpdateScreen />`, `<SoftUpdateDialog />`, `<OptionalUpdateBanner />` — or go headless and build your own.
48
+ - 📡 **Event-driven** — subscribe to `stateChanged`, `updateDetected`, `userAction`, and `error` instead of polling.
49
+ - 🌐 **Web-ready** — the same JS API runs under `react-native-web`; no separate SDK.
50
+ - 🪶 **Zero runtime dependencies** — the entire JS surface is hand-written, nothing pulled in at install time.
51
+ - 🧪 **Fully unit-tested domain core** — SemVer engine, policy engine, lifecycle state machine, and config pipeline all run against mocks, no native layer required.
52
+
53
+ ## Requirements
54
+
55
+ This library ships as a **TurboModule** with no legacy-bridge fallback, so the New Architecture must be enabled.
56
+
57
+ | Requirement | Minimum | Tested against |
58
+ |---|---|---|
59
+ | React Native | **0.74+**, New Architecture (Fabric/TurboModules) enabled | 0.85.0 |
60
+ | React | 18+ | 19.2.3 |
61
+ | iOS | **15.1+** | Xcode 16.1+ |
62
+ | Android | **API 24** (Android 7.0)+ | compileSdk 36, Kotlin 2.0.21, JDK 17 |
63
+ | Node.js | 20+ | 24.13.0 (see `.nvmrc`) |
64
+ | TypeScript | 5+ (optional, but the package ships full `.d.ts`) | 6.0.3 |
65
+
66
+ | Platform | Status |
67
+ |---|---|
68
+ | iOS | ✅ Native TurboModule (`ios/VersionCheck.mm`, Ed25519 verification) |
69
+ | Android | ✅ Native TurboModule (Kotlin, WorkManager-backed background scheduling) |
70
+ | Web (`react-native-web`) | ✅ Same JS API, browser-native `fetch`/`localStorage` bridge |
71
+
72
+ ## Installation
73
+
74
+ ```sh
75
+ npm install @rs-native-kit/version-check
76
+ # or
77
+ yarn add @rs-native-kit/version-check
78
+ ```
79
+
80
+ **iOS** — install pods after adding the package:
81
+
82
+ ```sh
83
+ cd ios && pod install
84
+ ```
85
+
86
+ **Android** — no extra steps; the TurboModule autolinks via `react-native.config.js`.
87
+
88
+ ## Quick start
89
+
90
+ ### Core API (framework-agnostic, zero React code pulled in)
91
+
92
+ ```ts
93
+ import { VersionManager } from '@rs-native-kit/version-check';
94
+
95
+ const manager = VersionManager.configure({
96
+ stores: {
97
+ ios: { appStoreId: '123456789' }, // optional — omit to fall back to this app's bundle id
98
+ android: { packageName: 'com.example.app' }, // optional — omit to use this app's own package name
99
+ },
100
+ });
101
+
102
+ await manager.ready(); // waits for config resolution to settle
103
+ const plan = await manager.checkForUpdates();
104
+
105
+ switch (plan.type) {
106
+ case 'FORCE_UPDATE':
107
+ // block the app, show ForceUpdateScreen
108
+ break;
109
+ case 'SOFT_UPDATE':
110
+ // dismissible prompt, show SoftUpdateDialog
111
+ break;
112
+ case 'OPTIONAL_REMINDER':
113
+ // low-intrusion nudge, show OptionalUpdateBanner
114
+ break;
115
+ case 'NO_ACTION':
116
+ // already current
117
+ break;
118
+ }
119
+ ```
120
+
121
+ ### React hooks + prebuilt UI (separate, tree-shakeable entry point)
122
+
123
+ ```tsx
124
+ import {
125
+ useVersionManager,
126
+ ForceUpdateScreen,
127
+ SoftUpdateDialog,
128
+ OptionalUpdateBanner,
129
+ } from '@rs-native-kit/version-check/ui';
130
+ import { Linking } from 'react-native';
131
+
132
+ function App() {
133
+ const { state, isUpdateAvailable, updateInfo, checkForUpdates } =
134
+ useVersionManager({
135
+ stores: {
136
+ ios: { appStoreId: '123456789' },
137
+ android: { packageName: 'com.example.app' },
138
+ },
139
+ policy: {
140
+ forceUpdateBelow: '2.0.0',
141
+ reminderIntervalMs: 3 * 24 * 60 * 60 * 1000, // 3 days
142
+ },
143
+ });
144
+
145
+ if (updateInfo?.isForceUpdate) {
146
+ return (
147
+ <ForceUpdateScreen
148
+ updateInfo={updateInfo}
149
+ onUpdatePress={() => Linking.openURL(updateInfo.storeUrl)}
150
+ />
151
+ );
152
+ }
153
+
154
+ return (
155
+ <>
156
+ {/* your app */}
157
+ {isUpdateAvailable && updateInfo && (
158
+ <SoftUpdateDialog
159
+ updateInfo={updateInfo}
160
+ onUpdatePress={() => Linking.openURL(updateInfo.storeUrl)}
161
+ onLaterPress={() => {}}
162
+ />
163
+ )}
164
+ </>
165
+ );
166
+ }
167
+ ```
168
+
169
+ ### Events
170
+
171
+ ```ts
172
+ manager.onUpdateDetected(({ updateInfo, actionPlan }) => { /* ... */ });
173
+ manager.onUserAction(({ action, updateInfo }) => { /* 'update_clicked' | 'later_clicked' | ... */ });
174
+ manager.onStateChanged(({ from, to }) => { /* lifecycle transitions */ });
175
+ manager.onError(({ error, phase }) => { /* 'config' | 'check' | 'policy' | 'presentation' */ });
176
+ ```
177
+
178
+ ## Configuration reference
179
+
180
+ Every field below is optional except `stores`, which needs at least one provider configured.
181
+
182
+ `stores.ios.appStoreId` and `stores.android.packageName` are themselves optional — pass them when you have them (App Store lookups by numeric id are the most reliable), and omit them to fall back automatically to the running app's own bundle id (`IAppInfoProvider.getBundleId()`, resolved at check time) — which *is* the package name on Android, and a supported alternate lookup key (`bundleId=`) against the iTunes Lookup API on iOS:
183
+
184
+ ```ts
185
+ VersionManager.configure({
186
+ stores: {
187
+ ios: { region: 'us' }, // appStoreId omitted -> looked up by this app's bundle id
188
+ android: {}, // packageName omitted -> uses this app's own package name
189
+ custom: { url: 'https://api.example.com/version.json', headers: {} },
190
+ },
191
+ policy: {
192
+ forceUpdateBelow: '2.0.0', // SemVer floor — anything older is FORCE_UPDATE
193
+ reminderIntervalMs: 259_200_000, // how often to re-nag after "Later" (default 3 days)
194
+ rolloutPercentage: 100, // staged rollout, 0-100
195
+ },
196
+ cache: {
197
+ ttlMs: 21_600_000, // how long a store lookup is cached (default 6h)
198
+ storage: 'persistent', // 'memory' | 'persistent'
199
+ bustOnManualCheck: true,
200
+ },
201
+ fallback: {
202
+ onNetworkError: 'useCache', // 'useCache' | 'useDefaultConfig' | 'noAction'
203
+ requestTimeoutMs: 8_000,
204
+ retry: { maxAttempts: 3, backoff: 'exponential-jitter', baseDelayMs: 500 },
205
+ },
206
+ remoteConfigUrl: 'https://api.example.com/vm-config.json', // optional hot-reloadable config
207
+ security: { signatureAlgorithm: 'ed25519', trustedKeyIds: ['prod-key-1'] },
208
+ logging: { level: 'warn' },
209
+ });
210
+ ```
211
+
212
+ ### Store providers
213
+
214
+ | Provider | Status |
215
+ |---|---|
216
+ | Apple App Store | ✅ Implemented |
217
+ | Google Play | ✅ Implemented |
218
+ | Custom API | ✅ Implemented |
219
+ | Huawei AppGallery | 🚧 Planned — throws `UnsupportedStoreException` today |
220
+ | Amazon Appstore | 🚧 Planned — throws `UnsupportedStoreException` today |
221
+ | Firebase Remote Config | 🚧 Planned — throws `UnsupportedStoreException` today |
222
+
223
+ ### Remote / signed configuration
224
+
225
+ On top of the options you hardcode at `configure()`, you can layer a **local bundled JSON** and/or a **remote URL**, both wrapped in a signed envelope (`schemaVersion`, `payload`, `signature`) and verified against `security.trustedKeyIds` before any field is trusted. Precedence is `env overrides > remote > local > configure() defaults`, merged field-by-field, with automatic fallback to the last known-good config if a remote fetch fails.
226
+
227
+ ## Lifecycle
228
+
229
+ ```
230
+ UNINITIALIZED → CONFIG_LOADING → IDLE ⇄ VERSION_CHECKING → DECIDING
231
+
232
+ ┌─────────────────┬───────────────────┼───────────────────┐
233
+ ▼ ▼ ▼ ▼
234
+ FORCE_UPDATE_DISPLAYED SOFT_UPDATE_DISPLAYED REJECTED_WAITING_REMINDER IDLE
235
+ ```
236
+
237
+ Read the current state any time with `manager.getCurrentState()`, or subscribe via `onStateChanged`.
238
+
239
+ ## Example app
240
+
241
+ ```sh
242
+ yarn
243
+ yarn example ios # or `yarn example android`, `yarn example web`
244
+ ```
245
+
246
+ ## Contributing
247
+
248
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
249
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
250
+ - [Code of conduct](CODE_OF_CONDUCT.md)
251
+
252
+ ## License
253
+
254
+ MIT © [Rajesh Shinde](https://github.com/rajssinde)
255
+
256
+ ---
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "VersionCheck"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/rajssinde/rs-native-kit-version-check.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ install_modules_dependencies(s)
20
+ end
@@ -0,0 +1,56 @@
1
+ buildscript {
2
+ ext.VersionCheck = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36
6
+ ]
7
+
8
+ ext.getExtOrDefault = { prop ->
9
+ if (rootProject.ext.has(prop)) {
10
+ return rootProject.ext.get(prop)
11
+ }
12
+
13
+ return VersionCheck[prop]
14
+ }
15
+
16
+ repositories {
17
+ google()
18
+ mavenCentral()
19
+ }
20
+
21
+ dependencies {
22
+ classpath "com.android.tools.build:gradle:8.7.2"
23
+ // noinspection DifferentKotlinGradleVersion
24
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
25
+ }
26
+ }
27
+
28
+
29
+ apply plugin: "com.android.library"
30
+ apply plugin: "kotlin-android"
31
+
32
+ apply plugin: "com.facebook.react"
33
+
34
+ android {
35
+ namespace "com.rsnativekit.versioncheck"
36
+
37
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
38
+
39
+ defaultConfig {
40
+ minSdkVersion getExtOrDefault("minSdkVersion")
41
+ }
42
+
43
+ compileOptions {
44
+ sourceCompatibility JavaVersion.VERSION_17
45
+ targetCompatibility JavaVersion.VERSION_17
46
+ }
47
+ }
48
+
49
+ dependencies {
50
+ implementation "com.facebook.react:react-android"
51
+ // Backs the scheduleBackgroundCheck/cancelBackgroundCheck TurboModule methods
52
+ // (doc 01 §4.1 "scheduler" row, Android WorkManager) — an internal implementation
53
+ // detail of this library's own native module; consuming apps do not need to declare
54
+ // this themselves (it is bundled at runtime via normal AAR dependency resolution).
55
+ implementation "androidx.work:work-runtime-ktx:2.9.1"
56
+ }
@@ -0,0 +1,9 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- Doc 01 §4.1/§10 background-scheduler headless JS bridge (Prompt 26). Merged
3
+ into the host app's manifest by Gradle's normal library-manifest merge. -->
4
+ <application>
5
+ <service
6
+ android:name=".VersionCheckHeadlessTaskService"
7
+ android:exported="false" />
8
+ </application>
9
+ </manifest>
@@ -0,0 +1,57 @@
1
+ package com.rsnativekit.versioncheck
2
+
3
+ import android.security.keystore.KeyGenParameterSpec
4
+ import android.security.keystore.KeyProperties
5
+ import android.util.Base64
6
+ import java.security.KeyStore
7
+ import javax.crypto.Cipher
8
+ import javax.crypto.KeyGenerator
9
+ import javax.crypto.SecretKey
10
+ import javax.crypto.spec.GCMParameterSpec
11
+
12
+ /**
13
+ * AES/GCM encryption backed by the Android Keystore — java.security/javax.crypto only,
14
+ * no external Gradle dependency (docs/architecture/03-configuration-system.md §3.4).
15
+ * The key is generated inside the keystore and never leaves it; only ciphertext + IV
16
+ * are persisted in SharedPreferences by the caller.
17
+ */
18
+ internal object SecureStorageCipher {
19
+ private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
20
+ private const val KEY_ALIAS = "rs_version_check_secure_storage_key"
21
+ private const val TRANSFORMATION = "AES/GCM/NoPadding"
22
+ private const val GCM_TAG_LENGTH_BITS = 128
23
+
24
+ private fun getOrCreateKey(): SecretKey {
25
+ val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) }
26
+ (keyStore.getKey(KEY_ALIAS, null) as? SecretKey)?.let { return it }
27
+
28
+ val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER)
29
+ val spec =
30
+ KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
31
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
32
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
33
+ .build()
34
+ keyGenerator.init(spec)
35
+ return keyGenerator.generateKey()
36
+ }
37
+
38
+ fun encrypt(plainText: String): String {
39
+ val cipher = Cipher.getInstance(TRANSFORMATION)
40
+ cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey())
41
+ val cipherBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
42
+ val iv = Base64.encodeToString(cipher.iv, Base64.NO_WRAP)
43
+ val payload = Base64.encodeToString(cipherBytes, Base64.NO_WRAP)
44
+ return "$iv:$payload"
45
+ }
46
+
47
+ fun decrypt(stored: String): String {
48
+ val parts = stored.split(":", limit = 2)
49
+ require(parts.size == 2) { "Malformed secure storage entry" }
50
+ val iv = Base64.decode(parts[0], Base64.NO_WRAP)
51
+ val cipherBytes = Base64.decode(parts[1], Base64.NO_WRAP)
52
+
53
+ val cipher = Cipher.getInstance(TRANSFORMATION)
54
+ cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv))
55
+ return String(cipher.doFinal(cipherBytes), Charsets.UTF_8)
56
+ }
57
+ }
@@ -0,0 +1,31 @@
1
+ package com.rsnativekit.versioncheck
2
+
3
+ import android.content.Intent
4
+ import com.facebook.react.HeadlessJsTaskService
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.jstasks.HeadlessJsTaskConfig
7
+
8
+ /**
9
+ * Doc 01 §4.1/§10 "Headless JS task + native WorkManager/BGTaskScheduler bridge" — the
10
+ * Android half. VersionCheckWorker (WorkManager) starts this service when a scheduled
11
+ * check fires; this service starts the "VersionCheckBackgroundTask" headless JS task,
12
+ * which the host app must register once via the `registerVersionCheckHeadlessTask()`
13
+ * helper exported from '@rs-native-kit/version-check' (same integration shape as
14
+ * react-native-background-fetch and similar libraries require).
15
+ */
16
+ class VersionCheckHeadlessTaskService : HeadlessJsTaskService() {
17
+ override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
18
+ val extras = intent.extras
19
+ return HeadlessJsTaskConfig(
20
+ TASK_NAME,
21
+ if (extras != null) Arguments.fromBundle(extras) else Arguments.createMap(),
22
+ TASK_TIMEOUT_MS,
23
+ true
24
+ )
25
+ }
26
+
27
+ companion object {
28
+ const val TASK_NAME = "VersionCheckBackgroundTask"
29
+ const val TASK_TIMEOUT_MS = 30_000L
30
+ }
31
+ }
@@ -0,0 +1,215 @@
1
+ package com.rsnativekit.versioncheck
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import android.os.Build
6
+ import android.util.Base64
7
+ import androidx.work.ExistingPeriodicWorkPolicy
8
+ import androidx.work.PeriodicWorkRequestBuilder
9
+ import androidx.work.WorkManager
10
+ import com.facebook.react.bridge.Promise
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import java.security.Signature
13
+ import java.util.Locale
14
+ import java.util.concurrent.TimeUnit
15
+ import javax.crypto.Mac
16
+ import javax.crypto.spec.SecretKeySpec
17
+
18
+ class VersionCheckModule(reactContext: ReactApplicationContext) :
19
+ NativeVersionCheckSpec(reactContext) {
20
+
21
+ private val prefs: SharedPreferences by lazy {
22
+ reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
23
+ }
24
+ private val securePrefs: SharedPreferences by lazy {
25
+ reactApplicationContext.getSharedPreferences(SECURE_PREFS_NAME, Context.MODE_PRIVATE)
26
+ }
27
+
28
+ // --- App info ---
29
+
30
+ override fun getCurrentAppVersion(promise: Promise) {
31
+ try {
32
+ val info = reactApplicationContext.packageManager.getPackageInfo(reactApplicationContext.packageName, 0)
33
+ promise.resolve(info.versionName ?: "")
34
+ } catch (error: Exception) {
35
+ promise.reject("get_current_app_version_failed", error)
36
+ }
37
+ }
38
+
39
+ override fun getBuildNumber(promise: Promise) {
40
+ try {
41
+ val info = reactApplicationContext.packageManager.getPackageInfo(reactApplicationContext.packageName, 0)
42
+ val code = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) info.longVersionCode else info.versionCode.toLong()
43
+ promise.resolve(code.toString())
44
+ } catch (error: Exception) {
45
+ promise.reject("get_build_number_failed", error)
46
+ }
47
+ }
48
+
49
+ override fun getBundleId(promise: Promise) {
50
+ promise.resolve(reactApplicationContext.packageName)
51
+ }
52
+
53
+ // --- Device info ---
54
+
55
+ override fun getOsVersion(promise: Promise) {
56
+ promise.resolve(Build.VERSION.RELEASE ?: Build.VERSION.SDK_INT.toString())
57
+ }
58
+
59
+ override fun getDeviceModel(promise: Promise) {
60
+ promise.resolve("${Build.MANUFACTURER} ${Build.MODEL}")
61
+ }
62
+
63
+ override fun getLocale(promise: Promise) {
64
+ promise.resolve(Locale.getDefault().toLanguageTag())
65
+ }
66
+
67
+ // --- Key-value storage (SharedPreferences) ---
68
+
69
+ override fun storageGet(key: String, promise: Promise) {
70
+ promise.resolve(prefs.getString(key, null))
71
+ }
72
+
73
+ override fun storageSet(key: String, value: String, promise: Promise) {
74
+ prefs.edit().putString(key, value).apply()
75
+ promise.resolve(null)
76
+ }
77
+
78
+ override fun storageRemove(key: String, promise: Promise) {
79
+ prefs.edit().remove(key).apply()
80
+ promise.resolve(null)
81
+ }
82
+
83
+ // --- Secure storage (Keystore-backed AES/GCM over SharedPreferences) ---
84
+
85
+ override fun secureStorageGet(key: String, promise: Promise) {
86
+ try {
87
+ val stored = securePrefs.getString(key, null)
88
+ if (stored == null) {
89
+ promise.resolve(null)
90
+ } else {
91
+ promise.resolve(SecureStorageCipher.decrypt(stored))
92
+ }
93
+ } catch (error: Exception) {
94
+ promise.reject("secure_storage_get_failed", error)
95
+ }
96
+ }
97
+
98
+ override fun secureStorageSet(key: String, value: String, promise: Promise) {
99
+ try {
100
+ securePrefs.edit().putString(key, SecureStorageCipher.encrypt(value)).apply()
101
+ promise.resolve(null)
102
+ } catch (error: Exception) {
103
+ promise.reject("secure_storage_set_failed", error)
104
+ }
105
+ }
106
+
107
+ override fun secureStorageRemove(key: String, promise: Promise) {
108
+ securePrefs.edit().remove(key).apply()
109
+ promise.resolve(null)
110
+ }
111
+
112
+ // --- Signature verification (doc 03 §3.4) ---
113
+ //
114
+ // Trusted key material is embedded at build time via a Gradle-injected BuildConfig
115
+ // field (TRUSTED_SIGNING_KEYS_JSON, a JSON object mapping keyId -> base64 key
116
+ // material) rather than shipped from JS, per §3.4's "embedded at build time, not
117
+ // hardcoded in the JS bundle" requirement. Host apps supply this via
118
+ // `android.defaultConfig.buildConfigField "String", "TRUSTED_SIGNING_KEYS_JSON", "..."`
119
+ // in their own build.gradle; if absent, verification fails closed (returns false)
120
+ // rather than silently trusting an unverifiable document.
121
+
122
+ private fun resolveKeyMaterial(keyId: String): ByteArray? {
123
+ return try {
124
+ val field = Class.forName("${reactApplicationContext.packageName}.BuildConfig")
125
+ .getField("TRUSTED_SIGNING_KEYS_JSON")
126
+ val json = org.json.JSONObject(field.get(null) as String)
127
+ if (!json.has(keyId)) null else Base64.decode(json.getString(keyId), Base64.NO_WRAP)
128
+ } catch (error: Exception) {
129
+ null
130
+ }
131
+ }
132
+
133
+ override fun verifyEd25519(keyId: String, messageBase64: String, signatureBase64: String, promise: Promise) {
134
+ try {
135
+ val keyMaterial = resolveKeyMaterial(keyId)
136
+ if (keyMaterial == null) {
137
+ promise.resolve(false)
138
+ return
139
+ }
140
+ // Ed25519 via java.security requires API 33+ (Conscrypt) on stock Android; on
141
+ // older API levels this throws NoSuchAlgorithmException and verification fails
142
+ // closed rather than silently succeeding — documented platform-version gap.
143
+ val keySpec = java.security.spec.X509EncodedKeySpec(keyMaterial)
144
+ val keyFactory = java.security.KeyFactory.getInstance("Ed25519")
145
+ val publicKey = keyFactory.generatePublic(keySpec)
146
+ val signature = Signature.getInstance("Ed25519")
147
+ signature.initVerify(publicKey)
148
+ signature.update(Base64.decode(messageBase64, Base64.NO_WRAP))
149
+ promise.resolve(signature.verify(Base64.decode(signatureBase64, Base64.NO_WRAP)))
150
+ } catch (error: Exception) {
151
+ promise.reject("verify_ed25519_failed", error)
152
+ }
153
+ }
154
+
155
+ override fun verifyHmacSha256(keyId: String, messageBase64: String, macBase64: String, promise: Promise) {
156
+ try {
157
+ val keyMaterial = resolveKeyMaterial(keyId)
158
+ if (keyMaterial == null) {
159
+ promise.resolve(false)
160
+ return
161
+ }
162
+ val mac = Mac.getInstance("HmacSHA256")
163
+ mac.init(SecretKeySpec(keyMaterial, "HmacSHA256"))
164
+ val computed = mac.doFinal(Base64.decode(messageBase64, Base64.NO_WRAP))
165
+ val expected = Base64.decode(macBase64, Base64.NO_WRAP)
166
+ promise.resolve(constantTimeEquals(computed, expected))
167
+ } catch (error: Exception) {
168
+ promise.reject("verify_hmac_sha256_failed", error)
169
+ }
170
+ }
171
+
172
+ private fun constantTimeEquals(a: ByteArray, b: ByteArray): Boolean {
173
+ if (a.size != b.size) return false
174
+ var diff = 0
175
+ for (i in a.indices) diff = diff or (a[i].toInt() xor b[i].toInt())
176
+ return diff == 0
177
+ }
178
+
179
+ // --- Background scheduling (doc 01 §4.1 "scheduler" row) ---
180
+ //
181
+ // WorkManager PeriodicWorkRequest, minimum periodic interval is 15 minutes (an
182
+ // OS-enforced floor on Android, not a choice of this library); requests below that
183
+ // are rounded up. VersionCheckWorker wakes VersionCheckHeadlessTaskService, which
184
+ // runs the "VersionCheckBackgroundTask" headless JS task the host app must register
185
+ // (see registerVersionCheckHeadlessTask() in the JS public API).
186
+
187
+ override fun scheduleBackgroundCheck(taskId: String, minIntervalMs: Double, promise: Promise) {
188
+ try {
189
+ val intervalMinutes = maxOf(15L, (minIntervalMs / 60_000.0).toLong())
190
+ val request = PeriodicWorkRequestBuilder<VersionCheckWorker>(intervalMinutes, TimeUnit.MINUTES)
191
+ .addTag(taskId)
192
+ .build()
193
+ WorkManager.getInstance(reactApplicationContext)
194
+ .enqueueUniquePeriodicWork(taskId, ExistingPeriodicWorkPolicy.UPDATE, request)
195
+ promise.resolve(null)
196
+ } catch (error: Exception) {
197
+ promise.reject("schedule_background_check_failed", error)
198
+ }
199
+ }
200
+
201
+ override fun cancelBackgroundCheck(taskId: String, promise: Promise) {
202
+ try {
203
+ WorkManager.getInstance(reactApplicationContext).cancelUniqueWork(taskId)
204
+ promise.resolve(null)
205
+ } catch (error: Exception) {
206
+ promise.reject("cancel_background_check_failed", error)
207
+ }
208
+ }
209
+
210
+ companion object {
211
+ const val NAME = NativeVersionCheckSpec.NAME
212
+ private const val PREFS_NAME = "rs_version_check_prefs"
213
+ private const val SECURE_PREFS_NAME = "rs_version_check_secure_prefs"
214
+ }
215
+ }