@react-native-firebase/perf 26.3.3 → 27.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/RNFBPerf.podspec +6 -1
  3. package/android/build.gradle +5 -0
  4. package/android/src/main/java/io/invertase/firebase/perf/RNFBPerfHandleRegistry.kt +71 -0
  5. package/android/src/main/java/io/invertase/firebase/perf/UniversalFirebasePerfModule.java +65 -28
  6. package/android/src/test/java/io/invertase/firebase/perf/RNFBPerfHandleRegistryTest.java +205 -0
  7. package/dist/module/version.js +1 -1
  8. package/dist/typescript/lib/index.d.ts +1 -1
  9. package/dist/typescript/lib/version.d.ts +1 -1
  10. package/ios/RNFBPerf/RNFBPerfHandleRegistry.h +41 -0
  11. package/ios/RNFBPerf/RNFBPerfHandleRegistry.m +87 -0
  12. package/ios/RNFBPerf/RNFBPerfHandleRegistryStorage.swift +58 -0
  13. package/ios/RNFBPerf/RNFBPerfHelper.h +71 -0
  14. package/ios/RNFBPerf/RNFBPerfHelper.m +219 -0
  15. package/ios/RNFBPerf/RNFBPerfHttpMethodMapper.swift +86 -0
  16. package/ios/RNFBPerf/RNFBPerfHttpMetricStopApplier.swift +87 -0
  17. package/ios/RNFBPerf/RNFBPerfModule.mm +41 -174
  18. package/ios/RNFBPerf/RNFBPerfTraceStopApplier.swift +68 -0
  19. package/ios/RNFBPerf.xcodeproj/project.pbxproj +32 -0
  20. package/ios/RNFBPerfUnitTests/HostStubs/Firebase/FIRPerformanceHostStub.m +170 -0
  21. package/ios/RNFBPerfUnitTests/HostStubs/Firebase/Firebase.h +75 -0
  22. package/ios/RNFBPerfUnitTests/HostStubs/RNFBApp/RNFBSharedUtils.h +12 -0
  23. package/ios/RNFBPerfUnitTests/HostStubs/RNFBApp/RNFBSharedUtilsHostStub.m +18 -0
  24. package/ios/RNFBPerfUnitTests/HostStubs/React/RCTBridgeModule.h +10 -0
  25. package/ios/RNFBPerfUnitTests/RNFBPerfHandleRegistryTests.m +169 -0
  26. package/ios/RNFBPerfUnitTests/RNFBPerfHelperHostStubTests.m +200 -0
  27. package/ios/RNFBPerfUnitTests/RNFBPerfHttpMethodMapperFacadeTests.m +52 -0
  28. package/ios/RNFBPerfUnitTests/RNFBPerfHttpMethodMapperTests.swift +92 -0
  29. package/ios/RNFBPerfUnitTests/RNFBPerfHttpMetricStopApplierFacadeTests.m +105 -0
  30. package/ios/RNFBPerfUnitTests/RNFBPerfHttpMetricStopApplierTests.swift +201 -0
  31. package/ios/RNFBPerfUnitTests/RNFBPerfTraceStopApplierFacadeTests.m +75 -0
  32. package/ios/RNFBPerfUnitTests/RNFBPerfTraceStopApplierTests.swift +142 -0
  33. package/ios/RNFBPerfUnitTests/RNFBPerfUnitTests.xcodeproj/project.pbxproj +321 -0
  34. package/ios/RNFBPerfUnitTests/RNFBPerfUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBPerfUnitTests.xcscheme +69 -0
  35. package/lib/version.ts +1 -1
  36. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [27.0.0](https://github.com/invertase/react-native-firebase/compare/v26.4.0...v27.0.0) (2026-10-19)
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ - **app, sdk:** bump Firebase Android 35, Apple 13, and JS 13
11
+
12
+ ### Features
13
+
14
+ - **app, sdk:** bump Firebase Android 35, Apple 13, and JS 13 ([0ca03d1](https://github.com/invertase/react-native-firebase/commit/0ca03d11b0c271fb37cfbee8a0c3074a21969f0b))
15
+
16
+ ### Bug Fixes
17
+
18
+ - **perf, ios:** avoid id cast shadowed by TurboModule id param ([9556118](https://github.com/invertase/react-native-firebase/commit/955611850796e2589c0286ab4700a4eb6ec5d2a3))
19
+ - **perf, ios:** typedef Swift protocol refs around TurboModule id shadowing ([db9cc60](https://github.com/invertase/react-native-firebase/commit/db9cc60e6eb2eb4331702393e2649f102b7cce70))
20
+ - **perf, ios:** use id casts for Swift protocols in ObjC++ ([1d5c7ca](https://github.com/invertase/react-native-firebase/commit/1d5c7ca28d674d7c3bdd19d085911abccda2b20e))
21
+
22
+ ## [26.4.0](https://github.com/invertase/react-native-firebase/compare/v26.3.3...v26.4.0) (2026-09-05)
23
+
24
+ ### Bug Fixes
25
+
26
+ - **perf, android:** use RNFBHandleMap for traces and http metrics ([60b6e7d](https://github.com/invertase/react-native-firebase/commit/60b6e7daa005d137228763291be0db75997b9162))
27
+
6
28
  ## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
7
29
 
8
30
  ### Bug Fixes
package/RNFBPerf.podspec CHANGED
@@ -29,16 +29,21 @@ Pod::Spec.new do |s|
29
29
  s.ios.deployment_target = firebase_ios_target
30
30
  s.macos.deployment_target = firebase_macos_target
31
31
  s.tvos.deployment_target = firebase_tvos_target
32
+ s.swift_version = '5.10'
32
33
  s.source_files = 'ios/**/*.{h,m,mm,cpp,swift}'
33
34
  s.private_header_files = "ios/**/*.h"
34
- s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*'
35
+ # CocoaPods FileList uses FNM_PATHNAME: `ios/*UnitTests/**` does not match nested
36
+ # paths like `ios/RNFBPerfUnitTests/HostStubs/**` (those would compile into the pod).
37
+ s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**/*'
35
38
 
36
39
  # Must be set before install_modules_dependencies so RN can append use_frameworks
37
40
  # HEADER_SEARCH_PATHS (React-debug etc.). Assigning after overwrites those paths
38
41
  # and breaks from-source builds: react/timing/primitives.h → react/debug/flags.h.
39
42
  # CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES is required so the
40
43
  # framework module validates when consumers build with use_frameworks!.
44
+ # DEFINES_MODULE is required for the generated RNFBPerf-Swift.h interface.
41
45
  s.pod_target_xcconfig = {
46
+ "DEFINES_MODULE" => "YES",
42
47
  "CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES" => "YES",
43
48
  }
44
49
 
@@ -15,6 +15,7 @@ buildscript {
15
15
 
16
16
  dependencies {
17
17
  classpath("com.android.tools.build:gradle:8.4.0")
18
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.findProperty('kotlinVersion') ?: '2.4.0'}")
18
19
  }
19
20
  }
20
21
  }
@@ -23,6 +24,8 @@ plugins {
23
24
  id "io.invertase.gradle.build" version "1.5"
24
25
  }
25
26
 
27
+ apply plugin: "org.jetbrains.kotlin.android"
28
+
26
29
  def appProject
27
30
  if (findProject(':@react-native-firebase_app')) {
28
31
  appProject = project(':@react-native-firebase_app')
@@ -135,6 +138,8 @@ dependencies {
135
138
  api appProject
136
139
  implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}")
137
140
  implementation "com.google.firebase:firebase-perf"
141
+
142
+ testImplementation "junit:junit:4.13.2"
138
143
  }
139
144
 
140
145
  ReactNative.shared.applyPackageVersion()
@@ -0,0 +1,71 @@
1
+ package io.invertase.firebase.perf
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import io.invertase.firebase.common.RNFBHandleCollisionException
21
+ import io.invertase.firebase.common.RNFBHandleMap
22
+ import java.util.function.Predicate
23
+
24
+ /**
25
+ * Perf id → metric handle map (traces, screen traces, HTTP metrics). Module start paths use
26
+ * [putReplacing] (last wins, stop displaced outside lock). Unique [put] / [putOrDiscard] remain for
27
+ * tests. Callers [get] then [take] then stop outside the HandleMap lock. Tear-down discards via
28
+ * [takeAll] without stopping (matches prior SparseArray clear).
29
+ */
30
+ internal class RNFBPerfHandleRegistry<V> {
31
+ private val map = RNFBHandleMap<Int, V>()
32
+
33
+ @Throws(RNFBHandleCollisionException::class)
34
+ fun put(
35
+ id: Int,
36
+ handle: V,
37
+ ) {
38
+ map.put(id, handle)
39
+ }
40
+
41
+ /**
42
+ * Unique put. On collision, drop the incoming handle without stopping and leave the existing
43
+ * mapping.
44
+ *
45
+ * @return true if stored
46
+ */
47
+ fun putOrDiscard(
48
+ id: Int,
49
+ handle: V,
50
+ ): Boolean = map.putIfAbsent(id, handle)
51
+
52
+ /**
53
+ * Atomically replaces the mapping for [id] (last wins). Returns the displaced handle, or `null`
54
+ * if the id was free. Stop the displaced handle after this method returns.
55
+ */
56
+ fun putReplacing(
57
+ id: Int,
58
+ handle: V,
59
+ ): V? = map.putReplacing(id, handle)
60
+
61
+ fun get(id: Int): V? = map.get(id)
62
+
63
+ fun take(id: Int): V? = map.take(id)
64
+
65
+ fun takeIf(
66
+ id: Int,
67
+ shouldTake: Predicate<V>,
68
+ ): V? = map.takeIf(id, shouldTake)
69
+
70
+ fun takeAll(): List<V> = map.takeAll()
71
+ }
@@ -20,7 +20,6 @@ package io.invertase.firebase.perf;
20
20
  import android.app.Activity;
21
21
  import android.content.Context;
22
22
  import android.os.Bundle;
23
- import android.util.SparseArray;
24
23
  import com.google.android.gms.tasks.Task;
25
24
  import com.google.android.gms.tasks.Tasks;
26
25
  import com.google.firebase.perf.FirebasePerformance;
@@ -33,9 +32,11 @@ import java.util.Objects;
33
32
  import java.util.Set;
34
33
 
35
34
  public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
36
- private static SparseArray<Trace> traces = new SparseArray<>();
37
- private static SparseArray<ScreenTrace> screenTraces = new SparseArray<>();
38
- private static SparseArray<HttpMetric> httpMetrics = new SparseArray<>();
35
+ private static final RNFBPerfHandleRegistry<Trace> traces = new RNFBPerfHandleRegistry<>();
36
+ private static final RNFBPerfHandleRegistry<ScreenTrace> screenTraces =
37
+ new RNFBPerfHandleRegistry<>();
38
+ private static final RNFBPerfHandleRegistry<HttpMetric> httpMetrics =
39
+ new RNFBPerfHandleRegistry<>();
39
40
 
40
41
  UniversalFirebasePerfModule(Context context, String serviceName) {
41
42
  super(context, serviceName);
@@ -44,9 +45,10 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
44
45
  @Override
45
46
  public void onTearDown() {
46
47
  super.onTearDown();
47
- traces.clear();
48
- httpMetrics.clear();
49
- screenTraces.clear();
48
+ // Drop mappings only — matches prior SparseArray.clear() (no stop/cancel).
49
+ traces.takeAll();
50
+ httpMetrics.takeAll();
51
+ screenTraces.takeAll();
50
52
  }
51
53
 
52
54
  @Override
@@ -72,9 +74,7 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
72
74
  () -> {
73
75
  Trace trace = FirebasePerformance.getInstance().newTrace(identifier);
74
76
  trace.start();
75
-
76
- traces.put(id, trace);
77
-
77
+ registerStartedTrace(id, trace);
78
78
  return null;
79
79
  });
80
80
  }
@@ -101,8 +101,10 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
101
101
  attributeKey, (String) Objects.requireNonNull(attributes.get(attributeKey)));
102
102
  }
103
103
 
104
- trace.stop();
105
- traces.remove(id);
104
+ Trace taken = traces.takeIf(id, t -> t == trace);
105
+ if (taken != null) {
106
+ taken.stop();
107
+ }
106
108
 
107
109
  return null;
108
110
  });
@@ -113,8 +115,7 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
113
115
  () -> {
114
116
  ScreenTrace screenTrace = new ScreenTrace(activity, identifier);
115
117
  screenTrace.recordScreenTrace();
116
- screenTraces.put(id, screenTrace);
117
-
118
+ registerRecordedScreenTrace(id, screenTrace);
118
119
  return null;
119
120
  });
120
121
  }
@@ -127,8 +128,11 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
127
128
  if (trace == null) {
128
129
  return null;
129
130
  }
130
- trace.sendScreenTrace();
131
- screenTraces.remove(id);
131
+
132
+ ScreenTrace taken = screenTraces.takeIf(id, t -> t == trace);
133
+ if (taken != null) {
134
+ taken.sendScreenTrace();
135
+ }
132
136
 
133
137
  return null;
134
138
  });
@@ -139,7 +143,7 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
139
143
  () -> {
140
144
  HttpMetric httpMetric = FirebasePerformance.getInstance().newHttpMetric(url, httpMethod);
141
145
  httpMetric.start();
142
- httpMetrics.put(id, httpMetric);
146
+ registerStartedHttpMetric(id, httpMetric);
143
147
  return null;
144
148
  });
145
149
  }
