@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,309 @@
|
|
|
1
|
+
import { parseVersion } from '../../domain/engines/semver/SemVerEngine';
|
|
2
|
+
import type {
|
|
3
|
+
BoundaryValidationResult,
|
|
4
|
+
RawConfigDocument,
|
|
5
|
+
SchemaValidationResult,
|
|
6
|
+
} from '../../domain/models/ConfigDocument';
|
|
7
|
+
import type { IConfigValidator } from '../../domain/ports/IConfigSources';
|
|
8
|
+
|
|
9
|
+
/** Doc 03 §2.2 — serialized document size cap, enforced before parsing even begins. */
|
|
10
|
+
export const CONFIG_ENVELOPE_MAX_BYTES = 64 * 1024;
|
|
11
|
+
|
|
12
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
13
|
+
const HTTPS_URL_PATTERN = /^https:\/\//;
|
|
14
|
+
const APP_STORE_ID_PATTERN = /^[0-9]{6,12}$/;
|
|
15
|
+
const ANDROID_PACKAGE_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
16
|
+
const AMAZON_ASIN_PATTERN = /^[A-Z0-9]{10}$/;
|
|
17
|
+
const HUAWEI_APP_ID_PATTERN = /^[0-9]{6,12}$/;
|
|
18
|
+
const SCHEMA_VERSION_PATTERN = /^\d+\.\d+$/;
|
|
19
|
+
|
|
20
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
21
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Hand-rolled, zero-dependency structural validator (Prompt 1 §9.3 — no ajv) that walks
|
|
26
|
+
* a SignedConfigEnvelope against the JSON Schema in doc 03 §6: required-field presence,
|
|
27
|
+
* type checks, enum membership, and additionalProperties:false at every level (unknown
|
|
28
|
+
* fields are rejected, not silently ignored — §2.1's downgrade-attack rationale).
|
|
29
|
+
*/
|
|
30
|
+
export class ConfigDocumentValidator implements IConfigValidator {
|
|
31
|
+
validateSchema(doc: RawConfigDocument): SchemaValidationResult {
|
|
32
|
+
const errors: string[] = [];
|
|
33
|
+
|
|
34
|
+
if (typeof doc === 'string' && doc.length > CONFIG_ENVELOPE_MAX_BYTES) {
|
|
35
|
+
// Cheap byte-length check that short-circuits pathological payloads (§2.2, VM-1007
|
|
36
|
+
// is raised by the caller — this method only reports the schema-shape verdict).
|
|
37
|
+
errors.push(
|
|
38
|
+
`Envelope exceeds ${CONFIG_ENVELOPE_MAX_BYTES} bytes before parsing`
|
|
39
|
+
);
|
|
40
|
+
return { valid: false, errors };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let envelope: unknown = doc;
|
|
44
|
+
if (typeof doc === 'string') {
|
|
45
|
+
try {
|
|
46
|
+
envelope = JSON.parse(doc);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
errors: [`Envelope is not valid JSON: ${String(error)}`],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!isPlainObject(envelope)) {
|
|
56
|
+
return { valid: false, errors: ['Envelope must be a JSON object'] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.checkAdditionalProperties(
|
|
60
|
+
envelope,
|
|
61
|
+
['schemaVersion', 'payload', 'signature'],
|
|
62
|
+
'envelope',
|
|
63
|
+
errors
|
|
64
|
+
);
|
|
65
|
+
this.requireField(envelope, 'schemaVersion', 'string', errors, 'envelope');
|
|
66
|
+
this.requireField(envelope, 'payload', 'object', errors, 'envelope');
|
|
67
|
+
this.requireField(envelope, 'signature', 'object', errors, 'envelope');
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
typeof envelope.schemaVersion === 'string' &&
|
|
71
|
+
!SCHEMA_VERSION_PATTERN.test(envelope.schemaVersion)
|
|
72
|
+
) {
|
|
73
|
+
errors.push('envelope.schemaVersion must match ^\\d+\\.\\d+$');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isPlainObject(envelope.signature)) {
|
|
77
|
+
const sig = envelope.signature;
|
|
78
|
+
this.checkAdditionalProperties(
|
|
79
|
+
sig,
|
|
80
|
+
['algorithm', 'keyId', 'value', 'signedAt'],
|
|
81
|
+
'signature',
|
|
82
|
+
errors
|
|
83
|
+
);
|
|
84
|
+
this.requireField(sig, 'algorithm', 'string', errors, 'signature');
|
|
85
|
+
this.requireField(sig, 'keyId', 'string', errors, 'signature');
|
|
86
|
+
this.requireField(sig, 'value', 'string', errors, 'signature');
|
|
87
|
+
this.requireField(sig, 'signedAt', 'string', errors, 'signature');
|
|
88
|
+
if (
|
|
89
|
+
typeof sig.algorithm === 'string' &&
|
|
90
|
+
!['ed25519', 'hmac-sha256', 'none'].includes(sig.algorithm)
|
|
91
|
+
) {
|
|
92
|
+
errors.push(
|
|
93
|
+
'signature.algorithm must be one of ed25519 | hmac-sha256 | none'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isPlainObject(envelope.payload)) {
|
|
99
|
+
this.validatePayloadShape(envelope.payload, errors);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { valid: errors.length === 0, errors };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
validateBoundaries(doc: RawConfigDocument): BoundaryValidationResult {
|
|
106
|
+
const fieldErrors = new Map<string, string>();
|
|
107
|
+
const envelope =
|
|
108
|
+
typeof doc === 'string'
|
|
109
|
+
? safeParse(doc)
|
|
110
|
+
: (doc as Record<string, unknown>);
|
|
111
|
+
const payload = isPlainObject(envelope)
|
|
112
|
+
? (envelope.payload as Record<string, unknown> | undefined)
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
115
|
+
if (!isPlainObject(payload)) {
|
|
116
|
+
return { valid: true, fieldErrors };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const network = payload.network as Record<string, unknown> | undefined;
|
|
120
|
+
if (isPlainObject(network)) {
|
|
121
|
+
const timeout = network.requestTimeoutMs;
|
|
122
|
+
if (typeof timeout === 'number' && timeout <= 0) {
|
|
123
|
+
fieldErrors.set('network.requestTimeoutMs', 'must be > 0');
|
|
124
|
+
}
|
|
125
|
+
const retry = network.retry as Record<string, unknown> | undefined;
|
|
126
|
+
if (isPlainObject(retry)) {
|
|
127
|
+
const maxAttempts = retry.maxAttempts;
|
|
128
|
+
if (
|
|
129
|
+
typeof maxAttempts === 'number' &&
|
|
130
|
+
(maxAttempts < 0 || maxAttempts > 10)
|
|
131
|
+
) {
|
|
132
|
+
fieldErrors.set(
|
|
133
|
+
'network.retry.maxAttempts',
|
|
134
|
+
'must be between 0 and 10'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const baseDelayMs = retry.baseDelayMs;
|
|
138
|
+
if (typeof baseDelayMs === 'number' && baseDelayMs <= 0) {
|
|
139
|
+
fieldErrors.set('network.retry.baseDelayMs', 'must be > 0');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const cache = payload.cache as Record<string, unknown> | undefined;
|
|
145
|
+
if (
|
|
146
|
+
isPlainObject(cache) &&
|
|
147
|
+
typeof cache.ttlMs === 'number' &&
|
|
148
|
+
cache.ttlMs < 0
|
|
149
|
+
) {
|
|
150
|
+
fieldErrors.set('cache.ttlMs', 'must be >= 0');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
typeof payload.localVersionIdentifier === 'string' &&
|
|
155
|
+
!this.isValidSemVer(payload.localVersionIdentifier)
|
|
156
|
+
) {
|
|
157
|
+
fieldErrors.set('localVersionIdentifier', 'not a valid SemVer string');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const alerts = payload.alerts as Record<string, unknown> | undefined;
|
|
161
|
+
if (
|
|
162
|
+
isPlainObject(alerts) &&
|
|
163
|
+
typeof alerts.forceUpdateBelow === 'string' &&
|
|
164
|
+
!this.isValidSemVer(alerts.forceUpdateBelow)
|
|
165
|
+
) {
|
|
166
|
+
fieldErrors.set('alerts.forceUpdateBelow', 'not a valid SemVer string');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
typeof payload.remoteConfigUrl === 'string' &&
|
|
171
|
+
!HTTPS_URL_PATTERN.test(payload.remoteConfigUrl)
|
|
172
|
+
) {
|
|
173
|
+
fieldErrors.set('remoteConfigUrl', 'must use https://');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const stores = payload.stores as Record<string, unknown> | undefined;
|
|
177
|
+
if (isPlainObject(stores)) {
|
|
178
|
+
this.checkStoreShape(
|
|
179
|
+
stores,
|
|
180
|
+
'ios',
|
|
181
|
+
'appStoreId',
|
|
182
|
+
APP_STORE_ID_PATTERN,
|
|
183
|
+
fieldErrors
|
|
184
|
+
);
|
|
185
|
+
this.checkStoreShape(
|
|
186
|
+
stores,
|
|
187
|
+
'android',
|
|
188
|
+
'packageName',
|
|
189
|
+
ANDROID_PACKAGE_PATTERN,
|
|
190
|
+
fieldErrors
|
|
191
|
+
);
|
|
192
|
+
this.checkStoreShape(
|
|
193
|
+
stores,
|
|
194
|
+
'huawei',
|
|
195
|
+
'appId',
|
|
196
|
+
HUAWEI_APP_ID_PATTERN,
|
|
197
|
+
fieldErrors
|
|
198
|
+
);
|
|
199
|
+
this.checkStoreShape(
|
|
200
|
+
stores,
|
|
201
|
+
'amazon',
|
|
202
|
+
'asin',
|
|
203
|
+
AMAZON_ASIN_PATTERN,
|
|
204
|
+
fieldErrors
|
|
205
|
+
);
|
|
206
|
+
const custom = stores.custom as Record<string, unknown> | undefined;
|
|
207
|
+
if (
|
|
208
|
+
isPlainObject(custom) &&
|
|
209
|
+
typeof custom.url === 'string' &&
|
|
210
|
+
!HTTPS_URL_PATTERN.test(custom.url)
|
|
211
|
+
) {
|
|
212
|
+
fieldErrors.set('stores.custom.url', 'must use https://');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { valid: fieldErrors.size === 0, fieldErrors };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private isValidSemVer(v: string): boolean {
|
|
220
|
+
if (!SEMVER_PATTERN.test(v)) return false;
|
|
221
|
+
try {
|
|
222
|
+
parseVersion(v);
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private checkStoreShape(
|
|
230
|
+
stores: Record<string, unknown>,
|
|
231
|
+
key: string,
|
|
232
|
+
field: string,
|
|
233
|
+
pattern: RegExp,
|
|
234
|
+
fieldErrors: Map<string, string>
|
|
235
|
+
): void {
|
|
236
|
+
const entry = stores[key] as Record<string, unknown> | undefined;
|
|
237
|
+
if (isPlainObject(entry) && typeof entry[field] === 'string') {
|
|
238
|
+
const value = entry[field] as string;
|
|
239
|
+
if (!pattern.test(value)) {
|
|
240
|
+
fieldErrors.set(
|
|
241
|
+
`stores.${key}.${field}`,
|
|
242
|
+
`"${value}" has invalid shape`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private validatePayloadShape(
|
|
249
|
+
payload: Record<string, unknown>,
|
|
250
|
+
errors: string[]
|
|
251
|
+
): void {
|
|
252
|
+
this.checkAdditionalProperties(
|
|
253
|
+
payload,
|
|
254
|
+
[
|
|
255
|
+
'localVersionIdentifier',
|
|
256
|
+
'remoteConfigUrl',
|
|
257
|
+
'cache',
|
|
258
|
+
'network',
|
|
259
|
+
'alerts',
|
|
260
|
+
'stores',
|
|
261
|
+
'security',
|
|
262
|
+
'schemaVersion',
|
|
263
|
+
],
|
|
264
|
+
'payload',
|
|
265
|
+
errors
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private requireField(
|
|
270
|
+
obj: Record<string, unknown>,
|
|
271
|
+
field: string,
|
|
272
|
+
type: 'string' | 'object',
|
|
273
|
+
errors: string[],
|
|
274
|
+
context: string
|
|
275
|
+
): void {
|
|
276
|
+
const value = obj[field];
|
|
277
|
+
if (value === undefined) {
|
|
278
|
+
errors.push(`${context}.${field} is required`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (type === 'string' && typeof value !== 'string') {
|
|
282
|
+
errors.push(`${context}.${field} must be a string`);
|
|
283
|
+
}
|
|
284
|
+
if (type === 'object' && !isPlainObject(value)) {
|
|
285
|
+
errors.push(`${context}.${field} must be an object`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private checkAdditionalProperties(
|
|
290
|
+
obj: Record<string, unknown>,
|
|
291
|
+
allowed: string[],
|
|
292
|
+
context: string,
|
|
293
|
+
errors: string[]
|
|
294
|
+
): void {
|
|
295
|
+
for (const key of Object.keys(obj)) {
|
|
296
|
+
if (!allowed.includes(key)) {
|
|
297
|
+
errors.push(`${context}.${key} is not a recognized field`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function safeParse(input: string): unknown {
|
|
304
|
+
try {
|
|
305
|
+
return JSON.parse(input);
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ConfigLoadRequest } from '../../domain/models/ConfigDocument';
|
|
2
|
+
import type { IConfigProvider } from '../../domain/ports/IConfigProvider';
|
|
3
|
+
import type { Unsubscribe } from '../../domain/models/Unsubscribe';
|
|
4
|
+
import type {
|
|
5
|
+
ResolvedVersionManagerConfig,
|
|
6
|
+
VersionManagerOptions,
|
|
7
|
+
} from '../../domain/models/VersionManagerOptions';
|
|
8
|
+
import { ConsoleLogSink } from '../../shared/logging/Logger';
|
|
9
|
+
import type { ConfigResolutionPipeline } from './ConfigResolutionPipeline';
|
|
10
|
+
import { validateOptions } from './ConfigValidator';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous default-merge (doc 03 §4.2 tier 4 only). Kept as a plain function, not
|
|
14
|
+
* just a method on ConfigLoader, so the DI composition root (src/di/container.ts) can
|
|
15
|
+
* validate + resolve synchronously at configure()-time (doc 02 §1.3's fail-fast
|
|
16
|
+
* requirement) without awaiting a Promise, and so ConfigResolutionPipeline can call it
|
|
17
|
+
* as its always-valid tier-4 floor (doc 03 §7.2's termination guarantee).
|
|
18
|
+
*/
|
|
19
|
+
export function resolveConfigSync(
|
|
20
|
+
defaults: VersionManagerOptions
|
|
21
|
+
): ResolvedVersionManagerConfig {
|
|
22
|
+
validateOptions(defaults);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
appVersion: defaults.appVersion ?? null,
|
|
26
|
+
logging: {
|
|
27
|
+
level: defaults.logging?.level ?? 'warn',
|
|
28
|
+
sink: defaults.logging?.sink ?? new ConsoleLogSink(),
|
|
29
|
+
},
|
|
30
|
+
cache: {
|
|
31
|
+
ttlMs: defaults.cache?.ttlMs ?? 21_600_000,
|
|
32
|
+
storage: defaults.cache?.storage ?? 'persistent',
|
|
33
|
+
bustOnManualCheck: defaults.cache?.bustOnManualCheck ?? true,
|
|
34
|
+
},
|
|
35
|
+
fallback: {
|
|
36
|
+
onNetworkError: defaults.fallback?.onNetworkError ?? 'useCache',
|
|
37
|
+
defaultConfig: defaults.fallback?.defaultConfig ?? null,
|
|
38
|
+
requestTimeoutMs: defaults.fallback?.requestTimeoutMs ?? 8_000,
|
|
39
|
+
retry: {
|
|
40
|
+
maxAttempts: defaults.fallback?.retry?.maxAttempts ?? 3,
|
|
41
|
+
backoff: defaults.fallback?.retry?.backoff ?? 'exponential-jitter',
|
|
42
|
+
baseDelayMs: defaults.fallback?.retry?.baseDelayMs ?? 500,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
stores: defaults.stores,
|
|
46
|
+
policy: {
|
|
47
|
+
forceUpdateBelow: defaults.policy?.forceUpdateBelow ?? null,
|
|
48
|
+
reminderIntervalMs: defaults.policy?.reminderIntervalMs ?? 259_200_000,
|
|
49
|
+
rolloutPercentage: defaults.policy?.rolloutPercentage ?? 100,
|
|
50
|
+
},
|
|
51
|
+
strictReadiness: defaults.strictReadiness ?? false,
|
|
52
|
+
remoteConfigUrl: defaults.remoteConfigUrl ?? null,
|
|
53
|
+
security: {
|
|
54
|
+
signatureAlgorithm: defaults.security?.signatureAlgorithm ?? 'none',
|
|
55
|
+
trustedKeyIds: defaults.security?.trustedKeyIds ?? [],
|
|
56
|
+
},
|
|
57
|
+
schemaVersion: '1.0',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Doc 03 §0's IConfigProvider implementation — thin wrapper around
|
|
63
|
+
* ConfigResolutionPipeline that adds change-detection so subscribe() only fires when a
|
|
64
|
+
* later resolution actually differs from the last one (§4.4 hot-update contract).
|
|
65
|
+
*/
|
|
66
|
+
export class ConfigLoader implements IConfigProvider {
|
|
67
|
+
private readonly listeners = new Set<
|
|
68
|
+
(config: ResolvedVersionManagerConfig) => void
|
|
69
|
+
>();
|
|
70
|
+
private lastResolved: ResolvedVersionManagerConfig | null = null;
|
|
71
|
+
|
|
72
|
+
constructor(private readonly pipeline?: ConfigResolutionPipeline) {}
|
|
73
|
+
|
|
74
|
+
async resolve(
|
|
75
|
+
defaults: VersionManagerOptions
|
|
76
|
+
): Promise<ResolvedVersionManagerConfig> {
|
|
77
|
+
const config = this.pipeline
|
|
78
|
+
? (await this.pipeline.resolve(defaults)).config
|
|
79
|
+
: resolveConfigSync(defaults);
|
|
80
|
+
|
|
81
|
+
const changed =
|
|
82
|
+
this.lastResolved !== null &&
|
|
83
|
+
JSON.stringify(this.lastResolved) !== JSON.stringify(config);
|
|
84
|
+
this.lastResolved = config;
|
|
85
|
+
if (changed) {
|
|
86
|
+
for (const listener of Array.from(this.listeners)) listener(config);
|
|
87
|
+
}
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async load(
|
|
92
|
+
request: ConfigLoadRequest
|
|
93
|
+
): Promise<ResolvedVersionManagerConfig> {
|
|
94
|
+
return this.resolve(request.defaults);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
subscribe(
|
|
98
|
+
listener: (config: ResolvedVersionManagerConfig) => void
|
|
99
|
+
): Unsubscribe {
|
|
100
|
+
this.listeners.add(listener);
|
|
101
|
+
return () => {
|
|
102
|
+
this.listeners.delete(listener);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ConfigResolutionPipeline } from './ConfigResolutionPipeline';
|
|
2
|
+
import type { ConfigResolutionPipelineDeps } from './ConfigResolutionPipeline';
|
|
3
|
+
import type { ConfigLoadRequest } from '../../domain/models/ConfigDocument';
|
|
4
|
+
import type { IConfigProvider } from '../../domain/ports/IConfigProvider';
|
|
5
|
+
import type { Unsubscribe } from '../../domain/ports/IPlatformBridge';
|
|
6
|
+
import type {
|
|
7
|
+
ResolvedVersionManagerConfig,
|
|
8
|
+
VersionManagerOptions,
|
|
9
|
+
} from '../../domain/models/VersionManagerOptions';
|
|
10
|
+
|
|
11
|
+
export interface ConfigProviderDeps extends ConfigResolutionPipelineDeps {
|
|
12
|
+
/** Background remote-refresh cadence for subscribe() (doc 03 §4.4). 0/undefined disables polling — resolve() still works. */
|
|
13
|
+
readonly refreshIntervalMs?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* IConfigProvider adapter around ConfigResolutionPipeline — resolve()/load() delegate
|
|
18
|
+
* straight through, and subscribe() adds the doc 03 §4.4 "hot-update" behavior the raw
|
|
19
|
+
* pipeline doesn't provide on its own: a background poll that re-runs resolution and
|
|
20
|
+
* notifies listeners only when the resolved config actually changed.
|
|
21
|
+
*/
|
|
22
|
+
export class ConfigProvider implements IConfigProvider {
|
|
23
|
+
private readonly pipeline: ConfigResolutionPipeline;
|
|
24
|
+
private readonly refreshIntervalMs: number;
|
|
25
|
+
private readonly listeners = new Set<
|
|
26
|
+
(config: ResolvedVersionManagerConfig) => void
|
|
27
|
+
>();
|
|
28
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
29
|
+
private lastConfig: ResolvedVersionManagerConfig | null = null;
|
|
30
|
+
private lastOptions: VersionManagerOptions | null = null;
|
|
31
|
+
|
|
32
|
+
constructor(deps: ConfigProviderDeps) {
|
|
33
|
+
this.pipeline = new ConfigResolutionPipeline(deps);
|
|
34
|
+
this.refreshIntervalMs = deps.refreshIntervalMs ?? 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async resolve(
|
|
38
|
+
defaults: VersionManagerOptions
|
|
39
|
+
): Promise<ResolvedVersionManagerConfig> {
|
|
40
|
+
this.lastOptions = defaults;
|
|
41
|
+
const { config } = await this.pipeline.resolve(defaults);
|
|
42
|
+
this.lastConfig = config;
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
load(request: ConfigLoadRequest): Promise<ResolvedVersionManagerConfig> {
|
|
47
|
+
return this.resolve(request.defaults);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
subscribe(
|
|
51
|
+
listener: (config: ResolvedVersionManagerConfig) => void
|
|
52
|
+
): Unsubscribe {
|
|
53
|
+
this.listeners.add(listener);
|
|
54
|
+
this.ensurePolling();
|
|
55
|
+
return () => {
|
|
56
|
+
this.listeners.delete(listener);
|
|
57
|
+
if (this.listeners.size === 0 && this.timer) {
|
|
58
|
+
clearInterval(this.timer);
|
|
59
|
+
this.timer = null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private ensurePolling(): void {
|
|
65
|
+
if (this.timer || this.refreshIntervalMs <= 0 || !this.lastOptions) return;
|
|
66
|
+
this.timer = setInterval(() => {
|
|
67
|
+
void this.refresh();
|
|
68
|
+
}, this.refreshIntervalMs);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async refresh(): Promise<void> {
|
|
72
|
+
if (!this.lastOptions) return;
|
|
73
|
+
// A background poll tick must never surface as an unhandled rejection — the
|
|
74
|
+
// pipeline already reports validation/network failures via its own onError hook.
|
|
75
|
+
const result = await this.pipeline
|
|
76
|
+
.resolve(this.lastOptions)
|
|
77
|
+
.catch(() => null);
|
|
78
|
+
if (!result) return;
|
|
79
|
+
if (JSON.stringify(result.config) === JSON.stringify(this.lastConfig))
|
|
80
|
+
return;
|
|
81
|
+
this.lastConfig = result.config;
|
|
82
|
+
for (const listener of this.listeners) listener(result.config);
|
|
83
|
+
}
|
|
84
|
+
}
|