@debugbundle/sdk-react-native 1.0.0 → 1.2.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.
@@ -1,31 +1,48 @@
1
1
  package com.debugbundle.reactnative;
2
2
 
3
+ import android.content.Context;
3
4
  import com.debugbundle.android.DebugBundle;
4
5
  import com.debugbundle.android.DebugBundleConfig;
5
6
  import com.debugbundle.android.DebugBundleLogLevel;
6
7
  import com.debugbundle.android.DebugBundleRequestInfo;
7
8
  import com.debugbundle.android.DebugBundleResponseInfo;
8
9
  import com.facebook.react.bridge.Arguments;
10
+ import com.facebook.react.bridge.Dynamic;
9
11
  import com.facebook.react.bridge.Promise;
10
12
  import com.facebook.react.bridge.ReactApplicationContext;
11
13
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
12
14
  import com.facebook.react.bridge.ReactMethod;
13
15
  import com.facebook.react.bridge.ReadableMap;
14
16
  import com.facebook.react.bridge.WritableMap;
15
- import java.lang.reflect.Constructor;
16
17
  import java.lang.reflect.Method;
18
+ import java.util.ArrayList;
17
19
  import java.util.Collections;
20
+ import java.util.HashSet;
21
+ import java.util.LinkedHashMap;
22
+ import java.util.List;
18
23
  import java.util.Locale;
19
24
  import java.util.Map;
25
+ import java.util.Set;
20
26
 
