expo-observe 58.0.0 → 58.0.3
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/.npmignore +5 -0
- package/CHANGELOG.md +14 -0
- package/android/src/main/java/expo/modules/observe/Event.kt +10 -0
- package/android/src/main/java/expo/modules/observe/EventDispatcher.kt +26 -0
- package/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt +5 -4
- package/android/src/main/java/expo/modules/observe/ObservabilityManager.kt +97 -0
- package/android/src/main/java/expo/modules/observe/ObserveModule.kt +1 -0
- package/android/src/main/java/expo/modules/observe/OpenTelemetry.kt +161 -1
- package/android/src/main/java/expo/modules/observe/SpanDispatchOutcome.kt +43 -0
- package/build/module.web.d.ts +1 -1
- package/build/types.d.ts +2 -1
- package/build/types.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/DispatchSerializer.swift +40 -0
- package/ios/Observability.swift +133 -20
- package/ios/OpenTelemetry.swift +150 -6
- package/ios/SpanDispatchLoop.swift +112 -0
- package/ios/Tests/DispatchLoopTests.swift +6 -1
- package/ios/Tests/DispatchSerializerTests.swift +100 -0
- package/ios/Tests/DispatchUtilsNextCursorTests.swift +6 -1
- package/ios/Tests/DispatchUtilsRetryGateTests.swift +6 -1
- package/ios/Tests/OTSpanTests.swift +283 -0
- package/ios/Tests/ObservabilityClassifyResponseTests.swift +3 -0
- package/ios/Tests/SpanDispatchLoopTests.swift +201 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha512 +1 -0
- package/{android/build/publications/release/module.json → local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module} +23 -23
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha512 +1 -0
- package/{android/build/publications/release/pom-default.xml → local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom} +2 -2
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/src/types.ts +2 -1
- package/android/build/generated/source/buildConfig/release/expo/modules/observe/BuildConfig.java +0 -12
- package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml +0 -7
- package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json +0 -18
- package/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar +0 -0
- package/android/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties +0 -7
- package/android/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json +0 -1
- package/android/build/intermediates/annotations_typedef_file/release/extractReleaseAnnotations/typedefs.txt +0 -0
- package/android/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/release/generateReleaseRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/mergeReleaseAssets/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml +0 -2
- package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +0 -1
- package/android/build/intermediates/incremental/release/packageReleaseResources/merger.xml +0 -2
- package/android/build/intermediates/incremental/release-mergeJavaRes/merge-state +0 -0
- package/android/build/intermediates/java_res/release/processReleaseJavaRes/out/META-INF/expo-observe_release.kotlin_module +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/expo/modules/observe/BuildConfig.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt +0 -2
- package/android/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt +0 -9
- package/android/build/intermediates/merged_java_res/release/mergeReleaseJavaResource/feature-expo-observe.jar +0 -0
- package/android/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml +0 -7
- package/android/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt +0 -1
- package/android/build/intermediates/source_jar/release/release-sources.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt +0 -1
- 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.at +0 -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.at +0 -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.at +0 -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 +0 -1
- 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 +0 -2
- 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.at +0 -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/outputs/aar/expo-observe-release.aar +0 -0
- package/android/build/outputs/logs/manifest-merger-release-report.txt +0 -14
- package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/release/META-INF/expo-observe_release.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$cleanup$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$dispatchUnsentLogs$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$dispatchUnsentMetrics$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BundleDefaults$__Pika.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BundleDefaults.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Config$__Pika.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Config.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ConstantsKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairCursorIfStale$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairLogCursorIfStale$4.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairMetricCursorIfStale$4.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$NonRetryableFailure.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$PartialSuccess.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$PayloadTooLarge.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$RetryableFailure.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$Success.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils$RetryGateState$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils$RetryGateState.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EventDispatcher.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ExpoManifest.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ExpoManifestKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Arr.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Bln.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Dbl.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Int64.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$KvList.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Str.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValueSerializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker$doWork$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityManager.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Constant$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Coroutine$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$2.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$3.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$4.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$FunctionWithoutArgs$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$OnActivityEntersBackground$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$OnCreate$1.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservePreferences.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservePreferencesKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OpenTelemetryKt.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig$$serializer.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig.class +0 -0
- package/android/build/tmp/publishReleasePublicationToNPMPackageRepository/module-maven-metadata.xml +0 -13
- package/android/build/tmp/sourceReleaseJar/MANIFEST.MF +0 -2
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module +0 -154
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom +0 -106
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha512 +0 -1
package/.npmignore
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 58.0.3 — 2026-09-14
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 58.0.2 — 2026-09-14
|
|
18
|
+
|
|
19
|
+
_This version does not introduce any user-facing changes._
|
|
20
|
+
|
|
21
|
+
## 58.0.1 — 2026-09-14
|
|
22
|
+
|
|
23
|
+
_This version does not introduce any user-facing changes._
|
|
24
|
+
|
|
13
25
|
## 58.0.0 — 2026-09-10
|
|
14
26
|
|
|
15
27
|
### 🎉 New features
|
|
@@ -19,10 +31,12 @@
|
|
|
19
31
|
- Add `reportError` to report caught, non-fatal errors from your own `try`/`catch` blocks. ([#47871](https://github.com/expo/expo/pull/47871) by [@tsapeta](https://github.com/tsapeta))
|
|
20
32
|
- Add an `errorHandlingEnabled` option to `configure` to opt out of recording unhandled JavaScript errors. ([#48506](https://github.com/expo/expo/pull/48506) by [@tsapeta](https://github.com/tsapeta))
|
|
21
33
|
- Add `Observe.clientId`, the EAS client id recorded on every event, so apps can correlate Observe data with other services. ([#49599](https://github.com/expo/expo/pull/49599) by [@kadikraman](https://github.com/kadikraman))
|
|
34
|
+
- Export network requests as OTLP traces. ([#48883](https://github.com/expo/expo/pull/48883) by [@tsapeta](https://github.com/tsapeta))
|
|
22
35
|
|
|
23
36
|
### 🐛 Bug fixes
|
|
24
37
|
|
|
25
38
|
- Fix OTel date conversion ([#48161](https://github.com/expo/expo/pull/48161) by [@Ubax](https://github.com/Ubax))
|
|
39
|
+
- [iOS] Stop overlapping dispatches from sending the same pending rows twice. `dispatchEvents()` now waits for a dispatch already in progress instead of overlapping with it. ([#48883](https://github.com/expo/expo/pull/48883) by [@tsapeta](https://github.com/tsapeta))
|
|
26
40
|
- [Android] Explicitly enable `buildFeatures.buildConfig`, required by AGP 9. ([#47729](https://github.com/expo/expo/pull/47729) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
27
41
|
|
|
28
42
|
### 💡 Others
|
|
@@ -145,3 +145,13 @@ data class Event(
|
|
|
145
145
|
val metrics: List<EASMetric>,
|
|
146
146
|
val logs: List<LogEvent> = emptyList()
|
|
147
147
|
)
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* One session's spans paired with the event carrying that session's resource metadata, ready
|
|
151
|
+
* for the traces dispatch. Spans travel separately from `Event` because they aren't part of
|
|
152
|
+
* the payload shape the metrics/logs signals share.
|
|
153
|
+
*/
|
|
154
|
+
data class SpanBatch(
|
|
155
|
+
val event: Event,
|
|
156
|
+
val spans: List<OTSpan>
|
|
157
|
+
)
|
|
@@ -27,6 +27,8 @@ class EventDispatcher(
|
|
|
27
27
|
|
|
28
28
|
private fun logsEndpointUrl(): String = "$baseProjectUrl/v1/logs"
|
|
29
29
|
|
|
30
|
+
private fun tracesEndpointUrl(): String = "$baseProjectUrl/v1/traces"
|
|
31
|
+
|
|
30
32
|
/**
|
|
31
33
|
* POSTs `events` to the metrics endpoint and classifies the response per the OTLP retry
|
|
32
34
|
* spec. Non-throwing: payload-encoding errors collapse to `NonRetryable` (same bytes will
|
|
@@ -76,6 +78,30 @@ class EventDispatcher(
|
|
|
76
78
|
executePost(continuation, logsEndpointUrl(), body)
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Dispatches span batches to `{baseUrl}/{projectId}/v1/traces`. Each batch carries one
|
|
83
|
+
* session's spans plus the event holding that session's resource metadata.
|
|
84
|
+
*/
|
|
85
|
+
suspend fun dispatchSpans(batches: List<SpanBatch>): DispatchResult =
|
|
86
|
+
suspendCancellableCoroutine { continuation ->
|
|
87
|
+
if (batches.isEmpty()) {
|
|
88
|
+
// Empty input isn't a server response - signal success.
|
|
89
|
+
continuation.resume(DispatchResult.Success)
|
|
90
|
+
return@suspendCancellableCoroutine Unit
|
|
91
|
+
}
|
|
92
|
+
val easId = EASClientID(context).uuid.toString()
|
|
93
|
+
val body = try {
|
|
94
|
+
OTTracesRequestBody(
|
|
95
|
+
resourceSpans = batches.map { it.event.toOTResourceSpans(easId, it.spans) }
|
|
96
|
+
).toJson(prettyPrint = true)
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
Log.w(OBSERVE_TAG, "Encoding traces request body failed: ${e.message}")
|
|
99
|
+
continuation.resume(DispatchResult.NonRetryableFailure("encoding error: ${e.message}"))
|
|
100
|
+
return@suspendCancellableCoroutine Unit
|
|
101
|
+
}
|
|
102
|
+
executePost(continuation, tracesEndpointUrl(), body)
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
private fun executePost(
|
|
80
106
|
continuation: kotlinx.coroutines.CancellableContinuation<DispatchResult>,
|
|
81
107
|
endpointUrl: String,
|
|
@@ -18,7 +18,7 @@ import androidx.work.workDataOf
|
|
|
18
18
|
import expo.modules.appmetrics.storage.SessionManager
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Background worker that dispatches stored metrics and
|
|
21
|
+
* Background worker that dispatches stored metrics, logs, and spans to EAS Observe.
|
|
22
22
|
*/
|
|
23
23
|
class ObservabilityBackgroundWorker(
|
|
24
24
|
context: Context,
|
|
@@ -72,10 +72,11 @@ class ObservabilityBackgroundWorker(
|
|
|
72
72
|
observabilityManager.cleanup()
|
|
73
73
|
observabilityManager.dispatchUnsentMetrics()
|
|
74
74
|
observabilityManager.dispatchUnsentLogs()
|
|
75
|
-
|
|
75
|
+
observabilityManager.dispatchUnsentSpans()
|
|
76
|
+
Log.d(OBSERVE_TAG, "Successfully dispatched unsent metrics, logs, and spans")
|
|
76
77
|
Result.success()
|
|
77
78
|
} catch (e: Exception) {
|
|
78
|
-
Log.e(OBSERVE_TAG, "Failed to dispatch metrics", e)
|
|
79
|
+
Log.e(OBSERVE_TAG, "Failed to dispatch metrics, logs, and spans", e)
|
|
79
80
|
// Retry with exponential backoff
|
|
80
81
|
Result.retry()
|
|
81
82
|
}
|
|
@@ -108,7 +109,7 @@ class ObservabilityBackgroundWorker(
|
|
|
108
109
|
.getInstance(context)
|
|
109
110
|
.enqueueUniqueWork(
|
|
110
111
|
WORK_NAME,
|
|
111
|
-
// Keep an in-flight dispatch;
|
|
112
|
+
// Keep an in-flight dispatch; canceling it can duplicate a request the server received.
|
|
112
113
|
ExistingWorkPolicy.KEEP,
|
|
113
114
|
periodicWork
|
|
114
115
|
)
|
|
@@ -4,6 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import expo.modules.easclient.EASClientID
|
|
6
6
|
import expo.modules.appmetrics.storage.SessionManager
|
|
7
|
+
import expo.modules.appmetrics.storage.Span
|
|
7
8
|
import expo.modules.appmetrics.utils.TimeUtils
|
|
8
9
|
import expo.modules.interfaces.constants.ConstantsInterface
|
|
9
10
|
import kotlinx.coroutines.currentCoroutineContext
|
|
@@ -49,6 +50,10 @@ class ObservabilityManager(
|
|
|
49
50
|
baseManager.dispatchUnsentLogs()
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
suspend fun dispatchUnsentSpans() {
|
|
54
|
+
baseManager.dispatchUnsentSpans()
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
fun scheduleBackgroundDispatch() {
|
|
53
58
|
ObservabilityBackgroundWorker.scheduleBackgroundDispatch(
|
|
54
59
|
context = context,
|
|
@@ -91,6 +96,7 @@ class BaseObservabilityManager(
|
|
|
91
96
|
*/
|
|
92
97
|
private var metricsRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
|
|
93
98
|
private var logsRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
|
|
99
|
+
private var spansRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
|
|
94
100
|
|
|
95
101
|
/**
|
|
96
102
|
* Returns true and logs when an active retry gate suppresses this dispatch round. Called
|
|
@@ -276,6 +282,90 @@ class BaseObservabilityManager(
|
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Dispatches persisted spans to `/v1/traces`.
|
|
287
|
+
*
|
|
288
|
+
* Unlike metrics and logs there is no persisted cursor yet: a consumed (or deliberately
|
|
289
|
+
* dropped) batch is deleted outright and the table itself acts as the queue. Rows survive
|
|
290
|
+
* on a retryable failure and go out on the next dispatch. Batches are chunked at the
|
|
291
|
+
* server's per-request span limit.
|
|
292
|
+
*/
|
|
293
|
+
suspend fun dispatchUnsentSpans(): Unit = spansDispatchMutex.withLock {
|
|
294
|
+
if (retryGateBlocks(spansRetryGate, "spans")) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!shouldDispatch()) {
|
|
299
|
+
// Mirrors metrics/logs advancing the cursor past rows they won't send.
|
|
300
|
+
sessionManager.getMaxSpanId()?.let { sessionManager.deleteSpansUpTo(it) }
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Rows are read in id order one server-sized chunk at a time; a chunk that fails retryably
|
|
305
|
+
// stops the loop and leaves its rows (and everything after them) for the next dispatch. An
|
|
306
|
+
// oversized chunk is split in half and retried, so one huge span doesn't drop its whole
|
|
307
|
+
// chunk.
|
|
308
|
+
var readCursor = -1L
|
|
309
|
+
val chunks = ArrayDeque<List<Span>>()
|
|
310
|
+
while (currentCoroutineContext().isActive) {
|
|
311
|
+
if (chunks.isEmpty()) {
|
|
312
|
+
val nextChunk = sessionManager.getSpans(readCursor, MAX_SPANS_PER_REQUEST)
|
|
313
|
+
if (nextChunk.isEmpty()) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
readCursor = nextChunk.last().id
|
|
317
|
+
chunks.addLast(nextChunk)
|
|
318
|
+
}
|
|
319
|
+
val chunk = chunks.removeFirst()
|
|
320
|
+
val spansBySessionId = chunk.groupBy { it.sessionId }
|
|
321
|
+
// Batched because an offline backlog spans many sessions, and each halving retry would
|
|
322
|
+
// otherwise re-run every lookup.
|
|
323
|
+
val sessions = sessionManager.getSessions(spansBySessionId.keys).associateBy { it.id }
|
|
324
|
+
val batches = spansBySessionId.mapNotNull { (sessionId, spans) ->
|
|
325
|
+
// Spans whose session row no longer exists are skipped, because their rows are about to be
|
|
326
|
+
// deleted below anyway.
|
|
327
|
+
val session = sessions[sessionId] ?: return@mapNotNull null
|
|
328
|
+
SpanBatch(
|
|
329
|
+
event = Event(metadata = Metadata.fromSessionMetadata(session), metrics = emptyList()),
|
|
330
|
+
spans = spans.map { it.toOTSpan() }
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
val highestId = chunk.last().id
|
|
334
|
+
if (batches.isEmpty()) {
|
|
335
|
+
sessionManager.deleteSpansUpTo(highestId)
|
|
336
|
+
continue
|
|
337
|
+
}
|
|
338
|
+
val result = eventDispatcher.dispatchSpans(batches)
|
|
339
|
+
spansRetryGate = nextGate(spansRetryGate, result)
|
|
340
|
+
when (result) {
|
|
341
|
+
is DispatchResult.PartialSuccess ->
|
|
342
|
+
Log.w(
|
|
343
|
+
OBSERVE_TAG,
|
|
344
|
+
"Partial success on batch of ${chunk.size} span(s): " +
|
|
345
|
+
"server rejected ${result.partial.rejectedCount} " +
|
|
346
|
+
"(${result.partial.errorMessage ?: "no error message"})"
|
|
347
|
+
)
|
|
348
|
+
is DispatchResult.NonRetryableFailure ->
|
|
349
|
+
Log.w(OBSERVE_TAG, "Dropping batch of ${chunk.size} span(s): ${result.reason}")
|
|
350
|
+
DispatchResult.PayloadTooLarge -> if (chunk.size == 1) {
|
|
351
|
+
Log.w(OBSERVE_TAG, "Dropping span that exceeds the server's payload limit")
|
|
352
|
+
}
|
|
353
|
+
DispatchResult.Success, is DispatchResult.RetryableFailure -> Unit
|
|
354
|
+
}
|
|
355
|
+
when (spanDispatchDisposition(result, chunk.size)) {
|
|
356
|
+
SpanDispatchDisposition.HALVE_AND_RETRY -> {
|
|
357
|
+
// `toList()` rather than the `subList` views: nested views would keep the whole
|
|
358
|
+
// original chunk (up to 512 rows with their JSON blobs) reachable until every
|
|
359
|
+
// descendant had been processed. Matches iOS, which materializes with `Array`.
|
|
360
|
+
chunks.addFirst(chunk.subList(chunk.size / 2, chunk.size).toList())
|
|
361
|
+
chunks.addFirst(chunk.subList(0, chunk.size / 2).toList())
|
|
362
|
+
}
|
|
363
|
+
SpanDispatchDisposition.DELETE_AND_CONTINUE -> sessionManager.deleteSpansUpTo(highestId)
|
|
364
|
+
SpanDispatchDisposition.KEEP_AND_STOP -> return
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
279
369
|
private fun isInSample(): Boolean {
|
|
280
370
|
val rate = ObservePreferences.getConfig(context)?.sampleRate ?: return true
|
|
281
371
|
val clamped = rate.coerceIn(0.0, 1.0)
|
|
@@ -303,8 +393,15 @@ class BaseObservabilityManager(
|
|
|
303
393
|
}
|
|
304
394
|
|
|
305
395
|
companion object {
|
|
396
|
+
/**
|
|
397
|
+
* Maximum spans per request accepted by the ingestion endpoint; it rejects everything past
|
|
398
|
+
* this count within one POST, so larger backlogs are sent as sequential chunks.
|
|
399
|
+
*/
|
|
400
|
+
const val MAX_SPANS_PER_REQUEST = 512
|
|
401
|
+
|
|
306
402
|
// Serialize foreground and background dispatches without blocking the other signal.
|
|
307
403
|
private val metricsDispatchMutex = Mutex()
|
|
308
404
|
private val logsDispatchMutex = Mutex()
|
|
405
|
+
private val spansDispatchMutex = Mutex()
|
|
309
406
|
}
|
|
310
407
|
}
|
|
@@ -66,6 +66,7 @@ class ObserveModule : Module() {
|
|
|
66
66
|
AsyncFunction("dispatchEvents") Coroutine { ->
|
|
67
67
|
observabilityManager.dispatchUnsentMetrics()
|
|
68
68
|
observabilityManager.dispatchUnsentLogs()
|
|
69
|
+
observabilityManager.dispatchUnsentSpans()
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
Function("configure") { config: Config ->
|
|
@@ -6,6 +6,7 @@ import expo.modules.appmetrics.logevents.Severity
|
|
|
6
6
|
import expo.modules.appmetrics.utils.TimeUtils.timestampToDateNS
|
|
7
7
|
import kotlinx.serialization.Serializable
|
|
8
8
|
import kotlinx.serialization.json.Json
|
|
9
|
+
import kotlinx.serialization.json.JsonArray
|
|
9
10
|
import kotlinx.serialization.json.JsonElement
|
|
10
11
|
import kotlinx.serialization.json.JsonObject
|
|
11
12
|
import kotlinx.serialization.json.JsonPrimitive
|
|
@@ -134,6 +135,59 @@ data class OTResourceLogs(
|
|
|
134
135
|
val schemaUrl: String
|
|
135
136
|
)
|
|
136
137
|
|
|
138
|
+
/**
|
|
139
|
+
* One OTLP span, per https://opentelemetry.io/docs/concepts/signals/traces/#spans.
|
|
140
|
+
*
|
|
141
|
+
* Nullable fields with defaults (`parentSpanId`, `status`) rely on kotlinx-serialization omitting
|
|
142
|
+
* defaults: the ingestion endpoint rejects a span whose present `parentSpanId` isn't valid hex, so
|
|
143
|
+
* a root span must omit the key entirely rather than send null or "".
|
|
144
|
+
*/
|
|
145
|
+
@Serializable
|
|
146
|
+
data class OTSpan(
|
|
147
|
+
val traceId: String,
|
|
148
|
+
val spanId: String,
|
|
149
|
+
val parentSpanId: String? = null,
|
|
150
|
+
val name: String,
|
|
151
|
+
val kind: Int,
|
|
152
|
+
val startTimeUnixNano: Long,
|
|
153
|
+
val endTimeUnixNano: Long,
|
|
154
|
+
val attributes: List<OTAttribute>,
|
|
155
|
+
val events: List<OTSpanEvent>,
|
|
156
|
+
val droppedEventsCount: Int = 0,
|
|
157
|
+
val status: OTSpanStatus? = null
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
/** `Span.Event` per the OTLP proto, used for redirect hops. */
|
|
161
|
+
@Serializable
|
|
162
|
+
data class OTSpanEvent(
|
|
163
|
+
val timeUnixNano: Long,
|
|
164
|
+
val name: String,
|
|
165
|
+
val attributes: List<OTAttribute>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* `Status` per the OTLP proto. Only attached when the span represents a failure; a successful
|
|
170
|
+
* client span stays UNSET by omitting the status entirely, per the semantic conventions.
|
|
171
|
+
*/
|
|
172
|
+
@Serializable
|
|
173
|
+
data class OTSpanStatus(
|
|
174
|
+
val code: Int,
|
|
175
|
+
val message: String? = null
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@Serializable
|
|
179
|
+
data class OTScopeSpans(
|
|
180
|
+
val scope: OTScope,
|
|
181
|
+
val spans: List<OTSpan>
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@Serializable
|
|
185
|
+
data class OTResourceSpans(
|
|
186
|
+
val resource: OTMetadata,
|
|
187
|
+
val scopeSpans: List<OTScopeSpans>,
|
|
188
|
+
val schemaUrl: String
|
|
189
|
+
)
|
|
190
|
+
|
|
137
191
|
// MARK: -- Request body for Open Telemetry events
|
|
138
192
|
|
|
139
193
|
@Serializable
|
|
@@ -160,6 +214,18 @@ data class OTLogsRequestBody(
|
|
|
160
214
|
}
|
|
161
215
|
}
|
|
162
216
|
|
|
217
|
+
@Serializable
|
|
218
|
+
data class OTTracesRequestBody(
|
|
219
|
+
val resourceSpans: List<OTResourceSpans>
|
|
220
|
+
) {
|
|
221
|
+
fun toJson(prettyPrint: Boolean = false): String {
|
|
222
|
+
val json = Json {
|
|
223
|
+
this.prettyPrint = prettyPrint
|
|
224
|
+
}
|
|
225
|
+
return json.encodeToString(serializer(), this)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
163
229
|
// MARK: -- Response shapes
|
|
164
230
|
|
|
165
231
|
/**
|
|
@@ -177,10 +243,11 @@ data class OTLogsRequestBody(
|
|
|
177
243
|
data class OTPartialSuccess(
|
|
178
244
|
val rejectedDataPoints: Int? = null,
|
|
179
245
|
val rejectedLogRecords: Int? = null,
|
|
246
|
+
val rejectedSpans: Int? = null,
|
|
180
247
|
val errorMessage: String? = null
|
|
181
248
|
) {
|
|
182
249
|
val rejectedCount: Int
|
|
183
|
-
get() = (rejectedDataPoints ?: 0) + (rejectedLogRecords ?: 0)
|
|
250
|
+
get() = (rejectedDataPoints ?: 0) + (rejectedLogRecords ?: 0) + (rejectedSpans ?: 0)
|
|
184
251
|
}
|
|
185
252
|
|
|
186
253
|
@Serializable
|
|
@@ -399,6 +466,99 @@ fun Event.toOTResourceLogs(easClientId: String): OTResourceLogs {
|
|
|
399
466
|
)
|
|
400
467
|
}
|
|
401
468
|
|
|
469
|
+
/**
|
|
470
|
+
* Wraps already-mapped spans in this event's resource envelope. Spans are passed in rather than
|
|
471
|
+
* derived from the event because they come from `spans` rows, which are not part of the `Event`
|
|
472
|
+
* payload shape the metrics/logs signals share.
|
|
473
|
+
*/
|
|
474
|
+
fun Event.toOTResourceSpans(easClientId: String, spans: List<OTSpan>): OTResourceSpans {
|
|
475
|
+
return OTResourceSpans(
|
|
476
|
+
resource = toOTMetadata(easClientId),
|
|
477
|
+
scopeSpans = listOf(
|
|
478
|
+
OTScopeSpans(
|
|
479
|
+
scope = OTScope(name = "expo-observe", version = BuildConfig.EXPO_OBSERVE_VERSION),
|
|
480
|
+
spans = spans
|
|
481
|
+
)
|
|
482
|
+
),
|
|
483
|
+
schemaUrl = SEMCONV_SCHEMA_URL
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Maximum span events the ingestion endpoint keeps per span; anything past it is counted as
|
|
489
|
+
* dropped server-side, so the SDK truncates locally and reports the loss itself instead of
|
|
490
|
+
* shipping payload that is guaranteed to be discarded.
|
|
491
|
+
*/
|
|
492
|
+
private const val MAX_SPAN_EVENT_COUNT = 32
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Converts a persisted unix-epoch millisecond timestamp to nanoseconds, saturating instead of
|
|
496
|
+
* overflowing: a corrupt row or a device clock set far into the future must never wrap into a
|
|
497
|
+
* negative timestamp.
|
|
498
|
+
*/
|
|
499
|
+
private fun spanNanosecondsFromMilliseconds(milliseconds: Long): Long {
|
|
500
|
+
val maxRepresentableMs = Long.MAX_VALUE / 1_000_000
|
|
501
|
+
return milliseconds.coerceIn(0, maxRepresentableMs) * 1_000_000
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Maps one persisted span row onto the OTLP wire shape. The mapping is producer-agnostic:
|
|
506
|
+
* attributes and events were shaped by whichever producer recorded the row (per its own
|
|
507
|
+
* semantic conventions), and this conversion only handles the generic concerns: timestamps,
|
|
508
|
+
* the session attribute, the event cap, and the JSON-to-`AnyValue` typing. Mirrors the iOS
|
|
509
|
+
* `SpanRow.toOTSpan`.
|
|
510
|
+
*/
|
|
511
|
+
fun expo.modules.appmetrics.storage.Span.toOTSpan(): OTSpan {
|
|
512
|
+
// Millisecond precision matches the backend's DateTime64(3) storage. A clock adjustment
|
|
513
|
+
// mid-span can invert the two wall-clock timestamps, and the server rejects a span whose end
|
|
514
|
+
// precedes its start, so the end clamps to the start.
|
|
515
|
+
val startNs = spanNanosecondsFromMilliseconds(startTimestampMs)
|
|
516
|
+
val endNs = maxOf(startNs, spanNanosecondsFromMilliseconds(endTimestampMs))
|
|
517
|
+
|
|
518
|
+
val otAttributes = mutableListOf(
|
|
519
|
+
OTAttribute.of(key = "session.id", rawValue = sessionId)
|
|
520
|
+
)
|
|
521
|
+
attributes
|
|
522
|
+
?.let { json -> runCatching { Json.decodeFromString<JsonObject>(json) }.getOrNull() }
|
|
523
|
+
?.let { obj -> otAttributes.addAll(otAttributesFromJsonObject(obj).first) }
|
|
524
|
+
|
|
525
|
+
// The ingestion endpoint keeps at most `MAX_SPAN_EVENT_COUNT` events per span and counts the
|
|
526
|
+
// rest as dropped; truncating locally reports the same loss without shipping payload that is
|
|
527
|
+
// guaranteed to be discarded.
|
|
528
|
+
val allEvents = events
|
|
529
|
+
?.let { json -> runCatching { Json.decodeFromString<JsonArray>(json) }.getOrNull() }
|
|
530
|
+
?: JsonArray(emptyList())
|
|
531
|
+
val otEvents = allEvents.take(MAX_SPAN_EVENT_COUNT).mapNotNull { element ->
|
|
532
|
+
val obj = element as? JsonObject ?: return@mapNotNull null
|
|
533
|
+
// The server drops nameless events anyway; skipping them locally keeps the payload honest.
|
|
534
|
+
val name = (obj["name"] as? JsonPrimitive)?.takeIf { it.isString }?.content
|
|
535
|
+
?: return@mapNotNull null
|
|
536
|
+
val eventAttributes = (obj["attributes"] as? JsonObject)
|
|
537
|
+
?.let { otAttributesFromJsonObject(it).first }
|
|
538
|
+
?: emptyList()
|
|
539
|
+
// Producers may record a per-event timestamp; without one the event anchors to the span
|
|
540
|
+
// start, which keeps it inside the span window.
|
|
541
|
+
val timeNs = (obj["timeMs"] as? JsonPrimitive)?.longOrNull
|
|
542
|
+
?.let { spanNanosecondsFromMilliseconds(it) }
|
|
543
|
+
?: startNs
|
|
544
|
+
OTSpanEvent(timeUnixNano = timeNs, name = name, attributes = eventAttributes)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return OTSpan(
|
|
548
|
+
traceId = traceId,
|
|
549
|
+
spanId = spanId,
|
|
550
|
+
parentSpanId = parentSpanId,
|
|
551
|
+
name = name,
|
|
552
|
+
kind = kind,
|
|
553
|
+
startTimeUnixNano = startNs,
|
|
554
|
+
endTimeUnixNano = endNs,
|
|
555
|
+
attributes = otAttributes,
|
|
556
|
+
events = otEvents,
|
|
557
|
+
droppedEventsCount = maxOf(0, allEvents.size - MAX_SPAN_EVENT_COUNT),
|
|
558
|
+
status = statusCode?.let { OTSpanStatus(code = it, message = statusMessage) }
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
|
|
402
562
|
internal fun otAnyValueFromJsonElement(element: JsonElement): OTAnyValue? {
|
|
403
563
|
if (element is kotlinx.serialization.json.JsonNull) {
|
|
404
564
|
return null
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright 2025-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
package expo.modules.observe
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What the span dispatch loop does with a chunk once the server has answered. Mirrors the iOS
|
|
7
|
+
* `SpanDispatchLoop.Disposition`.
|
|
8
|
+
*
|
|
9
|
+
* Spans deliberately do not reuse the metrics and logs loop: that one owns a persisted cursor and
|
|
10
|
+
* stops after a batch it cannot advance past, whereas spans have no cursor (the table is the
|
|
11
|
+
* queue) and delete a dropped chunk before continuing with the next one.
|
|
12
|
+
*/
|
|
13
|
+
internal enum class SpanDispatchDisposition {
|
|
14
|
+
/** Delete the chunk's rows and continue with the next chunk. */
|
|
15
|
+
DELETE_AND_CONTINUE,
|
|
16
|
+
|
|
17
|
+
/** Leave every remaining row in place and stop, so the next dispatch retries them. */
|
|
18
|
+
KEEP_AND_STOP,
|
|
19
|
+
|
|
20
|
+
/** Too large to send but still splittable: retry the halves before anything after them. */
|
|
21
|
+
HALVE_AND_RETRY
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a send outcome onto what the loop does next. Pure, so every branch is assertable without
|
|
26
|
+
* a database, a network, or a coroutine scope.
|
|
27
|
+
*
|
|
28
|
+
* @param chunkCount how many spans were in the chunk that produced [result].
|
|
29
|
+
*/
|
|
30
|
+
internal fun spanDispatchDisposition(result: DispatchResult, chunkCount: Int): SpanDispatchDisposition =
|
|
31
|
+
when (result) {
|
|
32
|
+
DispatchResult.Success, is DispatchResult.PartialSuccess ->
|
|
33
|
+
SpanDispatchDisposition.DELETE_AND_CONTINUE
|
|
34
|
+
is DispatchResult.RetryableFailure -> SpanDispatchDisposition.KEEP_AND_STOP
|
|
35
|
+
is DispatchResult.NonRetryableFailure -> SpanDispatchDisposition.DELETE_AND_CONTINUE
|
|
36
|
+
// A single row cannot be split, so it is dropped: keeping it would block every span behind
|
|
37
|
+
// it on every future dispatch.
|
|
38
|
+
DispatchResult.PayloadTooLarge -> if (chunkCount > 1) {
|
|
39
|
+
SpanDispatchDisposition.HALVE_AND_RETRY
|
|
40
|
+
} else {
|
|
41
|
+
SpanDispatchDisposition.DELETE_AND_CONTINUE
|
|
42
|
+
}
|
|
43
|
+
}
|
package/build/module.web.d.ts
CHANGED
package/build/types.d.ts
CHANGED
|
@@ -162,7 +162,8 @@ export declare class ObserveModule extends NativeModule<ObserveModuleEvents> {
|
|
|
162
162
|
* this method to flush events manually, for example, during testing or to ensure events
|
|
163
163
|
* are sent before a specific point.
|
|
164
164
|
*
|
|
165
|
-
* @returns A promise that resolves
|
|
165
|
+
* @returns A promise that resolves once the events have been dispatched. A call made while
|
|
166
|
+
* another dispatch is running waits for it to finish first.
|
|
166
167
|
*
|
|
167
168
|
* @example
|
|
168
169
|
* ```ts
|
package/build/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAqIA,
|
|
1
|
+
{"version":3,"sources":["types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAqIA,WAyJC"}
|
package/expo-module.config.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright 2025-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoAppMetrics
|
|
4
|
+
|
|
5
|
+
/// Runs dispatch passes one at a time, so a later caller waits for the pass ahead of it instead
|
|
6
|
+
/// of overlapping with it.
|
|
7
|
+
///
|
|
8
|
+
/// `ObservabilityManager` is actor-isolated but reentrant at every network `await`, so two
|
|
9
|
+
/// overlapping passes would read and send the same pending rows before either advanced its cursor
|
|
10
|
+
/// or deleted its batch. Serializing also lets a JS `dispatchEvents()` promise mean the queue was
|
|
11
|
+
/// drained, matching Android's per-signal mutexes.
|
|
12
|
+
@AppMetricsActor
|
|
13
|
+
internal final class DispatchSerializer {
|
|
14
|
+
private var inFlight: Task<Void, Never>?
|
|
15
|
+
private var generation = 0
|
|
16
|
+
private var inFlightGeneration = 0
|
|
17
|
+
|
|
18
|
+
/// Waits for any pass already running, then runs `work` as the new in-flight pass.
|
|
19
|
+
internal func run(_ work: @escaping () async -> Void) async {
|
|
20
|
+
// The predecessor is captured and awaited *inside* the new task, not before creating it.
|
|
21
|
+
// Awaiting first would suspend, letting two callers both observe a finished pass and start
|
|
22
|
+
// work concurrently. Chaining publishes this task synchronously, so the next caller links
|
|
23
|
+
// behind it.
|
|
24
|
+
let predecessor = inFlight
|
|
25
|
+
generation += 1
|
|
26
|
+
let ownGeneration = generation
|
|
27
|
+
let task = Task {
|
|
28
|
+
await predecessor?.value
|
|
29
|
+
await work()
|
|
30
|
+
}
|
|
31
|
+
inFlight = task
|
|
32
|
+
inFlightGeneration = ownGeneration
|
|
33
|
+
await task.value
|
|
34
|
+
// Cleared only when still the current pass, so a caller resuming late cannot orphan the pass
|
|
35
|
+
// that replaced it.
|
|
36
|
+
if inFlightGeneration == ownGeneration {
|
|
37
|
+
inFlight = nil
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|