@@ -178,8 +182,10 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
178
182
  attributeKey, Objects.requireNonNull(attributes.getString(attributeKey)));
179
183
  }
180
184
 
181
- httpMetric.stop();
182
- httpMetrics.remove(id);
185
+ HttpMetric taken = httpMetrics.takeIf(id, m -> m == httpMetric);
186
+ if (taken != null) {
187
+ taken.stop();
188
+ }
183
189
 
184
190
  return null;
185
191
  });
@@ -190,7 +196,7 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
190
196
  void startTraceSync(int id, String identifier) {
191
197
  Trace trace = FirebasePerformance.getInstance().newTrace(identifier);
192
198
  trace.start();
193
- traces.put(id, trace);
199
+ registerStartedTrace(id, trace);
194
200
  }
195
201
 
196
202
  void stopTraceSync(int id, Bundle metrics, Bundle attributes) {
@@ -213,14 +219,16 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
213
219
  attributeKey, (String) Objects.requireNonNull(attributes.get(attributeKey)));
214
220
  }
215
221
 
216
- trace.stop();
217
- traces.remove(id);
222
+ Trace taken = traces.takeIf(id, t -> t == trace);
223
+ if (taken != null) {
224
+ taken.stop();
225
+ }
218
226
  }
219
227
 
220
228
  void startScreenTraceSync(Activity activity, int id, String identifier) {
221
229
  ScreenTrace screenTrace = new ScreenTrace(activity, identifier);
222
230
  screenTrace.recordScreenTrace();
223
- screenTraces.put(id, screenTrace);
231
+ registerRecordedScreenTrace(id, screenTrace);
224
232
  }
