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