pulse-updates 1.0.0
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/README.md +333 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/results.bin +1 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/AssetModel.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/BuildConfig.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/BundleModel.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/CheckResult.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/FetchResult.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/ManifestModel$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/ManifestModel.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseController$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseController$Delegate.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseController$errorRecoveryDelegate$1.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseController.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseControllerKt.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseIntegrityCheck.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseReactHostDelegate.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseReactHostFactory.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseReaper.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseRemoteLoader.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseUpdatesConfig.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseUpdatesException.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseUpdatesModule$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseUpdatesModule.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/PulseUpdatesPackage.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/SignatureModel.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/UiThreadUtil.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseAsset.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseDatabase.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseDatabaseSchema.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseUpdate.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseUpdateStatus$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/database/PulseUpdateStatus.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecovery$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecovery.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$LauncherCallback.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$RemoteLoadStatus.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$MessageType.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Task.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$WhenMappings.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$tryRelaunchFromCache$1.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/EmbeddedAsset$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/EmbeddedAsset.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/EmbeddedManifest$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/EmbeddedManifest.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/LauncherException.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/PulseAppLauncher$Companion.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/PulseAppLauncher.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/PulseDefaultSelectionPolicy.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/PulseSelectionPolicy.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/app/pulse/updates/launcher/PulseUpdateInfo.dex +0 -0
- package/android/build/.transforms/051997ae5715069c07acae848c6dd9c5/transformed/bundleLibRuntimeToDirRelease/desugar_graph.bin +0 -0
- package/android/build/.transforms/1a2af57545c1657f87f3de30891a3470/results.bin +1 -0
- package/android/build/.transforms/1a2af57545c1657f87f3de30891a3470/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/generated/source/buildConfig/debug/app/pulse/updates/BuildConfig.java +10 -0
- package/android/build/generated/source/buildConfig/release/app/pulse/updates/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +9 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml +9 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/compile_symbol_list/release/generateReleaseRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeReleaseAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeReleaseShaders/merger.xml +2 -0
- package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/release/packageReleaseResources/merger.xml +2 -0
- package/android/build/intermediates/java_res/release/processReleaseJavaRes/out/META-INF/pulse-updates_release.kotlin_module +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/app/pulse/updates/BuildConfig.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
- package/android/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +11 -0
- package/android/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt +11 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +9 -0
- package/android/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml +9 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/navigation_json/release/extractDeepLinksRelease/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/META-INF/pulse-updates_release.kotlin_module +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/AssetModel.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/BundleModel.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/CheckResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/FetchResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/ManifestModel$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/ManifestModel.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseController$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseController$Delegate.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseController$errorRecoveryDelegate$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseController.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseControllerKt.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseIntegrityCheck.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseReactHostDelegate.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseReactHostFactory.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseReaper.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseRemoteLoader.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseUpdatesConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseUpdatesException.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseUpdatesModule$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseUpdatesModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/PulseUpdatesPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/SignatureModel.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/UiThreadUtil.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseAsset.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseDatabase.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseDatabaseSchema.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseUpdate.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseUpdateStatus$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/database/PulseUpdateStatus.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecovery$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecovery.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$LauncherCallback.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$RemoteLoadStatus.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$MessageType.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Task.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$WhenMappings.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$tryRelaunchFromCache$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/EmbeddedAsset$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/EmbeddedAsset.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/EmbeddedManifest$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/EmbeddedManifest.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/LauncherException.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/PulseAppLauncher$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/PulseAppLauncher.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/PulseDefaultSelectionPolicy.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/PulseSelectionPolicy.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/app/pulse/updates/launcher/PulseUpdateInfo.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
- package/android/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt +1 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +1 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.s +1 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.s +4 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.s +3 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +1 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.values +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.s +1 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
- package/android/build/kotlin/compileReleaseKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileReleaseKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileReleaseKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +20 -0
- package/android/build/outputs/logs/manifest-merger-release-report.txt +20 -0
- package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/META-INF/pulse-updates_debug.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/AssetModel.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/BundleModel.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/CheckResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/FetchResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/ManifestModel$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/ManifestModel.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseController$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseController$Delegate.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseController$errorRecoveryDelegate$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseController.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseControllerKt.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseIntegrityCheck.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseReaper.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseRemoteLoader.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseUpdatesConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseUpdatesException.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseUpdatesModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseUpdatesModule.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/PulseUpdatesPackage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/SignatureModel.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/UiThreadUtil.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseAsset.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseDatabase.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseDatabaseSchema.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseUpdate.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseUpdateStatus$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/database/PulseUpdateStatus.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecovery$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecovery.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$LauncherCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$RemoteLoadStatus.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$MessageType.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Task.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$WhenMappings.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$tryRelaunchFromCache$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/EmbeddedAsset$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/EmbeddedAsset.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/EmbeddedManifest$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/EmbeddedManifest.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/LauncherException.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/PulseAppLauncher$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/PulseAppLauncher.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/PulseDefaultSelectionPolicy.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/PulseSelectionPolicy.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/app/pulse/updates/launcher/PulseUpdateInfo.class +0 -0
- package/android/build/tmp/kotlin-classes/release/META-INF/pulse-updates_release.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/AssetModel.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/BundleModel.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/CheckResult.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/FetchResult.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/ManifestModel$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/ManifestModel.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseController$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseController$Delegate.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseController$errorRecoveryDelegate$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseController.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseControllerKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseIntegrityCheck.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseReactHostDelegate.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseReactHostFactory.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseReaper.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseRemoteLoader.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseUpdatesConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseUpdatesException.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseUpdatesModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseUpdatesModule.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/PulseUpdatesPackage.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/SignatureModel.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/UiThreadUtil.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseAsset.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseDatabase.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseDatabaseSchema.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseUpdate.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseUpdateStatus$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/database/PulseUpdateStatus.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecovery$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecovery.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$LauncherCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate$RemoteLoadStatus.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$MessageType.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$Task.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$WhenMappings.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler$tryRelaunchFromCache$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/EmbeddedAsset$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/EmbeddedAsset.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/EmbeddedManifest$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/EmbeddedManifest.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/LauncherException.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/PulseAppLauncher$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/PulseAppLauncher.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/PulseDefaultSelectionPolicy.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/PulseSelectionPolicy.class +0 -0
- package/android/build/tmp/kotlin-classes/release/app/pulse/updates/launcher/PulseUpdateInfo.class +0 -0
- package/android/build.gradle +53 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/app/pulse/updates/PulseController.kt +1419 -0
- package/android/src/main/java/app/pulse/updates/PulseReactHostFactory.kt +116 -0
- package/android/src/main/java/app/pulse/updates/PulseUpdatesModule.kt +314 -0
- package/android/src/main/java/app/pulse/updates/PulseUpdatesPackage.kt +16 -0
- package/android/src/main/java/app/pulse/updates/database/PulseDatabase.kt +400 -0
- package/android/src/main/java/app/pulse/updates/database/PulseDatabaseSchema.kt +90 -0
- package/android/src/main/java/app/pulse/updates/errorrecovery/PulseErrorRecovery.kt +89 -0
- package/android/src/main/java/app/pulse/updates/errorrecovery/PulseErrorRecoveryDelegate.kt +30 -0
- package/android/src/main/java/app/pulse/updates/errorrecovery/PulseErrorRecoveryHandler.kt +159 -0
- package/android/src/main/java/app/pulse/updates/launcher/PulseAppLauncher.kt +472 -0
- package/ios/PulseUpdates/AppLauncher/PulseAppLauncher.swift +551 -0
- package/ios/PulseUpdates/Database/PulseDatabase.swift +557 -0
- package/ios/PulseUpdates/Database/PulseDatabaseSchema.swift +229 -0
- package/ios/PulseUpdates/Database/PulseDatabaseUtils.swift +247 -0
- package/ios/PulseUpdates/ErrorRecovery/PulseErrorRecovery.swift +415 -0
- package/ios/PulseUpdates/PulseController.swift +1359 -0
- package/ios/PulseUpdates/PulseTypes.swift +289 -0
- package/ios/PulseUpdates/PulseUpdates.m +37 -0
- package/ios/PulseUpdates/PulseUpdates.swift +236 -0
- package/lib/commonjs/NativePulseUpdates.js +9 -0
- package/lib/commonjs/NativePulseUpdates.js.map +1 -0
- package/lib/commonjs/PulseUpdates.js +315 -0
- package/lib/commonjs/PulseUpdates.js.map +1 -0
- package/lib/commonjs/assetResolver.js +201 -0
- package/lib/commonjs/assetResolver.js.map +1 -0
- package/lib/commonjs/index.js +55 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/usePulseUpdates.js +115 -0
- package/lib/commonjs/usePulseUpdates.js.map +1 -0
- package/lib/module/NativePulseUpdates.js +3 -0
- package/lib/module/NativePulseUpdates.js.map +1 -0
- package/lib/module/PulseUpdates.js +291 -0
- package/lib/module/PulseUpdates.js.map +1 -0
- package/lib/module/assetResolver.js +193 -0
- package/lib/module/assetResolver.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/usePulseUpdates.js +108 -0
- package/lib/module/usePulseUpdates.js.map +1 -0
- package/lib/typescript/NativePulseUpdates.d.ts +34 -0
- package/lib/typescript/NativePulseUpdates.d.ts.map +1 -0
- package/lib/typescript/PulseUpdates.d.ts +93 -0
- package/lib/typescript/PulseUpdates.d.ts.map +1 -0
- package/lib/typescript/assetResolver.d.ts +30 -0
- package/lib/typescript/assetResolver.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +70 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/usePulseUpdates.d.ts +21 -0
- package/lib/typescript/usePulseUpdates.d.ts.map +1 -0
- package/package.json +63 -0
- package/pulse-updates.podspec +23 -0
- package/react-native.config.js +18 -0
- package/scripts/create-pulse-resources-android.sh +93 -0
- package/scripts/create-pulse-resources-ios.sh +114 -0
- package/scripts/generate-embedded-manifest.mjs +307 -0
- package/scripts/manifest-check.js +67 -0
- package/scripts/publish.mjs +902 -0
- package/src/NativePulseUpdates.ts +52 -0
- package/src/PulseUpdates.ts +332 -0
- package/src/assetResolver.ts +193 -0
- package/src/index.ts +4 -0
- package/src/types.ts +78 -0
- package/src/usePulseUpdates.ts +135 -0
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
// PulseUpdates Controller
|
|
2
|
+
// Based on expo-updates AppController
|
|
3
|
+
// Main controller that orchestrates update lifecycle
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
import Network
|
|
7
|
+
|
|
8
|
+
// MARK: - Controller Delegate
|
|
9
|
+
|
|
10
|
+
public protocol PulseControllerDelegate: AnyObject {
|
|
11
|
+
func pulseController(_ controller: PulseController, didStartWithSuccess success: Bool)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// MARK: - Main Controller
|
|
15
|
+
|
|
16
|
+
public final class PulseController {
|
|
17
|
+
|
|
18
|
+
// MARK: - Singleton
|
|
19
|
+
|
|
20
|
+
public static let shared = PulseController()
|
|
21
|
+
|
|
22
|
+
// MARK: - Public Properties
|
|
23
|
+
|
|
24
|
+
public weak var delegate: PulseControllerDelegate?
|
|
25
|
+
|
|
26
|
+
/// Whether the controller is active (updates are enabled)
|
|
27
|
+
public private(set) var isActive: Bool = false
|
|
28
|
+
|
|
29
|
+
/// Whether the controller has started
|
|
30
|
+
public private(set) var isStarted: Bool = false
|
|
31
|
+
|
|
32
|
+
/// Current configuration
|
|
33
|
+
public private(set) var config: PulseUpdatesConfig?
|
|
34
|
+
|
|
35
|
+
/// The currently launched update info
|
|
36
|
+
public private(set) var launchedUpdate: PulseUpdateInfo?
|
|
37
|
+
|
|
38
|
+
/// Whether we're running the embedded update
|
|
39
|
+
public private(set) var isEmbeddedLaunch: Bool = true
|
|
40
|
+
|
|
41
|
+
/// URL to the launch asset (JS bundle)
|
|
42
|
+
public private(set) var launchAssetUrl: URL?
|
|
43
|
+
|
|
44
|
+
/// Local assets map (asset key -> file URL)
|
|
45
|
+
public private(set) var localAssets: [String: String] = [:]
|
|
46
|
+
|
|
47
|
+
/// The manifest JSON for the launched update (for metadata like build number)
|
|
48
|
+
public private(set) var launchedManifestJson: String?
|
|
49
|
+
|
|
50
|
+
/// Error recovery handler
|
|
51
|
+
public private(set) var errorRecovery: PulseErrorRecovery?
|
|
52
|
+
|
|
53
|
+
/// Remote load status for error recovery
|
|
54
|
+
public private(set) var remoteLoadStatus: PulseRemoteLoadStatus = .idle
|
|
55
|
+
|
|
56
|
+
// MARK: - Private Properties
|
|
57
|
+
|
|
58
|
+
private var database: PulseDatabase?
|
|
59
|
+
private var launcher: PulseAppLauncher?
|
|
60
|
+
private let directory: URL
|
|
61
|
+
private let fileQueue = DispatchQueue(label: "app.pulse.files", qos: .userInitiated)
|
|
62
|
+
|
|
63
|
+
// Embedded manifest cache
|
|
64
|
+
private var embeddedManifest: PulseEmbeddedManifest?
|
|
65
|
+
private var embeddedAssetHashes: [String: URL] = [:]
|
|
66
|
+
private var embeddedBundleHash: String?
|
|
67
|
+
|
|
68
|
+
// MARK: - Native Config (from Info.plist, like expo-updates)
|
|
69
|
+
|
|
70
|
+
/// Load config from Info.plist (called before JS starts)
|
|
71
|
+
private func loadNativeConfig() -> PulseUpdatesConfig? {
|
|
72
|
+
let info = Bundle.main.infoDictionary
|
|
73
|
+
|
|
74
|
+
// Check if PulseUpdates is configured in Info.plist
|
|
75
|
+
guard let updateUrl = info?["PulseUpdatesURL"] as? String, !updateUrl.isEmpty else {
|
|
76
|
+
pulseLog("No PulseUpdatesURL in Info.plist, native config disabled")
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let enabled = (info?["PulseUpdatesEnabled"] as? Bool) ?? true
|
|
81
|
+
|
|
82
|
+
// Runtime version: embedded manifest > CFBundleShortVersionString
|
|
83
|
+
let runtimeVersion: String
|
|
84
|
+
if let embedded = embeddedManifest {
|
|
85
|
+
runtimeVersion = embedded.runtimeVersion
|
|
86
|
+
} else {
|
|
87
|
+
runtimeVersion = (info?["CFBundleShortVersionString"] as? String)
|
|
88
|
+
?? (info?["CFBundleVersion"] as? String)
|
|
89
|
+
?? "unknown"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return PulseUpdatesConfig(
|
|
93
|
+
enabled: enabled,
|
|
94
|
+
updateUrl: updateUrl,
|
|
95
|
+
runtimeVersion: runtimeVersion,
|
|
96
|
+
checkOnLaunch: (info?["PulseUpdatesCheckOnLaunch"] as? String) ?? "ALWAYS",
|
|
97
|
+
launchWaitMs: (info?["PulseUpdatesLaunchWaitMs"] as? Int) ?? 0,
|
|
98
|
+
channel: info?["PulseUpdatesChannel"] as? String,
|
|
99
|
+
signingKeyId: info?["PulseUpdatesSigningKeyId"] as? String,
|
|
100
|
+
signingPublicKey: info?["PulseUpdatesSigningPublicKey"] as? String
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Get the runtime version (from config or native sources)
|
|
105
|
+
private var nativeRuntimeVersion: String {
|
|
106
|
+
// Use config if available
|
|
107
|
+
if let configVersion = config?.runtimeVersion {
|
|
108
|
+
return configVersion
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Try embedded manifest
|
|
112
|
+
if let embedded = embeddedManifest {
|
|
113
|
+
return embedded.runtimeVersion
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Fall back to app version
|
|
117
|
+
let info = Bundle.main.infoDictionary
|
|
118
|
+
return (info?["CFBundleShortVersionString"] as? String)
|
|
119
|
+
?? (info?["CFBundleVersion"] as? String)
|
|
120
|
+
?? "unknown"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// MARK: - Initialization
|
|
124
|
+
|
|
125
|
+
private init() {
|
|
126
|
+
// Use Application Support directory
|
|
127
|
+
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
128
|
+
self.directory = appSupport.appendingPathComponent("pulse", isDirectory: true)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MARK: - Public API
|
|
132
|
+
|
|
133
|
+
/// Configure the controller (call before start)
|
|
134
|
+
public func configure(_ config: PulseUpdatesConfig) {
|
|
135
|
+
self.config = config
|
|
136
|
+
self.isActive = config.enabled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Initialize without starting (for custom initialization)
|
|
140
|
+
/// This is called synchronously by getBundleURL() before JS starts
|
|
141
|
+
public func initializeWithoutStarting() {
|
|
142
|
+
guard !isStarted else { return }
|
|
143
|
+
|
|
144
|
+
createDirectories()
|
|
145
|
+
initializeDatabase()
|
|
146
|
+
loadEmbeddedManifest()
|
|
147
|
+
|
|
148
|
+
// Load native config from Info.plist if not already configured by JS
|
|
149
|
+
if config == nil {
|
|
150
|
+
config = loadNativeConfig()
|
|
151
|
+
if let cfg = config {
|
|
152
|
+
isActive = cfg.enabled
|
|
153
|
+
pulseLog("Loaded native config: url=\(cfg.updateUrl) runtime=\(cfg.runtimeVersion)")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
seedEmbeddedUpdateIfNeeded()
|
|
158
|
+
runIntegrityCheck()
|
|
159
|
+
|
|
160
|
+
// Synchronously select the best update for launch
|
|
161
|
+
selectBestUpdateSync()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Synchronously select the best update (used for initial launch)
|
|
165
|
+
private func selectBestUpdateSync() {
|
|
166
|
+
// Use config.runtimeVersion if available, otherwise fall back to native
|
|
167
|
+
let runtimeVersion = config?.runtimeVersion ?? nativeRuntimeVersion
|
|
168
|
+
|
|
169
|
+
pulseLog("selectBestUpdateSync: database=\(database != nil) runtimeVersion=\(runtimeVersion)")
|
|
170
|
+
|
|
171
|
+
guard let database = database else {
|
|
172
|
+
pulseLog("selectBestUpdateSync: no database")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
do {
|
|
177
|
+
let updates = try database.launchableUpdates(forRuntimeVersion: runtimeVersion)
|
|
178
|
+
pulseLog("selectBestUpdateSync: found \(updates.count) updates, runtimeVersion=\(runtimeVersion)")
|
|
179
|
+
|
|
180
|
+
let policy = PulseDefaultSelectionPolicy()
|
|
181
|
+
|
|
182
|
+
guard let selectedUpdate = policy.selectUpdateToLaunch(from: updates) else {
|
|
183
|
+
pulseLog("selectBestUpdateSync: no update selected")
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pulseLog("selectBestUpdateSync: selected \(selectedUpdate.updateId) status=\(selectedUpdate.status) bundleHash=\(selectedUpdate.bundleHash ?? "nil")")
|
|
188
|
+
|
|
189
|
+
// Set the launch URL based on selected update
|
|
190
|
+
if selectedUpdate.status == .embedded {
|
|
191
|
+
launchAssetUrl = Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
192
|
+
isEmbeddedLaunch = true
|
|
193
|
+
// For embedded, use only embedded assets
|
|
194
|
+
localAssets = buildEmbeddedLocalAssets()
|
|
195
|
+
pulseLog("selectBestUpdateSync: using embedded bundle, localAssets count=\(localAssets.count)")
|
|
196
|
+
} else if let bundleHash = selectedUpdate.bundleHash {
|
|
197
|
+
let bundlePath = directory.appendingPathComponent("assets/sha256/\(bundleHash.lowercased())")
|
|
198
|
+
let exists = FileManager.default.fileExists(atPath: bundlePath.path)
|
|
199
|
+
pulseLog("selectBestUpdateSync: bundlePath exists=\(exists)")
|
|
200
|
+
|
|
201
|
+
if exists {
|
|
202
|
+
launchAssetUrl = bundlePath
|
|
203
|
+
isEmbeddedLaunch = false
|
|
204
|
+
|
|
205
|
+
// Set launchedUpdate info
|
|
206
|
+
launchedUpdate = PulseUpdateInfo(
|
|
207
|
+
updateId: selectedUpdate.updateId,
|
|
208
|
+
runtimeVersion: selectedUpdate.runtimeVersion,
|
|
209
|
+
commitTime: selectedUpdate.commitTime,
|
|
210
|
+
status: selectedUpdate.status,
|
|
211
|
+
successfulLaunchCount: selectedUpdate.successfulLaunchCount,
|
|
212
|
+
failedLaunchCount: selectedUpdate.failedLaunchCount,
|
|
213
|
+
isEmbedded: selectedUpdate.isEmbedded,
|
|
214
|
+
bundleHash: selectedUpdate.bundleHash,
|
|
215
|
+
scopeKey: selectedUpdate.scopeKey
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// Load manifest from database for metadata (build number, etc.)
|
|
219
|
+
if let manifest = selectedUpdate.manifest,
|
|
220
|
+
let data = try? JSONSerialization.data(withJSONObject: manifest, options: []),
|
|
221
|
+
let jsonString = String(data: data, encoding: .utf8) {
|
|
222
|
+
launchedManifestJson = jsonString
|
|
223
|
+
pulseLog("selectBestUpdateSync: loaded manifest json")
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Build localAssets: downloaded assets + embedded fallbacks
|
|
227
|
+
localAssets = buildLocalAssetsForUpdate(updateId: selectedUpdate.updateId, database: database)
|
|
228
|
+
pulseLog("selectBestUpdateSync: set launchAssetUrl to downloaded bundle, localAssets count=\(localAssets.count)")
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
pulseLogError("selectBestUpdateSync error: \(error)")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Build localAssets map for an OTA update (downloaded + embedded fallbacks)
|
|
237
|
+
/// Store BOTH hash AND path-based keys for maximum compatibility:
|
|
238
|
+
/// - hash: for SHA256 hash lookup (how expo-asset looks up)
|
|
239
|
+
/// - path: for Metro path-based lookup (httpServerLocation/name.type)
|
|
240
|
+
private func buildLocalAssetsForUpdate(updateId: String, database: PulseDatabase) -> [String: String] {
|
|
241
|
+
// Start with embedded assets as base (fallback)
|
|
242
|
+
var assets = buildEmbeddedLocalAssets()
|
|
243
|
+
pulseLog("buildLocalAssetsForUpdate: embedded base count=\(assets.count)")
|
|
244
|
+
|
|
245
|
+
// Get downloaded assets for this update from database
|
|
246
|
+
do {
|
|
247
|
+
let updateAssets = try database.assets(forUpdateId: updateId)
|
|
248
|
+
pulseLog("buildLocalAssetsForUpdate: found \(updateAssets.count) assets in database for update \(updateId)")
|
|
249
|
+
|
|
250
|
+
for asset in updateAssets where !asset.isLaunchAsset {
|
|
251
|
+
// Check if downloaded asset exists (stored by SHA256 hash)
|
|
252
|
+
let downloadedPath = directory.appendingPathComponent("assets/sha256/\(asset.hash.lowercased())")
|
|
253
|
+
if FileManager.default.fileExists(atPath: downloadedPath.path) {
|
|
254
|
+
let localUri = downloadedPath.absoluteString
|
|
255
|
+
|
|
256
|
+
// Store by SHA256 hash (how expo-asset looks up assets)
|
|
257
|
+
assets[asset.hash] = localUri
|
|
258
|
+
|
|
259
|
+
// Also store by manifest key (MD5 hash) for expo-asset compatibility
|
|
260
|
+
if let key = asset.key {
|
|
261
|
+
// Normalize key: remove double "assets/assets/" prefix -> "assets/"
|
|
262
|
+
var normalizedKey = key
|
|
263
|
+
if normalizedKey.hasPrefix("assets/assets/") {
|
|
264
|
+
normalizedKey = String(normalizedKey.dropFirst("assets/".count))
|
|
265
|
+
}
|
|
266
|
+
assets[normalizedKey] = localUri
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
// Downloaded file doesn't exist - use embedded fallback
|
|
270
|
+
// BUT we still need to add the MD5 key from OTA manifest pointing to the embedded asset!
|
|
271
|
+
// The embedded map has SHA256 keys, but JS looks up by MD5 hash (asset.key)
|
|
272
|
+
if let embeddedUri = assets[asset.hash], let key = asset.key {
|
|
273
|
+
// Add MD5 key pointing to embedded asset
|
|
274
|
+
assets[key] = embeddedUri
|
|
275
|
+
pulseLog("buildLocalAssetsForUpdate: using embedded fallback for key=\(key.prefix(16))... hash=\(asset.hash.prefix(16))...")
|
|
276
|
+
} else {
|
|
277
|
+
pulseLogWarn("buildLocalAssetsForUpdate: file not found AND no embedded fallback for hash=\(asset.hash.prefix(16))...")
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
pulseLogError("buildLocalAssetsForUpdate error: \(error)")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return assets
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Build localAssets map from embedded manifest for fallback
|
|
289
|
+
/// Key is the manifest key (MD5 hash) which matches how expo-asset looks up assets
|
|
290
|
+
private func buildEmbeddedLocalAssets() -> [String: String] {
|
|
291
|
+
guard let manifest = embeddedManifest else { return [:] }
|
|
292
|
+
|
|
293
|
+
var assets: [String: String] = [:]
|
|
294
|
+
|
|
295
|
+
for asset in manifest.assets where !asset.isLaunchAsset {
|
|
296
|
+
guard let filename = asset.nsBundleFilename else {
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let bundlePath: String?
|
|
301
|
+
if let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
302
|
+
bundlePath = Bundle.main.path(forResource: filename, ofType: nil, inDirectory: dir)
|
|
303
|
+
} else {
|
|
304
|
+
bundlePath = Bundle.main.path(forResource: filename, ofType: nil)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if let path = bundlePath {
|
|
308
|
+
let localUri = URL(fileURLWithPath: path).absoluteString
|
|
309
|
+
|
|
310
|
+
// Store by manifest key (MD5 hash - matches how expo-asset looks up)
|
|
311
|
+
if let key = asset.key {
|
|
312
|
+
assets[key] = localUri
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Also store by SHA256 hash for fallback
|
|
316
|
+
assets[asset.hash] = localUri
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return assets
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Start the controller (call after configure, or auto-starts with native config)
|
|
324
|
+
public func start() {
|
|
325
|
+
guard !isStarted else { return }
|
|
326
|
+
isStarted = true
|
|
327
|
+
|
|
328
|
+
pulseLog("Starting controller")
|
|
329
|
+
|
|
330
|
+
// Initialize
|
|
331
|
+
createDirectories()
|
|
332
|
+
initializeDatabase()
|
|
333
|
+
loadEmbeddedManifest()
|
|
334
|
+
|
|
335
|
+
// Load native config from Info.plist if not already configured by JS
|
|
336
|
+
if config == nil {
|
|
337
|
+
config = loadNativeConfig()
|
|
338
|
+
if let cfg = config {
|
|
339
|
+
isActive = cfg.enabled
|
|
340
|
+
pulseLog("Loaded native config: url=\(cfg.updateUrl) runtime=\(cfg.runtimeVersion)")
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
seedEmbeddedUpdateIfNeeded()
|
|
345
|
+
runIntegrityCheck()
|
|
346
|
+
|
|
347
|
+
// Create and start error recovery
|
|
348
|
+
errorRecovery = PulseErrorRecovery()
|
|
349
|
+
errorRecovery?.delegate = self
|
|
350
|
+
errorRecovery?.startMonitoring()
|
|
351
|
+
|
|
352
|
+
// Launch best available update
|
|
353
|
+
launchBestUpdate { [weak self] success in
|
|
354
|
+
guard let self = self else { return }
|
|
355
|
+
|
|
356
|
+
if success {
|
|
357
|
+
pulseLog("Started successfully with update: \(self.launchedUpdate?.updateId ?? "embedded")")
|
|
358
|
+
} else {
|
|
359
|
+
pulseLog("Failed to start, falling back to embedded")
|
|
360
|
+
self.launchEmbedded()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
self.delegate?.pulseController(self, didStartWithSuccess: success || self.launchAssetUrl != nil)
|
|
364
|
+
|
|
365
|
+
// Check for updates on launch based on config
|
|
366
|
+
self.performCheckOnLaunchIfNeeded()
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// MARK: - Check on Launch
|
|
371
|
+
|
|
372
|
+
private func performCheckOnLaunchIfNeeded() {
|
|
373
|
+
guard let config = config, config.enabled else { return }
|
|
374
|
+
|
|
375
|
+
switch config.checkOnLaunch.uppercased() {
|
|
376
|
+
case "NEVER":
|
|
377
|
+
pulseLog("checkOnLaunch is NEVER, skipping automatic check")
|
|
378
|
+
return
|
|
379
|
+
case "WIFI_ONLY":
|
|
380
|
+
guard isOnWifi() else {
|
|
381
|
+
pulseLog("checkOnLaunch is WIFI_ONLY but not on WiFi, skipping")
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
case "ALWAYS":
|
|
385
|
+
break
|
|
386
|
+
default:
|
|
387
|
+
break
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Wait for launchWaitMs before checking
|
|
391
|
+
let waitMs = config.launchWaitMs
|
|
392
|
+
if waitMs > 0 {
|
|
393
|
+
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .milliseconds(waitMs)) { [weak self] in
|
|
394
|
+
self?.performBackgroundUpdateCheck()
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
DispatchQueue.global(qos: .utility).async { [weak self] in
|
|
398
|
+
self?.performBackgroundUpdateCheck()
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private func performBackgroundUpdateCheck() {
|
|
404
|
+
pulseLog("Performing background update check")
|
|
405
|
+
|
|
406
|
+
checkForUpdate { [weak self] result in
|
|
407
|
+
switch result {
|
|
408
|
+
case .success(let checkResult):
|
|
409
|
+
if checkResult.isAvailable {
|
|
410
|
+
pulseLog("Background check: update available, fetching...")
|
|
411
|
+
self?.fetchUpdate { fetchResult in
|
|
412
|
+
switch fetchResult {
|
|
413
|
+
case .success(let fetch):
|
|
414
|
+
if fetch.isNew {
|
|
415
|
+
pulseLog("Background check: update downloaded and ready")
|
|
416
|
+
// Notify via event emitter that update is available
|
|
417
|
+
NotificationCenter.default.post(
|
|
418
|
+
name: NSNotification.Name("PulseUpdatesAvailable"),
|
|
419
|
+
object: nil,
|
|
420
|
+
userInfo: ["manifest": fetch.manifest as Any]
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
case .failure(let error):
|
|
424
|
+
pulseLog("Background check: fetch failed - \(error)")
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
pulseLog("Background check: no update available")
|
|
429
|
+
}
|
|
430
|
+
case .failure(let error):
|
|
431
|
+
pulseLog("Background check failed: \(error)")
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private func isOnWifi() -> Bool {
|
|
437
|
+
// Use Network framework for WiFi detection
|
|
438
|
+
let monitor = NWPathMonitor()
|
|
439
|
+
var isWifi = false
|
|
440
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
441
|
+
|
|
442
|
+
monitor.pathUpdateHandler = { path in
|
|
443
|
+
isWifi = path.usesInterfaceType(.wifi)
|
|
444
|
+
semaphore.signal()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let queue = DispatchQueue(label: "wifi.check")
|
|
448
|
+
monitor.start(queue: queue)
|
|
449
|
+
_ = semaphore.wait(timeout: .now() + 1)
|
|
450
|
+
monitor.cancel()
|
|
451
|
+
|
|
452
|
+
return isWifi
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/// Get the bundle URL for React Native
|
|
456
|
+
public func launchAssetURL() -> URL? {
|
|
457
|
+
if launchAssetUrl != nil {
|
|
458
|
+
return launchAssetUrl
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Fall back to embedded bundle
|
|
462
|
+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Check for updates
|
|
466
|
+
public func checkForUpdate(completion: @escaping (Result<PulseCheckResult, Error>) -> Void) {
|
|
467
|
+
guard let config = config else {
|
|
468
|
+
completion(.failure(PulseUpdatesError.notConfigured))
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
guard config.enabled else {
|
|
473
|
+
completion(.failure(PulseUpdatesError.notEnabled))
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
PulseRemoteLoader.checkForUpdate(
|
|
478
|
+
config: config,
|
|
479
|
+
currentUpdateId: launchedUpdate?.updateId,
|
|
480
|
+
completion: completion
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/// Fetch and download an update
|
|
485
|
+
public func fetchUpdate(completion: @escaping (Result<PulseFetchResult, Error>) -> Void) {
|
|
486
|
+
guard let config = config else {
|
|
487
|
+
completion(.failure(PulseUpdatesError.notConfigured))
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
guard config.enabled else {
|
|
492
|
+
completion(.failure(PulseUpdatesError.notEnabled))
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
PulseRemoteLoader.fetchUpdate(
|
|
497
|
+
config: config,
|
|
498
|
+
database: database,
|
|
499
|
+
directory: directory,
|
|
500
|
+
completion: completion
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/// Reload the app with the new update
|
|
505
|
+
public func reload(completion: ((Bool) -> Void)? = nil) {
|
|
506
|
+
// Re-select the best update before reloading
|
|
507
|
+
launchBestUpdate { success in
|
|
508
|
+
// Signal React Native to reload with the new bundle
|
|
509
|
+
NotificationCenter.default.post(name: NSNotification.Name("PulseUpdatesReload"), object: nil)
|
|
510
|
+
completion?(success)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/// Mark the app as ready (error recovery)
|
|
515
|
+
/// Note: With the new error recovery system, this is handled automatically
|
|
516
|
+
/// via RCTContentDidAppear notification. This method is kept for manual marking if needed.
|
|
517
|
+
public func markAppReady() {
|
|
518
|
+
if let updateId = launchedUpdate?.updateId {
|
|
519
|
+
try? database?.recordSuccessfulLaunch(updateId: updateId)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Run reaper after successful launch
|
|
523
|
+
runReaper()
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/// Report a fatal error for error recovery handling
|
|
527
|
+
public func reportError(_ error: NSError) {
|
|
528
|
+
errorRecovery?.handle(error: error)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// Report a fatal exception for error recovery handling
|
|
532
|
+
public func reportException(_ exception: NSException) {
|
|
533
|
+
errorRecovery?.handle(exception: exception)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// MARK: - Private Methods
|
|
537
|
+
|
|
538
|
+
private func createDirectories() {
|
|
539
|
+
let dirs = [
|
|
540
|
+
directory,
|
|
541
|
+
directory.appendingPathComponent("updates"),
|
|
542
|
+
directory.appendingPathComponent("assets/sha256"),
|
|
543
|
+
directory.appendingPathComponent("bundles/sha256"),
|
|
544
|
+
directory.appendingPathComponent("staging")
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
for dir in dirs {
|
|
548
|
+
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private func initializeDatabase() {
|
|
553
|
+
do {
|
|
554
|
+
database = PulseDatabase(directory: directory)
|
|
555
|
+
try database?.open()
|
|
556
|
+
pulseLog("Database initialized")
|
|
557
|
+
} catch {
|
|
558
|
+
pulseLog("Failed to initialize database: \(error)")
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private func loadEmbeddedManifest() {
|
|
563
|
+
guard let url = Bundle.main.url(forResource: "embedded-manifest", withExtension: "json"),
|
|
564
|
+
let data = try? Data(contentsOf: url),
|
|
565
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
566
|
+
pulseLog("No embedded manifest found")
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
embeddedManifest = PulseEmbeddedManifest(json: json)
|
|
571
|
+
|
|
572
|
+
// Cache embedded asset hashes
|
|
573
|
+
if let manifest = embeddedManifest {
|
|
574
|
+
for asset in manifest.assets {
|
|
575
|
+
if let filename = asset.nsBundleFilename {
|
|
576
|
+
let bundlePath: String?
|
|
577
|
+
if let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
578
|
+
bundlePath = Bundle.main.path(forResource: filename, ofType: nil, inDirectory: dir)
|
|
579
|
+
} else {
|
|
580
|
+
bundlePath = Bundle.main.path(forResource: filename, ofType: nil)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if let path = bundlePath {
|
|
584
|
+
embeddedAssetHashes[asset.hash.lowercased()] = URL(fileURLWithPath: path)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if asset.isLaunchAsset {
|
|
589
|
+
embeddedBundleHash = asset.hash.lowercased()
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
pulseLog("Loaded embedded manifest: \(embeddedManifest?.updateId ?? "unknown")")
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private func seedEmbeddedUpdateIfNeeded() {
|
|
598
|
+
guard let embedded = embeddedManifest,
|
|
599
|
+
let database = database else {
|
|
600
|
+
return
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let embeddedId = "embedded:\(embedded.updateId)"
|
|
604
|
+
|
|
605
|
+
// Check if already seeded
|
|
606
|
+
if let _ = try? database.update(withId: embeddedId) {
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Create embedded update record
|
|
611
|
+
// scopeKey from config, or default to "default" if not configured yet
|
|
612
|
+
let update = PulseUpdate(
|
|
613
|
+
updateId: embeddedId,
|
|
614
|
+
scopeKey: config?.scopeKey ?? "default",
|
|
615
|
+
runtimeVersion: embedded.runtimeVersion,
|
|
616
|
+
commitTime: embedded.commitTime,
|
|
617
|
+
status: .embedded,
|
|
618
|
+
manifest: nil,
|
|
619
|
+
bundleHash: embeddedBundleHash,
|
|
620
|
+
lastAccessed: Date(),
|
|
621
|
+
successfulLaunchCount: 0,
|
|
622
|
+
failedLaunchCount: 0
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
do {
|
|
626
|
+
try database.addUpdate(update)
|
|
627
|
+
pulseLog("Seeded embedded update: \(embeddedId)")
|
|
628
|
+
} catch {
|
|
629
|
+
pulseLog("Failed to seed embedded update: \(error)")
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private func runIntegrityCheck() {
|
|
634
|
+
guard let database = database else { return }
|
|
635
|
+
|
|
636
|
+
let currentEmbeddedId = embeddedManifest.map { "embedded:\($0.updateId)" }
|
|
637
|
+
|
|
638
|
+
PulseIntegrityCheck.run(
|
|
639
|
+
database: database,
|
|
640
|
+
directory: directory,
|
|
641
|
+
currentEmbeddedId: currentEmbeddedId
|
|
642
|
+
)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private func launchBestUpdate(completion: @escaping (Bool) -> Void) {
|
|
646
|
+
guard let database = database,
|
|
647
|
+
let config = config else {
|
|
648
|
+
completion(false)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let launcher = PulseAppLauncher(
|
|
653
|
+
database: database,
|
|
654
|
+
directory: directory,
|
|
655
|
+
selectionPolicy: PulseDefaultSelectionPolicy(),
|
|
656
|
+
runtimeVersion: config.runtimeVersion
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
self.launcher = launcher
|
|
660
|
+
|
|
661
|
+
launcher.launch { [weak self] success, error in
|
|
662
|
+
guard let self = self else {
|
|
663
|
+
completion(false)
|
|
664
|
+
return
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if success {
|
|
668
|
+
self.launchAssetUrl = launcher.launchAssetUrl
|
|
669
|
+
self.localAssets = launcher.assetFilesMap
|
|
670
|
+
self.isEmbeddedLaunch = launcher.isUsingEmbeddedAssets
|
|
671
|
+
|
|
672
|
+
if let update = launcher.launchedUpdate {
|
|
673
|
+
self.launchedUpdate = PulseUpdateInfo(
|
|
674
|
+
updateId: update.updateId,
|
|
675
|
+
runtimeVersion: update.runtimeVersion,
|
|
676
|
+
commitTime: update.commitTime,
|
|
677
|
+
status: update.status,
|
|
678
|
+
successfulLaunchCount: update.successfulLaunchCount,
|
|
679
|
+
failedLaunchCount: update.failedLaunchCount,
|
|
680
|
+
isEmbedded: update.isEmbedded,
|
|
681
|
+
bundleHash: update.bundleHash,
|
|
682
|
+
scopeKey: update.scopeKey
|
|
683
|
+
)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if let error = error {
|
|
688
|
+
pulseLog("Launch error: \(error)")
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
completion(success)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private func launchEmbedded() {
|
|
696
|
+
launchAssetUrl = Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
697
|
+
isEmbeddedLaunch = true
|
|
698
|
+
|
|
699
|
+
// Build local assets from embedded manifest - use KEY for React Native compatibility
|
|
700
|
+
if let manifest = embeddedManifest {
|
|
701
|
+
for asset in manifest.assets where !asset.isLaunchAsset {
|
|
702
|
+
if let key = asset.key, let url = embeddedAssetHashes[asset.hash.lowercased()] {
|
|
703
|
+
// Use KEY as dictionary key - React Native asset resolver looks up by key
|
|
704
|
+
localAssets[key] = url.absoluteString
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private func rollbackToPreviousUpdate() {
|
|
711
|
+
pulseLog("Rolling back to previous update")
|
|
712
|
+
// Implementation depends on error recovery strategy
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private func runReaper() {
|
|
716
|
+
guard let database = database,
|
|
717
|
+
let launchedUpdate = launchedUpdate else {
|
|
718
|
+
return
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
PulseReaper.reap(
|
|
722
|
+
database: database,
|
|
723
|
+
directory: directory,
|
|
724
|
+
selectionPolicy: PulseDefaultSelectionPolicy(),
|
|
725
|
+
launchedUpdateId: launchedUpdate.updateId
|
|
726
|
+
)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// MARK: - Integrity Check
|
|
731
|
+
|
|
732
|
+
final class PulseIntegrityCheck {
|
|
733
|
+
static func run(database: PulseDatabase, directory: URL, currentEmbeddedId: String?) {
|
|
734
|
+
DispatchQueue.global(qos: .utility).async {
|
|
735
|
+
do {
|
|
736
|
+
// Delete old embedded updates
|
|
737
|
+
let embeddedUpdates = try database.embeddedUpdates()
|
|
738
|
+
let toDelete = embeddedUpdates.filter { update in
|
|
739
|
+
guard let currentId = currentEmbeddedId else { return true }
|
|
740
|
+
return update.updateId != currentId
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if !toDelete.isEmpty {
|
|
744
|
+
try database.deleteUpdates(toDelete.map { $0.updateId })
|
|
745
|
+
pulseLog("IntegrityCheck: Deleted \(toDelete.count) old embedded updates")
|
|
746
|
+
|
|
747
|
+
// Clean up unused assets
|
|
748
|
+
_ = try database.deleteUnusedAssets()
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
pulseLog("IntegrityCheck error: \(error)")
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// MARK: - Reaper
|
|
758
|
+
|
|
759
|
+
final class PulseReaper {
|
|
760
|
+
/// Clean up old updates after successful launch
|
|
761
|
+
/// Based on Expo's UpdatesReaper
|
|
762
|
+
static func reap(
|
|
763
|
+
database: PulseDatabase,
|
|
764
|
+
directory: URL,
|
|
765
|
+
selectionPolicy: PulseSelectionPolicy,
|
|
766
|
+
launchedUpdateId: String
|
|
767
|
+
) {
|
|
768
|
+
DispatchQueue.global(qos: .utility).async {
|
|
769
|
+
do {
|
|
770
|
+
let allUpdates = try database.allUpdates()
|
|
771
|
+
|
|
772
|
+
guard let launchedUpdate = allUpdates.first(where: { $0.updateId == launchedUpdateId }) else {
|
|
773
|
+
return
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Convert to PulseUpdateInfo for selection policy
|
|
777
|
+
let launchedInfo = PulseUpdateInfo(
|
|
778
|
+
updateId: launchedUpdate.updateId,
|
|
779
|
+
runtimeVersion: launchedUpdate.runtimeVersion,
|
|
780
|
+
commitTime: launchedUpdate.commitTime,
|
|
781
|
+
status: launchedUpdate.status,
|
|
782
|
+
successfulLaunchCount: launchedUpdate.successfulLaunchCount,
|
|
783
|
+
failedLaunchCount: launchedUpdate.failedLaunchCount,
|
|
784
|
+
isEmbedded: launchedUpdate.isEmbedded,
|
|
785
|
+
bundleHash: launchedUpdate.bundleHash,
|
|
786
|
+
scopeKey: launchedUpdate.scopeKey
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
let allInfos = allUpdates.map { update in
|
|
790
|
+
PulseUpdateInfo(
|
|
791
|
+
updateId: update.updateId,
|
|
792
|
+
runtimeVersion: update.runtimeVersion,
|
|
793
|
+
commitTime: update.commitTime,
|
|
794
|
+
status: update.status,
|
|
795
|
+
successfulLaunchCount: update.successfulLaunchCount,
|
|
796
|
+
failedLaunchCount: update.failedLaunchCount,
|
|
797
|
+
isEmbedded: update.isEmbedded,
|
|
798
|
+
bundleHash: update.bundleHash,
|
|
799
|
+
scopeKey: update.scopeKey
|
|
800
|
+
)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
let toDeleteInfos = selectionPolicy.selectUpdatesToDelete(
|
|
804
|
+
launchedUpdate: launchedInfo,
|
|
805
|
+
allUpdates: allInfos
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
if toDeleteInfos.isEmpty {
|
|
809
|
+
return
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let toDeleteIds = toDeleteInfos.map { $0.updateId }
|
|
813
|
+
try database.deleteUpdates(toDeleteIds)
|
|
814
|
+
|
|
815
|
+
// Delete unused assets from filesystem
|
|
816
|
+
let unusedAssets = try database.deleteUnusedAssets()
|
|
817
|
+
for asset in unusedAssets {
|
|
818
|
+
let assetPath = directory
|
|
819
|
+
.appendingPathComponent("assets/sha256")
|
|
820
|
+
.appendingPathComponent(asset.hash.lowercased())
|
|
821
|
+
try? FileManager.default.removeItem(at: assetPath)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Also clean up unused bundles
|
|
825
|
+
cleanupUnusedBundles(database: database, directory: directory)
|
|
826
|
+
|
|
827
|
+
pulseLog("Reaper: Deleted \(toDeleteIds.count) updates, \(unusedAssets.count) assets")
|
|
828
|
+
} catch {
|
|
829
|
+
pulseLog("Reaper error: \(error)")
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/// Clean up bundle files that are no longer referenced
|
|
835
|
+
private static func cleanupUnusedBundles(database: PulseDatabase, directory: URL) {
|
|
836
|
+
do {
|
|
837
|
+
let allUpdates = try database.allUpdates()
|
|
838
|
+
let referencedHashes = Set(allUpdates.compactMap { $0.bundleHash?.lowercased() })
|
|
839
|
+
|
|
840
|
+
let bundlesDir = directory.appendingPathComponent("bundles/sha256")
|
|
841
|
+
guard let items = try? FileManager.default.contentsOfDirectory(atPath: bundlesDir.path) else {
|
|
842
|
+
return
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
for item in items {
|
|
846
|
+
if !referencedHashes.contains(item.lowercased()) {
|
|
847
|
+
let bundlePath = bundlesDir.appendingPathComponent(item)
|
|
848
|
+
try? FileManager.default.removeItem(at: bundlePath)
|
|
849
|
+
pulseLog("Reaper: Deleted unused bundle \(item)")
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
} catch {
|
|
853
|
+
pulseLog("Reaper bundle cleanup error: \(error)")
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// MARK: - Remote Loader
|
|
859
|
+
|
|
860
|
+
final class PulseRemoteLoader {
|
|
861
|
+
|
|
862
|
+
static func checkForUpdate(
|
|
863
|
+
config: PulseUpdatesConfig,
|
|
864
|
+
currentUpdateId: String?,
|
|
865
|
+
completion: @escaping (Result<PulseCheckResult, Error>) -> Void
|
|
866
|
+
) {
|
|
867
|
+
guard let request = buildManifestRequest(config: config, currentUpdateId: currentUpdateId) else {
|
|
868
|
+
completion(.failure(PulseUpdatesError.invalidManifest(reason: "Invalid update URL")))
|
|
869
|
+
return
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
873
|
+
if let error = error {
|
|
874
|
+
completion(.failure(PulseUpdatesError.networkError(underlying: error)))
|
|
875
|
+
return
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
879
|
+
completion(.success(PulseCheckResult(isAvailable: false)))
|
|
880
|
+
return
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// 204 = no update available
|
|
884
|
+
if httpResponse.statusCode == 204 {
|
|
885
|
+
completion(.success(PulseCheckResult(isAvailable: false)))
|
|
886
|
+
return
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
guard httpResponse.statusCode == 200, let data = data else {
|
|
890
|
+
completion(.success(PulseCheckResult(isAvailable: false)))
|
|
891
|
+
return
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
do {
|
|
895
|
+
let manifest = try JSONDecoder().decode(PulseManifestModel.self, from: data)
|
|
896
|
+
|
|
897
|
+
// Check runtime version
|
|
898
|
+
if manifest.runtimeVersion != config.runtimeVersion {
|
|
899
|
+
pulseLog("Runtime version mismatch: manifest=\(manifest.runtimeVersion) expected=\(config.runtimeVersion)")
|
|
900
|
+
completion(.success(PulseCheckResult(isAvailable: false)))
|
|
901
|
+
return
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Verify signature if required
|
|
905
|
+
if config.signingPublicKey != nil {
|
|
906
|
+
guard verifyManifestSignature(manifest: manifest, manifestJson: data, config: config) else {
|
|
907
|
+
completion(.failure(PulseUpdatesError.signatureVerificationFailed))
|
|
908
|
+
return
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Check if this is the same update
|
|
913
|
+
if manifest.updateId == currentUpdateId {
|
|
914
|
+
completion(.success(PulseCheckResult(isAvailable: false)))
|
|
915
|
+
return
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
pulseLog("Update available: \(manifest.updateId)")
|
|
919
|
+
completion(.success(PulseCheckResult(isAvailable: true, manifest: manifest)))
|
|
920
|
+
|
|
921
|
+
} catch {
|
|
922
|
+
completion(.failure(PulseUpdatesError.invalidManifest(reason: error.localizedDescription)))
|
|
923
|
+
}
|
|
924
|
+
}.resume()
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
static func fetchUpdate(
|
|
928
|
+
config: PulseUpdatesConfig,
|
|
929
|
+
database: PulseDatabase?,
|
|
930
|
+
directory: URL,
|
|
931
|
+
completion: @escaping (Result<PulseFetchResult, Error>) -> Void
|
|
932
|
+
) {
|
|
933
|
+
// First check for update
|
|
934
|
+
checkForUpdate(config: config, currentUpdateId: PulseController.shared.launchedUpdate?.updateId) { result in
|
|
935
|
+
switch result {
|
|
936
|
+
case .failure(let error):
|
|
937
|
+
completion(.failure(error))
|
|
938
|
+
|
|
939
|
+
case .success(let checkResult):
|
|
940
|
+
guard checkResult.isAvailable, let manifest = checkResult.manifest else {
|
|
941
|
+
completion(.success(PulseFetchResult(isNew: false)))
|
|
942
|
+
return
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Download the update
|
|
946
|
+
downloadUpdate(manifest: manifest, config: config, database: database, directory: directory) { downloadResult in
|
|
947
|
+
switch downloadResult {
|
|
948
|
+
case .failure(let error):
|
|
949
|
+
completion(.failure(error))
|
|
950
|
+
case .success:
|
|
951
|
+
completion(.success(PulseFetchResult(isNew: true, manifest: manifest)))
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// MARK: - Private Methods
|
|
959
|
+
|
|
960
|
+
private static func buildManifestRequest(config: PulseUpdatesConfig, currentUpdateId: String?) -> URLRequest? {
|
|
961
|
+
guard let url = URL(string: config.updateUrl) else { return nil }
|
|
962
|
+
|
|
963
|
+
var request = URLRequest(url: url)
|
|
964
|
+
request.httpMethod = "GET"
|
|
965
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
966
|
+
request.setValue("2", forHTTPHeaderField: "Pulse-Protocol-Version")
|
|
967
|
+
request.setValue("ios", forHTTPHeaderField: "X-Pulse-Platform")
|
|
968
|
+
request.setValue(config.runtimeVersion, forHTTPHeaderField: "X-Pulse-Runtime-Version")
|
|
969
|
+
|
|
970
|
+
if let channel = config.channel {
|
|
971
|
+
request.setValue(channel, forHTTPHeaderField: "X-Pulse-Channel-Name")
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if let currentId = currentUpdateId {
|
|
975
|
+
request.setValue(currentId, forHTTPHeaderField: "X-Pulse-Current-Update-Id")
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return request
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
private static func downloadUpdate(
|
|
982
|
+
manifest: PulseManifestModel,
|
|
983
|
+
config: PulseUpdatesConfig,
|
|
984
|
+
database: PulseDatabase?,
|
|
985
|
+
directory: URL,
|
|
986
|
+
completion: @escaping (Result<Void, Error>) -> Void
|
|
987
|
+
) {
|
|
988
|
+
let updateId = manifest.updateId
|
|
989
|
+
let bundleHash = manifest.bundle.hash.lowercased()
|
|
990
|
+
let stagingDir = directory.appendingPathComponent("staging/\(updateId)")
|
|
991
|
+
|
|
992
|
+
// Create staging directory
|
|
993
|
+
try? FileManager.default.createDirectory(at: stagingDir, withIntermediateDirectories: true)
|
|
994
|
+
|
|
995
|
+
// Convert manifest to dictionary for storage
|
|
996
|
+
var manifestDict: [String: Any]?
|
|
997
|
+
if let data = try? JSONEncoder().encode(manifest),
|
|
998
|
+
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
999
|
+
manifestDict = dict
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Store update in database
|
|
1003
|
+
let update = PulseUpdate(
|
|
1004
|
+
updateId: updateId,
|
|
1005
|
+
scopeKey: config.scopeKey,
|
|
1006
|
+
runtimeVersion: manifest.runtimeVersion,
|
|
1007
|
+
commitTime: manifest.commitTime ?? Date(),
|
|
1008
|
+
status: .downloading,
|
|
1009
|
+
manifest: manifestDict,
|
|
1010
|
+
bundleHash: bundleHash,
|
|
1011
|
+
lastAccessed: Date(),
|
|
1012
|
+
successfulLaunchCount: 0,
|
|
1013
|
+
failedLaunchCount: 0
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
try? database?.addUpdate(update)
|
|
1017
|
+
|
|
1018
|
+
// Download bundle
|
|
1019
|
+
downloadBundle(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { bundleResult in
|
|
1020
|
+
switch bundleResult {
|
|
1021
|
+
case .failure(let error):
|
|
1022
|
+
try? database?.setStatus(.failed, forUpdateId: updateId)
|
|
1023
|
+
cleanupStaging(stagingDir)
|
|
1024
|
+
completion(.failure(error))
|
|
1025
|
+
|
|
1026
|
+
case .success:
|
|
1027
|
+
// Download assets
|
|
1028
|
+
downloadAssets(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { assetsResult in
|
|
1029
|
+
switch assetsResult {
|
|
1030
|
+
case .failure(let error):
|
|
1031
|
+
try? database?.setStatus(.failed, forUpdateId: updateId)
|
|
1032
|
+
cleanupStaging(stagingDir)
|
|
1033
|
+
completion(.failure(error))
|
|
1034
|
+
|
|
1035
|
+
case .success:
|
|
1036
|
+
try? database?.setStatus(.ready, forUpdateId: updateId)
|
|
1037
|
+
cleanupStaging(stagingDir)
|
|
1038
|
+
pulseLog("Update \(updateId) downloaded and ready")
|
|
1039
|
+
completion(.success(()))
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
private static func downloadBundle(
|
|
1047
|
+
manifest: PulseManifestModel,
|
|
1048
|
+
directory: URL,
|
|
1049
|
+
stagingDir: URL,
|
|
1050
|
+
database: PulseDatabase?,
|
|
1051
|
+
updateId: String,
|
|
1052
|
+
completion: @escaping (Result<Void, Error>) -> Void
|
|
1053
|
+
) {
|
|
1054
|
+
let bundleHash = manifest.bundle.hash.lowercased()
|
|
1055
|
+
// Store bundle in assets directory (same as other assets) so launcher can find it
|
|
1056
|
+
let destination = directory.appendingPathComponent("assets/sha256/\(bundleHash)")
|
|
1057
|
+
|
|
1058
|
+
// Store bundle as launch asset in database
|
|
1059
|
+
func storeBundleInDatabase() {
|
|
1060
|
+
var dbAsset = PulseAsset(key: "bundle", hash: bundleHash)
|
|
1061
|
+
dbAsset.type = "application/javascript"
|
|
1062
|
+
|
|
1063
|
+
if let assetId = try? database?.addAsset(dbAsset) {
|
|
1064
|
+
try? database?.linkAsset(assetId: assetId, toUpdate: updateId, assetKey: "bundle", assetHash: bundleHash, isLaunchAsset: true)
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Already cached?
|
|
1069
|
+
if FileManager.default.fileExists(atPath: destination.path) {
|
|
1070
|
+
storeBundleInDatabase()
|
|
1071
|
+
completion(.success(()))
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
guard let url = URL(string: manifest.bundle.url) else {
|
|
1076
|
+
completion(.failure(PulseUpdatesError.downloadFailed(underlying: NSError(domain: "PulseUpdates", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid bundle URL"]))))
|
|
1077
|
+
return
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
let tempPath = stagingDir.appendingPathComponent("bundle.tmp")
|
|
1081
|
+
|
|
1082
|
+
downloadFile(from: url, to: tempPath) { result in
|
|
1083
|
+
switch result {
|
|
1084
|
+
case .failure(let error):
|
|
1085
|
+
completion(.failure(error))
|
|
1086
|
+
case .success:
|
|
1087
|
+
// Verify hash
|
|
1088
|
+
guard let fileHash = sha256Hex(fileUrl: tempPath), fileHash == bundleHash else {
|
|
1089
|
+
completion(.failure(PulseUpdatesError.hashMismatch(expected: bundleHash, actual: "unknown")))
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Move to final destination
|
|
1094
|
+
try? FileManager.default.createDirectory(at: destination.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
1095
|
+
try? FileManager.default.removeItem(at: destination)
|
|
1096
|
+
try? FileManager.default.moveItem(at: tempPath, to: destination)
|
|
1097
|
+
|
|
1098
|
+
// Store in database as launch asset
|
|
1099
|
+
storeBundleInDatabase()
|
|
1100
|
+
|
|
1101
|
+
completion(.success(()))
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
private static func downloadAssets(
|
|
1107
|
+
manifest: PulseManifestModel,
|
|
1108
|
+
directory: URL,
|
|
1109
|
+
stagingDir: URL,
|
|
1110
|
+
database: PulseDatabase?,
|
|
1111
|
+
updateId: String,
|
|
1112
|
+
completion: @escaping (Result<Void, Error>) -> Void
|
|
1113
|
+
) {
|
|
1114
|
+
let assets = manifest.assets
|
|
1115
|
+
guard !assets.isEmpty else {
|
|
1116
|
+
completion(.success(()))
|
|
1117
|
+
return
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
let group = DispatchGroup()
|
|
1121
|
+
var downloadError: Error?
|
|
1122
|
+
|
|
1123
|
+
for asset in assets {
|
|
1124
|
+
group.enter()
|
|
1125
|
+
|
|
1126
|
+
let assetHash = asset.hash.lowercased()
|
|
1127
|
+
let destination = directory.appendingPathComponent("assets/sha256/\(assetHash)")
|
|
1128
|
+
|
|
1129
|
+
// Already cached?
|
|
1130
|
+
if FileManager.default.fileExists(atPath: destination.path) {
|
|
1131
|
+
// Link to update
|
|
1132
|
+
storeAssetInDatabase(asset: asset, hash: assetHash, database: database, updateId: updateId)
|
|
1133
|
+
group.leave()
|
|
1134
|
+
continue
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
guard let url = URL(string: asset.url) else {
|
|
1138
|
+
group.leave()
|
|
1139
|
+
continue
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
let tempPath = stagingDir.appendingPathComponent("asset-\(assetHash).tmp")
|
|
1143
|
+
|
|
1144
|
+
downloadFile(from: url, to: tempPath) { result in
|
|
1145
|
+
switch result {
|
|
1146
|
+
case .failure(let error):
|
|
1147
|
+
downloadError = error
|
|
1148
|
+
case .success:
|
|
1149
|
+
// Verify hash
|
|
1150
|
+
if let fileHash = sha256Hex(fileUrl: tempPath), fileHash == assetHash {
|
|
1151
|
+
try? FileManager.default.createDirectory(at: destination.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
1152
|
+
try? FileManager.default.removeItem(at: destination)
|
|
1153
|
+
try? FileManager.default.moveItem(at: tempPath, to: destination)
|
|
1154
|
+
|
|
1155
|
+
storeAssetInDatabase(asset: asset, hash: assetHash, database: database, updateId: updateId)
|
|
1156
|
+
} else {
|
|
1157
|
+
downloadError = PulseUpdatesError.hashMismatch(expected: assetHash, actual: "unknown")
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
group.leave()
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
group.notify(queue: .global(qos: .userInitiated)) {
|
|
1165
|
+
if let error = downloadError {
|
|
1166
|
+
completion(.failure(error))
|
|
1167
|
+
} else {
|
|
1168
|
+
completion(.success(()))
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private static func storeAssetInDatabase(asset: PulseAssetModel, hash: String, database: PulseDatabase?, updateId: String) {
|
|
1174
|
+
var dbAsset = PulseAsset(key: asset.key, hash: hash)
|
|
1175
|
+
dbAsset.type = asset.contentType
|
|
1176
|
+
dbAsset.nsBundleDir = asset.nsBundleDir
|
|
1177
|
+
dbAsset.nsBundleFilename = asset.nsBundleFilename
|
|
1178
|
+
|
|
1179
|
+
if let assetId = try? database?.addAsset(dbAsset) {
|
|
1180
|
+
try? database?.linkAsset(assetId: assetId, toUpdate: updateId, assetKey: asset.key, assetHash: hash, isLaunchAsset: false)
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
private static func downloadFile(from url: URL, to destination: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
|
1185
|
+
URLSession.shared.downloadTask(with: url) { tempUrl, response, error in
|
|
1186
|
+
if let error = error {
|
|
1187
|
+
completion(.failure(PulseUpdatesError.downloadFailed(underlying: error)))
|
|
1188
|
+
return
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if let httpResponse = response as? HTTPURLResponse,
|
|
1192
|
+
httpResponse.statusCode < 200 || httpResponse.statusCode >= 300 {
|
|
1193
|
+
completion(.failure(PulseUpdatesError.downloadFailed(underlying: NSError(domain: "HTTP", code: httpResponse.statusCode, userInfo: nil))))
|
|
1194
|
+
return
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
guard let tempUrl = tempUrl else {
|
|
1198
|
+
completion(.failure(PulseUpdatesError.downloadFailed(underlying: NSError(domain: "PulseUpdates", code: 0, userInfo: [NSLocalizedDescriptionKey: "No download file"]))))
|
|
1199
|
+
return
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
do {
|
|
1203
|
+
try? FileManager.default.removeItem(at: destination)
|
|
1204
|
+
try? FileManager.default.createDirectory(at: destination.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
1205
|
+
try FileManager.default.moveItem(at: tempUrl, to: destination)
|
|
1206
|
+
completion(.success(()))
|
|
1207
|
+
} catch {
|
|
1208
|
+
completion(.failure(PulseUpdatesError.downloadFailed(underlying: error)))
|
|
1209
|
+
}
|
|
1210
|
+
}.resume()
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
private static func cleanupStaging(_ dir: URL) {
|
|
1214
|
+
try? FileManager.default.removeItem(at: dir)
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
private static func sha256Hex(fileUrl: URL) -> String? {
|
|
1218
|
+
guard let handle = try? FileHandle(forReadingFrom: fileUrl) else { return nil }
|
|
1219
|
+
defer { try? handle.close() }
|
|
1220
|
+
|
|
1221
|
+
var hasher = SHA256()
|
|
1222
|
+
while true {
|
|
1223
|
+
let data = try? handle.read(upToCount: 1024 * 1024)
|
|
1224
|
+
guard let chunk = data, !chunk.isEmpty else { break }
|
|
1225
|
+
hasher.update(data: chunk)
|
|
1226
|
+
}
|
|
1227
|
+
let digest = hasher.finalize()
|
|
1228
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
private static func verifyManifestSignature(manifest: PulseManifestModel, manifestJson: Data, config: PulseUpdatesConfig) -> Bool {
|
|
1232
|
+
guard let signature = manifest.signature else { return false }
|
|
1233
|
+
guard signature.alg.lowercased() == "ed25519" else { return false }
|
|
1234
|
+
guard let keyId = config.signingKeyId, signature.keyId == keyId else { return false }
|
|
1235
|
+
guard let publicKeyBase64 = config.signingPublicKey,
|
|
1236
|
+
let publicKeyData = Data(base64Encoded: publicKeyBase64),
|
|
1237
|
+
let signatureData = Data(base64Encoded: signature.sig) else { return false }
|
|
1238
|
+
|
|
1239
|
+
let canonical = canonicalizeManifestJson(manifestJson)
|
|
1240
|
+
|
|
1241
|
+
guard let publicKey = try? Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData) else { return false }
|
|
1242
|
+
return publicKey.isValidSignature(signatureData, for: canonical)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private static func canonicalizeManifestJson(_ data: Data) -> Data {
|
|
1246
|
+
guard let json = try? JSONSerialization.jsonObject(with: data),
|
|
1247
|
+
var dict = json as? [String: Any] else { return data }
|
|
1248
|
+
|
|
1249
|
+
dict.removeValue(forKey: "signature")
|
|
1250
|
+
let canonical = canonicalizeJson(dict)
|
|
1251
|
+
return canonical.data(using: .utf8) ?? data
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
private static func canonicalizeJson(_ value: Any) -> String {
|
|
1255
|
+
if let dict = value as? [String: Any] {
|
|
1256
|
+
let keys = dict.keys.sorted()
|
|
1257
|
+
let items = keys.compactMap { key -> String? in
|
|
1258
|
+
guard let val = dict[key] else { return nil }
|
|
1259
|
+
return "\"\(escapeJson(key))\":\(canonicalizeJson(val))"
|
|
1260
|
+
}
|
|
1261
|
+
return "{\(items.joined(separator: ","))}"
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if let array = value as? [Any] {
|
|
1265
|
+
return "[\(array.map { canonicalizeJson($0) }.joined(separator: ","))]"
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if value is NSNull { return "null" }
|
|
1269
|
+
|
|
1270
|
+
if let number = value as? NSNumber {
|
|
1271
|
+
if CFGetTypeID(number) == CFBooleanGetTypeID() {
|
|
1272
|
+
return number.boolValue ? "true" : "false"
|
|
1273
|
+
}
|
|
1274
|
+
return number.stringValue
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if let string = value as? String {
|
|
1278
|
+
return "\"\(escapeJson(string))\""
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
return "null"
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
private static func escapeJson(_ input: String) -> String {
|
|
1285
|
+
var result = ""
|
|
1286
|
+
for char in input {
|
|
1287
|
+
switch char {
|
|
1288
|
+
case "\"": result += "\\\""
|
|
1289
|
+
case "\\": result += "\\\\"
|
|
1290
|
+
case "\n": result += "\\n"
|
|
1291
|
+
case "\r": result += "\\r"
|
|
1292
|
+
case "\t": result += "\\t"
|
|
1293
|
+
default:
|
|
1294
|
+
if char.asciiValue ?? 0 < 32 {
|
|
1295
|
+
result += String(format: "\\u%04x", char.asciiValue ?? 0)
|
|
1296
|
+
} else {
|
|
1297
|
+
result.append(char)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return result
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// MARK: - Error Recovery Delegate
|
|
1306
|
+
|
|
1307
|
+
extension PulseController: PulseErrorRecoveryDelegate {
|
|
1308
|
+
|
|
1309
|
+
func getLaunchedUpdate() -> PulseUpdateInfo? {
|
|
1310
|
+
return self.launchedUpdate
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
var checkOnLaunch: String {
|
|
1314
|
+
return config?.checkOnLaunch ?? "ALWAYS"
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
func relaunch(completion: @escaping (Error?, Bool) -> Void) {
|
|
1318
|
+
// Try to launch a different update from cache
|
|
1319
|
+
launchBestUpdate { success in
|
|
1320
|
+
if success {
|
|
1321
|
+
// Trigger a reload of React Native
|
|
1322
|
+
NotificationCenter.default.post(
|
|
1323
|
+
name: NSNotification.Name("PulseUpdatesReloadRequest"),
|
|
1324
|
+
object: nil
|
|
1325
|
+
)
|
|
1326
|
+
}
|
|
1327
|
+
completion(nil, success)
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
func loadRemoteUpdate() {
|
|
1332
|
+
remoteLoadStatus = .loading
|
|
1333
|
+
fetchUpdate { [weak self] result in
|
|
1334
|
+
switch result {
|
|
1335
|
+
case .success(let fetchResult):
|
|
1336
|
+
self?.remoteLoadStatus = fetchResult.isNew ? .newUpdateLoaded : .idle
|
|
1337
|
+
case .failure:
|
|
1338
|
+
self?.remoteLoadStatus = .idle
|
|
1339
|
+
}
|
|
1340
|
+
self?.errorRecovery?.notify(newRemoteLoadStatus: self?.remoteLoadStatus ?? .idle)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
func markFailedLaunchForLaunchedUpdate() {
|
|
1345
|
+
guard let updateId = launchedUpdate?.updateId else { return }
|
|
1346
|
+
try? database?.recordFailedLaunch(updateId: updateId)
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
func markSuccessfulLaunchForLaunchedUpdate() {
|
|
1350
|
+
guard let updateId = launchedUpdate?.updateId else { return }
|
|
1351
|
+
try? database?.recordSuccessfulLaunch(updateId: updateId)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
func throwException(_ exception: NSException) {
|
|
1355
|
+
exception.raise()
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
import CryptoKit
|