@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.
- package/CHANGELOG.md +35 -0
- package/DebugBundleReactNative.podspec +3 -2
- package/README.md +13 -3
- package/android/build.gradle +70 -1
- package/android/src/main/java/com/debugbundle/reactnative/DebugBundleReactNativeModule.java +369 -78
- package/android/src/test/java/com/debugbundle/reactnative/DebugBundleReactNativeModuleTest.java +474 -0
- package/app.plugin.js +1 -1
- package/dist/NativeDebugBundleReactNative.d.ts +5 -1
- package/dist/NativeDebugBundleReactNative.d.ts.map +1 -1
- package/dist/before-send.d.ts +3 -0
- package/dist/before-send.d.ts.map +1 -0
- package/dist/before-send.js +136 -0
- package/dist/client.d.ts +0 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +224 -64
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +3 -15
- package/dist/testing.d.ts +9 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +18 -0
- package/dist/types.d.ts +16 -5
- package/dist/types.d.ts.map +1 -1
- package/ios/DebugBundleReactNative.swift +120 -13
- package/ios/DebugBundleReactNativeBridge.mm +73 -0
- package/package.json +6 -2
- package/src/NativeDebugBundleReactNative.ts +5 -1
- package/src/before-send.ts +172 -0
- package/src/client.ts +245 -68
- package/src/index.ts +2 -0
- package/src/native.ts +3 -25
- package/src/testing.ts +28 -2
- package/src/types.ts +22 -5
- package/ios/DebugBundleReactNativeBridge.m +0 -28
package/android/src/test/java/com/debugbundle/reactnative/DebugBundleReactNativeModuleTest.java
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
package com.debugbundle.reactnative;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.junit.Assert.assertFalse;
|
|
5
|
+
import static org.junit.Assert.assertTrue;
|
|
6
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
7
|
+
import static org.mockito.ArgumentMatchers.anyBoolean;
|
|
8
|
+
import static org.mockito.ArgumentMatchers.anyString;
|
|
9
|
+
import static org.mockito.ArgumentMatchers.eq;
|
|
10
|
+
import static org.mockito.ArgumentMatchers.isNull;
|
|
11
|
+
import static org.mockito.Mockito.doThrow;
|
|
12
|
+
import static org.mockito.Mockito.mock;
|
|
13
|
+
import static org.mockito.Mockito.never;
|
|
14
|
+
import static org.mockito.Mockito.reset;
|
|
15
|
+
import static org.mockito.Mockito.verify;
|
|
16
|
+
import static org.mockito.Mockito.when;
|
|
17
|
+
|
|
18
|
+
import android.content.Context;
|
|
19
|
+
import com.debugbundle.android.DebugBundleConfig;
|
|
20
|
+
import com.debugbundle.android.DebugBundleLogLevel;
|
|
21
|
+
import com.debugbundle.android.DebugBundleRequestInfo;
|
|
22
|
+
import com.debugbundle.android.DebugBundleResponseInfo;
|
|
23
|
+
import com.facebook.react.bridge.Dynamic;
|
|
24
|
+
import com.facebook.react.bridge.Promise;
|
|
25
|
+
import com.facebook.react.bridge.NativeModule;
|
|
26
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
27
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
28
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
29
|
+
import com.facebook.react.bridge.ReadableType;
|
|
30
|
+
import com.facebook.react.bridge.WritableMap;
|
|
31
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
32
|
+
import java.util.HashMap;
|
|
33
|
+
import java.util.List;
|
|
34
|
+
import java.util.Map;
|
|
35
|
+
import org.junit.Before;
|
|
36
|
+
import org.junit.Test;
|
|
37
|
+
import org.mockito.ArgumentCaptor;
|
|
38
|
+
|
|
39
|
+
public final class DebugBundleReactNativeModuleTest {
|
|
40
|
+
private ReactApplicationContext reactContext;
|
|
41
|
+
private Context applicationContext;
|
|
42
|
+
private DebugBundleReactNativeModule.NativeOperations nativeOperations;
|
|
43
|
+
private DebugBundleReactNativeModule.WritableMapFactory writableMapFactory;
|
|
44
|
+
private WritableMap writableMap;
|
|
45
|
+
private DebugBundleReactNativeModule module;
|
|
46
|
+
|
|
47
|
+
@Before
|
|
48
|
+
public void setUp() {
|
|
49
|
+
reactContext = mock(ReactApplicationContext.class);
|
|
50
|
+
applicationContext = mock(Context.class);
|
|
51
|
+
nativeOperations = mock(DebugBundleReactNativeModule.NativeOperations.class);
|
|
52
|
+
writableMapFactory = mock(DebugBundleReactNativeModule.WritableMapFactory.class);
|
|
53
|
+
writableMap = mock(WritableMap.class);
|
|
54
|
+
when(reactContext.getApplicationContext()).thenReturn(applicationContext);
|
|
55
|
+
when(nativeOperations.statusName()).thenReturn("ONLINE");
|
|
56
|
+
when(writableMapFactory.create()).thenReturn(writableMap);
|
|
57
|
+
module = new DebugBundleReactNativeModule(
|
|
58
|
+
reactContext,
|
|
59
|
+
nativeOperations,
|
|
60
|
+
writableMapFactory);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
public void initializesWithFullConfigurationAndReturnsNativeState() {
|
|
65
|
+
when(nativeOperations.lastEventAt()).thenReturn(1_234L);
|
|
66
|
+
Promise promise = mock(Promise.class);
|
|
67
|
+
ReadableMap config = readableMap(Map.ofEntries(
|
|
68
|
+
Map.entry("sdkVersion", "1.2.7"),
|
|
69
|
+
Map.entry("projectToken", "project-token"),
|
|
70
|
+
Map.entry("enabled", false),
|
|
71
|
+
Map.entry("environment", "staging"),
|
|
72
|
+
Map.entry("service", "checkout-mobile"),
|
|
73
|
+
Map.entry("endpoint", "https://ingest.example.test"),
|
|
74
|
+
Map.entry("batchSize", 4),
|
|
75
|
+
Map.entry("flushInterval", 1_250.0),
|
|
76
|
+
Map.entry("sampleRate", 0.5),
|
|
77
|
+
Map.entry("sessionSampleRate", 0.75),
|
|
78
|
+
Map.entry("requestTimeout", 2_500.0),
|
|
79
|
+
Map.entry("releaseChannel", "beta"),
|
|
80
|
+
Map.entry("appVersion", "4.2.0"),
|
|
81
|
+
Map.entry("buildNumber", "42"),
|
|
82
|
+
Map.entry("maxEventsPerSession", 55),
|
|
83
|
+
Map.entry("maxBreadcrumbs", 12),
|
|
84
|
+
Map.entry("captureScreens", false),
|
|
85
|
+
Map.entry("captureActions", true),
|
|
86
|
+
Map.entry("captureNetwork", false),
|
|
87
|
+
Map.entry("captureLogs", false),
|
|
88
|
+
Map.entry("logLevel", "debug"),
|
|
89
|
+
Map.entry("headerAllowlist", List.of("x-request-id", 4)),
|
|
90
|
+
Map.entry("offlineQueueMaxEvents", 75),
|
|
91
|
+
Map.entry("offlineQueueMaxBytes", 4_096.0),
|
|
92
|
+
Map.entry("offlineQueueTtl", 9_000.0),
|
|
93
|
+
Map.entry("maxProbeLabels", 8),
|
|
94
|
+
Map.entry("maxProbeEntriesPerLabel", 3),
|
|
95
|
+
Map.entry("probeFlushOnError", false),
|
|
96
|
+
Map.entry("redactFields", List.of("password"))));
|
|
97
|
+
|
|
98
|
+
module.initialize(config, promise);
|
|
99
|
+
|
|
100
|
+
verify(nativeOperations).initialize(eq(applicationContext), any(DebugBundleConfig.class));
|
|
101
|
+
verify(writableMap).putString("status", "online");
|
|
102
|
+
verify(writableMap).putDouble("lastEventAt", 1_234.0);
|
|
103
|
+
verify(writableMap).putBoolean("nativeModuleAvailable", true);
|
|
104
|
+
verify(writableMap).putNull("degradedReason");
|
|
105
|
+
verify(promise).resolve(writableMap);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
public void initializationAndStatusFailOpenWithoutThrowingIntoReactNative() {
|
|
110
|
+
Promise failedPromise = mock(Promise.class);
|
|
111
|
+
doThrow(new IllegalStateException("native failure"))
|
|
112
|
+
.when(nativeOperations)
|
|
113
|
+
.initialize(any(Context.class), any(DebugBundleConfig.class));
|
|
114
|
+
|
|
115
|
+
module.initialize(readableMap(Map.of()), failedPromise);
|
|
116
|
+
|
|
117
|
+
verify(writableMap).putString("status", "degraded");
|
|
118
|
+
verify(writableMap).putNull("lastEventAt");
|
|
119
|
+
verify(writableMap).putString("degradedReason", "android_initialize_failed");
|
|
120
|
+
verify(failedPromise).resolve(writableMap);
|
|
121
|
+
|
|
122
|
+
reset(writableMap);
|
|
123
|
+
when(nativeOperations.lastEventAt()).thenReturn(null);
|
|
124
|
+
Promise statusPromise = mock(Promise.class);
|
|
125
|
+
module.getStatus(statusPromise);
|
|
126
|
+
verify(writableMap).putNull("lastEventAt");
|
|
127
|
+
verify(statusPromise).resolve(writableMap);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
public void translatesEveryLegacyEventFamilyThroughNativeOperations() {
|
|
132
|
+
assertLegacyEventCalls(
|
|
133
|
+
Map.of(
|
|
134
|
+
"event_type", "frontend_exception",
|
|
135
|
+
"payload", Map.of("error", "checkout failed")),
|
|
136
|
+
() -> verify(nativeOperations).captureException(
|
|
137
|
+
any(RuntimeException.class),
|
|
138
|
+
any(Map.class)));
|
|
139
|
+
assertLegacyEventCalls(
|
|
140
|
+
Map.of(
|
|
141
|
+
"event_type", "frontend_breadcrumb",
|
|
142
|
+
"payload", Map.of("breadcrumb_type", "screen_transition")),
|
|
143
|
+
() -> verify(nativeOperations).captureBreadcrumb(
|
|
144
|
+
eq("screen_transition"),
|
|
145
|
+
isNull(),
|
|
146
|
+
any(Map.class)));
|
|
147
|
+
|
|
148
|
+
for (Map.Entry<String, DebugBundleLogLevel> entry : Map.of(
|
|
149
|
+
"debug", DebugBundleLogLevel.Debug,
|
|
150
|
+
"info", DebugBundleLogLevel.Info,
|
|
151
|
+
"error", DebugBundleLogLevel.Error,
|
|
152
|
+
"critical", DebugBundleLogLevel.Critical,
|
|
153
|
+
"warning", DebugBundleLogLevel.Warning).entrySet()) {
|
|
154
|
+
assertLegacyEventCalls(
|
|
155
|
+
Map.of(
|
|
156
|
+
"event_type", "log_event",
|
|
157
|
+
"payload", Map.of("message", "checkout", "level", entry.getKey())),
|
|
158
|
+
() -> verify(nativeOperations).captureLog(
|
|
159
|
+
eq("checkout"),
|
|
160
|
+
eq(entry.getValue()),
|
|
161
|
+
any(Map.class)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
assertLegacyEventCalls(
|
|
165
|
+
Map.of(
|
|
166
|
+
"event_type", "request_event",
|
|
167
|
+
"correlation", Map.of("trace_id", "trace-rn"),
|
|
168
|
+
"payload", Map.of(
|
|
169
|
+
"method", "POST",
|
|
170
|
+
"url", "https://example.test/checkout",
|
|
171
|
+
"route_template", "/checkout",
|
|
172
|
+
"response_status", 503,
|
|
173
|
+
"duration_ms", 12.5)),
|
|
174
|
+
() -> verify(nativeOperations).captureRequest(
|
|
175
|
+
any(DebugBundleRequestInfo.class),
|
|
176
|
+
any(DebugBundleResponseInfo.class),
|
|
177
|
+
any(Map.class),
|
|
178
|
+
eq(false)));
|
|
179
|
+
assertLegacyEventCalls(
|
|
180
|
+
Map.of("event_type", "custom_event"),
|
|
181
|
+
() -> verify(nativeOperations).captureMessage(
|
|
182
|
+
eq("react_native_event:custom_event"),
|
|
183
|
+
eq(DebugBundleLogLevel.Warning),
|
|
184
|
+
any(Map.class)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Test
|
|
188
|
+
public void canonicalEventsAndProbeOperationsReturnTruthfulResultsAndFailClosed() {
|
|
189
|
+
ReadableMap event = readableMap(Map.of("event_type", "frontend_exception"));
|
|
190
|
+
Promise canonicalPromise = mock(Promise.class);
|
|
191
|
+
when(nativeOperations.captureExternalEvent(any(Map.class))).thenReturn(true);
|
|
192
|
+
module.enqueueCanonicalEvent(event, canonicalPromise);
|
|
193
|
+
verify(canonicalPromise).resolve(true);
|
|
194
|
+
|
|
195
|
+
when(nativeOperations.isProbeActive("checkout.*")).thenReturn(true);
|
|
196
|
+
assertTrue(module.isProbeActive("checkout.*"));
|
|
197
|
+
doThrow(new IllegalStateException("probe failure"))
|
|
198
|
+
.when(nativeOperations)
|
|
199
|
+
.isProbeActive("broken");
|
|
200
|
+
assertFalse(module.isProbeActive("broken"));
|
|
201
|
+
|
|
202
|
+
Promise initializePromise = mock(Promise.class);
|
|
203
|
+
module.initialize(
|
|
204
|
+
readableMap(Map.of(
|
|
205
|
+
"sdkVersion", "1.2.7",
|
|
206
|
+
"service", "checkout-mobile",
|
|
207
|
+
"environment", "test")),
|
|
208
|
+
initializePromise);
|
|
209
|
+
Promise probePromise = mock(Promise.class);
|
|
210
|
+
when(nativeOperations.captureExternalProbe(
|
|
211
|
+
anyString(),
|
|
212
|
+
anyString(),
|
|
213
|
+
anyString(),
|
|
214
|
+
anyString(),
|
|
215
|
+
any(Map.class),
|
|
216
|
+
anyString())).thenReturn(true);
|
|
217
|
+
module.captureProbe(
|
|
218
|
+
"checkout.cart",
|
|
219
|
+
readableMap(Map.of("count", 2)),
|
|
220
|
+
"2026-07-28T12:00:00.000Z",
|
|
221
|
+
probePromise);
|
|
222
|
+
verify(nativeOperations).captureExternalProbe(
|
|
223
|
+
"1.2.7",
|
|
224
|
+
"checkout-mobile",
|
|
225
|
+
"test",
|
|
226
|
+
"checkout.cart",
|
|
227
|
+
Map.of("count", 2),
|
|
228
|
+
"2026-07-28T12:00:00.000Z");
|
|
229
|
+
verify(probePromise).resolve(true);
|
|
230
|
+
|
|
231
|
+
Promise failedCanonicalPromise = mock(Promise.class);
|
|
232
|
+
doThrow(new IllegalStateException("capture failure"))
|
|
233
|
+
.when(nativeOperations)
|
|
234
|
+
.captureExternalEvent(any(Map.class));
|
|
235
|
+
module.enqueueCanonicalEvent(event, failedCanonicalPromise);
|
|
236
|
+
verify(failedCanonicalPromise).resolve(false);
|
|
237
|
+
|
|
238
|
+
Promise failedProbePromise = mock(Promise.class);
|
|
239
|
+
doThrow(new IllegalStateException("probe capture failure"))
|
|
240
|
+
.when(nativeOperations)
|
|
241
|
+
.captureExternalProbe(anyString(), anyString(), anyString(), anyString(), any(Map.class), anyString());
|
|
242
|
+
module.captureProbe(
|
|
243
|
+
"checkout.cart",
|
|
244
|
+
readableMap(Map.of()),
|
|
245
|
+
"2026-07-28T12:00:00.000Z",
|
|
246
|
+
failedProbePromise);
|
|
247
|
+
verify(failedProbePromise).resolve(false);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@Test
|
|
251
|
+
public void canonicalEventsNormalizeIntegralReactNativeNumbersRecursively() {
|
|
252
|
+
ReadableMap event = readableMap(Map.of(
|
|
253
|
+
"event_type", "request_event",
|
|
254
|
+
"payload", Map.of(
|
|
255
|
+
"response_status", 503.0,
|
|
256
|
+
"duration_ms", 12.5,
|
|
257
|
+
"probe_data", Map.of(
|
|
258
|
+
"version", 1.0,
|
|
259
|
+
"values", List.of(2.0, 2.5)))));
|
|
260
|
+
Promise promise = mock(Promise.class);
|
|
261
|
+
when(nativeOperations.captureExternalEvent(any(Map.class))).thenReturn(true);
|
|
262
|
+
|
|
263
|
+
module.enqueueCanonicalEvent(event, promise);
|
|
264
|
+
|
|
265
|
+
@SuppressWarnings("unchecked")
|
|
266
|
+
ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class);
|
|
267
|
+
verify(nativeOperations).captureExternalEvent(captor.capture());
|
|
268
|
+
Map<String, Object> payload = (Map<String, Object>) captor.getValue().get("payload");
|
|
269
|
+
Map<String, Object> probeData = (Map<String, Object>) payload.get("probe_data");
|
|
270
|
+
assertEquals(503L, payload.get("response_status"));
|
|
271
|
+
assertEquals(12.5, payload.get("duration_ms"));
|
|
272
|
+
assertEquals(1L, probeData.get("version"));
|
|
273
|
+
assertEquals(List.of(2L, 2.5), probeData.get("values"));
|
|
274
|
+
verify(promise).resolve(true);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@Test
|
|
278
|
+
public void contextTriggerAndFlushOperationsAlwaysResolve() throws Exception {
|
|
279
|
+
Promise contextPromise = mock(Promise.class);
|
|
280
|
+
Dynamic tenant = dynamic(ReadableType.String, "checkout");
|
|
281
|
+
module.setContext("tenant", tenant, contextPromise);
|
|
282
|
+
verify(nativeOperations).setContext("tenant", "checkout");
|
|
283
|
+
verify(tenant).recycle();
|
|
284
|
+
verify(contextPromise).resolve(isNull());
|
|
285
|
+
|
|
286
|
+
Promise contextValuePromise = mock(Promise.class);
|
|
287
|
+
module.setContextValue(
|
|
288
|
+
"cart",
|
|
289
|
+
readableMap(Map.of("value", 2)),
|
|
290
|
+
contextValuePromise);
|
|
291
|
+
verify(nativeOperations).setContext("cart", 2);
|
|
292
|
+
verify(contextValuePromise).resolve(isNull());
|
|
293
|
+
|
|
294
|
+
Promise triggerPromise = mock(Promise.class);
|
|
295
|
+
when(nativeOperations.activateProbeTriggerToken("trigger")).thenReturn(true);
|
|
296
|
+
module.activateProbeTriggerToken("trigger", triggerPromise);
|
|
297
|
+
verify(triggerPromise).resolve(true);
|
|
298
|
+
|
|
299
|
+
Promise flushPromise = mock(Promise.class);
|
|
300
|
+
module.flush(flushPromise);
|
|
301
|
+
verify(nativeOperations).flush();
|
|
302
|
+
verify(flushPromise).resolve(isNull());
|
|
303
|
+
|
|
304
|
+
doThrow(new IllegalStateException("context failure"))
|
|
305
|
+
.when(nativeOperations)
|
|
306
|
+
.setContext(eq("broken"), any());
|
|
307
|
+
Promise failedContext = mock(Promise.class);
|
|
308
|
+
Dynamic failedValue = dynamic(ReadableType.String, "value");
|
|
309
|
+
module.setContext("broken", failedValue, failedContext);
|
|
310
|
+
verify(failedValue).recycle();
|
|
311
|
+
verify(failedContext).resolve(isNull());
|
|
312
|
+
|
|
313
|
+
doThrow(new ReflectiveOperationException("flush failure"))
|
|
314
|
+
.when(nativeOperations)
|
|
315
|
+
.flush();
|
|
316
|
+
Promise failedFlush = mock(Promise.class);
|
|
317
|
+
module.flush(failedFlush);
|
|
318
|
+
verify(failedFlush).resolve(isNull());
|
|
319
|
+
|
|
320
|
+
doThrow(new IllegalStateException("trigger failure"))
|
|
321
|
+
.when(nativeOperations)
|
|
322
|
+
.activateProbeTriggerToken("broken");
|
|
323
|
+
Promise failedTrigger = mock(Promise.class);
|
|
324
|
+
module.activateProbeTriggerToken("broken", failedTrigger);
|
|
325
|
+
verify(failedTrigger).resolve(false);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@Test
|
|
329
|
+
public void legacyDynamicContextPreservesEverySupportedValueShape() {
|
|
330
|
+
assertDynamicContext("null", dynamic(ReadableType.Null, null), null);
|
|
331
|
+
assertDynamicContext("boolean", dynamic(ReadableType.Boolean, true), true);
|
|
332
|
+
assertDynamicContext("number", dynamic(ReadableType.Number, 2.5), 2.5);
|
|
333
|
+
assertDynamicContext("string", dynamic(ReadableType.String, "checkout"), "checkout");
|
|
334
|
+
assertDynamicContext(
|
|
335
|
+
"map",
|
|
336
|
+
dynamic(ReadableType.Map, readableMap(Map.of("count", 2))),
|
|
337
|
+
Map.of("count", 2));
|
|
338
|
+
assertDynamicContext(
|
|
339
|
+
"array",
|
|
340
|
+
dynamic(ReadableType.Array, readableArray(List.of("cart", 2))),
|
|
341
|
+
List.of("cart", 2));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@Test
|
|
345
|
+
public void legacyTranslationFailureIsSwallowedAndModuleNameIsStable() {
|
|
346
|
+
Promise promise = mock(Promise.class);
|
|
347
|
+
doThrow(new IllegalStateException("legacy failure"))
|
|
348
|
+
.when(nativeOperations)
|
|
349
|
+
.captureMessage(anyString(), any(DebugBundleLogLevel.class), any(Map.class));
|
|
350
|
+
|
|
351
|
+
module.enqueueEvent(readableMap(Map.of("event_type", "custom_event")), promise);
|
|
352
|
+
|
|
353
|
+
verify(promise).resolve(isNull());
|
|
354
|
+
verify(promise, never()).reject(anyString(), anyString());
|
|
355
|
+
org.junit.Assert.assertEquals(DebugBundleReactNativeModule.NAME, module.getName());
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@Test
|
|
359
|
+
public void reactPackageRegistersOneModuleAndNoViewManagers() {
|
|
360
|
+
DebugBundleReactNativePackage reactPackage = new DebugBundleReactNativePackage();
|
|
361
|
+
|
|
362
|
+
List<NativeModule> modules = reactPackage.createNativeModules(reactContext);
|
|
363
|
+
List<ViewManager> viewManagers = reactPackage.createViewManagers(reactContext);
|
|
364
|
+
|
|
365
|
+
org.junit.Assert.assertEquals(1, modules.size());
|
|
366
|
+
org.junit.Assert.assertEquals(DebugBundleReactNativeModule.NAME, modules.get(0).getName());
|
|
367
|
+
assertTrue(viewManagers.isEmpty());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
@Test
|
|
371
|
+
public void newArchitectureInteropCanParseEveryExportedMethod() throws Exception {
|
|
372
|
+
Class<?> interop = Class.forName(
|
|
373
|
+
"com.facebook.react.internal.turbomodule.core.TurboModuleInteropUtils");
|
|
374
|
+
java.lang.reflect.Method parser = interop.getDeclaredMethod(
|
|
375
|
+
"getMethodDescriptorsFromModule",
|
|
376
|
+
NativeModule.class);
|
|
377
|
+
parser.setAccessible(true);
|
|
378
|
+
|
|
379
|
+
List<?> descriptors = (List<?>) parser.invoke(null, module);
|
|
380
|
+
|
|
381
|
+
assertFalse(descriptors.isEmpty());
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private void assertLegacyEventCalls(
|
|
385
|
+
Map<String, Object> event,
|
|
386
|
+
Runnable verification) {
|
|
387
|
+
Promise promise = mock(Promise.class);
|
|
388
|
+
module.enqueueEvent(readableMap(event), promise);
|
|
389
|
+
verification.run();
|
|
390
|
+
verify(promise).resolve(isNull());
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private void assertDynamicContext(String key, Dynamic value, Object expected) {
|
|
394
|
+
Promise promise = mock(Promise.class);
|
|
395
|
+
module.setContext(key, value, promise);
|
|
396
|
+
verify(nativeOperations).setContext(key, expected);
|
|
397
|
+
verify(value).recycle();
|
|
398
|
+
verify(promise).resolve(isNull());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private static Dynamic dynamic(ReadableType type, Object value) {
|
|
402
|
+
Dynamic dynamic = mock(Dynamic.class);
|
|
403
|
+
when(dynamic.getType()).thenReturn(type);
|
|
404
|
+
when(dynamic.isNull()).thenReturn(type == ReadableType.Null);
|
|
405
|
+
switch (type) {
|
|
406
|
+
case Boolean:
|
|
407
|
+
when(dynamic.asBoolean()).thenReturn((Boolean) value);
|
|
408
|
+
break;
|
|
409
|
+
case Number:
|
|
410
|
+
when(dynamic.asDouble()).thenReturn((Double) value);
|
|
411
|
+
break;
|
|
412
|
+
case String:
|
|
413
|
+
when(dynamic.asString()).thenReturn((String) value);
|
|
414
|
+
break;
|
|
415
|
+
case Map:
|
|
416
|
+
when(dynamic.asMap()).thenReturn((ReadableMap) value);
|
|
417
|
+
break;
|
|
418
|
+
case Array:
|
|
419
|
+
when(dynamic.asArray()).thenReturn((ReadableArray) value);
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
return dynamic;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
428
|
+
private static ReadableArray readableArray(List<?> values) {
|
|
429
|
+
ReadableArray array = mock(ReadableArray.class);
|
|
430
|
+
when(array.toArrayList()).thenReturn((java.util.ArrayList) new java.util.ArrayList<>((List) values));
|
|
431
|
+
return array;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
435
|
+
private static ReadableMap readableMap(Map<String, ?> values) {
|
|
436
|
+
Map<String, Object> copy = new HashMap<>((Map) values);
|
|
437
|
+
ReadableMap readableMap = mock(ReadableMap.class);
|
|
438
|
+
when(readableMap.hasKey(anyString()))
|
|
439
|
+
.thenAnswer(invocation -> copy.containsKey(invocation.getArgument(0)));
|
|
440
|
+
when(readableMap.isNull(anyString()))
|
|
441
|
+
.thenAnswer(invocation -> copy.get(invocation.getArgument(0)) == null);
|
|
442
|
+
when(readableMap.getString(anyString()))
|
|
443
|
+
.thenAnswer(invocation -> (String) copy.get(invocation.getArgument(0)));
|
|
444
|
+
when(readableMap.getBoolean(anyString()))
|
|
445
|
+
.thenAnswer(invocation -> (Boolean) copy.get(invocation.getArgument(0)));
|
|
446
|
+
when(readableMap.getInt(anyString()))
|
|
447
|
+
.thenAnswer(invocation -> ((Number) copy.get(invocation.getArgument(0))).intValue());
|
|
448
|
+
when(readableMap.getDouble(anyString()))
|
|
449
|
+
.thenAnswer(invocation -> ((Number) copy.get(invocation.getArgument(0))).doubleValue());
|
|
450
|
+
when(readableMap.getMap(anyString()))
|
|
451
|
+
.thenAnswer(invocation -> {
|
|
452
|
+
Object value = copy.get(invocation.getArgument(0));
|
|
453
|
+
if (value instanceof ReadableMap) {
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
if (value instanceof Map) {
|
|
457
|
+
return readableMap((Map<String, ?>) value);
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
});
|
|
461
|
+
when(readableMap.getArray(anyString()))
|
|
462
|
+
.thenAnswer(invocation -> {
|
|
463
|
+
Object value = copy.get(invocation.getArgument(0));
|
|
464
|
+
if (!(value instanceof List)) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
ReadableArray array = mock(ReadableArray.class);
|
|
468
|
+
when(array.toArrayList()).thenReturn((java.util.ArrayList) new java.util.ArrayList<>((List) value));
|
|
469
|
+
return array;
|
|
470
|
+
});
|
|
471
|
+
when(readableMap.toHashMap()).thenReturn(new HashMap<>(copy));
|
|
472
|
+
return readableMap;
|
|
473
|
+
}
|
|
474
|
+
}
|
package/app.plugin.js
CHANGED
|
@@ -2,10 +2,14 @@ import type { TurboModule } from "react-native";
|
|
|
2
2
|
import type { UnsafeObject } from "react-native/Libraries/Types/CodegenTypes";
|
|
3
3
|
export interface Spec extends TurboModule {
|
|
4
4
|
initialize(config: UnsafeObject): Promise<UnsafeObject>;
|
|
5
|
+
enqueueCanonicalEvent?(event: UnsafeObject): Promise<boolean>;
|
|
5
6
|
enqueueEvent(event: UnsafeObject): Promise<void>;
|
|
7
|
+
captureProbe?(label: string, data: UnsafeObject, occurredAt: string): Promise<boolean>;
|
|
8
|
+
isProbeActive?(label: string): boolean;
|
|
6
9
|
flush(): Promise<void>;
|
|
7
10
|
getStatus(): Promise<UnsafeObject>;
|
|
8
|
-
setContext(key: string, value:
|
|
11
|
+
setContext(key: string, value: UnsafeObject): Promise<void>;
|
|
12
|
+
setContextValue?(key: string, entry: UnsafeObject): Promise<void>;
|
|
9
13
|
activateProbeTriggerToken?(token: string): Promise<boolean>;
|
|
10
14
|
}
|
|
11
15
|
declare const _default: Spec;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeDebugBundleReactNative.d.ts","sourceRoot":"","sources":["../src/NativeDebugBundleReactNative.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAG9E,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACxD,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACnC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeDebugBundleReactNative.d.ts","sourceRoot":"","sources":["../src/NativeDebugBundleReactNative.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAG9E,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACxD,qBAAqB,CAAC,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,YAAY,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvF,aAAa,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACnC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,eAAe,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,yBAAyB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7D;;AAED,wBAAgF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-send.d.ts","sourceRoot":"","sources":["../src/before-send.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AA2ClF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,wBAAwB,EAC/B,IAAI,EAAE,qBAAqB,GAAG,IAAI,GACjC,wBAAwB,GAAG,IAAI,CAajC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const ROOT_KEYS = new Set([
|
|
2
|
+
"schema_version",
|
|
3
|
+
"event_id",
|
|
4
|
+
"event_type",
|
|
5
|
+
"sdk_name",
|
|
6
|
+
"sdk_version",
|
|
7
|
+
"service",
|
|
8
|
+
"occurred_at",
|
|
9
|
+
"correlation",
|
|
10
|
+
"context",
|
|
11
|
+
"payload"
|
|
12
|
+
]);
|
|
13
|
+
const REQUIRED_PAYLOAD_FIELDS = {
|
|
14
|
+
frontend_exception: ["name", "message", "stack"],
|
|
15
|
+
frontend_breadcrumb: ["breadcrumb_type", "data"],
|
|
16
|
+
log_event: ["level", "message", "attributes"],
|
|
17
|
+
request_event: ["method", "path", "query", "headers", "response_status", "duration_ms"],
|
|
18
|
+
error_suppressed: ["fingerprint", "suppressed_count", "window_seconds", "first_seen", "last_seen"],
|
|
19
|
+
probe_event: ["label", "data", "activation_id", "probe_label_pattern"]
|
|
20
|
+
};
|
|
21
|
+
const ALLOWED_PAYLOAD_FIELDS = {
|
|
22
|
+
frontend_exception: new Set([
|
|
23
|
+
"name", "message", "stack", "route", "browser", "breadcrumbs", "device",
|
|
24
|
+
"browser_event", "rejection_reason", "dom_context", "probe_data"
|
|
25
|
+
]),
|
|
26
|
+
frontend_breadcrumb: new Set(["breadcrumb_type", "route", "data", "device"]),
|
|
27
|
+
log_event: new Set(["level", "message", "attributes", "device"]),
|
|
28
|
+
request_event: new Set([
|
|
29
|
+
"method", "path", "query", "headers", "body", "response_status", "duration_ms",
|
|
30
|
+
"route_template", "response_headers", "response_body", "device"
|
|
31
|
+
]),
|
|
32
|
+
error_suppressed: new Set([
|
|
33
|
+
"fingerprint", "suppressed_count", "window_seconds", "first_seen", "last_seen", "device"
|
|
34
|
+
]),
|
|
35
|
+
probe_event: new Set(["label", "data", "activation_id", "probe_label_pattern", "device"])
|
|
36
|
+
};
|
|
37
|
+
const EVENT_TYPES = new Set(Object.keys(REQUIRED_PAYLOAD_FIELDS));
|
|
38
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
39
|
+
export function applyBeforeSend(event, hook) {
|
|
40
|
+
if (!hook) {
|
|
41
|
+
return event;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const result = hook(cloneEvent(event));
|
|
45
|
+
if (result === null) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return isValidEvent(result) ? result : event;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return event;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function cloneEvent(event) {
|
|
55
|
+
return JSON.parse(JSON.stringify(event));
|
|
56
|
+
}
|
|
57
|
+
function isValidEvent(event) {
|
|
58
|
+
if (!isRecord(event) || !Object.keys(event).every((key) => ROOT_KEYS.has(key))) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (event.schema_version !== "2026-03-01" ||
|
|
62
|
+
!UUID_PATTERN.test(stringValue(event.event_id)) ||
|
|
63
|
+
!EVENT_TYPES.has(stringValue(event.event_type)) ||
|
|
64
|
+
event.sdk_name !== "@debugbundle/sdk-react-native" ||
|
|
65
|
+
!nonEmptyString(event.sdk_version) ||
|
|
66
|
+
!isIsoTimestamp(event.occurred_at) ||
|
|
67
|
+
!isRecord(event.service) ||
|
|
68
|
+
!nonEmptyString(event.service.name) ||
|
|
69
|
+
!nonEmptyString(event.service.environment) ||
|
|
70
|
+
event.service.runtime !== "react-native" ||
|
|
71
|
+
event.service.framework !== "react-native" ||
|
|
72
|
+
!isRecord(event.payload)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const eventType = event.event_type;
|
|
76
|
+
return (REQUIRED_PAYLOAD_FIELDS[eventType].every((field) => Object.prototype.hasOwnProperty.call(event.payload, field)) &&
|
|
77
|
+
Object.keys(event.payload).every((field) => ALLOWED_PAYLOAD_FIELDS[eventType].has(field)) &&
|
|
78
|
+
hasValidPayloadShape(eventType, event.payload));
|
|
79
|
+
}
|
|
80
|
+
function hasValidPayloadShape(eventType, payload) {
|
|
81
|
+
switch (eventType) {
|
|
82
|
+
case "frontend_exception":
|
|
83
|
+
return (hasNonEmptyStrings(payload, "name", "message", "stack") &&
|
|
84
|
+
(!("breadcrumbs" in payload) || Array.isArray(payload.breadcrumbs)) &&
|
|
85
|
+
optionalRecord(payload, "probe_data"));
|
|
86
|
+
case "frontend_breadcrumb":
|
|
87
|
+
return hasNonEmptyStrings(payload, "breadcrumb_type") && isRecord(payload.data);
|
|
88
|
+
case "log_event":
|
|
89
|
+
return hasNonEmptyStrings(payload, "level", "message") && isRecord(payload.attributes);
|
|
90
|
+
case "request_event":
|
|
91
|
+
return (hasNonEmptyStrings(payload, "method", "path") &&
|
|
92
|
+
isRecord(payload.query) &&
|
|
93
|
+
isRecord(payload.headers) &&
|
|
94
|
+
isNonNegativeNumber(payload.response_status) &&
|
|
95
|
+
isNonNegativeNumber(payload.duration_ms) &&
|
|
96
|
+
optionalRecord(payload, "response_headers"));
|
|
97
|
+
case "error_suppressed":
|
|
98
|
+
return (hasNonEmptyStrings(payload, "fingerprint") &&
|
|
99
|
+
isNonNegativeInteger(payload.suppressed_count) &&
|
|
100
|
+
isPositiveInteger(payload.window_seconds) &&
|
|
101
|
+
isIsoTimestamp(payload.first_seen) &&
|
|
102
|
+
isIsoTimestamp(payload.last_seen));
|
|
103
|
+
case "probe_event":
|
|
104
|
+
return (hasNonEmptyStrings(payload, "label", "probe_label_pattern") &&
|
|
105
|
+
isRecord(payload.data) &&
|
|
106
|
+
(payload.activation_id === null ||
|
|
107
|
+
nonEmptyString(payload.activation_id) && UUID_PATTERN.test(payload.activation_id)));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function hasNonEmptyStrings(payload, ...fields) {
|
|
111
|
+
return fields.every((field) => nonEmptyString(payload[field]));
|
|
112
|
+
}
|
|
113
|
+
function optionalRecord(payload, field) {
|
|
114
|
+
return !(field in payload) || isRecord(payload[field]);
|
|
115
|
+
}
|
|
116
|
+
function isNonNegativeNumber(value) {
|
|
117
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
118
|
+
}
|
|
119
|
+
function isNonNegativeInteger(value) {
|
|
120
|
+
return Number.isInteger(value) && value >= 0;
|
|
121
|
+
}
|
|
122
|
+
function isPositiveInteger(value) {
|
|
123
|
+
return Number.isInteger(value) && value > 0;
|
|
124
|
+
}
|
|
125
|
+
function isRecord(value) {
|
|
126
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
127
|
+
}
|
|
128
|
+
function nonEmptyString(value) {
|
|
129
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
130
|
+
}
|
|
131
|
+
function stringValue(value) {
|
|
132
|
+
return typeof value === "string" ? value : "";
|
|
133
|
+
}
|
|
134
|
+
function isIsoTimestamp(value) {
|
|
135
|
+
return nonEmptyString(value) && Number.isFinite(Date.parse(value));
|
|
136
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ export declare class DebugBundleReactNativeClient implements DebugBundleClient {
|
|
|
7
7
|
private breadcrumbs;
|
|
8
8
|
private probeBuffers;
|
|
9
9
|
private jsProbeActivationExpiresAt;
|
|
10
|
-
private sessionSampledIn;
|
|
11
|
-
private sessionEventCount;
|
|
12
10
|
constructor(config?: DebugBundleConfig);
|
|
13
11
|
get status(): DebugBundleStatus;
|
|
14
12
|
get lastEventAt(): number | null;
|
|
@@ -29,7 +27,6 @@ export declare class DebugBundleReactNativeClient implements DebugBundleClient {
|
|
|
29
27
|
private mergeContext;
|
|
30
28
|
private snapshotProbeData;
|
|
31
29
|
private hasActiveJsProbeActivation;
|
|
32
|
-
private shouldCaptureRequestEvent;
|
|
33
30
|
}
|
|
34
31
|
export declare function createDebugBundleClient(config?: DebugBundleConfig): DebugBundleReactNativeClient;
|
|
35
32
|
export declare function resolveConfig(config: DebugBundleConfig): ResolvedDebugBundleConfig;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EAEjB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EAIjB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAYpB,qBAAa,4BAA6B,YAAW,iBAAiB;IACpE,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,WAAW,CAAkE;IACrF,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,YAAY,CAAqD;IACzE,OAAO,CAAC,0BAA0B,CAAK;gBAE3B,MAAM,GAAE,iBAAsB;IAO1C,IAAI,MAAM,IAAI,iBAAiB,CAE9B;IAED,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IA8BrC,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,yBAA8B,GAAG,IAAI;IAc/E,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,yBAA8B,GAAG,IAAI;IAI3E,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,mBAA+B,EAAE,OAAO,GAAE,yBAA8B,GAAG,IAAI;IAYlH,cAAc,CACZ,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,yBAA8B,GACtC,IAAI;IA+BP,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,mBAA+B,EAAE,OAAO,GAAE,yBAA8B,GAAG,IAAI;IAQtH,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAS7C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,EAAE,OAAO,GAAE,uBAA4B,GAAG,IAAI;IA4B5F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAWtB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IAcxE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,GAAG,IAAW,EAAE,MAAM,SAAqB,GAAG,IAAI;IAWzG,OAAO,CAAC,OAAO;IA6Cf,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,0BAA0B;CAInC;AAED,wBAAgB,uBAAuB,CAAC,MAAM,GAAE,iBAAsB,GAAG,4BAA4B,CAEpG;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,yBAAyB,CAsClF"}
|