225
233
 
226
234
  void stopScreenTraceSync(int id) {
@@ -229,14 +237,17 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
229
237
  if (trace == null) {
230
238
  return;
231
239
  }
232
- trace.sendScreenTrace();
233
- screenTraces.remove(id);
240
+
241
+ ScreenTrace taken = screenTraces.takeIf(id, t -> t == trace);
242
+ if (taken != null) {
243
+ taken.sendScreenTrace();
244
+ }
234
245
  }
235
246
 
236
247
  void startHttpMetricSync(int id, String url, String httpMethod) {
237
248
  HttpMetric httpMetric = FirebasePerformance.getInstance().newHttpMetric(url, httpMethod);
238
249
  httpMetric.start();
239
- httpMetrics.put(id, httpMetric);
250
+ registerStartedHttpMetric(id, httpMetric);
240
251
  }
241
252
 
242
253
  void stopHttpMetricSync(int id, Bundle httpMetricConfig, Bundle attributes) {
@@ -269,7 +280,33 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
269
280
  attributeKey, Objects.requireNonNull(attributes.getString(attributeKey)));
270
281
  }
271
282
 
272
- httpMetric.stop();
273
- httpMetrics.remove(id);
283
+ HttpMetric taken = httpMetrics.takeIf(id, m -> m == httpMetric);
284
+ if (taken != null) {
285
+ taken.stop();
286
+ }
287
+ }
288
+
289
+ /** Last-wins registration: stop any displaced trace outside the HandleMap lock. */
290
+ private static void registerStartedTrace(int id, Trace trace) {
291
+ Trace displaced = traces.putReplacing(id, trace);
292
+ if (displaced != null) {
293
+ displaced.stop();
294
+ }
295
+ }
296
+
297
+ /** Last-wins registration: stop any displaced HTTP metric outside the HandleMap lock. */
298
+ private static void registerStartedHttpMetric(int id, HttpMetric httpMetric) {
299
+ HttpMetric displaced = httpMetrics.putReplacing(id, httpMetric);
300
+ if (displaced != null) {
301
+ displaced.stop();
302
+ }
303
+ }
304
+
305
+ /** Last-wins registration: finalize any displaced screen trace outside the HandleMap lock. */
306
+ private static void registerRecordedScreenTrace(int id, ScreenTrace screenTrace) {
307
+ ScreenTrace displaced = screenTraces.putReplacing(id, screenTrace);
308
+ if (displaced != null) {
309
+ displaced.sendScreenTrace();
310
+ }
274
311
  }
