@debugbundle/sdk-react-native 3.0.0 → 3.1.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 (84) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/DebugBundleReactNative.podspec +2 -2
  3. package/MIGRATION-3.1.md +116 -0
  4. package/README.md +38 -2
  5. package/android/build.gradle +1 -1
  6. package/android/src/main/java/com/debugbundle/reactnative/DebugBundleReactNativeModule.java +34 -3
  7. package/app.plugin.js +1 -1
  8. package/dist/NativeDebugBundleReactNative.d.ts +1 -0
  9. package/dist/NativeDebugBundleReactNative.d.ts.map +1 -1
  10. package/dist/before-send.d.ts.map +1 -1
  11. package/dist/before-send.js +9 -131
  12. package/dist/canonical-schema.d.ts +15 -0
  13. package/dist/canonical-schema.d.ts.map +1 -0
  14. package/dist/canonical-schema.js +139 -0
  15. package/dist/client-values.d.ts +17 -0
  16. package/dist/client-values.d.ts.map +1 -0
  17. package/dist/client-values.js +185 -0
  18. package/dist/client.d.ts +7 -0
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +102 -205
  21. package/dist/diagnostic-budget.d.ts +16 -0
  22. package/dist/diagnostic-budget.d.ts.map +1 -0
  23. package/dist/diagnostic-budget.js +120 -0
  24. package/dist/event-schema-data.d.ts +2 -0
  25. package/dist/event-schema-data.d.ts.map +1 -0
  26. package/dist/event-schema-data.js +49 -0
  27. package/dist/evidence-capabilities.d.ts +5 -0
  28. package/dist/evidence-capabilities.d.ts.map +1 -0
  29. package/dist/evidence-capabilities.js +21 -0
  30. package/dist/evidence-controller.d.ts +24 -0
  31. package/dist/evidence-controller.d.ts.map +1 -0
  32. package/dist/evidence-controller.js +113 -0
  33. package/dist/evidence-types.d.ts +33 -0
  34. package/dist/evidence-types.d.ts.map +1 -0
  35. package/dist/evidence-types.js +1 -0
  36. package/dist/evidence-wire.d.ts +7 -0
  37. package/dist/evidence-wire.d.ts.map +1 -0
  38. package/dist/evidence-wire.js +132 -0
  39. package/dist/exception-evidence.d.ts +25 -0
  40. package/dist/exception-evidence.d.ts.map +1 -0
  41. package/dist/exception-evidence.js +175 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/owned-json.d.ts +11 -0
  46. package/dist/owned-json.d.ts.map +1 -0
  47. package/dist/owned-json.js +85 -0
  48. package/dist/privacy-keys.d.ts +2 -0
  49. package/dist/privacy-keys.d.ts.map +1 -1
  50. package/dist/privacy-keys.js +10 -2
  51. package/dist/privacy-telemetry.js +7 -7
  52. package/dist/privacy-text.d.ts +2 -2
  53. package/dist/privacy-text.d.ts.map +1 -1
  54. package/dist/privacy-text.js +35 -33
  55. package/dist/redaction.d.ts.map +1 -1
  56. package/dist/redaction.js +9 -7
  57. package/dist/safe-error-fields.d.ts +4 -0
  58. package/dist/safe-error-fields.d.ts.map +1 -0
  59. package/dist/safe-error-fields.js +54 -0
  60. package/dist/types.d.ts +21 -6
  61. package/dist/types.d.ts.map +1 -1
  62. package/ios/DebugBundleReactNative.swift +58 -4
  63. package/ios/DebugBundleReactNativeBridge.mm +4 -0
  64. package/package.json +3 -2
  65. package/src/NativeDebugBundleReactNative.ts +1 -0
  66. package/src/before-send.ts +10 -174
  67. package/src/canonical-schema.ts +108 -0
  68. package/src/client-values.ts +196 -0
  69. package/src/client.ts +92 -213
  70. package/src/diagnostic-budget.ts +90 -0
  71. package/src/event-schema-data.ts +49 -0
  72. package/src/evidence-capabilities.ts +19 -0
  73. package/src/evidence-controller.ts +89 -0
  74. package/src/evidence-types.ts +14 -0
  75. package/src/evidence-wire.ts +100 -0
  76. package/src/exception-evidence.ts +112 -0
  77. package/src/index.ts +6 -0
  78. package/src/owned-json.ts +60 -0
  79. package/src/privacy-keys.ts +12 -2
  80. package/src/privacy-telemetry.ts +9 -9
  81. package/src/privacy-text.ts +32 -31
  82. package/src/redaction.ts +8 -7
  83. package/src/safe-error-fields.ts +46 -0
  84. package/src/types.ts +23 -5
