@debugbundle/sdk-react-native 1.1.0 → 1.3.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 +43 -0
- package/DebugBundleReactNative.podspec +4 -3
- package/LICENSE +202 -0
- 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 +7 -3
- 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/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,GAAG,cAAc,CAAC;AAExE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,28 +4,65 @@ import DebugBundle
|
|
|
4
4
|
|
|
5
5
|
@objc(DebugBundleReactNative)
|
|
6
6
|
final class DebugBundleReactNative: NSObject {
|
|
7
|
-
|
|
7
|
+
private var currentSDKVersion = "1.3.0"
|
|
8
|
+
private var currentService = "react-native-app"
|
|
9
|
+
private var currentEnvironment = "production"
|
|
10
|
+
|
|
11
|
+
@objc(initialize:resolve:reject:)
|
|
8
12
|
func initialize(
|
|
9
13
|
_ config: NSDictionary,
|
|
10
14
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
11
15
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
12
16
|
) {
|
|
17
|
+
currentSDKVersion = config["sdkVersion"] as? String ?? "1.3.0"
|
|
18
|
+
currentService = config["service"] as? String ?? "react-native-app"
|
|
19
|
+
currentEnvironment = config["environment"] as? String ?? "production"
|
|
13
20
|
let sdkConfig = DebugBundleConfig(
|
|
14
21
|
projectToken: config["projectToken"] as? String ?? "",
|
|
15
22
|
enabled: config["enabled"] as? Bool ?? true,
|
|
16
|
-
environment:
|
|
17
|
-
service:
|
|
23
|
+
environment: currentEnvironment,
|
|
24
|
+
service: currentService,
|
|
18
25
|
endpoint: URL(string: config["endpoint"] as? String ?? DebugBundleConfig.defaultEndpoint.absoluteString) ?? DebugBundleConfig.defaultEndpoint,
|
|
26
|
+
batchSize: integer(config["batchSize"], fallback: 10),
|
|
27
|
+
flushInterval: milliseconds(config["flushInterval"], fallback: 3_000),
|
|
28
|
+
sampleRate: number(config["sampleRate"], fallback: 1),
|
|
29
|
+
sessionSampleRate: number(config["sessionSampleRate"], fallback: 1),
|
|
30
|
+
requestTimeout: milliseconds(config["requestTimeout"], fallback: 5_000),
|
|
19
31
|
releaseChannel: config["releaseChannel"] as? String ?? "production",
|
|
20
32
|
appVersion: config["appVersion"] as? String,
|
|
21
33
|
buildNumber: config["buildNumber"] as? String,
|
|
22
|
-
|
|
34
|
+
maxEventsPerSession: integer(config["maxEventsPerSession"], fallback: 100),
|
|
35
|
+
maxBreadcrumbs: integer(config["maxBreadcrumbs"], fallback: 20),
|
|
36
|
+
captureScreens: config["captureScreens"] as? Bool ?? true,
|
|
37
|
+
captureActions: config["captureActions"] as? Bool ?? false,
|
|
38
|
+
captureNetwork: config["captureNetwork"] as? Bool ?? true,
|
|
39
|
+
captureLogs: config["captureLogs"] as? Bool ?? true,
|
|
40
|
+
logLevel: logLevel(config["logLevel"] as? String),
|
|
41
|
+
tracePropagationTargets: config["tracePropagationTargets"] as? [String] ?? [],
|
|
42
|
+
offlineQueueMaxEvents: integer(config["offlineQueueMaxEvents"], fallback: 500),
|
|
43
|
+
offlineQueueMaxBytes: integer(config["offlineQueueMaxBytes"], fallback: 5 * 1024 * 1024),
|
|
44
|
+
offlineQueueTtl: milliseconds(config["offlineQueueTtl"], fallback: 72 * 60 * 60 * 1_000),
|
|
45
|
+
maxProbeLabels: integer(config["maxProbeLabels"], fallback: 50),
|
|
46
|
+
maxProbeEntriesPerLabel: integer(config["maxProbeEntriesPerLabel"], fallback: 10),
|
|
47
|
+
probeFlushOnError: config["probeFlushOnError"] as? Bool ?? true,
|
|
48
|
+
redactFields: stringSet(config["redactFields"], fallback: DebugBundleConfig.defaultRedactFields),
|
|
49
|
+
headerAllowlist: stringSet(config["headerAllowlist"], fallback: DebugBundleConfig.defaultHeaderAllowlist),
|
|
50
|
+
sdkVersion: currentSDKVersion
|
|
23
51
|
)
|
|
24
52
|
DebugBundle.initialize(sdkConfig, transport: DebugBundleHTTPTransport())
|
|
25
53
|
resolve(state())
|
|
26
54
|
}
|
|
27
55
|
|
|
28
|
-
@objc(
|
|
56
|
+
@objc(enqueueCanonicalEvent:resolve:reject:)
|
|
57
|
+
func enqueueCanonicalEvent(
|
|
58
|
+
_ event: NSDictionary,
|
|
59
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
60
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
61
|
+
) {
|
|
62
|
+
resolve(DebugBundle.captureExternalEvent(dictionary(event)))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@objc(enqueueEvent:resolve:reject:)
|
|
29
66
|
func enqueueEvent(
|
|
30
67
|
_ event: NSDictionary,
|
|
31
68
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -61,7 +98,7 @@ final class DebugBundleReactNative: NSObject {
|
|
|
61
98
|
resolve(nil)
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
@objc(flush:
|
|
101
|
+
@objc(flush:reject:)
|
|
65
102
|
func flush(resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
66
103
|
Task {
|
|
67
104
|
await DebugBundle.flush()
|
|
@@ -69,12 +106,12 @@ final class DebugBundleReactNative: NSObject {
|
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
|
|
72
|
-
@objc(getStatus:
|
|
109
|
+
@objc(getStatus:reject:)
|
|
73
110
|
func getStatus(resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
74
111
|
resolve(state())
|
|
75
112
|
}
|
|
76
113
|
|
|
77
|
-
@objc(setContext:value:
|
|
114
|
+
@objc(setContext:value:resolve:reject:)
|
|
78
115
|
func setContext(
|
|
79
116
|
_ key: String,
|
|
80
117
|
value: Any?,
|
|
@@ -85,7 +122,19 @@ final class DebugBundleReactNative: NSObject {
|
|
|
85
122
|
resolve(nil)
|
|
86
123
|
}
|
|
87
124
|
|
|
88
|
-
@objc(
|
|
125
|
+
@objc(setContextValue:entry:resolve:reject:)
|
|
126
|
+
func setContextValue(
|
|
127
|
+
_ key: String,
|
|
128
|
+
entry: NSDictionary,
|
|
129
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
130
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
131
|
+
) {
|
|
132
|
+
let value = entry["value"]
|
|
133
|
+
DebugBundle.setContext(key, value: value is NSNull ? nil : value)
|
|
134
|
+
resolve(nil)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc(activateProbeTriggerToken:resolve:reject:)
|
|
89
138
|
func activateProbeTriggerToken(
|
|
90
139
|
_ token: String,
|
|
91
140
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -94,21 +143,79 @@ final class DebugBundleReactNative: NSObject {
|
|
|
94
143
|
resolve(DebugBundle.activateProbeTriggerToken(token))
|
|
95
144
|
}
|
|
96
145
|
|
|
146
|
+
@objc(isProbeActive:)
|
|
147
|
+
func isProbeActive(_ label: String) -> Bool {
|
|
148
|
+
DebugBundle.isExternalProbeActive(label)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@objc(captureProbe:data:occurredAt:resolve:reject:)
|
|
152
|
+
func captureProbe(
|
|
153
|
+
_ label: String,
|
|
154
|
+
data: NSDictionary,
|
|
155
|
+
occurredAt: String,
|
|
156
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
157
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
158
|
+
) {
|
|
159
|
+
resolve(
|
|
160
|
+
DebugBundle.captureExternalProbe(
|
|
161
|
+
sdkVersion: currentSDKVersion,
|
|
162
|
+
service: currentService,
|
|
163
|
+
environment: currentEnvironment,
|
|
164
|
+
label: label,
|
|
165
|
+
data: dictionary(data),
|
|
166
|
+
occurredAt: occurredAt
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
97
171
|
@objc
|
|
98
172
|
static func requiresMainQueueSetup() -> Bool {
|
|
99
173
|
false
|
|
100
174
|
}
|
|
101
175
|
|
|
102
|
-
private func state() -> [String: Any
|
|
176
|
+
private func state() -> [String: Any] {
|
|
103
177
|
[
|
|
104
178
|
"status": DebugBundle.status.rawValue,
|
|
105
|
-
"lastEventAt": DebugBundle.lastEventAt.map { $0.timeIntervalSince1970 * 1000 },
|
|
106
|
-
"nativeModuleAvailable": true
|
|
107
|
-
"degradedReason": nil
|
|
179
|
+
"lastEventAt": DebugBundle.lastEventAt.map { $0.timeIntervalSince1970 * 1000 } ?? NSNull(),
|
|
180
|
+
"nativeModuleAvailable": true
|
|
108
181
|
]
|
|
109
182
|
}
|
|
110
183
|
}
|
|
111
184
|
|
|
185
|
+
private func dictionary(_ value: NSDictionary) -> [String: Any?] {
|
|
186
|
+
var result: [String: Any?] = [:]
|
|
187
|
+
for (rawKey, rawValue) in value {
|
|
188
|
+
guard let key = rawKey as? String else {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
if rawValue is NSNull {
|
|
192
|
+
result.updateValue(nil, forKey: key)
|
|
193
|
+
} else {
|
|
194
|
+
result[key] = rawValue
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return result
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private func integer(_ value: Any?, fallback: Int) -> Int {
|
|
201
|
+
(value as? NSNumber)?.intValue ?? fallback
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private func number(_ value: Any?, fallback: Double) -> Double {
|
|
205
|
+
(value as? NSNumber)?.doubleValue ?? fallback
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func milliseconds(_ value: Any?, fallback: Double) -> TimeInterval {
|
|
209
|
+
number(value, fallback: fallback) / 1_000
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func stringSet(_ value: Any?, fallback: Set<String>) -> Set<String> {
|
|
213
|
+
guard let values = value as? [String] else {
|
|
214
|
+
return fallback
|
|
215
|
+
}
|
|
216
|
+
return Set(values)
|
|
217
|
+
}
|
|
218
|
+
|
|
112
219
|
private func logLevel(_ value: String?) -> DebugBundleLogLevel {
|
|
113
220
|
switch value?.lowercased() {
|
|
114
221
|
case "debug":
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
#if defined(RCT_NEW_ARCH_ENABLED) || defined(RCT_REMOVE_LEGACY_ARCH)
|
|
4
|
+
#if __has_include(<ReactCodegen/DebugBundleReactNativeSpec/DebugBundleReactNativeSpec.h>)
|
|
5
|
+
#import <ReactCodegen/DebugBundleReactNativeSpec/DebugBundleReactNativeSpec.h>
|
|
6
|
+
#elif __has_include(<ReactCodegen/DebugBundleReactNativeSpec.h>)
|
|
7
|
+
#import <ReactCodegen/DebugBundleReactNativeSpec.h>
|
|
8
|
+
#else
|
|
9
|
+
#error "DebugBundle React Native codegen header was not generated"
|
|
10
|
+
#endif
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
@interface RCT_EXTERN_MODULE(DebugBundleReactNative, NSObject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(initialize:(NSDictionary *)config
|
|
16
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
17
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
18
|
+
|
|
19
|
+
RCT_EXTERN_METHOD(enqueueEvent:(NSDictionary *)event
|
|
20
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
21
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
22
|
+
|
|
23
|
+
RCT_EXTERN_METHOD(enqueueCanonicalEvent:(NSDictionary *)event
|
|
24
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
25
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
26
|
+
|
|
27
|
+
RCT_EXTERN_METHOD(flush:(RCTPromiseResolveBlock)resolve
|
|
28
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
29
|
+
|
|
30
|
+
RCT_EXTERN_METHOD(getStatus:(RCTPromiseResolveBlock)resolve
|
|
31
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
32
|
+
|
|
33
|
+
RCT_EXTERN_METHOD(setContext:(NSString *)key
|
|
34
|
+
value:(id)value
|
|
35
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
36
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
37
|
+
|
|
38
|
+
RCT_EXTERN_METHOD(setContextValue:(NSString *)key
|
|
39
|
+
entry:(NSDictionary *)entry
|
|
40
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
41
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
42
|
+
|
|
43
|
+
RCT_EXTERN_METHOD(activateProbeTriggerToken:(NSString *)token
|
|
44
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
45
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
46
|
+
|
|
47
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(isProbeActive:(NSString *)label)
|
|
48
|
+
|
|
49
|
+
RCT_EXTERN_METHOD(captureProbe:(NSString *)label
|
|
50
|
+
data:(NSDictionary *)data
|
|
51
|
+
occurredAt:(NSString *)occurredAt
|
|
52
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
53
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
54
|
+
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
#if defined(RCT_NEW_ARCH_ENABLED) || defined(RCT_REMOVE_LEGACY_ARCH)
|
|
58
|
+
@interface DebugBundleReactNative (NativeDebugBundleReactNativeSpecConformance) <NativeDebugBundleReactNativeSpec>
|
|
59
|
+
@end
|
|
60
|
+
|
|
61
|
+
#pragma clang diagnostic push
|
|
62
|
+
#pragma clang diagnostic ignored "-Wprotocol"
|
|
63
|
+
@implementation DebugBundleReactNative (NativeDebugBundleReactNativeSpecConformance)
|
|
64
|
+
|
|
65
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
66
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
67
|
+
{
|
|
68
|
+
return std::make_shared<facebook::react::NativeDebugBundleReactNativeSpecJSI>(params);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@end
|
|
72
|
+
#pragma clang diagnostic pop
|
|
73
|
+
#endif
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@debugbundle/sdk-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "React Native SDK for DebugBundle mobile event capture.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://debugbundle.com/docs/sdks/react-native",
|
|
8
8
|
"keywords": [
|
|
@@ -47,9 +47,11 @@
|
|
|
47
47
|
"dist",
|
|
48
48
|
"app.plugin.js",
|
|
49
49
|
"DebugBundleReactNative.podspec",
|
|
50
|
+
"CHANGELOG.md",
|
|
50
51
|
"README.md"
|
|
51
52
|
],
|
|
52
53
|
"exports": {
|
|
54
|
+
"./package.json": "./package.json",
|
|
53
55
|
".": {
|
|
54
56
|
"types": "./dist/index.d.ts",
|
|
55
57
|
"default": "./dist/index.js"
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
"scripts": {
|
|
83
85
|
"build": "tsc -p tsconfig.json",
|
|
84
86
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
85
|
-
"test": "vitest run",
|
|
87
|
+
"test": "vitest run --coverage",
|
|
86
88
|
"pack:check": "npm pack --dry-run",
|
|
87
89
|
"smoke:registry": "node scripts/smoke-registry.mjs"
|
|
88
90
|
},
|
|
@@ -102,6 +104,8 @@
|
|
|
102
104
|
},
|
|
103
105
|
"devDependencies": {
|
|
104
106
|
"@types/react": "^19.1.2",
|
|
107
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
108
|
+
"react": "^19.2.4",
|
|
105
109
|
"typescript": "^6.0.2",
|
|
106
110
|
"vitest": "^4.1.2"
|
|
107
111
|
}
|
|
@@ -4,10 +4,14 @@ import { TurboModuleRegistry } from "react-native";
|
|
|
4
4
|
|
|
5
5
|
export interface Spec extends TurboModule {
|
|
6
6
|
initialize(config: UnsafeObject): Promise<UnsafeObject>;
|
|
7
|
+
enqueueCanonicalEvent?(event: UnsafeObject): Promise<boolean>;
|
|
7
8
|
enqueueEvent(event: UnsafeObject): Promise<void>;
|
|
9
|
+
captureProbe?(label: string, data: UnsafeObject, occurredAt: string): Promise<boolean>;
|
|
10
|
+
isProbeActive?(label: string): boolean;
|
|
8
11
|
flush(): Promise<void>;
|
|
9
12
|
getStatus(): Promise<UnsafeObject>;
|
|
10
|
-
setContext(key: string, value:
|
|
13
|
+
setContext(key: string, value: UnsafeObject): Promise<void>;
|
|
14
|
+
setContextValue?(key: string, entry: UnsafeObject): Promise<void>;
|
|
11
15
|
activateProbeTriggerToken?(token: string): Promise<boolean>;
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { DebugBundleBeforeSend, DebugBundleEventEnvelope } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const ROOT_KEYS = new Set([
|
|
4
|
+
"schema_version",
|
|
5
|
+
"event_id",
|
|
6
|
+
"event_type",
|
|
7
|
+
"sdk_name",
|
|
8
|
+
"sdk_version",
|
|
9
|
+
"service",
|
|
10
|
+
"occurred_at",
|
|
11
|
+
"correlation",
|
|
12
|
+
"context",
|
|
13
|
+
"payload"
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const REQUIRED_PAYLOAD_FIELDS: Record<DebugBundleEventEnvelope["event_type"], string[]> = {
|
|
17
|
+
frontend_exception: ["name", "message", "stack"],
|
|
18
|
+
frontend_breadcrumb: ["breadcrumb_type", "data"],
|
|
19
|
+
log_event: ["level", "message", "attributes"],
|
|
20
|
+
request_event: ["method", "path", "query", "headers", "response_status", "duration_ms"],
|
|
21
|
+
error_suppressed: ["fingerprint", "suppressed_count", "window_seconds", "first_seen", "last_seen"],
|
|
22
|
+
probe_event: ["label", "data", "activation_id", "probe_label_pattern"]
|
|
23
|
+
};
|
|
24
|
+
const ALLOWED_PAYLOAD_FIELDS: Record<DebugBundleEventEnvelope["event_type"], Set<string>> = {
|
|
25
|
+
frontend_exception: new Set([
|
|
26
|
+
"name", "message", "stack", "route", "browser", "breadcrumbs", "device",
|
|
27
|
+
"browser_event", "rejection_reason", "dom_context", "probe_data"
|
|
28
|
+
]),
|
|
29
|
+
frontend_breadcrumb: new Set(["breadcrumb_type", "route", "data", "device"]),
|
|
30
|
+
log_event: new Set(["level", "message", "attributes", "device"]),
|
|
31
|
+
request_event: new Set([
|
|
32
|
+
"method", "path", "query", "headers", "body", "response_status", "duration_ms",
|
|
33
|
+
"route_template", "response_headers", "response_body", "device"
|
|
34
|
+
]),
|
|
35
|
+
error_suppressed: new Set([
|
|
36
|
+
"fingerprint", "suppressed_count", "window_seconds", "first_seen", "last_seen", "device"
|
|
37
|
+
]),
|
|
38
|
+
probe_event: new Set(["label", "data", "activation_id", "probe_label_pattern", "device"])
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const EVENT_TYPES = new Set(Object.keys(REQUIRED_PAYLOAD_FIELDS));
|
|
42
|
+
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;
|
|
43
|
+
|
|
44
|
+
export function applyBeforeSend(
|
|
45
|
+
event: DebugBundleEventEnvelope,
|
|
46
|
+
hook: DebugBundleBeforeSend | null
|
|
47
|
+
): DebugBundleEventEnvelope | null {
|
|
48
|
+
if (!hook) {
|
|
49
|
+
return event;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const result = hook(cloneEvent(event));
|
|
53
|
+
if (result === null) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return isValidEvent(result) ? result : event;
|
|
57
|
+
} catch {
|
|
58
|
+
return event;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cloneEvent(event: DebugBundleEventEnvelope): DebugBundleEventEnvelope {
|
|
63
|
+
return JSON.parse(JSON.stringify(event)) as DebugBundleEventEnvelope;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isValidEvent(event: unknown): event is DebugBundleEventEnvelope {
|
|
67
|
+
if (!isRecord(event) || !Object.keys(event).every((key) => ROOT_KEYS.has(key))) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
event.schema_version !== "2026-03-01" ||
|
|
72
|
+
!UUID_PATTERN.test(stringValue(event.event_id)) ||
|
|
73
|
+
!EVENT_TYPES.has(stringValue(event.event_type)) ||
|
|
74
|
+
event.sdk_name !== "@debugbundle/sdk-react-native" ||
|
|
75
|
+
!nonEmptyString(event.sdk_version) ||
|
|
76
|
+
!isIsoTimestamp(event.occurred_at) ||
|
|
77
|
+
!isRecord(event.service) ||
|
|
78
|
+
!nonEmptyString(event.service.name) ||
|
|
79
|
+
!nonEmptyString(event.service.environment) ||
|
|
80
|
+
event.service.runtime !== "react-native" ||
|
|
81
|
+
event.service.framework !== "react-native" ||
|
|
82
|
+
!isRecord(event.payload)
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const eventType = event.event_type as DebugBundleEventEnvelope["event_type"];
|
|
87
|
+
return (
|
|
88
|
+
REQUIRED_PAYLOAD_FIELDS[eventType].every((field) =>
|
|
89
|
+
Object.prototype.hasOwnProperty.call(event.payload, field)
|
|
90
|
+
) &&
|
|
91
|
+
Object.keys(event.payload).every((field) => ALLOWED_PAYLOAD_FIELDS[eventType].has(field)) &&
|
|
92
|
+
hasValidPayloadShape(eventType, event.payload)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasValidPayloadShape(
|
|
97
|
+
eventType: DebugBundleEventEnvelope["event_type"],
|
|
98
|
+
payload: Record<string, unknown>
|
|
99
|
+
): boolean {
|
|
100
|
+
switch (eventType) {
|
|
101
|
+
case "frontend_exception":
|
|
102
|
+
return (
|
|
103
|
+
hasNonEmptyStrings(payload, "name", "message", "stack") &&
|
|
104
|
+
(!("breadcrumbs" in payload) || Array.isArray(payload.breadcrumbs)) &&
|
|
105
|
+
optionalRecord(payload, "probe_data")
|
|
106
|
+
);
|
|
107
|
+
case "frontend_breadcrumb":
|
|
108
|
+
return hasNonEmptyStrings(payload, "breadcrumb_type") && isRecord(payload.data);
|
|
109
|
+
case "log_event":
|
|
110
|
+
return hasNonEmptyStrings(payload, "level", "message") && isRecord(payload.attributes);
|
|
111
|
+
case "request_event":
|
|
112
|
+
return (
|
|
113
|
+
hasNonEmptyStrings(payload, "method", "path") &&
|
|
114
|
+
isRecord(payload.query) &&
|
|
115
|
+
isRecord(payload.headers) &&
|
|
116
|
+
isNonNegativeNumber(payload.response_status) &&
|
|
117
|
+
isNonNegativeNumber(payload.duration_ms) &&
|
|
118
|
+
optionalRecord(payload, "response_headers")
|
|
119
|
+
);
|
|
120
|
+
case "error_suppressed":
|
|
121
|
+
return (
|
|
122
|
+
hasNonEmptyStrings(payload, "fingerprint") &&
|
|
123
|
+
isNonNegativeInteger(payload.suppressed_count) &&
|
|
124
|
+
isPositiveInteger(payload.window_seconds) &&
|
|
125
|
+
isIsoTimestamp(payload.first_seen) &&
|
|
126
|
+
isIsoTimestamp(payload.last_seen)
|
|
127
|
+
);
|
|
128
|
+
case "probe_event":
|
|
129
|
+
return (
|
|
130
|
+
hasNonEmptyStrings(payload, "label", "probe_label_pattern") &&
|
|
131
|
+
isRecord(payload.data) &&
|
|
132
|
+
(payload.activation_id === null ||
|
|
133
|
+
nonEmptyString(payload.activation_id) && UUID_PATTERN.test(payload.activation_id))
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasNonEmptyStrings(payload: Record<string, unknown>, ...fields: string[]): boolean {
|
|
139
|
+
return fields.every((field) => nonEmptyString(payload[field]));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function optionalRecord(payload: Record<string, unknown>, field: string): boolean {
|
|
143
|
+
return !(field in payload) || isRecord(payload[field]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isNonNegativeNumber(value: unknown): boolean {
|
|
147
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isNonNegativeInteger(value: unknown): boolean {
|
|
151
|
+
return Number.isInteger(value) && (value as number) >= 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isPositiveInteger(value: unknown): boolean {
|
|
155
|
+
return Number.isInteger(value) && (value as number) > 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
159
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function nonEmptyString(value: unknown): value is string {
|
|
163
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function stringValue(value: unknown): string {
|
|
167
|
+
return typeof value === "string" ? value : "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isIsoTimestamp(value: unknown): boolean {
|
|
171
|
+
return nonEmptyString(value) && Number.isFinite(Date.parse(value));
|
|
172
|
+
}
|