275
312
  }
@@ -0,0 +1,205 @@
1
+ package io.invertase.firebase.perf;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import static org.junit.Assert.assertEquals;
21
+ import static org.junit.Assert.assertFalse;
22
+ import static org.junit.Assert.assertNull;
23
+ import static org.junit.Assert.assertSame;
24
+ import static org.junit.Assert.assertTrue;
25
+ import static org.junit.Assert.fail;
26
+
27
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
28
+ import java.util.List;
29
+ import org.junit.Test;
30
+
31
+ /**
32
+ * JVM coverage for {@link RNFBPerfHandleRegistry}. Does not instantiate {@code
33
+ * UniversalFirebasePerfModule} / Firebase Perf types — D12.
34
+ */
35
+ public class RNFBPerfHandleRegistryTest {
36
+
37
+ @Test
38
+ public void putTake_happyPath() throws Exception {
39
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
40
+ registry.put(1, "trace");
41
+ assertSame("trace", registry.take(1));
42
+ assertNull(registry.take(1));
43
+ }
44
+
45
+ @Test
46
+ public void put_occupiedId_throwsCollision() throws Exception {
47
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
48
+ registry.put(1, "first");
49
+ try {
50
+ registry.put(1, "second");
51
+ fail("expected RNFBHandleCollisionException");
52
+ } catch (RNFBHandleCollisionException e) {
53
+ assertTrue(e.getMessage().contains("1"));
54
+ assertSame("first", registry.take(1));
55
+ }
56
+ }
57
+
58
+ @Test
59
+ public void take_whenFree_isNull() {
60
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
61
+ assertNull(registry.take(99));
62
+ }
63
+
64
+ @Test
65
+ public void putOrDiscard_storesWhenFree() {
66
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
67
+ assertTrue(registry.putOrDiscard(1, "trace"));
68
+ assertSame("trace", registry.take(1));
69
+ }
70
+
71
+ @Test
72
+ public void putOrDiscard_collision_dropsIncomingWithoutDiscard() throws Exception {
73
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
74
+ registry.put(1, "first");
75
+ assertFalse(registry.putOrDiscard(1, "second"));
76
+ assertSame("first", registry.take(1));
77
+ }
78
+
79
+ @Test
80
+ public void putOrDiscard_collision_nullIncoming_isNoOp() throws Exception {
81
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
82
+ registry.put(1, "first");
83
+ assertFalse(registry.putOrDiscard(1, null));
84
+ assertSame("first", registry.take(1));
85
+ }
86
+
87
+ @Test
88
+ public void putReplacing_whenFree_stores() {
89
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
90
+ assertNull(registry.putReplacing(1, "trace"));
91
+ assertSame("trace", registry.get(1));
92
+ }
93
+
94
+ @Test
95
+ public void putReplacing_whenOccupied_returnsDisplaced() throws Exception {
96
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
97
+ registry.put(1, "first");
98
+ assertSame("first", registry.putReplacing(1, "second"));
99
+ assertSame("second", registry.get(1));
100
+ }
101
+
102
+ @Test
103
+ public void putReplacing_moduleCollisionPattern_stopsDisplacedOutsideLock() throws Exception {
104
+ RNFBPerfHandleRegistry<FakeStoppableHandle> registry = new RNFBPerfHandleRegistry<>();
105
+ FakeStoppableHandle first = new FakeStoppableHandle();
106
+ FakeStoppableHandle second = new FakeStoppableHandle();
107
+ registry.put(1, first);
108
+ FakeStoppableHandle displaced = registry.putReplacing(1, second);
109
+ if (displaced != null) {
110
+ displaced.stop();
111
+ }
112
+ assertEquals(1, first.stopCount);
113
+ assertEquals(0, second.stopCount);
114
+ assertSame(second, registry.get(1));
115
+ }
116
+
117
+ @Test
118
+ public void takeIf_whenMissing_returnsNullWithoutCallingPredicate() {
119
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
120
+ boolean[] called = {false};
121
+ assertNull(
122
+ registry.takeIf(
123
+ 99,
124
+ value -> {
125
+ called[0] = true;
126
+ return true;
127
+ }));
128
+ assertFalse(called[0]);
129
+ }
130
+
131
+ @Test
132
+ public void takeIf_whenPredicateFalse_leavesMapping() throws Exception {
133
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
134
+ registry.put(1, "trace");
135
+ assertNull(registry.takeIf(1, value -> false));
136
+ assertSame("trace", registry.get(1));
137
+ }
138
+
139
+ @Test
140
+ public void takeIf_whenPredicateTrue_removesAndReturns() throws Exception {
141
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
142
+ registry.put(1, "trace");
143
+ assertSame("trace", registry.takeIf(1, value -> value == "trace"));
144
+ assertNull(registry.get(1));
145
+ }
146
+
147
+ @Test
148
+ public void get_whenFree_isNull() {
149
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
150
+ assertNull(registry.get(99));
151
+ }
152
+
153
+ @Test
154
+ public void get_whenOccupied_returnsHandle() throws Exception {
155
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
156
+ registry.put(1, "trace");
157
+ assertSame("trace", registry.get(1));
158
+ assertSame("trace", registry.take(1));
159
+ }
160
+
161
+ @Test
162
+ public void takeAll_returnsSnapshotAndLeavesEmpty() throws Exception {
163
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
164
+ registry.put(1, "a");
165
+ registry.put(2, "b");
166
+ List<String> remaining = registry.takeAll();
167
+ assertEquals(2, remaining.size());
168
+ assertTrue(remaining.contains("a"));
169
+ assertTrue(remaining.contains("b"));
170
+ assertNull(registry.take(1));
171
+ assertNull(registry.take(2));
172
+ }
173
+
174
+ @Test
175
+ public void takeAll_empty_returnsEmptyList() {
176
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
177
+ assertTrue(registry.takeAll().isEmpty());
178
+ }
179
+
180
+ @Test
181
+ public void takeAll_nullHandle_isIncluded() throws Exception {
182
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
183
+ registry.put(1, null);
184
+ List<String> remaining = registry.takeAll();
185
+ assertEquals(1, remaining.size());
186
+ assertNull(remaining.get(0));
187
+ }
188
+
189
+ @Test
190
+ public void put_afterTake_allowsReuse() throws Exception {
191
+ RNFBPerfHandleRegistry<String> registry = new RNFBPerfHandleRegistry<>();
192
+ registry.put(1, "first");
193
+ assertSame("first", registry.take(1));
194
+ registry.put(1, "second");
195
+ assertSame("second", registry.take(1));
196
+ }
197
+
198
+ private static final class FakeStoppableHandle {
199
+ int stopCount = 0;
200
+
201
+ void stop() {
202
+ stopCount++;
203
+ }
204
+ }
205
+ }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
 
