@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.
- package/LICENSE +20 -0
- package/README.md +256 -0
- package/VersionCheck.podspec +20 -0
- package/android/build.gradle +56 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/rsnativekit/versioncheck/SecureStorageCipher.kt +57 -0
- package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckHeadlessTaskService.kt +31 -0
- package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckModule.kt +215 -0
- package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckPackage.kt +31 -0
- package/android/src/main/java/com/rsnativekit/versioncheck/VersionCheckWorker.kt +29 -0
- package/ios/PlatformBridge/Ed25519Verifier.swift +19 -0
- package/ios/VersionCheck.h +5 -0
- package/ios/VersionCheck.mm +278 -0
- package/lib/module/data/cache/MemoryCacheStore.js +30 -0
- package/lib/module/data/cache/MemoryCacheStore.js.map +1 -0
- package/lib/module/data/cache/PersistentCacheStore.js +56 -0
- package/lib/module/data/cache/PersistentCacheStore.js.map +1 -0
- package/lib/module/data/config/ConfigCache.js +29 -0
- package/lib/module/data/config/ConfigCache.js.map +1 -0
- package/lib/module/data/config/ConfigDocumentValidator.js +185 -0
- package/lib/module/data/config/ConfigDocumentValidator.js.map +1 -0
- package/lib/module/data/config/ConfigLoader.js +82 -0
- package/lib/module/data/config/ConfigLoader.js.map +1 -0
- package/lib/module/data/config/ConfigProvider.js +58 -0
- package/lib/module/data/config/ConfigProvider.js.map +1 -0
- package/lib/module/data/config/ConfigResolutionPipeline.js +268 -0
- package/lib/module/data/config/ConfigResolutionPipeline.js.map +1 -0
- package/lib/module/data/config/ConfigValidator.js +69 -0
- package/lib/module/data/config/ConfigValidator.js.map +1 -0
- package/lib/module/data/config/SignatureVerifier.js +80 -0
- package/lib/module/data/config/SignatureVerifier.js.map +1 -0
- package/lib/module/data/config/canonicalize.js +88 -0
- package/lib/module/data/config/canonicalize.js.map +1 -0
- package/lib/module/data/config/sources/EnvironmentOverrideSource.js +20 -0
- package/lib/module/data/config/sources/EnvironmentOverrideSource.js.map +1 -0
- package/lib/module/data/config/sources/LocalConfigSource.js +22 -0
- package/lib/module/data/config/sources/LocalConfigSource.js.map +1 -0
- package/lib/module/data/config/sources/RemoteConfigSource.js +38 -0
- package/lib/module/data/config/sources/RemoteConfigSource.js.map +1 -0
- package/lib/module/data/providers/amazon/AmazonAppstoreProvider.js +13 -0
- package/lib/module/data/providers/amazon/AmazonAppstoreProvider.js.map +1 -0
- package/lib/module/data/providers/apple/AppleStoreProvider.js +67 -0
- package/lib/module/data/providers/apple/AppleStoreProvider.js.map +1 -0
- package/lib/module/data/providers/custom-api/CustomApiProvider.js +51 -0
- package/lib/module/data/providers/custom-api/CustomApiProvider.js.map +1 -0
- package/lib/module/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.js +13 -0
- package/lib/module/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.js.map +1 -0
- package/lib/module/data/providers/google-play/GooglePlayProvider.js +62 -0
- package/lib/module/data/providers/google-play/GooglePlayProvider.js.map +1 -0
- package/lib/module/data/providers/huawei/HuaweiAppGalleryProvider.js +18 -0
- package/lib/module/data/providers/huawei/HuaweiAppGalleryProvider.js.map +1 -0
- package/lib/module/data/repositories/VersionRepositoryImpl.js +69 -0
- package/lib/module/data/repositories/VersionRepositoryImpl.js.map +1 -0
- package/lib/module/di/container.js +80 -0
- package/lib/module/di/container.js.map +1 -0
- package/lib/module/domain/IVersionManagerCore.js +4 -0
- package/lib/module/domain/IVersionManagerCore.js.map +1 -0
- package/lib/module/domain/VersionManagerCore.js +276 -0
- package/lib/module/domain/VersionManagerCore.js.map +1 -0
- package/lib/module/domain/engines/decision/DecisionEngine.js +40 -0
- package/lib/module/domain/engines/decision/DecisionEngine.js.map +1 -0
- package/lib/module/domain/engines/policy/IUpdatePolicyEngine.js +4 -0
- package/lib/module/domain/engines/policy/IUpdatePolicyEngine.js.map +1 -0
- package/lib/module/domain/engines/policy/PolicyEngine.js +42 -0
- package/lib/module/domain/engines/policy/PolicyEngine.js.map +1 -0
- package/lib/module/domain/engines/semver/IVersionComparator.js +4 -0
- package/lib/module/domain/engines/semver/IVersionComparator.js.map +1 -0
- package/lib/module/domain/engines/semver/SemVerEngine.js +176 -0
- package/lib/module/domain/engines/semver/SemVerEngine.js.map +1 -0
- package/lib/module/domain/errors/VersionManagerException.js +104 -0
- package/lib/module/domain/errors/VersionManagerException.js.map +1 -0
- package/lib/module/domain/models/ActionPlan.js +9 -0
- package/lib/module/domain/models/ActionPlan.js.map +1 -0
- package/lib/module/domain/models/ConfigDocument.js +4 -0
- package/lib/module/domain/models/ConfigDocument.js.map +1 -0
- package/lib/module/domain/models/Events.js +4 -0
- package/lib/module/domain/models/Events.js.map +1 -0
- package/lib/module/domain/models/LifecycleState.js +29 -0
- package/lib/module/domain/models/LifecycleState.js.map +1 -0
- package/lib/module/domain/models/ParsedVersion.js +2 -0
- package/lib/module/domain/models/ParsedVersion.js.map +1 -0
- package/lib/module/domain/models/PlatformId.js +2 -0
- package/lib/module/domain/models/PlatformId.js.map +1 -0
- package/lib/module/domain/models/PublicApiOptions.js +2 -0
- package/lib/module/domain/models/PublicApiOptions.js.map +1 -0
- package/lib/module/domain/models/RemoteVersionInfo.js +4 -0
- package/lib/module/domain/models/RemoteVersionInfo.js.map +1 -0
- package/lib/module/domain/models/StoreLookup.js +2 -0
- package/lib/module/domain/models/StoreLookup.js.map +1 -0
- package/lib/module/domain/models/Unsubscribe.js +2 -0
- package/lib/module/domain/models/Unsubscribe.js.map +1 -0
- package/lib/module/domain/models/UpdateInfo.js +4 -0
- package/lib/module/domain/models/UpdateInfo.js.map +1 -0
- package/lib/module/domain/models/VersionManagerOptions.js +4 -0
- package/lib/module/domain/models/VersionManagerOptions.js.map +1 -0
- package/lib/module/domain/ports/ICacheStore.js +2 -0
- package/lib/module/domain/ports/ICacheStore.js.map +1 -0
- package/lib/module/domain/ports/IClock.js +2 -0
- package/lib/module/domain/ports/IClock.js.map +1 -0
- package/lib/module/domain/ports/IConfigProvider.js +4 -0
- package/lib/module/domain/ports/IConfigProvider.js.map +1 -0
- package/lib/module/domain/ports/IConfigSources.js +4 -0
- package/lib/module/domain/ports/IConfigSources.js.map +1 -0
- package/lib/module/domain/ports/IPlatformBridge.js +4 -0
- package/lib/module/domain/ports/IPlatformBridge.js.map +1 -0
- package/lib/module/domain/ports/IStoreProvider.js +4 -0
- package/lib/module/domain/ports/IStoreProvider.js.map +1 -0
- package/lib/module/domain/ports/IVersionRepository.js +4 -0
- package/lib/module/domain/ports/IVersionRepository.js.map +1 -0
- package/lib/module/domain/statemachine/LifecycleStateMachine.js +55 -0
- package/lib/module/domain/statemachine/LifecycleStateMachine.js.map +1 -0
- package/lib/module/domain/usecases/CheckForUpdatesUseCase.js +31 -0
- package/lib/module/domain/usecases/CheckForUpdatesUseCase.js.map +1 -0
- package/lib/module/domain/usecases/ForceTriggerUpdateUseCase.js +20 -0
- package/lib/module/domain/usecases/ForceTriggerUpdateUseCase.js.map +1 -0
- package/lib/module/domain/usecases/ResetIgnoredVersionsUseCase.js +24 -0
- package/lib/module/domain/usecases/ResetIgnoredVersionsUseCase.js.map +1 -0
- package/lib/module/index.js +10 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/platform/contracts/IPlatformBridge.js +2 -0
- package/lib/module/platform/contracts/IPlatformBridge.js.map +1 -0
- package/lib/module/platform/createPlatformBridge.js +17 -0
- package/lib/module/platform/createPlatformBridge.js.map +1 -0
- package/lib/module/platform/createPlatformBridge.native.js +9 -0
- package/lib/module/platform/createPlatformBridge.native.js.map +1 -0
- package/lib/module/platform/native/NativePlatformBridge.js +143 -0
- package/lib/module/platform/native/NativePlatformBridge.js.map +1 -0
- package/lib/module/platform/native/NativeVersionCheck.js +33 -0
- package/lib/module/platform/native/NativeVersionCheck.js.map +1 -0
- package/lib/module/platform/web/WebPlatformBridge.js +228 -0
- package/lib/module/platform/web/WebPlatformBridge.js.map +1 -0
- package/lib/module/presentation/components/ForceUpdateScreen.js +68 -0
- package/lib/module/presentation/components/ForceUpdateScreen.js.map +1 -0
- package/lib/module/presentation/components/OptionalUpdateBanner.js +69 -0
- package/lib/module/presentation/components/OptionalUpdateBanner.js.map +1 -0
- package/lib/module/presentation/components/SoftUpdateDialog.js +101 -0
- package/lib/module/presentation/components/SoftUpdateDialog.js.map +1 -0
- package/lib/module/presentation/hooks/VersionManagerProvider.js +18 -0
- package/lib/module/presentation/hooks/VersionManagerProvider.js.map +1 -0
- package/lib/module/presentation/hooks/context.js +5 -0
- package/lib/module/presentation/hooks/context.js.map +1 -0
- package/lib/module/presentation/hooks/useUpdateState.js +26 -0
- package/lib/module/presentation/hooks/useUpdateState.js.map +1 -0
- package/lib/module/presentation/hooks/useVersionManager.js +33 -0
- package/lib/module/presentation/hooks/useVersionManager.js.map +1 -0
- package/lib/module/presentation/state/VersionManagerStore.js +47 -0
- package/lib/module/presentation/state/VersionManagerStore.js.map +1 -0
- package/lib/module/publicApi.js +114 -0
- package/lib/module/publicApi.js.map +1 -0
- package/lib/module/shared/eventbus/EventBus.js +34 -0
- package/lib/module/shared/eventbus/EventBus.js.map +1 -0
- package/lib/module/shared/logging/Logger.js +56 -0
- package/lib/module/shared/logging/Logger.js.map +1 -0
- package/lib/module/ui.js +13 -0
- package/lib/module/ui.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/data/cache/MemoryCacheStore.d.ts +12 -0
- package/lib/typescript/src/data/cache/MemoryCacheStore.d.ts.map +1 -0
- package/lib/typescript/src/data/cache/PersistentCacheStore.d.ts +14 -0
- package/lib/typescript/src/data/cache/PersistentCacheStore.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigCache.d.ts +15 -0
- package/lib/typescript/src/data/config/ConfigCache.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigDocumentValidator.d.ts +20 -0
- package/lib/typescript/src/data/config/ConfigDocumentValidator.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigLoader.d.ts +28 -0
- package/lib/typescript/src/data/config/ConfigLoader.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigProvider.d.ts +30 -0
- package/lib/typescript/src/data/config/ConfigProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigResolutionPipeline.d.ts +49 -0
- package/lib/typescript/src/data/config/ConfigResolutionPipeline.d.ts.map +1 -0
- package/lib/typescript/src/data/config/ConfigValidator.d.ts +10 -0
- package/lib/typescript/src/data/config/ConfigValidator.d.ts.map +1 -0
- package/lib/typescript/src/data/config/SignatureVerifier.d.ts +16 -0
- package/lib/typescript/src/data/config/SignatureVerifier.d.ts.map +1 -0
- package/lib/typescript/src/data/config/canonicalize.d.ts +13 -0
- package/lib/typescript/src/data/config/canonicalize.d.ts.map +1 -0
- package/lib/typescript/src/data/config/sources/EnvironmentOverrideSource.d.ts +17 -0
- package/lib/typescript/src/data/config/sources/EnvironmentOverrideSource.d.ts.map +1 -0
- package/lib/typescript/src/data/config/sources/LocalConfigSource.d.ts +19 -0
- package/lib/typescript/src/data/config/sources/LocalConfigSource.d.ts.map +1 -0
- package/lib/typescript/src/data/config/sources/RemoteConfigSource.d.ts +10 -0
- package/lib/typescript/src/data/config/sources/RemoteConfigSource.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/amazon/AmazonAppstoreProvider.d.ts +9 -0
- package/lib/typescript/src/data/providers/amazon/AmazonAppstoreProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/apple/AppleStoreProvider.d.ts +19 -0
- package/lib/typescript/src/data/providers/apple/AppleStoreProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/custom-api/CustomApiProvider.d.ts +14 -0
- package/lib/typescript/src/data/providers/custom-api/CustomApiProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.d.ts +9 -0
- package/lib/typescript/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/google-play/GooglePlayProvider.d.ts +22 -0
- package/lib/typescript/src/data/providers/google-play/GooglePlayProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/providers/huawei/HuaweiAppGalleryProvider.d.ts +14 -0
- package/lib/typescript/src/data/providers/huawei/HuaweiAppGalleryProvider.d.ts.map +1 -0
- package/lib/typescript/src/data/repositories/VersionRepositoryImpl.d.ts +23 -0
- package/lib/typescript/src/data/repositories/VersionRepositoryImpl.d.ts.map +1 -0
- package/lib/typescript/src/di/container.d.ts +12 -0
- package/lib/typescript/src/di/container.d.ts.map +1 -0
- package/lib/typescript/src/domain/IVersionManagerCore.d.ts +30 -0
- package/lib/typescript/src/domain/IVersionManagerCore.d.ts.map +1 -0
- package/lib/typescript/src/domain/VersionManagerCore.d.ts +94 -0
- package/lib/typescript/src/domain/VersionManagerCore.d.ts.map +1 -0
- package/lib/typescript/src/domain/engines/decision/DecisionEngine.d.ts +16 -0
- package/lib/typescript/src/domain/engines/decision/DecisionEngine.d.ts.map +1 -0
- package/lib/typescript/src/domain/engines/policy/IUpdatePolicyEngine.d.ts +17 -0
- package/lib/typescript/src/domain/engines/policy/IUpdatePolicyEngine.d.ts.map +1 -0
- package/lib/typescript/src/domain/engines/policy/PolicyEngine.d.ts +16 -0
- package/lib/typescript/src/domain/engines/policy/PolicyEngine.d.ts.map +1 -0
- package/lib/typescript/src/domain/engines/semver/IVersionComparator.d.ts +7 -0
- package/lib/typescript/src/domain/engines/semver/IVersionComparator.d.ts.map +1 -0
- package/lib/typescript/src/domain/engines/semver/SemVerEngine.d.ts +11 -0
- package/lib/typescript/src/domain/engines/semver/SemVerEngine.d.ts.map +1 -0
- package/lib/typescript/src/domain/errors/VersionManagerException.d.ts +97 -0
- package/lib/typescript/src/domain/errors/VersionManagerException.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/ActionPlan.d.ts +15 -0
- package/lib/typescript/src/domain/models/ActionPlan.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/ConfigDocument.d.ts +86 -0
- package/lib/typescript/src/domain/models/ConfigDocument.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/Events.d.ts +39 -0
- package/lib/typescript/src/domain/models/Events.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/LifecycleState.d.ts +18 -0
- package/lib/typescript/src/domain/models/LifecycleState.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/ParsedVersion.d.ts +9 -0
- package/lib/typescript/src/domain/models/ParsedVersion.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/PlatformId.d.ts +3 -0
- package/lib/typescript/src/domain/models/PlatformId.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/PublicApiOptions.d.ts +13 -0
- package/lib/typescript/src/domain/models/PublicApiOptions.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/RemoteVersionInfo.d.ts +21 -0
- package/lib/typescript/src/domain/models/RemoteVersionInfo.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/StoreLookup.d.ts +12 -0
- package/lib/typescript/src/domain/models/StoreLookup.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/Unsubscribe.d.ts +2 -0
- package/lib/typescript/src/domain/models/Unsubscribe.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/UpdateInfo.d.ts +11 -0
- package/lib/typescript/src/domain/models/UpdateInfo.d.ts.map +1 -0
- package/lib/typescript/src/domain/models/VersionManagerOptions.d.ts +149 -0
- package/lib/typescript/src/domain/models/VersionManagerOptions.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/ICacheStore.d.ts +6 -0
- package/lib/typescript/src/domain/ports/ICacheStore.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IClock.d.ts +4 -0
- package/lib/typescript/src/domain/ports/IClock.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IConfigProvider.d.ts +11 -0
- package/lib/typescript/src/domain/ports/IConfigProvider.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IConfigSources.d.ts +25 -0
- package/lib/typescript/src/domain/ports/IConfigSources.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IPlatformBridge.d.ts +77 -0
- package/lib/typescript/src/domain/ports/IPlatformBridge.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IStoreProvider.d.ts +7 -0
- package/lib/typescript/src/domain/ports/IStoreProvider.d.ts.map +1 -0
- package/lib/typescript/src/domain/ports/IVersionRepository.d.ts +8 -0
- package/lib/typescript/src/domain/ports/IVersionRepository.d.ts.map +1 -0
- package/lib/typescript/src/domain/statemachine/LifecycleStateMachine.d.ts +25 -0
- package/lib/typescript/src/domain/statemachine/LifecycleStateMachine.d.ts.map +1 -0
- package/lib/typescript/src/domain/usecases/CheckForUpdatesUseCase.d.ts +30 -0
- package/lib/typescript/src/domain/usecases/CheckForUpdatesUseCase.d.ts.map +1 -0
- package/lib/typescript/src/domain/usecases/ForceTriggerUpdateUseCase.d.ts +17 -0
- package/lib/typescript/src/domain/usecases/ForceTriggerUpdateUseCase.d.ts.map +1 -0
- package/lib/typescript/src/domain/usecases/ResetIgnoredVersionsUseCase.d.ts +7 -0
- package/lib/typescript/src/domain/usecases/ResetIgnoredVersionsUseCase.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +17 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/platform/contracts/IPlatformBridge.d.ts +2 -0
- package/lib/typescript/src/platform/contracts/IPlatformBridge.d.ts.map +1 -0
- package/lib/typescript/src/platform/createPlatformBridge.d.ts +12 -0
- package/lib/typescript/src/platform/createPlatformBridge.d.ts.map +1 -0
- package/lib/typescript/src/platform/createPlatformBridge.native.d.ts +4 -0
- package/lib/typescript/src/platform/createPlatformBridge.native.d.ts.map +1 -0
- package/lib/typescript/src/platform/native/NativePlatformBridge.d.ts +17 -0
- package/lib/typescript/src/platform/native/NativePlatformBridge.d.ts.map +1 -0
- package/lib/typescript/src/platform/native/NativeVersionCheck.d.ts +45 -0
- package/lib/typescript/src/platform/native/NativeVersionCheck.d.ts.map +1 -0
- package/lib/typescript/src/platform/web/WebPlatformBridge.d.ts +17 -0
- package/lib/typescript/src/platform/web/WebPlatformBridge.d.ts.map +1 -0
- package/lib/typescript/src/presentation/components/ForceUpdateScreen.d.ts +12 -0
- package/lib/typescript/src/presentation/components/ForceUpdateScreen.d.ts.map +1 -0
- package/lib/typescript/src/presentation/components/OptionalUpdateBanner.d.ts +12 -0
- package/lib/typescript/src/presentation/components/OptionalUpdateBanner.d.ts.map +1 -0
- package/lib/typescript/src/presentation/components/SoftUpdateDialog.d.ts +14 -0
- package/lib/typescript/src/presentation/components/SoftUpdateDialog.d.ts.map +1 -0
- package/lib/typescript/src/presentation/hooks/VersionManagerProvider.d.ts +9 -0
- package/lib/typescript/src/presentation/hooks/VersionManagerProvider.d.ts.map +1 -0
- package/lib/typescript/src/presentation/hooks/context.d.ts +3 -0
- package/lib/typescript/src/presentation/hooks/context.d.ts.map +1 -0
- package/lib/typescript/src/presentation/hooks/useUpdateState.d.ts +17 -0
- package/lib/typescript/src/presentation/hooks/useUpdateState.d.ts.map +1 -0
- package/lib/typescript/src/presentation/hooks/useVersionManager.d.ts +22 -0
- package/lib/typescript/src/presentation/hooks/useVersionManager.d.ts.map +1 -0
- package/lib/typescript/src/presentation/state/VersionManagerStore.d.ts +28 -0
- package/lib/typescript/src/presentation/state/VersionManagerStore.d.ts.map +1 -0
- package/lib/typescript/src/publicApi.d.ts +24 -0
- package/lib/typescript/src/publicApi.d.ts.map +1 -0
- package/lib/typescript/src/shared/eventbus/EventBus.d.ts +21 -0
- package/lib/typescript/src/shared/eventbus/EventBus.d.ts.map +1 -0
- package/lib/typescript/src/shared/logging/Logger.d.ts +17 -0
- package/lib/typescript/src/shared/logging/Logger.d.ts.map +1 -0
- package/lib/typescript/src/ui.d.ts +15 -0
- package/lib/typescript/src/ui.d.ts.map +1 -0
- package/package.json +192 -0
- package/src/data/cache/MemoryCacheStore.ts +36 -0
- package/src/data/cache/PersistentCacheStore.ts +63 -0
- package/src/data/config/ConfigCache.ts +30 -0
- package/src/data/config/ConfigDocumentValidator.ts +309 -0
- package/src/data/config/ConfigLoader.ts +105 -0
- package/src/data/config/ConfigProvider.ts +84 -0
- package/src/data/config/ConfigResolutionPipeline.ts +424 -0
- package/src/data/config/ConfigValidator.ts +79 -0
- package/src/data/config/SignatureVerifier.ts +90 -0
- package/src/data/config/canonicalize.ts +111 -0
- package/src/data/config/sources/EnvironmentOverrideSource.ts +22 -0
- package/src/data/config/sources/LocalConfigSource.ts +24 -0
- package/src/data/config/sources/RemoteConfigSource.ts +41 -0
- package/src/data/providers/amazon/AmazonAppstoreProvider.ts +16 -0
- package/src/data/providers/apple/AppleStoreProvider.ts +94 -0
- package/src/data/providers/custom-api/CustomApiProvider.ts +75 -0
- package/src/data/providers/firebase-remote-config/FirebaseRemoteConfigProvider.ts +16 -0
- package/src/data/providers/google-play/GooglePlayProvider.ts +75 -0
- package/src/data/providers/huawei/HuaweiAppGalleryProvider.ts +21 -0
- package/src/data/repositories/VersionRepositoryImpl.ts +90 -0
- package/src/di/container.ts +128 -0
- package/src/domain/IVersionManagerCore.ts +54 -0
- package/src/domain/VersionManagerCore.ts +425 -0
- package/src/domain/engines/decision/DecisionEngine.ts +47 -0
- package/src/domain/engines/policy/IUpdatePolicyEngine.ts +18 -0
- package/src/domain/engines/policy/PolicyEngine.ts +60 -0
- package/src/domain/engines/semver/IVersionComparator.ts +7 -0
- package/src/domain/engines/semver/SemVerEngine.ts +200 -0
- package/src/domain/errors/VersionManagerException.ts +166 -0
- package/src/domain/models/ActionPlan.ts +17 -0
- package/src/domain/models/ConfigDocument.ts +97 -0
- package/src/domain/models/Events.ts +48 -0
- package/src/domain/models/LifecycleState.ts +50 -0
- package/src/domain/models/ParsedVersion.ts +8 -0
- package/src/domain/models/PlatformId.ts +9 -0
- package/src/domain/models/PublicApiOptions.ts +14 -0
- package/src/domain/models/RemoteVersionInfo.ts +23 -0
- package/src/domain/models/StoreLookup.ts +12 -0
- package/src/domain/models/Unsubscribe.ts +1 -0
- package/src/domain/models/UpdateInfo.ts +11 -0
- package/src/domain/models/VersionManagerOptions.ts +150 -0
- package/src/domain/ports/ICacheStore.ts +5 -0
- package/src/domain/ports/IClock.ts +3 -0
- package/src/domain/ports/IConfigProvider.ts +18 -0
- package/src/domain/ports/IConfigSources.ts +44 -0
- package/src/domain/ports/IPlatformBridge.ts +97 -0
- package/src/domain/ports/IStoreProvider.ts +12 -0
- package/src/domain/ports/IVersionRepository.ts +14 -0
- package/src/domain/statemachine/LifecycleStateMachine.ts +76 -0
- package/src/domain/usecases/CheckForUpdatesUseCase.ts +63 -0
- package/src/domain/usecases/ForceTriggerUpdateUseCase.ts +35 -0
- package/src/domain/usecases/ResetIgnoredVersionsUseCase.ts +26 -0
- package/src/index.tsx +84 -0
- package/src/platform/contracts/IPlatformBridge.ts +14 -0
- package/src/platform/createPlatformBridge.native.ts +7 -0
- package/src/platform/createPlatformBridge.ts +15 -0
- package/src/platform/native/NativePlatformBridge.ts +179 -0
- package/src/platform/native/NativeVersionCheck.ts +64 -0
- package/src/platform/web/WebPlatformBridge.ts +347 -0
- package/src/presentation/components/ForceUpdateScreen.tsx +64 -0
- package/src/presentation/components/OptionalUpdateBanner.tsx +53 -0
- package/src/presentation/components/SoftUpdateDialog.tsx +84 -0
- package/src/presentation/hooks/VersionManagerProvider.tsx +23 -0
- package/src/presentation/hooks/context.ts +6 -0
- package/src/presentation/hooks/useUpdateState.ts +50 -0
- package/src/presentation/hooks/useVersionManager.ts +68 -0
- package/src/presentation/state/VersionManagerStore.ts +72 -0
- package/src/publicApi.ts +126 -0
- package/src/shared/eventbus/EventBus.ts +56 -0
- package/src/shared/logging/Logger.ts +70 -0
- package/src/ui.tsx +19 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VersionManagerConfigDocument } from '../../../domain/models/ConfigDocument';
|
|
2
|
+
import type { IEnvironmentOverrideSource } from '../../../domain/ports/IConfigSources';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Doc 03 §4 — "there is no OS process environment on a device"; this tier maps to
|
|
6
|
+
* build-time-injected constants and explicit runtime overrides, not `process.env` on a
|
|
7
|
+
* server. Build-time constants (Info.plist / BuildConfig fields) are app-specific and
|
|
8
|
+
* have no generic cross-platform read path from JS, so this implementation covers the
|
|
9
|
+
* "explicit configure({ configSources: { envOverrides } })" half of §4.1 tier 1 — the
|
|
10
|
+
* build-time-constant half is a per-app integration concern (documented, not a gap in
|
|
11
|
+
* this port's contract, which only promises "read whatever overrides are available").
|
|
12
|
+
*/
|
|
13
|
+
export class EnvironmentOverrideSource implements IEnvironmentOverrideSource {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly overrides:
|
|
16
|
+
Partial<VersionManagerConfigDocument> | undefined
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
async read(): Promise<Partial<VersionManagerConfigDocument>> {
|
|
20
|
+
return this.overrides ?? {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RawConfigDocument,
|
|
3
|
+
SignedConfigEnvelope,
|
|
4
|
+
} from '../../../domain/models/ConfigDocument';
|
|
5
|
+
import type { ILocalConfigSource } from '../../../domain/ports/IConfigSources';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Doc 03 §5.1 — the design calls for a native mmap reader (iOS Data(alwaysMapped),
|
|
9
|
+
* Android FileChannel.map) to avoid a double-copy when reading a bundled config asset.
|
|
10
|
+
* This implementation takes a documented, lower-risk equivalent instead: the host app
|
|
11
|
+
* imports its bundled config JSON via Metro/webpack's native JSON module support
|
|
12
|
+
* (`import localConfig from './vm-config.json'`) and passes the parsed envelope through
|
|
13
|
+
* `VersionManagerOptions.configSources.local`. Metro's JSON loader already avoids the
|
|
14
|
+
* page-cache -> native-heap -> JS-heap double copy for typical bundle sizes (the asset
|
|
15
|
+
* is compiled into the JS bundle itself), which satisfies §5.1's actual goal (avoid GC
|
|
16
|
+
* churn against the 5MB budget for a <=64KB document) without a dedicated native reader.
|
|
17
|
+
*/
|
|
18
|
+
export class LocalConfigSource implements ILocalConfigSource {
|
|
19
|
+
constructor(private readonly envelope: SignedConfigEnvelope | undefined) {}
|
|
20
|
+
|
|
21
|
+
async read(): Promise<RawConfigDocument | null> {
|
|
22
|
+
return this.envelope ?? null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OfflineException,
|
|
3
|
+
StoreResponseParseException,
|
|
4
|
+
} from '../../../domain/errors/VersionManagerException';
|
|
5
|
+
import type {
|
|
6
|
+
FetchOptions,
|
|
7
|
+
RawConfigDocument,
|
|
8
|
+
} from '../../../domain/models/ConfigDocument';
|
|
9
|
+
import type { IRemoteConfigSource } from '../../../domain/ports/IConfigSources';
|
|
10
|
+
import type { IHttpClient } from '../../../domain/ports/IPlatformBridge';
|
|
11
|
+
|
|
12
|
+
/** Doc 03 §4.3 — the remote-fetch tier, reached through the same IHttpClient port every other network call in this SDK uses. */
|
|
13
|
+
export class RemoteConfigSource implements IRemoteConfigSource {
|
|
14
|
+
constructor(private readonly http: IHttpClient) {}
|
|
15
|
+
|
|
16
|
+
async fetch(url: string, options: FetchOptions): Promise<RawConfigDocument> {
|
|
17
|
+
const response = await this.http.request({
|
|
18
|
+
url,
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: options.headers,
|
|
21
|
+
timeoutMs: options.timeoutMs,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (response.status !== 200) {
|
|
25
|
+
throw new OfflineException({
|
|
26
|
+
message: `Remote config fetch returned HTTP ${response.status}`,
|
|
27
|
+
metadata: { url, status: response.status },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(response.body);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new StoreResponseParseException({
|
|
35
|
+
message: 'Remote config endpoint returned malformed JSON',
|
|
36
|
+
cause: error,
|
|
37
|
+
metadata: { url },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnsupportedStoreException } from '../../../domain/errors/VersionManagerException';
|
|
2
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
3
|
+
import type { StoreLookupResult } from '../../../domain/models/StoreLookup';
|
|
4
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
5
|
+
|
|
6
|
+
/** Not yet implemented — see Prompt 11 in the design series. */
|
|
7
|
+
export class AmazonAppstoreProvider implements IStoreProvider {
|
|
8
|
+
readonly id: StoreProviderId = 'amazon';
|
|
9
|
+
|
|
10
|
+
async fetchLatestVersionInfo(): Promise<StoreLookupResult> {
|
|
11
|
+
throw new UnsupportedStoreException({
|
|
12
|
+
message:
|
|
13
|
+
'Amazon Appstore provider is not yet implemented — see Prompt 11 in the design series',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppNotFoundException,
|
|
3
|
+
RateLimitException,
|
|
4
|
+
StoreResponseParseException,
|
|
5
|
+
} from '../../../domain/errors/VersionManagerException';
|
|
6
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
7
|
+
import type {
|
|
8
|
+
StoreLookupRequest,
|
|
9
|
+
StoreLookupResult,
|
|
10
|
+
} from '../../../domain/models/StoreLookup';
|
|
11
|
+
import type { IHttpClient } from '../../../domain/ports/IPlatformBridge';
|
|
12
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
13
|
+
|
|
14
|
+
interface AppleLookupResult {
|
|
15
|
+
version: string;
|
|
16
|
+
trackViewUrl: string;
|
|
17
|
+
releaseNotes?: string;
|
|
18
|
+
minimumOsVersion?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface AppleLookupResponse {
|
|
22
|
+
resultCount: number;
|
|
23
|
+
results: AppleLookupResult[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Real implementation against the public iTunes Lookup API (Prompt 10). Accepts either
|
|
28
|
+
* the numeric App Store Connect id (`id=`) or, when the app developer doesn't want to
|
|
29
|
+
* hardcode one, falls back to looking the app up by its own bundle id (`bundleId=`) —
|
|
30
|
+
* the iTunes Lookup API supports both query params equivalently.
|
|
31
|
+
*/
|
|
32
|
+
export class AppleStoreProvider implements IStoreProvider {
|
|
33
|
+
readonly id: StoreProviderId = 'apple';
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly http: IHttpClient,
|
|
37
|
+
private readonly appStoreId?: string,
|
|
38
|
+
private readonly region: string = 'us'
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
async fetchLatestVersionInfo(
|
|
42
|
+
request: StoreLookupRequest
|
|
43
|
+
): Promise<StoreLookupResult> {
|
|
44
|
+
const country = request.region ?? this.region;
|
|
45
|
+
const identifierParam = this.appStoreId
|
|
46
|
+
? `id=${encodeURIComponent(this.appStoreId)}`
|
|
47
|
+
: `bundleId=${encodeURIComponent(request.bundleId)}`;
|
|
48
|
+
const url = `https://itunes.apple.com/lookup?${identifierParam}&country=${encodeURIComponent(country)}`;
|
|
49
|
+
const response = await this.http.request({
|
|
50
|
+
url,
|
|
51
|
+
method: 'GET',
|
|
52
|
+
timeoutMs: request.timeoutMs,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const identifier = this.appStoreId ?? request.bundleId;
|
|
56
|
+
|
|
57
|
+
if (response.status === 429) {
|
|
58
|
+
throw new RateLimitException({
|
|
59
|
+
message: 'Apple iTunes Lookup API rate limit exceeded (HTTP 429)',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (response.status !== 200) {
|
|
63
|
+
throw new AppNotFoundException({
|
|
64
|
+
message: `Apple iTunes Lookup API returned HTTP ${response.status}`,
|
|
65
|
+
metadata: { identifier, status: response.status },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let payload: AppleLookupResponse;
|
|
70
|
+
try {
|
|
71
|
+
payload = JSON.parse(response.body) as AppleLookupResponse;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new StoreResponseParseException({
|
|
74
|
+
message: 'Apple iTunes Lookup API returned malformed JSON',
|
|
75
|
+
cause: error,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = payload.results[0];
|
|
80
|
+
if (!result) {
|
|
81
|
+
throw new AppNotFoundException({
|
|
82
|
+
message: `No app found in the App Store for "${identifier}"`,
|
|
83
|
+
metadata: { identifier },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
latestVersion: result.version,
|
|
89
|
+
storeUrl: result.trackViewUrl,
|
|
90
|
+
releaseNotes: result.releaseNotes ?? null,
|
|
91
|
+
minimumOsVersion: result.minimumOsVersion ?? null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StorePermissionException,
|
|
3
|
+
StoreResponseParseException,
|
|
4
|
+
} from '../../../domain/errors/VersionManagerException';
|
|
5
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
6
|
+
import type {
|
|
7
|
+
StoreLookupRequest,
|
|
8
|
+
StoreLookupResult,
|
|
9
|
+
} from '../../../domain/models/StoreLookup';
|
|
10
|
+
import type { IHttpClient } from '../../../domain/ports/IPlatformBridge';
|
|
11
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
12
|
+
|
|
13
|
+
interface CustomApiResponsePayload {
|
|
14
|
+
latestVersion?: string;
|
|
15
|
+
storeUrl?: string;
|
|
16
|
+
releaseNotes?: string;
|
|
17
|
+
minimumOsVersion?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Generic enterprise/private-distribution provider (Prompt 12). */
|
|
21
|
+
export class CustomApiProvider implements IStoreProvider {
|
|
22
|
+
readonly id: StoreProviderId = 'custom';
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly http: IHttpClient,
|
|
26
|
+
private readonly url: string,
|
|
27
|
+
private readonly headers: Readonly<Record<string, string>> = {}
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
async fetchLatestVersionInfo(
|
|
31
|
+
request: StoreLookupRequest
|
|
32
|
+
): Promise<StoreLookupResult> {
|
|
33
|
+
const response = await this.http.request({
|
|
34
|
+
url: this.url,
|
|
35
|
+
method: 'GET',
|
|
36
|
+
headers: this.headers,
|
|
37
|
+
timeoutMs: request.timeoutMs,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (response.status === 401 || response.status === 403) {
|
|
41
|
+
throw new StorePermissionException({
|
|
42
|
+
message: `Custom API rejected the request (HTTP ${response.status})`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (response.status !== 200) {
|
|
46
|
+
throw new StoreResponseParseException({
|
|
47
|
+
message: `Custom API returned HTTP ${response.status}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let payload: CustomApiResponsePayload;
|
|
52
|
+
try {
|
|
53
|
+
payload = JSON.parse(response.body) as CustomApiResponsePayload;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new StoreResponseParseException({
|
|
56
|
+
message: 'Custom API returned malformed JSON',
|
|
57
|
+
cause: error,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!payload.latestVersion || !payload.storeUrl) {
|
|
62
|
+
throw new StoreResponseParseException({
|
|
63
|
+
message:
|
|
64
|
+
'Custom API response is missing required fields "latestVersion"/"storeUrl"',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
latestVersion: payload.latestVersion,
|
|
70
|
+
storeUrl: payload.storeUrl,
|
|
71
|
+
releaseNotes: payload.releaseNotes ?? null,
|
|
72
|
+
minimumOsVersion: payload.minimumOsVersion ?? null,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnsupportedStoreException } from '../../../domain/errors/VersionManagerException';
|
|
2
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
3
|
+
import type { StoreLookupResult } from '../../../domain/models/StoreLookup';
|
|
4
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
5
|
+
|
|
6
|
+
/** Not yet implemented — see Prompt 13 in the design series. */
|
|
7
|
+
export class FirebaseRemoteConfigProvider implements IStoreProvider {
|
|
8
|
+
readonly id: StoreProviderId = 'firebase-remote-config';
|
|
9
|
+
|
|
10
|
+
async fetchLatestVersionInfo(): Promise<StoreLookupResult> {
|
|
11
|
+
throw new UnsupportedStoreException({
|
|
12
|
+
message:
|
|
13
|
+
'Firebase Remote Config provider is not yet implemented — see Prompt 13 in the design series',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppNotFoundException,
|
|
3
|
+
StoreResponseParseException,
|
|
4
|
+
} from '../../../domain/errors/VersionManagerException';
|
|
5
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
6
|
+
import type {
|
|
7
|
+
StoreLookupRequest,
|
|
8
|
+
StoreLookupResult,
|
|
9
|
+
} from '../../../domain/models/StoreLookup';
|
|
10
|
+
import type { IHttpClient } from '../../../domain/ports/IPlatformBridge';
|
|
11
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Google Play has no public, unauthenticated lookup API (Prompt 10). This provider is a
|
|
15
|
+
* best-effort parser of the public store listing page's inline JSON blob. It is
|
|
16
|
+
* inherently fragile — Play Store markup can change without notice — and is offered as
|
|
17
|
+
* a pragmatic default; production deployments with a Google Play Developer API service
|
|
18
|
+
* account should prefer a IStoreProvider backed by that authenticated API instead.
|
|
19
|
+
*
|
|
20
|
+
* packageName is optional — on Android the app's own bundle id *is* its package name,
|
|
21
|
+
* so when it's not supplied explicitly this falls back to request.bundleId.
|
|
22
|
+
*/
|
|
23
|
+
export class GooglePlayProvider implements IStoreProvider {
|
|
24
|
+
readonly id: StoreProviderId = 'google-play';
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly http: IHttpClient,
|
|
28
|
+
private readonly packageName?: string
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async fetchLatestVersionInfo(
|
|
32
|
+
request: StoreLookupRequest
|
|
33
|
+
): Promise<StoreLookupResult> {
|
|
34
|
+
const packageName = this.packageName ?? request.bundleId;
|
|
35
|
+
const storeUrl = `https://play.google.com/store/apps/details?id=${encodeURIComponent(packageName)}&hl=en`;
|
|
36
|
+
const response = await this.http.request({
|
|
37
|
+
url: storeUrl,
|
|
38
|
+
method: 'GET',
|
|
39
|
+
timeoutMs: request.timeoutMs,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (response.status === 404) {
|
|
43
|
+
throw new AppNotFoundException({
|
|
44
|
+
message: `No app found on Google Play for package "${packageName}"`,
|
|
45
|
+
metadata: { packageName },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (response.status !== 200) {
|
|
49
|
+
throw new StoreResponseParseException({
|
|
50
|
+
message: `Google Play returned HTTP ${response.status}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const version = extractPlayStoreVersion(response.body);
|
|
55
|
+
if (!version) {
|
|
56
|
+
throw new StoreResponseParseException({
|
|
57
|
+
message:
|
|
58
|
+
'Could not extract a version string from the Google Play store page',
|
|
59
|
+
metadata: { packageName },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
latestVersion: version,
|
|
65
|
+
storeUrl: `https://play.google.com/store/apps/details?id=${packageName}`,
|
|
66
|
+
releaseNotes: null,
|
|
67
|
+
minimumOsVersion: null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractPlayStoreVersion(html: string): string | null {
|
|
73
|
+
const match = /\[\[\["(\d+(?:\.\d+){1,3})"\]\]/.exec(html);
|
|
74
|
+
return match?.[1] ?? null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UnsupportedStoreException } from '../../../domain/errors/VersionManagerException';
|
|
2
|
+
import type { StoreProviderId } from '../../../domain/models/PlatformId';
|
|
3
|
+
import type { StoreLookupResult } from '../../../domain/models/StoreLookup';
|
|
4
|
+
import type { IStoreProvider } from '../../../domain/ports/IStoreProvider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Not yet implemented — Huawei AppGallery integration is designed in Prompt 11 of this
|
|
8
|
+
* series (AG Connect API parameters, error-code mapping) but not built in this pass.
|
|
9
|
+
* The file exists so the provider is independently importable/tree-shakeable
|
|
10
|
+
* (Prompt 1 §8) without pulling in Apple/Google/Custom code.
|
|
11
|
+
*/
|
|
12
|
+
export class HuaweiAppGalleryProvider implements IStoreProvider {
|
|
13
|
+
readonly id: StoreProviderId = 'huawei';
|
|
14
|
+
|
|
15
|
+
async fetchLatestVersionInfo(): Promise<StoreLookupResult> {
|
|
16
|
+
throw new UnsupportedStoreException({
|
|
17
|
+
message:
|
|
18
|
+
'Huawei AppGallery provider is not yet implemented — see Prompt 11 in the design series',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toVersionManagerException,
|
|
3
|
+
UnsupportedStoreException,
|
|
4
|
+
} from '../../domain/errors/VersionManagerException';
|
|
5
|
+
import type {
|
|
6
|
+
RemoteVersionInfo,
|
|
7
|
+
UserVersionDecision,
|
|
8
|
+
VersionCheckRequest,
|
|
9
|
+
} from '../../domain/models/RemoteVersionInfo';
|
|
10
|
+
import type { ICacheStore } from '../../domain/ports/ICacheStore';
|
|
11
|
+
import type { IClock } from '../../domain/ports/IClock';
|
|
12
|
+
import type { IKeyValueStorage } from '../../domain/ports/IPlatformBridge';
|
|
13
|
+
import type { IStoreProvider } from '../../domain/ports/IStoreProvider';
|
|
14
|
+
import type { IVersionRepository } from '../../domain/ports/IVersionRepository';
|
|
15
|
+
|
|
16
|
+
const REMOTE_INFO_CACHE_KEY = 'remote-version-info';
|
|
17
|
+
const USER_DECISION_STORAGE_KEY = 'vm_user_decision';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sequential fallback across registered providers (Prompt 14's simpler tier): the first
|
|
21
|
+
* provider to succeed wins. Only providers the host app actually registers via DI
|
|
22
|
+
* reach this list — nothing here imports a provider it doesn't need (Prompt 1 §8).
|
|
23
|
+
*/
|
|
24
|
+
export class VersionRepositoryImpl implements IVersionRepository {
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly providers: readonly IStoreProvider[],
|
|
27
|
+
private readonly cache: ICacheStore<RemoteVersionInfo>,
|
|
28
|
+
private readonly storage: IKeyValueStorage,
|
|
29
|
+
private readonly clock: IClock
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
async getRemoteVersionInfo(
|
|
33
|
+
request: VersionCheckRequest
|
|
34
|
+
): Promise<RemoteVersionInfo> {
|
|
35
|
+
if (!request.bypassCache) {
|
|
36
|
+
const cached = await this.cache.get(REMOTE_INFO_CACHE_KEY);
|
|
37
|
+
if (cached) return cached;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.providers.length === 0) {
|
|
41
|
+
throw new UnsupportedStoreException({
|
|
42
|
+
message:
|
|
43
|
+
'No store providers are registered — configure() must include at least one of stores.ios/.android/.custom/etc',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let lastError: unknown = null;
|
|
48
|
+
for (const provider of this.providers) {
|
|
49
|
+
try {
|
|
50
|
+
const result = await provider.fetchLatestVersionInfo({
|
|
51
|
+
bundleId: request.bundleId,
|
|
52
|
+
region: null,
|
|
53
|
+
timeoutMs: request.timeoutMs,
|
|
54
|
+
});
|
|
55
|
+
const info: RemoteVersionInfo = {
|
|
56
|
+
latestVersion: result.latestVersion,
|
|
57
|
+
storeUrl: result.storeUrl,
|
|
58
|
+
releaseNotes: result.releaseNotes,
|
|
59
|
+
minimumOsVersion: result.minimumOsVersion,
|
|
60
|
+
fetchedAt: this.clock.now(),
|
|
61
|
+
provider: provider.id,
|
|
62
|
+
};
|
|
63
|
+
await this.cache.set(REMOTE_INFO_CACHE_KEY, info);
|
|
64
|
+
return info;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
lastError = error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw toVersionManagerException(lastError);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getCachedVersionInfo(): Promise<RemoteVersionInfo | null> {
|
|
74
|
+
return this.cache.get(REMOTE_INFO_CACHE_KEY);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async persistUserDecision(decision: UserVersionDecision): Promise<void> {
|
|
78
|
+
await this.storage.set(USER_DECISION_STORAGE_KEY, JSON.stringify(decision));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getUserDecision(): Promise<UserVersionDecision | null> {
|
|
82
|
+
const raw = await this.storage.get(USER_DECISION_STORAGE_KEY);
|
|
83
|
+
if (!raw) return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(raw) as UserVersionDecision;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { PersistentCacheStore } from '../data/cache/PersistentCacheStore';
|
|
2
|
+
import { MemoryCacheStore } from '../data/cache/MemoryCacheStore';
|
|
3
|
+
import { ConfigCache } from '../data/config/ConfigCache';
|
|
4
|
+
import { ConfigDocumentValidator } from '../data/config/ConfigDocumentValidator';
|
|
5
|
+
import { resolveConfigSync } from '../data/config/ConfigLoader';
|
|
6
|
+
import { ConfigProvider } from '../data/config/ConfigProvider';
|
|
7
|
+
import { SignatureVerifier } from '../data/config/SignatureVerifier';
|
|
8
|
+
import { EnvironmentOverrideSource } from '../data/config/sources/EnvironmentOverrideSource';
|
|
9
|
+
import { LocalConfigSource } from '../data/config/sources/LocalConfigSource';
|
|
10
|
+
import { RemoteConfigSource } from '../data/config/sources/RemoteConfigSource';
|
|
11
|
+
import { AppleStoreProvider } from '../data/providers/apple/AppleStoreProvider';
|
|
12
|
+
import { CustomApiProvider } from '../data/providers/custom-api/CustomApiProvider';
|
|
13
|
+
import { GooglePlayProvider } from '../data/providers/google-play/GooglePlayProvider';
|
|
14
|
+
import { VersionRepositoryImpl } from '../data/repositories/VersionRepositoryImpl';
|
|
15
|
+
import { DecisionEngine } from '../domain/engines/decision/DecisionEngine';
|
|
16
|
+
import { PolicyEngine } from '../domain/engines/policy/PolicyEngine';
|
|
17
|
+
import { SemVerEngine } from '../domain/engines/semver/SemVerEngine';
|
|
18
|
+
import type { VersionManagerEventMap } from '../domain/models/Events';
|
|
19
|
+
import type { RemoteVersionInfo } from '../domain/models/RemoteVersionInfo';
|
|
20
|
+
import type {
|
|
21
|
+
ResolvedVersionManagerConfig,
|
|
22
|
+
VersionManagerOptions,
|
|
23
|
+
} from '../domain/models/VersionManagerOptions';
|
|
24
|
+
import type { IPlatformBridge } from '../domain/ports/IPlatformBridge';
|
|
25
|
+
import type { IStoreProvider } from '../domain/ports/IStoreProvider';
|
|
26
|
+
import { VersionManagerCore } from '../domain/VersionManagerCore';
|
|
27
|
+
import type { IVersionManagerCore } from '../domain/IVersionManagerCore';
|
|
28
|
+
import { createPlatformBridge } from '../platform/createPlatformBridge';
|
|
29
|
+
import { EventBus } from '../shared/eventbus/EventBus';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Composition root (Prompt 1 §1.3) — the only file in the codebase that imports both a
|
|
33
|
+
* port and its concrete adapter simultaneously. Only Apple/Google Play/Custom API
|
|
34
|
+
* providers are wired here; Huawei/Amazon/Firebase Remote Config providers exist as
|
|
35
|
+
* independently importable files (Prompt 1 §8) but are intentionally not referenced
|
|
36
|
+
* from this file yet since their implementations are still stubs (see those files'
|
|
37
|
+
* doc comments) — wiring them here would defeat tree-shaking for no behavioral benefit.
|
|
38
|
+
*/
|
|
39
|
+
export function createVersionManagerCore(
|
|
40
|
+
options: VersionManagerOptions
|
|
41
|
+
): IVersionManagerCore {
|
|
42
|
+
// Synchronous — throws InvalidConfigException immediately for a bad options bag
|
|
43
|
+
// (Prompt 2 §1.3's fail-fast requirement), before any adapter is constructed.
|
|
44
|
+
const config: ResolvedVersionManagerConfig = resolveConfigSync(options);
|
|
45
|
+
|
|
46
|
+
const platformBridge: IPlatformBridge =
|
|
47
|
+
options.platformBridge ?? createPlatformBridge();
|
|
48
|
+
const clock = platformBridge.clock;
|
|
49
|
+
const comparator = new SemVerEngine();
|
|
50
|
+
const policyEngine = new PolicyEngine(comparator);
|
|
51
|
+
const decisionEngine = new DecisionEngine(clock);
|
|
52
|
+
const eventBus = new EventBus<VersionManagerEventMap>();
|
|
53
|
+
|
|
54
|
+
const providers = buildProviders(config, platformBridge);
|
|
55
|
+
const remoteInfoCache =
|
|
56
|
+
config.cache.storage === 'memory'
|
|
57
|
+
? new MemoryCacheStore<RemoteVersionInfo>(clock, config.cache.ttlMs)
|
|
58
|
+
: new PersistentCacheStore<RemoteVersionInfo>(
|
|
59
|
+
platformBridge.storage,
|
|
60
|
+
clock,
|
|
61
|
+
config.cache.ttlMs
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const repository = new VersionRepositoryImpl(
|
|
65
|
+
providers,
|
|
66
|
+
remoteInfoCache,
|
|
67
|
+
platformBridge.storage,
|
|
68
|
+
clock
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const configProvider = new ConfigProvider({
|
|
72
|
+
local: new LocalConfigSource(options.configSources?.local),
|
|
73
|
+
remote: new RemoteConfigSource(platformBridge.http),
|
|
74
|
+
envOverrides: new EnvironmentOverrideSource(
|
|
75
|
+
options.configSources?.envOverrides
|
|
76
|
+
),
|
|
77
|
+
cache: new ConfigCache(platformBridge.storage),
|
|
78
|
+
validator: new ConfigDocumentValidator(),
|
|
79
|
+
signatureVerifier: new SignatureVerifier(platformBridge.crypto),
|
|
80
|
+
clock,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return new VersionManagerCore({
|
|
84
|
+
config,
|
|
85
|
+
rawOptions: options,
|
|
86
|
+
platformBridge,
|
|
87
|
+
repository,
|
|
88
|
+
comparator,
|
|
89
|
+
policyEngine,
|
|
90
|
+
decisionEngine,
|
|
91
|
+
eventBus,
|
|
92
|
+
clock,
|
|
93
|
+
configProvider,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildProviders(
|
|
98
|
+
config: ResolvedVersionManagerConfig,
|
|
99
|
+
bridge: IPlatformBridge
|
|
100
|
+
): IStoreProvider[] {
|
|
101
|
+
const providers: IStoreProvider[] = [];
|
|
102
|
+
|
|
103
|
+
if (bridge.platform === 'ios' && config.stores.ios) {
|
|
104
|
+
providers.push(
|
|
105
|
+
new AppleStoreProvider(
|
|
106
|
+
bridge.http,
|
|
107
|
+
config.stores.ios.appStoreId,
|
|
108
|
+
config.stores.ios.region
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (bridge.platform === 'android' && config.stores.android) {
|
|
113
|
+
providers.push(
|
|
114
|
+
new GooglePlayProvider(bridge.http, config.stores.android.packageName)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (config.stores.custom) {
|
|
118
|
+
providers.push(
|
|
119
|
+
new CustomApiProvider(
|
|
120
|
+
bridge.http,
|
|
121
|
+
config.stores.custom.url,
|
|
122
|
+
config.stores.custom.headers
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return providers;
|
|
128
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ActionPlan } from './models/ActionPlan';
|
|
2
|
+
import type {
|
|
3
|
+
StateChangedEvent,
|
|
4
|
+
UpdateDetectedEvent,
|
|
5
|
+
UserActionEvent,
|
|
6
|
+
VersionManagerErrorEvent,
|
|
7
|
+
VersionManagerEventMap,
|
|
8
|
+
} from './models/Events';
|
|
9
|
+
import type { LifecycleState } from './models/LifecycleState';
|
|
10
|
+
import type {
|
|
11
|
+
CheckForUpdatesOptions,
|
|
12
|
+
ForceTriggerOptions,
|
|
13
|
+
ResetIgnoredVersionsOptions,
|
|
14
|
+
} from './models/PublicApiOptions';
|
|
15
|
+
import type { UpdateInfo } from './models/UpdateInfo';
|
|
16
|
+
import type { Unsubscribe } from './models/Unsubscribe';
|
|
17
|
+
|
|
18
|
+
/** Public event subscription surface (Prompt 2 §4.2). */
|
|
19
|
+
export interface IVersionManagerEvents {
|
|
20
|
+
on<K extends keyof VersionManagerEventMap>(
|
|
21
|
+
event: K,
|
|
22
|
+
handler: (payload: VersionManagerEventMap[K]) => void
|
|
23
|
+
): Unsubscribe;
|
|
24
|
+
once<K extends keyof VersionManagerEventMap>(
|
|
25
|
+
event: K,
|
|
26
|
+
handler: (payload: VersionManagerEventMap[K]) => void
|
|
27
|
+
): Unsubscribe;
|
|
28
|
+
off<K extends keyof VersionManagerEventMap>(
|
|
29
|
+
event: K,
|
|
30
|
+
handler: (payload: VersionManagerEventMap[K]) => void
|
|
31
|
+
): void;
|
|
32
|
+
|
|
33
|
+
onUpdateDetected(handler: (e: UpdateDetectedEvent) => void): Unsubscribe;
|
|
34
|
+
onUserAction(handler: (e: UserActionEvent) => void): Unsubscribe;
|
|
35
|
+
onError(handler: (e: VersionManagerErrorEvent) => void): Unsubscribe;
|
|
36
|
+
onStateChanged(handler: (e: StateChangedEvent) => void): Unsubscribe;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The single class Domain exposes outward (Prompt 1 §3; full spec in Prompt 2). */
|
|
40
|
+
export interface IVersionManagerCore extends IVersionManagerEvents {
|
|
41
|
+
ready(): Promise<void>;
|
|
42
|
+
|
|
43
|
+
checkForUpdates(options?: CheckForUpdatesOptions): Promise<ActionPlan>;
|
|
44
|
+
forceTriggerUpdate(options?: ForceTriggerOptions): Promise<void>;
|
|
45
|
+
resetIgnoredVersions(options?: ResetIgnoredVersionsOptions): Promise<void>;
|
|
46
|
+
|
|
47
|
+
getCurrentState(): LifecycleState;
|
|
48
|
+
getUpdateInfo(): UpdateInfo | null;
|
|
49
|
+
isUpdateAvailable(): boolean;
|
|
50
|
+
getActionPlan(): ActionPlan | null;
|
|
51
|
+
getLastCheckedAt(): number | null;
|
|
52
|
+
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|