@@ -0,0 +1,54 @@
1
+ /** Read primitive native error fields without calling application accessors. */
2
+ const nativePrototypes = new Set([Error.prototype, TypeError.prototype, RangeError.prototype, ReferenceError.prototype,
3
+ SyntaxError.prototype, URIError.prototype, EvalError.prototype, ...(typeof AggregateError === "function" ? [AggregateError.prototype] : [])]);
4
+ const brandDescriptor = Object.getOwnPropertyDescriptor(Error, "isError");
5
+ export const nativeBrand = brandDescriptor && "value" in brandDescriptor && typeof brandDescriptor.value === "function"
6
+ ? brandDescriptor.value : undefined;
7
+ let nativeStackGetter;
8
+ export function errorField(value, key) {
9
+ const own = Object.getOwnPropertyDescriptor(value, key);
10
+ if (own)
11
+ return "value" in own ? own.value : undefined;
12
+ let prototype = Object.getPrototypeOf(value);
13
+ for (let depth = 0; prototype && depth < 4; depth++) {
14
+ if (!nativePrototypes.has(prototype))
15
+ return undefined;
16
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
17
+ if (descriptor)
18
+ return "value" in descriptor ? descriptor.value : undefined;
19
+ prototype = Object.getPrototypeOf(prototype);
20
+ }
21
+ return undefined;
22
+ }
23
+ export function safeErrorStack(value) {
24
+ let prototype = Object.getPrototypeOf(value);
25
+ for (let depth = 0; prototype && depth < 4; depth++) {
26
+ if (!nativePrototypes.has(prototype) && prototype !== Object.prototype)
27
+ return undefined;
28
+ for (const key of ["name", "message"]) {
29
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
30
+ if (descriptor && (!("value" in descriptor) || typeof descriptor.value !== "string" || descriptor.value.length > 8192))
31
+ return undefined;
32
+ }
33
+ prototype = Object.getPrototypeOf(prototype);
34
+ }
35
+ for (const key of ["name", "message"]) {
36
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
37
+ if (descriptor && (!("value" in descriptor) || typeof descriptor.value !== "string" || descriptor.value.length > 8192))
38
+ return undefined;
39
+ }
40
+ const formatter = Object.getOwnPropertyDescriptor(Error, "prepareStackTrace");
41
+ const limit = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit");
42
+ if (formatter && (!("value" in formatter) || formatter.value !== undefined) ||
43
+ limit && (!("value" in limit) || typeof limit.value !== "number" || limit.value < 0 || limit.value > 64))
44
+ return undefined;
45
+ const descriptor = Object.getOwnPropertyDescriptor(value, "stack");
46
+ if (descriptor && "value" in descriptor)
47
+ return descriptor.value;
48
+ if (descriptor?.get && nativeBrand?.(value)) {
49
+ nativeStackGetter ??= Object.getOwnPropertyDescriptor(new Error(), "stack")?.get;
50
+ if (descriptor.get === nativeStackGetter)
51
+ return descriptor.get.call(value);
52
+ }
53
+ return undefined;
54
+ }
package/dist/types.d.ts CHANGED
@@ -17,6 +17,9 @@ export interface DebugBundleConfig {
17
17
  maxEventsPerSession?: number;
18
18
  maxBreadcrumbs?: number;
19
19
  captureErrors?: boolean;
20
+ captureExceptionEvidence?: boolean;
21
+ captureRecoveryObservations?: boolean;
22
+ evidenceCapabilities?: EvidenceCapabilities;
20
23
  captureUnhandledRejections?: boolean;
21
24
  captureScreens?: boolean;
22
25
  captureActions?: boolean;
@@ -54,6 +57,9 @@ export interface ResolvedDebugBundleConfig {
54
57
  maxEventsPerSession: number;
55
58
  maxBreadcrumbs: number;
56
59
  captureErrors: boolean;
60
+ captureExceptionEvidence: boolean;
61
+ captureRecoveryObservations: boolean;
62
+ evidenceCapabilities: EvidenceCapabilities | null;
57
63
  captureUnhandledRejections: boolean;
58
64
  captureScreens: boolean;
59
65
  captureActions: boolean;
@@ -94,26 +100,28 @@ export interface DebugBundleResponseInfo {
94
100
  headers?: Record<string, string>;
95
101
  }
96
102
  export interface DebugBundleCaptureContext {
103
+ failureRef?: string;
104
+ evidenceScope?: EvidenceScope;
97
105
  [key: string]: unknown;
98
106
  }
99
107
  export interface DebugBundleProbeOptions {
100
108
  heavy?: boolean;
101
109
  }
102
110
  export interface DebugBundleEventEnvelope {
103
- schema_version: "2026-03-01";
111
+ schema_version: string;
104
112
  event_id: string;
105
- event_type: "frontend_exception" | "frontend_breadcrumb" | "log_event" | "request_event" | "error_suppressed" | "probe_event";
106
- sdk_name: "@debugbundle/sdk-react-native";
113
+ event_type: "frontend_exception" | "backend_exception" | "diagnostic_context" | "deploy_metadata" | "frontend_breadcrumb" | "log_event" | "request_event" | "error_suppressed" | "probe_event";
114
+ sdk_name: string;
107
115
  sdk_version: string;
108
116
  service: {
109
117
  name: string;
110
118
  environment: string;
111
- runtime: "react-native";
112
- framework: "react-native";
119
+ runtime?: string | null;
120
+ framework?: string | null;
113
121
  };
114
122
  occurred_at: string;
115
123
  correlation?: {
116
- trace_id: string | null;
124
+ trace_id?: string | null;
117
125
  request_id?: string | null;
118
126
  session_id?: string | null;
119
127
  user_id_hash?: string | null;
@@ -124,6 +132,10 @@ export interface DebugBundleEventEnvelope {
124
132
  export interface DebugBundleClient {
125
133
  readonly status: DebugBundleStatus;
126
134
  readonly lastEventAt: number | null;
135
+ readonly evidenceCapabilities?: EvidenceCapabilities;
136
+ beginOperation?(): OperationScope;
137
+ withOperationContext?<T>(operation: (scope: OperationScope) => T): T;
138
+ captureRecovery?(observation: RecoveryObservation): void;
127
139
  init(config: DebugBundleConfig): void;
128
140
  captureException(error: unknown, context?: DebugBundleCaptureContext): void;
129
141
  captureError(error: unknown, context?: DebugBundleCaptureContext): void;
@@ -140,6 +152,7 @@ export interface DebugBundleClient {
140
152
  export interface NativeDebugBundleModule {
141
153
  initialize(config: NativeDebugBundleConfig): Promise<NativeDebugBundleState> | NativeDebugBundleState;
142
154
  enqueueCanonicalEvent?(event: DebugBundleEventEnvelope): Promise<boolean | void> | boolean | void;
155
+ enqueueCanonicalEventJson?(event: string): Promise<boolean | void> | boolean | void;
143
156
  /** Compatibility fallback implemented by native binaries released before the canonical bridge. */
144
157
  enqueueEvent(event: DebugBundleEventEnvelope): Promise<void> | void;
145
158
  captureProbe?(label: string, data: unknown, occurredAt: string): Promise<boolean> | boolean;
@@ -157,7 +170,9 @@ export interface NativeDebugBundleState {
157
170
  status: DebugBundleStatus;
158
171
  lastEventAt?: number | null;
159
172
  device?: Record<string, unknown> | null;
173
+ evidenceCapabilities?: unknown;
160
174
  nativeModuleAvailable?: boolean;
161
175
  degradedReason?: string | null;
162
176
  }
177
+ import type { EvidenceCapabilities, EvidenceScope, OperationScope, RecoveryObservation } from "./evidence-types.js";
163
178
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import DebugBundle
4
4
 
5
5
  @objc(DebugBundleReactNative)
6
6
  final class DebugBundleReactNative: NSObject {
7
- private var currentSDKVersion = "3.0.0"
7
+ private var currentSDKVersion = "3.1.0"
8
8
  private var currentService = "react-native-app"
9
9
  private var currentEnvironment = "production"
10
10
 
@@ -14,7 +14,7 @@ final class DebugBundleReactNative: NSObject {
14
14
  resolver resolve: @escaping RCTPromiseResolveBlock,
15
15
  rejecter reject: @escaping RCTPromiseRejectBlock
16
16
  ) {
17
- currentSDKVersion = config["sdkVersion"] as? String ?? "3.0.0"
17
+ currentSDKVersion = config["sdkVersion"] as? String ?? "3.1.0"
18
18
  currentService = config["service"] as? String ?? "react-native-app"
19
19
  currentEnvironment = config["environment"] as? String ?? "production"
20
20
  let sdkConfig = DebugBundleConfig(
@@ -48,7 +48,11 @@ final class DebugBundleReactNative: NSObject {
48
48
  redactFields: stringSet(config["redactFields"], fallback: DebugBundleConfig.defaultRedactFields),
49
49
  headerAllowlist: stringSet(config["headerAllowlist"], fallback: DebugBundleConfig.defaultHeaderAllowlist),
50
50
  sdkVersion: currentSDKVersion
51
- )
51
+ ).withEvidenceOptions(DebugBundleEvidenceOptions(
52
+ captureExceptionEvidence: config["captureExceptionEvidence"] as? Bool ?? true,
53
+ captureRecoveryObservations: config["captureRecoveryObservations"] as? Bool ?? false,
54
+ evidenceCapabilities: evidenceCapabilities(config["evidenceCapabilities"])
55
+ ))
52
56
  DebugBundle.initialize(sdkConfig, transport: DebugBundleHTTPTransport())
53
57
  resolve(state())
54
58
  }
@@ -62,6 +66,22 @@ final class DebugBundleReactNative: NSObject {
62
66
  resolve(DebugBundle.captureExternalEvent(dictionary(event)))
63
67
  }
64
68
 
69
+ @objc(enqueueCanonicalEventJson:resolve:reject:)
70
+ func enqueueCanonicalEventJson(
71
+ _ event: String,
72
+ resolver resolve: @escaping RCTPromiseResolveBlock,
73
+ rejecter reject: @escaping RCTPromiseRejectBlock
74
+ ) {
75
+ // TurboModule's NSDictionary conversion drops nested nulls before Swift sees
76
+ // them. Decode only bounded JSON bytes, then use the same native admission path.
77
+ guard let data = boundedEventJSON(event),
78
+ let object = try? JSONSerialization.jsonObject(with: data) as? NSDictionary else {
79
+ resolve(false)
80
+ return
81
+ }
82
+ resolve(DebugBundle.captureExternalEvent(dictionary(object)))
83
+ }
84
+
65
85
  @objc(enqueueEvent:resolve:reject:)
66
86
  func enqueueEvent(
67
87
  _ event: NSDictionary,
@@ -177,11 +197,45 @@ final class DebugBundleReactNative: NSObject {
177
197
  [
178
198
  "status": DebugBundle.status.rawValue,
179
199
  "lastEventAt": DebugBundle.lastEventAt.map { $0.timeIntervalSince1970 * 1000 } ?? NSNull(),
180
- "nativeModuleAvailable": true
200
+ "nativeModuleAvailable": true,
201
+ "evidenceCapabilities": ["version": DebugBundle.evidenceCapabilities.version, "features": DebugBundle.evidenceCapabilities.features]
181
202
  ]
182
203
  }
183
204
  }
184
205
 
206
+ private func evidenceCapabilities(_ value: Any?) -> DebugBundleEvidenceCapabilities? {
207
+ guard let value, !(value is NSNull) else { return nil }
208
+ guard let descriptor = value as? [String: Any], Set(descriptor.keys) == Set(["version", "features"]),
209
+ let features = descriptor["features"] as? [String], features.count <= 4,
210
+ features.allSatisfy({ $0.utf8.prefix(65).count <= 64 }),
211
+ let data = try? JSONSerialization.data(withJSONObject: descriptor), data.count <= 512,
212
+ let decoded = try? JSONDecoder().decode(DebugBundleEvidenceCapabilities.self, from: data) else {
213
+ return DebugBundleEvidenceCapabilities()
214
+ }
215
+ return decoded
216
+ }
217
+
218
+ private func boundedEventJSON(_ value: String) -> Data? {
219
+ guard value.utf8.prefix(65_537).count <= 65_536 else { return nil }
220
+ var depth = 0, quoted = false, escaped = false
221
+ for byte in value.utf8 {
222
+ if quoted {
223
+ if escaped { escaped = false }
224
+ else if byte == 92 { escaped = true }
225
+ else if byte == 34 { quoted = false }
226
+ } else if byte == 34 { quoted = true }
227
+ else if byte == 123 || byte == 91 {
228
+ depth += 1
229
+ if depth > 17 { return nil }
230
+ } else if byte == 125 || byte == 93 {
231
+ depth -= 1
232
+ if depth < 0 { return nil }
233
+ }
234
+ }
235
+ guard depth == 0, !quoted else { return nil }
236
+ return value.data(using: .utf8)
237
+ }
238
+
185
239
  private func dictionary(_ value: NSDictionary) -> [String: Any?] {
186
240
  var result: [String: Any?] = [:]
187
241
  for (rawKey, rawValue) in value {
@@ -24,6 +24,10 @@ RCT_EXTERN_METHOD(enqueueCanonicalEvent:(NSDictionary *)event
24
24
  resolve:(RCTPromiseResolveBlock)resolve
25
25
  reject:(RCTPromiseRejectBlock)reject)
26
26
 
27
+ RCT_EXTERN_METHOD(enqueueCanonicalEventJson:(NSString *)event
28
+ resolve:(RCTPromiseResolveBlock)resolve
29
+ reject:(RCTPromiseRejectBlock)reject)
30
+
27
31
  RCT_EXTERN_METHOD(flush:(RCTPromiseResolveBlock)resolve
28
32
  reject:(RCTPromiseRejectBlock)reject)
29
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugbundle/sdk-react-native",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "React Native SDK for DebugBundle mobile event capture.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -49,7 +49,8 @@
49
49
  "DebugBundleReactNative.podspec",
50
50
  "CHANGELOG.md",
51
51
  "README.md",
52
- "MIGRATION-3.0.md"
52
+ "MIGRATION-3.0.md",
53
+ "MIGRATION-3.1.md"
53
54
  ],
54
55
  "exports": {
55
56
  "./package.json": "./package.json",
@@ -5,6 +5,7 @@ import { TurboModuleRegistry } from "react-native";
5
5
  export interface Spec extends TurboModule {
6
6
  initialize(config: UnsafeObject): Promise<UnsafeObject>;
7
7
  enqueueCanonicalEvent?(event: UnsafeObject): Promise<boolean>;
8
+ enqueueCanonicalEventJson?(event: string): Promise<boolean>;
8
9
  enqueueEvent(event: UnsafeObject): Promise<void>;
9
10
  captureProbe?(label: string, data: UnsafeObject, occurredAt: string): Promise<boolean>;
10
11
  isProbeActive?(label: string): boolean;
@@ -1,178 +1,14 @@
1
1
  import type { DebugBundleBeforeSend, DebugBundleEventEnvelope } from "./types.js";
2
+ import { canonicalEvent } from "./evidence-wire.js";
3
+ import { EventValidationUnavailable, ownJSON } from "./owned-json.js";
2
4
 
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
- }
5
+ export function applyBeforeSend(event: DebugBundleEventEnvelope, hook: DebugBundleBeforeSend | null): DebugBundleEventEnvelope | null {
6
+ if (!hook) return event;
51
7
  try {
52
- const result = hook(cloneEvent(event));
53
- // Hooks have synchronous return contracts. Contain mistaken async failures
54
- // before treating their Promise as invalid and keeping the protected original.
55
- if (result instanceof Promise) {
56
- void result.catch(() => undefined);
57
- return event;
58
- }
59
- if (result === null) {
60
- return null;
61
- }
62
- return isValidEvent(result) ? result : event;
63
- } catch {
64
- return event;
65
- }
66
- }
67
-
68
- function cloneEvent(event: DebugBundleEventEnvelope): DebugBundleEventEnvelope {
69
- return JSON.parse(JSON.stringify(event)) as DebugBundleEventEnvelope;
70
- }
71
-
72
- function isValidEvent(event: unknown): event is DebugBundleEventEnvelope {
73
- if (!isRecord(event) || !Object.keys(event).every((key) => ROOT_KEYS.has(key))) {
74
- return false;
75
- }
76
- if (
77
- event.schema_version !== "2026-03-01" ||
78
- !UUID_PATTERN.test(stringValue(event.event_id)) ||
79
- !EVENT_TYPES.has(stringValue(event.event_type)) ||
80
- event.sdk_name !== "@debugbundle/sdk-react-native" ||
81
- !nonEmptyString(event.sdk_version) ||
82
- !isIsoTimestamp(event.occurred_at) ||
83
- !isRecord(event.service) ||
84
- !nonEmptyString(event.service.name) ||
85
- !nonEmptyString(event.service.environment) ||
86
- event.service.runtime !== "react-native" ||
87
- event.service.framework !== "react-native" ||
88
- !isRecord(event.payload)
89
- ) {
90
- return false;
91
- }
92
- const eventType = event.event_type as DebugBundleEventEnvelope["event_type"];
93
- return (
94
- REQUIRED_PAYLOAD_FIELDS[eventType].every((field) =>
95
- Object.prototype.hasOwnProperty.call(event.payload, field)
96
- ) &&
97
- Object.keys(event.payload).every((field) => ALLOWED_PAYLOAD_FIELDS[eventType].has(field)) &&
98
- hasValidPayloadShape(eventType, event.payload)
99
- );
100
- }
101
-
102
- function hasValidPayloadShape(
103
- eventType: DebugBundleEventEnvelope["event_type"],
104
- payload: Record<string, unknown>
105
- ): boolean {
106
- switch (eventType) {
107
- case "frontend_exception":
108
- return (
109
- hasNonEmptyStrings(payload, "name", "message", "stack") &&
110
- (!("breadcrumbs" in payload) || Array.isArray(payload.breadcrumbs)) &&
111
- optionalRecord(payload, "probe_data")
112
- );
113
- case "frontend_breadcrumb":
114
- return hasNonEmptyStrings(payload, "breadcrumb_type") && isRecord(payload.data);
115
- case "log_event":
116
- return hasNonEmptyStrings(payload, "level", "message") && isRecord(payload.attributes);
117
- case "request_event":
118
- return (
119
- hasNonEmptyStrings(payload, "method", "path") &&
120
- isRecord(payload.query) &&
121
- isRecord(payload.headers) &&
122
- isNonNegativeNumber(payload.response_status) &&
123
- isNonNegativeNumber(payload.duration_ms) &&
124
- optionalRecord(payload, "response_headers")
125
- );
126
- case "error_suppressed":
127
- return (
128
- hasNonEmptyStrings(payload, "fingerprint") &&
129
- isNonNegativeInteger(payload.suppressed_count) &&
130
- isPositiveInteger(payload.window_seconds) &&
131
- isIsoTimestamp(payload.first_seen) &&
132
- isIsoTimestamp(payload.last_seen)
133
- );
134
- case "probe_event":
135
- return (
136
- hasNonEmptyStrings(payload, "label", "probe_label_pattern") &&
137
- isRecord(payload.data) &&
138
- (payload.activation_id === null ||
139
- nonEmptyString(payload.activation_id) && UUID_PATTERN.test(payload.activation_id))
140
- );
141
- }
142
- }
143
-
144
- function hasNonEmptyStrings(payload: Record<string, unknown>, ...fields: string[]): boolean {
145
- return fields.every((field) => nonEmptyString(payload[field]));
146
- }
147
-
148
- function optionalRecord(payload: Record<string, unknown>, field: string): boolean {
149
- return !(field in payload) || isRecord(payload[field]);
150
- }
151
-
152
- function isNonNegativeNumber(value: unknown): boolean {
153
- return typeof value === "number" && Number.isFinite(value) && value >= 0;
154
- }
155
-
156
- function isNonNegativeInteger(value: unknown): boolean {
157
- return Number.isInteger(value) && (value as number) >= 0;
158
- }
159
-
160
- function isPositiveInteger(value: unknown): boolean {
161
- return Number.isInteger(value) && (value as number) > 0;
162
- }
163
-
164
- function isRecord(value: unknown): value is Record<string, unknown> {
165
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
166
- }
167
-
168
- function nonEmptyString(value: unknown): value is string {
169
- return typeof value === "string" && value.trim().length > 0;
170
- }
171
-
172
- function stringValue(value: unknown): string {
173
- return typeof value === "string" ? value : "";
174
- }
175
-
176
- function isIsoTimestamp(value: unknown): boolean {
177
- return nonEmptyString(value) && Number.isFinite(Date.parse(value));
8
+ const result = hook(JSON.parse(JSON.stringify(event)) as DebugBundleEventEnvelope);
9
+ if (result instanceof Promise) { void result.catch(() => undefined); return event; }
10
+ if (result === null) return null;
11
+ const owned = ownJSON(result);
12
+ return canonicalEvent(owned) ? owned as unknown as DebugBundleEventEnvelope : event;
13
+ } catch (error) { return error instanceof EventValidationUnavailable ? null : event; }
178
14
  }
@@ -0,0 +1,108 @@
1
+ import { eventSchemaData } from "./event-schema-data.js";
2
+ import { EventValidationUnavailable, jsonObject, type OwnedJSON } from "./owned-json.js";
3
+
4
+ const KEYWORDS = new Set(["$schema", "$id", "$comment", "$ref", "type", "anyOf", "const", "enum", "properties",
5
+ "required", "additionalProperties", "items", "minimum", "maximum", "exclusiveMinimum", "minLength", "maxLength",
6
+ "minItems", "maxItems", "format", "pattern"]);
7
+ const TYPES = new Set(["null", "boolean", "number", "integer", "string", "array", "object"]);
8
+ const choices = (value: OwnedJSON | undefined): OwnedJSON[] => value === undefined ? [] : Array.isArray(value) ? value : [value];
9
+
10
+ export function canonicalTimestamp(text: string): boolean {
11
+ if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(text)) return false;
12
+ const year = Number(text.slice(0, 4)), month = Number(text.slice(5, 7)), day = Number(text.slice(8, 10));
13
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
14
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
15
+ return month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1]! &&
16
+ Number(text.slice(11, 13)) < 24 && Number(text.slice(14, 16)) < 60 && Number(text.slice(17, 19)) < 60;
17
+ }
18
+
19
+ /** Small closed Draft-7 interpreter for the generated accepting contract. */
20
+ export class CanonicalSchema {
21
+ private refs = new Map<string, OwnedJSON>();
22
+ private patterns = new Map<string, RegExp>();
23
+ private root: OwnedJSON;
24
+ constructor(schema: unknown) { this.root = schema as OwnedJSON; this.compile(this.root); }
25
+
26
+ private resolve(ref: string): OwnedJSON {
27
+ const cached = this.refs.get(ref);
28
+ if (cached !== undefined) return cached;
29
+ if (!ref.startsWith("#/")) throw new EventValidationUnavailable();
30
+ let value: OwnedJSON | undefined = this.root;
31
+ for (const part of ref.slice(2).split("/")) {
32
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
33
+ value = Array.isArray(value) ? value[Number(key)] : jsonObject(value)?.[key];
34
+ if (value === undefined) throw new EventValidationUnavailable();
35
+ }
36
+ this.refs.set(ref, value);
37
+ return value;
38
+ }
39
+
40
+ private compile(schema: OwnedJSON): void {
41
+ if (typeof schema === "boolean") return;
42
+ const node = jsonObject(schema);
43
+ if (!node || Object.keys(node).some(key => !KEYWORDS.has(key)) ||
44
+ choices(node.type).some(value => typeof value !== "string" || !TYPES.has(value)) ||
45
+ node.format !== undefined && !["uuid", "date-time"].includes(String(node.format))) throw new EventValidationUnavailable();
46
+ if (typeof node.pattern === "string") this.patterns.set(node.pattern, new RegExp(node.pattern));
47
+ if (typeof node.$ref === "string" && !this.refs.has(node.$ref)) this.compile(this.resolve(node.$ref));
48
+ for (const child of Object.values(jsonObject(node.properties) ?? {})) this.compile(child);
49
+ for (const child of choices(node.anyOf)) this.compile(child);
50
+ for (const key of ["items", "additionalProperties"]) if (node[key] !== undefined) this.compile(node[key]);
51
+ }
52
+
53
+ accepts(value: OwnedJSON): boolean {
54
+ let steps = 100_000;
55
+ const check = (schema: OwnedJSON, value: OwnedJSON, depth: number): boolean => {
56
+ if (--steps < 0 || depth > 64) throw new EventValidationUnavailable();
57
+ if (typeof schema === "boolean") return schema;
58
+ const node = jsonObject(schema);
59
+ if (!node) throw new EventValidationUnavailable();
60
+ if (typeof node.$ref === "string") return check(this.resolve(node.$ref), value, depth + 1);
61
+ if (Array.isArray(node.anyOf) && !node.anyOf.some(child => check(child, value, depth + 1))) return false;
62
+ if (node.type !== undefined && !choices(node.type).some(type => this.matches(value, type))) return false;
63
+ if (node.const !== undefined && node.const !== value) return false;
64
+ if (Array.isArray(node.enum) && !node.enum.includes(value)) return false;
65
+ const object = jsonObject(value);
66
+ if (object) {
67
+ if (choices(node.required).some(key => typeof key !== "string" || !Object.hasOwn(object, key))) return false;
68
+ const fields = jsonObject(node.properties) ?? {};
69
+ for (const [key, child] of Object.entries(object)) {
70
+ if (!check((Object.hasOwn(fields, key) ? fields[key] : undefined) ?? node.additionalProperties ?? true, child, depth + 1)) return false;
71
+ }
72
+ }
73
+ if (Array.isArray(value)) {
74
+ if (typeof node.minItems === "number" && value.length < node.minItems ||
75
+ typeof node.maxItems === "number" && value.length > node.maxItems) return false;
76
+ if (node.items !== undefined && !value.every(child => check(node.items!, child, depth + 1))) return false;
77
+ }
78
+ if (typeof value === "string") {
79
+ if (typeof node.minLength === "number" && value.length < node.minLength ||
80
+ typeof node.maxLength === "number" && value.length > node.maxLength) return false;
81
+ if (typeof node.pattern === "string" && !this.patterns.get(node.pattern)!.test(value)) return false;
82
+ if (node.format === "uuid" && !/^[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}$/i.test(value)) return false;
83
+ if (node.format === "date-time" && !canonicalTimestamp(value)) return false;
84
+ }
85
+ if (typeof value === "number") {
86
+ if (typeof node.minimum === "number" && value < node.minimum || typeof node.maximum === "number" && value > node.maximum ||
87
+ typeof node.exclusiveMinimum === "number" && value <= node.exclusiveMinimum) return false;
88
+ }
89
+ return true;
90
+ };
91
+ return check(this.root, value, 0);
92
+ }
93
+ private matches(value: OwnedJSON, type: OwnedJSON): boolean {
94
+ if (type === "integer") return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
95
+ if (type === "number") return typeof value === "number" && Number.isFinite(value);
96
+ if (type === "null") return value === null;
97
+ if (type === "array") return Array.isArray(value);
98
+ if (type === "object") return jsonObject(value) !== null;
99
+ return typeof value === type;
100
+ }
101
+ }
102
+
103
+ let schema: CanonicalSchema | null = null;
104
+ try { schema = new CanonicalSchema(eventSchemaData); } catch { /* Unknown generated keywords fail closed. */ }
105
+ export function canonicalShape(value: OwnedJSON): boolean {
106
+ if (!schema) throw new EventValidationUnavailable();
107
+ return schema.accepts(value);
108
+ }