3
3
  // Generated by genversion.
4
- export const version = '26.3.3';
4
+ export const version = '27.0.0';
5
5
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  import type { FirebaseApp } from '@react-native-firebase/app';
2
2
  import './types/internal';
3
3
  import type { FirebasePerformance, HttpMetric, HttpMethod, PerformanceSettings, PerformanceTrace, ScreenTrace } from './types/perf';
4
- export declare const SDK_VERSION = "26.3.3";
4
+ export declare const SDK_VERSION = "27.0.0";
5
5
  export declare function getPerformance(app?: FirebaseApp): FirebasePerformance;
6
6
  /**
7
7
  * Creates a Performance Monitoring instance and applies optional settings.
@@ -1,2 +1,2 @@
1
- export declare const version = "26.3.3";
1
+ export declare const version = "27.0.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import <Foundation/Foundation.h>
19
+
20
+ NS_ASSUME_NONNULL_BEGIN
21
+
22
+ /**
23
+ * Perf trace / HTTP metric handle map. Module start paths use `putReplacing` (last wins, stop
24
+ * displaced outside lock). Unique `put` / `putOrDiscard` remain for tests. Callers `get` then
25
+ * `take` then stop outside the HandleMap lock. Tear-down uses `takeAll` without stopping.
26
+ */
27
+ @interface RNFBPerfHandleRegistry : NSObject
28
+
29
+ - (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
30
+ /// Unique put. On collision, drop the incoming handle without stopping.
31
+ - (BOOL)putOrDiscard:(id)key value:(id)value;
32
+ /// Last wins. Returns the displaced handle, or nil if the key was free.
33
+ - (nullable id)putReplacing:(id)key value:(id)value;
34
+ - (nullable id)get:(id)key;
35
+ - (nullable id)take:(id)key;
36
+ - (nullable id)takeIf:(id)key when:(BOOL (^)(id value))condition;
37
+ - (NSArray *)takeAll;
38
+
39
+ @end
40
+
41
+ NS_ASSUME_NONNULL_END