21
27
  public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModule {
22
28
  public static final String NAME = "DebugBundleReactNative";
23
29
 
24
30
  private final ReactApplicationContext reactContext;
31
+ private final NativeOperations nativeOperations;
32
+ private final WritableMapFactory writableMapFactory;
25
33
 
26
34
  public DebugBundleReactNativeModule(ReactApplicationContext reactContext) {
35
+ this(reactContext, new DefaultNativeOperations(), Arguments::createMap);
36
+ }
37
+
38
+ DebugBundleReactNativeModule(
39
+ ReactApplicationContext reactContext,
40
+ NativeOperations nativeOperations,
41
+ WritableMapFactory writableMapFactory) {
27
42
  super(reactContext);
28
43
  this.reactContext = reactContext;
44
+ this.nativeOperations = nativeOperations;
45
+ this.writableMapFactory = writableMapFactory;
29
46
  }
30
47
 
31
48
  @Override
@@ -36,7 +53,7 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
36
53
  @ReactMethod
37
54
  public void initialize(ReadableMap config, Promise promise) {
38
55
  try {
39
- DebugBundle.init(reactContext.getApplicationContext(), createConfig(config));
56
+ nativeOperations.initialize(reactContext.getApplicationContext(), createConfig(config));
40
57
  promise.resolve(stateMap());
41
58
  } catch (Throwable ignored) {
42
59
  promise.resolve(degradedState("android_initialize_failed"));
@@ -48,25 +65,24 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
48
65
  try {
49
66
  String eventType = stringOrDefault(event, "event_type", "");
50
67
  Map<String, Object> payload = hashMap(event, "payload");
51
- DebugBundle debugBundle = DebugBundle.INSTANCE;
52
68
 
53
69
  if ("frontend_exception".equals(eventType)) {
54
- debugBundle.captureException(
70
+ nativeOperations.captureException(
55
71
  new RuntimeException(stringValue(payload.get("error"), "React Native exception")),
56
72
  payload);
57
73
  } else if ("frontend_breadcrumb".equals(eventType)) {
58
- debugBundle.captureBreadcrumb(
74
+ nativeOperations.captureBreadcrumb(
59
75
  stringValue(payload.get("breadcrumb_type"), "react_native"),
60
76
  null,
61
77
  payload);
62
78
  } else if ("log_event".equals(eventType)) {
63
- debugBundle.captureLog(
79
+ nativeOperations.captureLog(
64
80
  stringValue(payload.get("message"), ""),
65
81
  logLevel(stringValue(payload.get("level"), null)),
66
82
  payload);
67
83
  } else if ("request_event".equals(eventType)) {
68
84
  ReadableMap correlation = event.hasKey("correlation") ? event.getMap("correlation") : null;
69
- debugBundle.captureRequest(
85
+ nativeOperations.captureRequest(
70
86
  new DebugBundleRequestInfo(
71
87
  stringValue(payload.get("method"), "GET"),
72
88
  stringValue(payload.get("url"), ""),
@@ -80,7 +96,7 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
80
96
  payload,
81
97
  false);
82
98
  } else {
83
- debugBundle.captureMessage(
99
+ nativeOperations.captureMessage(
84
100
  "react_native_event:" + eventType,
85
101
  DebugBundleLogLevel.Warning,
86
102
  payload);
@@ -91,16 +107,52 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
91
107
  }
92
108
  }
93
109
 
110
+ /**
111
+ * Canonical bridge added after the original translation-based enqueue API.
112
+ * The legacy method above remains available for installed JS/native pairs.
113
+ */
114
+ @ReactMethod
115
+ public void enqueueCanonicalEvent(ReadableMap event, Promise promise) {
116
+ try {
117
+ promise.resolve(
118
+ nativeOperations.captureExternalEvent(normalizeBridgeMap(event.toHashMap())));
119
+ } catch (Throwable ignored) {
120
+ promise.resolve(false);
121
+ }
122
+ }
123
+
124
+ @ReactMethod(isBlockingSynchronousMethod = true)
125
+ public boolean isProbeActive(String label) {
126
+ try {
127
+ return nativeOperations.isProbeActive(label);
128
+ } catch (Throwable ignored) {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ @ReactMethod
134
+ public void captureProbe(
135
+ String label,
136
+ ReadableMap data,
137
+ String occurredAt,
138
+ Promise promise) {
139
+ try {
140
+ promise.resolve(nativeOperations.captureExternalProbe(
141
+ currentSdkVersion,
142
+ currentService,
143
+ currentEnvironment,
144
+ label,
145
+ data.toHashMap(),
146
+ occurredAt));
147
+ } catch (Throwable ignored) {
148
+ promise.resolve(false);
149
+ }
150
+ }
151
+
94
152
  @ReactMethod
95
153
  public void flush(Promise promise) {
96
154
  try {
97
- Method flushDefault = DebugBundle.class.getDeclaredMethod(
98
- "flush-LRDsOJo$default",
99
- DebugBundle.class,
100
- long.class,
101
- int.class,
102
- Object.class);
103
- flushDefault.invoke(null, DebugBundle.INSTANCE, 0L, 1, null);
155
+ nativeOperations.flush();
104
156
  promise.resolve(null);
105
157
  } catch (Throwable ignored) {
106
158
  promise.resolve(null);
@@ -113,89 +165,84 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
113
165
  }
114
166
 
115
167
  @ReactMethod
116
- public void setContext(String key, Object value, Promise promise) {
168
+ public void setContext(String key, Dynamic value, Promise promise) {
117
169
  try {
118
- DebugBundle.INSTANCE.setContext(key, value);
170
+ nativeOperations.setContext(key, dynamicValue(value));
119
171
  promise.resolve(null);
120
172
  } catch (Throwable ignored) {
121
173
  promise.resolve(null);
174
+ } finally {
175
+ try {
176
+ value.recycle();
177
+ } catch (Throwable ignored) {
178
+ // React Native bridge cleanup must not escape into the host process.
179
+ }
122
180
  }
123
181
  }
124
182
 
125
183
  @ReactMethod
126
- public void activateProbeTriggerToken(String token, Promise promise) {
184
+ public void setContextValue(String key, ReadableMap entry, Promise promise) {
127
185
  try {
128
- promise.resolve(DebugBundle.INSTANCE.activateProbeTriggerToken(token));
186
+ nativeOperations.setContext(key, entry.toHashMap().get("value"));
187
+ promise.resolve(null);
129
188
  } catch (Throwable ignored) {
130
- promise.resolve(false);
189
+ promise.resolve(null);
131
190
  }
132
191
  }
133
192
 
134
- private DebugBundleConfig createConfig(ReadableMap config) throws ReflectiveOperationException {
135
- Constructor<?> constructor = null;
136
- for (Constructor<?> candidate : DebugBundleConfig.class.getDeclaredConstructors()) {
137
- if (candidate.getParameterTypes().length == 34) {
138
- constructor = candidate;
139
- break;
140
- }
141
- }
142
- if (constructor == null) {
143
- throw new NoSuchMethodException("DebugBundleConfig default constructor");
193
+ @ReactMethod
194
+ public void activateProbeTriggerToken(String token, Promise promise) {
195
+ try {
196
+ promise.resolve(nativeOperations.activateProbeTriggerToken(token));
197
+ } catch (Throwable ignored) {
198
+ promise.resolve(false);
144
199
  }
145
- constructor.setAccessible(true);
200
+ }
146
201
 
147
- int mask = -1;
148
- mask = useProvided(mask, 0);
149
- mask = useProvided(mask, 1);
150
- mask = useProvided(mask, 2);
151
- mask = useProvided(mask, 3);
152
- mask = useProvided(mask, 4);
153
- mask = useProvided(mask, 10);
154
- mask = useProvided(mask, 11);
155
- mask = useProvided(mask, 12);
156
- mask = useProvided(mask, 31);
202
+ private String currentSdkVersion = "1.2.0";
203
+ private String currentService = "react-native-app";
204
+ private String currentEnvironment = "production";
157
205
 
158
- return (DebugBundleConfig) constructor.newInstance(
206
+ private DebugBundleConfig createConfig(ReadableMap config) {
207
+ currentSdkVersion = stringOrDefault(config, "sdkVersion", "1.2.0");
208
+ currentService = stringOrDefault(config, "service", "react-native-app");
209
+ currentEnvironment = stringOrDefault(config, "environment", "production");
210
+ return DebugBundleConfig.create(
159
211
  stringOrDefault(config, "projectToken", ""),
160
212
  booleanOrDefault(config, "enabled", true),
161
- stringOrDefault(config, "environment", "production"),
162
- stringOrDefault(config, "service", "react-native-app"),
213
+ currentEnvironment,
214
+ currentService,
163
215
  stringOrDefault(config, "endpoint", DebugBundleConfig.DEFAULT_ENDPOINT),
164
- 0,
165
- 0L,
166
- 0.0,
167
- 0.0,
168
- 0L,
216
+ intOrDefault(config, "batchSize", 10),
217
+ longOrDefault(config, "flushInterval", 3000L),
218
+ doubleOrDefault(config, "sampleRate", 1.0),
219
+ doubleOrDefault(config, "sessionSampleRate", 1.0),
220
+ longOrDefault(config, "requestTimeout", 5000L),
169
221
  stringOrDefault(config, "releaseChannel", "production"),
170
222
  stringOrNull(config, "appVersion"),
171
223
  stringOrNull(config, "buildNumber"),
172
- 0,
173
- 0,
174
- false,
175
- false,
176
- false,
177
- false,
178
- DebugBundleLogLevel.Warning,
179
- false,
180
- Collections.emptySet(),
181
- 0,
182
- 0L,
183
- 0L,
184
- null,
185
- null,
186
- 0,
187
- 0,
188
- false,
189
- Collections.emptySet(),
190
- stringOrDefault(config, "sdkVersion", "1.0.0"),
191
- mask,
192
- null);
224
+ intOrDefault(config, "maxEventsPerSession", 100),
225
+ intOrDefault(config, "maxBreadcrumbs", 20),
226
+ booleanOrDefault(config, "captureScreens", true),
227
+ booleanOrDefault(config, "captureActions", false),
228
+ booleanOrDefault(config, "captureNetwork", true),
229
+ booleanOrDefault(config, "captureLogs", true),
230
+ logLevel(stringOrNull(config, "logLevel")),
231
+ stringSet(config, "headerAllowlist", Collections.emptySet()),
232
+ intOrDefault(config, "offlineQueueMaxEvents", 500),
233
+ longOrDefault(config, "offlineQueueMaxBytes", 5L * 1024L * 1024L),
234
+ longOrDefault(config, "offlineQueueTtl", 72L * 60L * 60L * 1000L),
235
+ intOrDefault(config, "maxProbeLabels", 50),
236
+ intOrDefault(config, "maxProbeEntriesPerLabel", 10),
237
+ booleanOrDefault(config, "probeFlushOnError", true),
238
+ stringSet(config, "redactFields", Collections.emptySet()),
239
+ currentSdkVersion);
193
240
  }
194
241
 
195
242
  private WritableMap stateMap() {
196
- WritableMap state = Arguments.createMap();
197
- state.putString("status", DebugBundle.INSTANCE.getStatus().name().toLowerCase(Locale.ROOT));
198
- Long lastEventAt = DebugBundle.INSTANCE.getLastEventAt();
243
+ WritableMap state = writableMapFactory.create();
244
+ state.putString("status", nativeOperations.statusName().toLowerCase(Locale.ROOT));
245
+ Long lastEventAt = nativeOperations.lastEventAt();
199
246
  if (lastEventAt == null) {
200
247
  state.putNull("lastEventAt");
201
248
  } else {
@@ -207,7 +254,7 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
207
254
  }
208
255
 
209
256
  private WritableMap degradedState(String reason) {
210
- WritableMap state = Arguments.createMap();
257
+ WritableMap state = writableMapFactory.create();
211
258
  state.putString("status", "degraded");
212
259
  state.putNull("lastEventAt");
213
260
  state.putBoolean("nativeModuleAvailable", true);
@@ -215,10 +262,6 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
215
262
  return state;
216
263
  }
217
264
 
218
- private static int useProvided(int mask, int index) {
219
- return mask & ~(1 << index);
220
- }
221
-
222
265
  private static Map<String, Object> hashMap(ReadableMap map, String key) {
223
266
  ReadableMap nested = map.hasKey(key) ? map.getMap(key) : null;
224
267
  if (nested == null) {
@@ -249,6 +292,44 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
249
292
  return map.getBoolean(key);
250
293
  }
251
294
 
295
+ private static int intOrDefault(ReadableMap map, String key, int fallback) {
296
+ if (!map.hasKey(key) || map.isNull(key)) {
297
+ return fallback;
298
+ }
299
+ return map.getInt(key);
300
+ }
301
+
302
+ private static long longOrDefault(ReadableMap map, String key, long fallback) {
303
+ if (!map.hasKey(key) || map.isNull(key)) {
304
+ return fallback;
305
+ }
306
+ return (long) map.getDouble(key);
307
+ }
308
+
309
+ private static double doubleOrDefault(ReadableMap map, String key, double fallback) {
310
+ if (!map.hasKey(key) || map.isNull(key)) {
311
+ return fallback;
312
+ }
313
+ return map.getDouble(key);
314
+ }
315
+
316
+ private static Set<String> stringSet(
317
+ ReadableMap map,
318
+ String key,
319
+ Set<String> fallback) {
320
+ if (!map.hasKey(key) || map.isNull(key) || map.getArray(key) == null) {
321
+ return fallback;
322
+ }
323
+ List<Object> values = map.getArray(key).toArrayList();
324
+ Set<String> result = new HashSet<>();
325
+ for (Object value : values) {
326
+ if (value instanceof String) {
327
+ result.add((String) value);
328
+ }
329
+ }
330
+ return result;
331
+ }
332
+
252
333
  private static String stringValue(Object value, String fallback) {
253
334
  return value == null ? fallback : value.toString();
254
335
  }
@@ -261,6 +342,66 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
261
342
  return value instanceof Number ? ((Number) value).longValue() : null;
262
343
  }
263
344
 
345
+ private static Map<String, Object> normalizeBridgeMap(Map<String, Object> values) {
346
+ Map<String, Object> normalized = new LinkedHashMap<>();
347
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
348
+ normalized.put(entry.getKey(), normalizeBridgeValue(entry.getValue()));
349
+ }
350
+ return normalized;
351
+ }
352
+
353
+ private static Object normalizeBridgeValue(Object value) {
354
+ if (value instanceof Double) {
355
+ double number = (Double) value;
356
+ if (Double.isFinite(number)
357
+ && number == Math.rint(number)
358
+ && number >= Long.MIN_VALUE
359
+ && number <= Long.MAX_VALUE) {
360
+ return (long) number;
361
+ }
362
+ return number;
363
+ }
364
+ if (value instanceof Map<?, ?>) {
365
+ Map<String, Object> normalized = new LinkedHashMap<>();
366
+ for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
367
+ if (entry.getKey() instanceof String) {
368
+ normalized.put(
369
+ (String) entry.getKey(),
370
+ normalizeBridgeValue(entry.getValue()));
371
+ }
372
+ }
373
+ return normalized;
374
+ }
375
+ if (value instanceof List<?>) {
376
+ List<Object> normalized = new ArrayList<>();
377
+ for (Object item : (List<?>) value) {
378
+ normalized.add(normalizeBridgeValue(item));
379
+ }
380
+ return normalized;
381
+ }
382
+ return value;
383
+ }
384
+
385
+ private static Object dynamicValue(Dynamic value) {
386
+ if (value == null || value.isNull()) {
387
+ return null;
388
+ }
389
+ switch (value.getType()) {
390
+ case Boolean:
391
+ return value.asBoolean();
392
+ case Number:
393
+ return value.asDouble();
394
+ case String:
395
+ return value.asString();
396
+ case Map:
397
+ return value.asMap().toHashMap();
398
+ case Array:
399
+ return value.asArray().toArrayList();
400
+ default:
401
+ return null;
402
+ }
403
+ }
404
+
264
405
  private static DebugBundleLogLevel logLevel(String value) {
265
406
  if (value == null) {
266
407
  return DebugBundleLogLevel.Warning;
@@ -278,4 +419,154 @@ public final class DebugBundleReactNativeModule extends ReactContextBaseJavaModu
278
419
  return DebugBundleLogLevel.Warning;
279
420
  }
280
421
  }
422
+
423
+ interface WritableMapFactory {
424
+ WritableMap create();
425
+ }
426
+
427
+ interface NativeOperations {
428
+ void initialize(Context context, DebugBundleConfig config);
429
+
430
+ void captureException(Throwable error, Map<String, Object> context);
431
+
432
+ void captureBreadcrumb(String type, String route, Map<String, Object> data);
433
+
434
+ void captureLog(
435
+ String message,
436
+ DebugBundleLogLevel level,
437
+ Map<String, Object> context);
438
+
439
+ void captureRequest(
440
+ DebugBundleRequestInfo request,
441
+ DebugBundleResponseInfo response,
442
+ Map<String, Object> context,
443
+ boolean force);
444
+
445
+ void captureMessage(
446
+ String message,
447
+ DebugBundleLogLevel level,
448
+ Map<String, Object> context);
449
+
450
+ boolean captureExternalEvent(Map<String, Object> event);
451
+
452
+ boolean isProbeActive(String label);
453
+
454
+ boolean captureExternalProbe(
455
+ String sdkVersion,
456
+ String service,
457
+ String environment,
458
+ String label,
459
+ Map<String, Object> data,
460
+ String occurredAt);
461
+
462
+ void flush() throws ReflectiveOperationException;
463
+
464
+ void setContext(String key, Object value);
465
+
466
+ boolean activateProbeTriggerToken(String token);
467
+
468
+ String statusName();
469
+
470
+ Long lastEventAt();
471
+ }
472
+
473
+ private static final class DefaultNativeOperations implements NativeOperations {
474
+ @Override
475
+ public void initialize(Context context, DebugBundleConfig config) {
476
+ DebugBundle.init(context, config);
477
+ }
478
+
479
+ @Override
480
+ public void captureException(Throwable error, Map<String, Object> context) {
481
+ DebugBundle.INSTANCE.captureException(error, context);
482
+ }
483
+
484
+ @Override
485
+ public void captureBreadcrumb(String type, String route, Map<String, Object> data) {
486
+ DebugBundle.INSTANCE.captureBreadcrumb(type, route, data);
487
+ }
488
+
489
+ @Override
490
+ public void captureLog(
491
+ String message,
492
+ DebugBundleLogLevel level,
493
+ Map<String, Object> context) {
494
+ DebugBundle.INSTANCE.captureLog(message, level, context);
495
+ }
496
+
497
+ @Override
498
+ public void captureRequest(
499
+ DebugBundleRequestInfo request,
500
+ DebugBundleResponseInfo response,
501
+ Map<String, Object> context,
502
+ boolean force) {
503
+ DebugBundle.INSTANCE.captureRequest(request, response, context, force);
504
+ }
505
+
506
+ @Override
507
+ public void captureMessage(
508
+ String message,
509
+ DebugBundleLogLevel level,
510
+ Map<String, Object> context) {
511
+ DebugBundle.INSTANCE.captureMessage(message, level, context);
512
+ }
513
+
514
+ @Override
515
+ public boolean captureExternalEvent(Map<String, Object> event) {
516
+ return DebugBundle.captureExternalEvent(event);
517
+ }
518
+
519
+ @Override
520
+ public boolean isProbeActive(String label) {
521
+ return DebugBundle.isExternalProbeActive(label);
522
+ }
523
+
524
+ @Override
525
+ public boolean captureExternalProbe(
526
+ String sdkVersion,
527
+ String service,
528
+ String environment,
529
+ String label,
530
+ Map<String, Object> data,
531
+ String occurredAt) {
532
+ return DebugBundle.captureExternalProbe(
533
+ sdkVersion,
534
+ service,
535
+ environment,
536
+ label,
537
+ data,
538
+ occurredAt);
539
+ }
540
+
541
+ @Override
542
+ public void flush() throws ReflectiveOperationException {
543
+ Method flushDefault = DebugBundle.class.getDeclaredMethod(
544
+ "flush-LRDsOJo$default",
545
+ DebugBundle.class,
546
+ long.class,
547
+ int.class,
548
+ Object.class);
549
+ flushDefault.invoke(null, DebugBundle.INSTANCE, 0L, 1, null);
550
+ }
551
+
552
+ @Override
553
+ public void setContext(String key, Object value) {
554
+ DebugBundle.INSTANCE.setContext(key, value);
555
+ }
556
+
557
+ @Override
558
+ public boolean activateProbeTriggerToken(String token) {
559
+ return DebugBundle.INSTANCE.activateProbeTriggerToken(token);
560
+ }
561
+
562
+ @Override
563
+ public String statusName() {
564
+ return DebugBundle.INSTANCE.getStatus().name();
565
+ }
566
+
567
+ @Override
568
+ public Long lastEventAt() {
569
+ return DebugBundle.INSTANCE.getLastEventAt();
570
+ }
571
+ }
281
572
  }