@logbrew/react-native 0.1.21 → 0.1.22
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/README.md +12 -3
- package/apple-native-diagnostics.d.ts +5 -0
- package/apple-native-diagnostics.js +36 -3
- package/index.cjs +1 -1
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +6 -3
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnosticEvidence.swift +2 -1
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +6 -2
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +20 -14
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +113 -79
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +5 -1
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +5 -5
- package/lifecycle.js +6 -120
- package/native-bridge.js +5 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -327,7 +327,11 @@ setLogBrewAppleNativeCrashContext({
|
|
|
327
327
|
schemaVersion: 1,
|
|
328
328
|
trace: { traceId: trace.traceId, spanId: trace.spanId, sampled: trace.sampled },
|
|
329
329
|
session: { id: "session_123" },
|
|
330
|
-
subject: { id: "subject_456", kind: "user" }
|
|
330
|
+
subject: { id: "subject_456", kind: "user" },
|
|
331
|
+
impact: {
|
|
332
|
+
failedAction: "checkout.submit",
|
|
333
|
+
userVisibleOutcome: "The order was not confirmed."
|
|
334
|
+
}
|
|
331
335
|
});
|
|
332
336
|
|
|
333
337
|
void replayLogBrewAppleNativeDiagnostics().catch((error) => {
|
|
@@ -342,10 +346,15 @@ allowed, but only one integration may install native fatal capture in a given
|
|
|
342
346
|
process. LogBrew cannot transfer or remove that ownership before process
|
|
343
347
|
restart.
|
|
344
348
|
|
|
345
|
-
Update this snapshot when the active trace, session, or
|
|
349
|
+
Update this snapshot when the active trace, session, subject, or failed action changes, and
|
|
346
350
|
clear it with `setLogBrewAppleNativeCrashContext(null)` on logout or session
|
|
347
351
|
end. The crash report keeps one atomic snapshot from the crashed process, so a
|
|
348
|
-
later app launch cannot replace it.
|
|
352
|
+
later app launch cannot replace it. `impact` is explicit app knowledge. Use a
|
|
353
|
+
stable action name and an optional safe user-visible result. Never include raw
|
|
354
|
+
input, request data, authentication values, or identity. Session and subject values must
|
|
355
|
+
be opaque app-owned identifiers made from ASCII letters, numbers, `_`, or `-`.
|
|
356
|
+
Resource context, tags, arbitrary fields, and values over the fixed 4 KiB
|
|
357
|
+
snapshot limit fail before storage.
|
|
349
358
|
|
|
350
359
|
The iOS `createLogBrewReactNativeClient()` entry also mirrors its validated
|
|
351
360
|
breadcrumb history into native crash capture. Install Apple diagnostics first,
|
|
@@ -36,6 +36,11 @@ export type LogBrewAppleNativeDiagnosticsReplayResult = Readonly<{
|
|
|
36
36
|
|
|
37
37
|
export type LogBrewAppleNativeCrashContext = {
|
|
38
38
|
schemaVersion: 1;
|
|
39
|
+
/** App-reported failed action and optional user-visible result. Never include raw input or identity. */
|
|
40
|
+
impact?: {
|
|
41
|
+
failedAction: string;
|
|
42
|
+
userVisibleOutcome?: string;
|
|
43
|
+
};
|
|
39
44
|
trace?: TelemetryTraceContext;
|
|
40
45
|
session?: TelemetrySessionContext;
|
|
41
46
|
subject?: TelemetrySubjectContext;
|
|
@@ -144,7 +144,11 @@ function normalizeConfiguration(configuration) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function normalizeCorrelationContext(context) {
|
|
147
|
-
const source = exactObject(
|
|
147
|
+
const source = exactObject(
|
|
148
|
+
context,
|
|
149
|
+
["impact", "schemaVersion", "session", "subject", "trace"],
|
|
150
|
+
"context"
|
|
151
|
+
);
|
|
148
152
|
if (source.schemaVersion !== 1) {
|
|
149
153
|
throw configurationError("context schemaVersion must be 1");
|
|
150
154
|
}
|
|
@@ -185,8 +189,26 @@ function normalizeCorrelationContext(context) {
|
|
|
185
189
|
}
|
|
186
190
|
output.subject = { id: opaqueCorrelationId(subject.id, "subject id"), kind: subject.kind };
|
|
187
191
|
}
|
|
192
|
+
if (source.impact !== undefined) {
|
|
193
|
+
const impact = exactObject(
|
|
194
|
+
source.impact,
|
|
195
|
+
["failedAction", "userVisibleOutcome"],
|
|
196
|
+
"context impact"
|
|
197
|
+
);
|
|
198
|
+
output.impact = {
|
|
199
|
+
failedAction: diagnosticText(impact.failedAction, 256, true, "failedAction")
|
|
200
|
+
};
|
|
201
|
+
if (impact.userVisibleOutcome !== undefined) {
|
|
202
|
+
output.impact.userVisibleOutcome = diagnosticText(
|
|
203
|
+
impact.userVisibleOutcome,
|
|
204
|
+
512,
|
|
205
|
+
false,
|
|
206
|
+
"userVisibleOutcome"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
188
210
|
if (Object.keys(output).length === 1) {
|
|
189
|
-
throw configurationError("context must include trace, session, or
|
|
211
|
+
throw configurationError("context must include trace, session, subject, or impact");
|
|
190
212
|
}
|
|
191
213
|
return output;
|
|
192
214
|
}
|
|
@@ -218,6 +240,17 @@ function opaqueCorrelationId(value, name) {
|
|
|
218
240
|
return value;
|
|
219
241
|
}
|
|
220
242
|
|
|
243
|
+
function diagnosticText(value, maximum, rejectLocationText, name) {
|
|
244
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
245
|
+
if (normalized.length === 0
|
|
246
|
+
|| Array.from(normalized).length > maximum
|
|
247
|
+
|| hasControlCharacter(normalized)
|
|
248
|
+
|| (rejectLocationText && /[?#]/u.test(normalized))) {
|
|
249
|
+
throw configurationError(`context impact ${name} is invalid`);
|
|
250
|
+
}
|
|
251
|
+
return normalized;
|
|
252
|
+
}
|
|
253
|
+
|
|
221
254
|
function requireApplePlatform() {
|
|
222
255
|
if (Platform?.OS !== "ios") {
|
|
223
256
|
throw new SdkError(
|
|
@@ -366,7 +399,7 @@ function exactDeliveryEndpoint(value) {
|
|
|
366
399
|
function hasControlCharacter(value) {
|
|
367
400
|
for (const scalar of value) {
|
|
368
401
|
const codePoint = scalar.codePointAt(0);
|
|
369
|
-
if (codePoint <= 0x1f || codePoint
|
|
402
|
+
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
370
403
|
return true;
|
|
371
404
|
}
|
|
372
405
|
}
|
package/index.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
} = require("./metadata.cjs");
|
|
17
17
|
|
|
18
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
19
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.22";
|
|
20
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
21
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
22
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
@@ -3,7 +3,7 @@ import Foundation
|
|
|
3
3
|
@objc(LBRNAppleNativeDiagnostics)
|
|
4
4
|
public final class LBRNAppleNativeDiagnostics: NSObject, @unchecked Sendable {
|
|
5
5
|
private static let shared = LBRNAppleNativeDiagnostics()
|
|
6
|
-
private static let sdkVersion = "0.1.
|
|
6
|
+
private static let sdkVersion = "0.1.22"
|
|
7
7
|
|
|
8
8
|
private let lock = NSLock()
|
|
9
9
|
private let replayQueue = DispatchQueue(label: "co.logbrew.react-native.apple-diagnostics-replay")
|
|
@@ -149,8 +149,11 @@ public final class LBRNAppleNativeDiagnostics: NSObject, @unchecked Sendable {
|
|
|
149
149
|
return failure("native_diagnostics_not_installed")
|
|
150
150
|
}
|
|
151
151
|
do {
|
|
152
|
-
let
|
|
153
|
-
try capture.
|
|
152
|
+
let snapshot = try rawContext.map { try NativeCrashCorrelation.validated($0) }
|
|
153
|
+
try capture.setDiagnosticContext(
|
|
154
|
+
context: snapshot?.correlationContext,
|
|
155
|
+
impact: snapshot?.impact,
|
|
156
|
+
)
|
|
154
157
|
return ["status": rawContext == nil ? "cleared" : "updated"]
|
|
155
158
|
} catch let error as NativeCrashError {
|
|
156
159
|
return failure(error.code.rawValue)
|
|
@@ -77,7 +77,8 @@ public struct IssueDiagnosticEvidence: Codable, Equatable, Sendable {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
@_spi(CrashReplay)
|
|
81
|
+
public func validateIssueDiagnosticEvidence(_ value: IssueDiagnosticEvidence) throws -> IssueDiagnosticEvidence {
|
|
81
82
|
let cause = try value.likelyRootCause.map {
|
|
82
83
|
try issueText($0, label: "issue evidence likelyRootCause", maximum: 1024)
|
|
83
84
|
}
|
|
@@ -23,6 +23,9 @@ struct CrashReportSanitizer {
|
|
|
23
23
|
let nativeStackFrames = NativeStackFrameSanitizer().frames(from: rawReport)
|
|
24
24
|
let artifactIdentity = try NativeArtifactIdentityValue.persistedIdentity(in: rawReport)
|
|
25
25
|
let correlation = NativeCrashCorrelation.captured(in: rawReport)
|
|
26
|
+
let correlationState = correlation.0?.correlationContext == nil && correlation.1 == .captured
|
|
27
|
+
? NativeCrashContextState.notCaptured
|
|
28
|
+
: correlation.1
|
|
26
29
|
let breadcrumbs = NativeCrashBreadcrumbs.captured(in: rawReport)
|
|
27
30
|
return NativeCrashRecord(
|
|
28
31
|
eventID: uuid.uuidString.lowercased(),
|
|
@@ -33,9 +36,10 @@ struct CrashReportSanitizer {
|
|
|
33
36
|
context: nativeCrashContext(
|
|
34
37
|
system: rawReport["system"] as? [String: Any],
|
|
35
38
|
artifactIdentity: artifactIdentity,
|
|
36
|
-
correlation: correlation.0,
|
|
39
|
+
correlation: correlation.0?.correlationContext,
|
|
37
40
|
),
|
|
38
|
-
correlationState:
|
|
41
|
+
correlationState: correlationState,
|
|
42
|
+
impact: correlation.0?.impact,
|
|
39
43
|
breadcrumbs: breadcrumbs.0?.breadcrumbs,
|
|
40
44
|
breadcrumbsTruncated: breadcrumbs.0?.truncated,
|
|
41
45
|
breadcrumbState: breadcrumbs.1,
|
|
@@ -109,17 +109,17 @@ public final class NativeCrashCapture: NSObject, @unchecked Sendable {
|
|
|
109
109
|
/// Replaces the crash-time trace, session, and opaque subject snapshot in one bounded write.
|
|
110
110
|
@nonobjc
|
|
111
111
|
public func setCorrelationContext(_ context: TelemetryContext?) throws {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
try setDiagnosticContext(context: context, impact: nil)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@nonobjc
|
|
116
|
+
func setDiagnosticContext(
|
|
117
|
+
context: TelemetryContext?,
|
|
118
|
+
impact: IssueImpactEvidence?,
|
|
119
|
+
) throws {
|
|
120
|
+
try setSnapshot(forKey: NativeCrashCorrelation.reportKey) {
|
|
121
|
+
try NativeCrashCorrelation.encoded(context: context, impact: impact)
|
|
117
122
|
}
|
|
118
|
-
try verifyStorageLocked()
|
|
119
|
-
try driver.setUserInfo(
|
|
120
|
-
NativeCrashCorrelation.encoded(context),
|
|
121
|
-
forKey: NativeCrashCorrelation.reportKey,
|
|
122
|
-
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/// Replaces the crash-time breadcrumb snapshot in one bounded write.
|
|
@@ -127,6 +127,15 @@ public final class NativeCrashCapture: NSObject, @unchecked Sendable {
|
|
|
127
127
|
public func setBreadcrumbs(
|
|
128
128
|
_ breadcrumbs: [IssueBreadcrumb]?,
|
|
129
129
|
truncated: Bool = false,
|
|
130
|
+
) throws {
|
|
131
|
+
try setSnapshot(forKey: NativeCrashBreadcrumbs.reportKey) {
|
|
132
|
+
try NativeCrashBreadcrumbs.encoded(breadcrumbs, truncated: truncated)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private func setSnapshot(
|
|
137
|
+
forKey key: String,
|
|
138
|
+
encoded: () throws -> String?,
|
|
130
139
|
) throws {
|
|
131
140
|
lock.lock()
|
|
132
141
|
defer { lock.unlock() }
|
|
@@ -135,10 +144,7 @@ public final class NativeCrashCapture: NSObject, @unchecked Sendable {
|
|
|
135
144
|
throw NativeCrashError(.notInstalled)
|
|
136
145
|
}
|
|
137
146
|
try verifyStorageLocked()
|
|
138
|
-
try driver.setUserInfo(
|
|
139
|
-
NativeCrashBreadcrumbs.encoded(breadcrumbs, truncated: truncated),
|
|
140
|
-
forKey: NativeCrashBreadcrumbs.reportKey,
|
|
141
|
-
)
|
|
147
|
+
try driver.setUserInfo(encoded(), forKey: key)
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
@objc(replayPendingReportsWithHandler:error:)
|
|
@@ -8,16 +8,60 @@ enum NativeCrashContextState: String {
|
|
|
8
8
|
case unavailable
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
struct NativeCrashDiagnosticSnapshot: Codable, Equatable {
|
|
12
|
+
let schemaVersion: Int
|
|
13
|
+
let trace: TelemetryTraceContext?
|
|
14
|
+
let session: TelemetrySessionContext?
|
|
15
|
+
let subject: TelemetrySubjectContext?
|
|
16
|
+
let impact: IssueImpactEvidence?
|
|
17
|
+
|
|
18
|
+
init(context: TelemetryContext?, impact: IssueImpactEvidence?) throws {
|
|
19
|
+
let context = try context.map(validateNativeCrashCorrelationContext)
|
|
20
|
+
let impact = try impact.map { value in
|
|
21
|
+
guard value.affectedUserSegment == nil,
|
|
22
|
+
let value = try validateIssueDiagnosticEvidence(
|
|
23
|
+
IssueDiagnosticEvidence(impact: value),
|
|
24
|
+
).impact,
|
|
25
|
+
value.failedAction != nil
|
|
26
|
+
else {
|
|
27
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
28
|
+
}
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
guard context != nil || impact != nil else {
|
|
32
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
33
|
+
}
|
|
34
|
+
schemaVersion = 1
|
|
35
|
+
trace = context?.trace
|
|
36
|
+
session = context?.session
|
|
37
|
+
subject = context?.subject
|
|
38
|
+
self.impact = impact
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var correlationContext: TelemetryContext? {
|
|
42
|
+
trace == nil && session == nil && subject == nil
|
|
43
|
+
? nil
|
|
44
|
+
: TelemetryContext(trace: trace, session: session, subject: subject)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
enum NativeCrashCorrelation {
|
|
12
50
|
static let reportKey = "logbrew_native_correlation"
|
|
13
|
-
private static let maximumBytes = 1024
|
|
51
|
+
private static let maximumBytes = 4 * 1024
|
|
14
52
|
|
|
15
|
-
static func encoded(
|
|
16
|
-
|
|
53
|
+
static func encoded(
|
|
54
|
+
context: TelemetryContext?,
|
|
55
|
+
impact: IssueImpactEvidence? = nil,
|
|
56
|
+
) throws -> String? {
|
|
57
|
+
guard context != nil || impact != nil else {
|
|
17
58
|
return nil
|
|
18
59
|
}
|
|
19
60
|
do {
|
|
20
|
-
let data = try
|
|
61
|
+
let data = try nativeCrashEncoded(NativeCrashDiagnosticSnapshot(
|
|
62
|
+
context: context,
|
|
63
|
+
impact: impact,
|
|
64
|
+
))
|
|
21
65
|
guard data.count <= maximumBytes, let value = String(data: data, encoding: .utf8) else {
|
|
22
66
|
throw NativeCrashError(.invalidConfiguration)
|
|
23
67
|
}
|
|
@@ -27,51 +71,23 @@ enum NativeCrashCorrelation {
|
|
|
27
71
|
}
|
|
28
72
|
}
|
|
29
73
|
|
|
30
|
-
static func captured(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
guard let value = value as? String,
|
|
35
|
-
let data = value.data(using: .utf8),
|
|
36
|
-
data.count <= maximumBytes,
|
|
37
|
-
let object = try? JSONSerialization.jsonObject(with: data),
|
|
38
|
-
let context = try? validated(object)
|
|
39
|
-
else {
|
|
40
|
-
return (nil, .unavailable)
|
|
41
|
-
}
|
|
42
|
-
return (context, .captured)
|
|
74
|
+
static func captured(
|
|
75
|
+
in rawReport: [String: Any],
|
|
76
|
+
) -> (NativeCrashDiagnosticSnapshot?, NativeCrashContextState) {
|
|
77
|
+
nativeCrashCaptured(in: rawReport, key: reportKey, maximumBytes: maximumBytes, validate: validated)
|
|
43
78
|
}
|
|
44
79
|
|
|
45
|
-
static func validated(_ object: Any) throws ->
|
|
46
|
-
|
|
47
|
-
guard
|
|
48
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
49
|
-
}
|
|
50
|
-
let data = try JSONSerialization.data(withJSONObject: object, options: [.sortedKeys])
|
|
51
|
-
guard data.count <= maximumBytes else {
|
|
80
|
+
static func validated(_ object: Any) throws -> NativeCrashDiagnosticSnapshot {
|
|
81
|
+
try nativeCrashValidated(object, maximumBytes: maximumBytes) { decoded in
|
|
82
|
+
guard decoded.schemaVersion == 1 else {
|
|
52
83
|
throw NativeCrashError(.invalidConfiguration)
|
|
53
84
|
}
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
return try NativeCrashDiagnosticSnapshot(
|
|
86
|
+
context: decoded.correlationContext,
|
|
87
|
+
impact: decoded.impact,
|
|
56
88
|
)
|
|
57
|
-
let normalized = try encoder().encode(context)
|
|
58
|
-
let normalizedObject = try JSONSerialization.jsonObject(with: normalized)
|
|
59
|
-
guard object as? NSDictionary == normalizedObject as? NSDictionary else {
|
|
60
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
61
|
-
}
|
|
62
|
-
return context
|
|
63
|
-
} catch let error as NativeCrashError {
|
|
64
|
-
throw error
|
|
65
|
-
} catch {
|
|
66
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
|
-
|
|
70
|
-
private static func encoder() -> JSONEncoder {
|
|
71
|
-
let encoder = JSONEncoder()
|
|
72
|
-
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
73
|
-
return encoder
|
|
74
|
-
}
|
|
75
91
|
}
|
|
76
92
|
|
|
77
93
|
struct NativeCrashBreadcrumbSnapshot: Codable, Equatable {
|
|
@@ -98,7 +114,7 @@ enum NativeCrashBreadcrumbs {
|
|
|
98
114
|
var breadcrumbs = try values.suffix(maximumIssueBreadcrumbs).map(validateIssueBreadcrumb)
|
|
99
115
|
var wasTruncated = truncated || breadcrumbs.count < values.count
|
|
100
116
|
while !breadcrumbs.isEmpty {
|
|
101
|
-
let data = try
|
|
117
|
+
let data = try nativeCrashEncoded(NativeCrashBreadcrumbSnapshot(
|
|
102
118
|
breadcrumbs: breadcrumbs,
|
|
103
119
|
truncated: wasTruncated,
|
|
104
120
|
))
|
|
@@ -119,56 +135,74 @@ enum NativeCrashBreadcrumbs {
|
|
|
119
135
|
static func captured(
|
|
120
136
|
in rawReport: [String: Any],
|
|
121
137
|
) -> (NativeCrashBreadcrumbSnapshot?, NativeCrashContextState) {
|
|
122
|
-
|
|
123
|
-
return (nil, .notCaptured)
|
|
124
|
-
}
|
|
125
|
-
guard let value = value as? String,
|
|
126
|
-
let data = value.data(using: .utf8),
|
|
127
|
-
data.count <= maximumBytes,
|
|
128
|
-
let object = try? JSONSerialization.jsonObject(with: data),
|
|
129
|
-
let snapshot = try? validated(object)
|
|
130
|
-
else {
|
|
131
|
-
return (nil, .unavailable)
|
|
132
|
-
}
|
|
133
|
-
return (snapshot, .captured)
|
|
138
|
+
nativeCrashCaptured(in: rawReport, key: reportKey, maximumBytes: maximumBytes, validate: validated)
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
static func validated(_ object: Any) throws -> NativeCrashBreadcrumbSnapshot {
|
|
137
|
-
|
|
138
|
-
guard JSONSerialization.isValidJSONObject(object) else {
|
|
139
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
140
|
-
}
|
|
141
|
-
let data = try JSONSerialization.data(withJSONObject: object, options: [.sortedKeys])
|
|
142
|
-
guard data.count <= maximumBytes else {
|
|
143
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
144
|
-
}
|
|
145
|
-
let decoded = try JSONDecoder().decode(NativeCrashBreadcrumbSnapshot.self, from: data)
|
|
142
|
+
try nativeCrashValidated(object, maximumBytes: maximumBytes) { decoded in
|
|
146
143
|
guard decoded.schemaVersion == 1,
|
|
147
144
|
!decoded.breadcrumbs.isEmpty,
|
|
148
145
|
decoded.breadcrumbs.count <= maximumIssueBreadcrumbs
|
|
149
146
|
else {
|
|
150
147
|
throw NativeCrashError(.invalidConfiguration)
|
|
151
148
|
}
|
|
152
|
-
|
|
149
|
+
return try NativeCrashBreadcrumbSnapshot(
|
|
153
150
|
breadcrumbs: decoded.breadcrumbs.map(validateIssueBreadcrumb),
|
|
154
151
|
truncated: decoded.truncated,
|
|
155
152
|
)
|
|
156
|
-
let normalized = try encoder().encode(snapshot)
|
|
157
|
-
let normalizedObject = try JSONSerialization.jsonObject(with: normalized)
|
|
158
|
-
guard object as? NSDictionary == normalizedObject as? NSDictionary else {
|
|
159
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
160
|
-
}
|
|
161
|
-
return snapshot
|
|
162
|
-
} catch let error as NativeCrashError {
|
|
163
|
-
throw error
|
|
164
|
-
} catch {
|
|
165
|
-
throw NativeCrashError(.invalidConfiguration)
|
|
166
153
|
}
|
|
167
154
|
}
|
|
155
|
+
}
|
|
168
156
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
157
|
+
private func nativeCrashCaptured<Value>(
|
|
158
|
+
in rawReport: [String: Any],
|
|
159
|
+
key: String,
|
|
160
|
+
maximumBytes: Int,
|
|
161
|
+
validate: (Any) throws -> Value,
|
|
162
|
+
) -> (Value?, NativeCrashContextState) {
|
|
163
|
+
guard let user = rawReport["user"] as? [String: Any], let rawValue = user[key] else {
|
|
164
|
+
return (nil, .notCaptured)
|
|
165
|
+
}
|
|
166
|
+
guard let value = rawValue as? String,
|
|
167
|
+
let data = value.data(using: .utf8),
|
|
168
|
+
data.count <= maximumBytes,
|
|
169
|
+
let object = try? JSONSerialization.jsonObject(with: data),
|
|
170
|
+
let snapshot = try? validate(object)
|
|
171
|
+
else {
|
|
172
|
+
return (nil, .unavailable)
|
|
173
|
+
}
|
|
174
|
+
return (snapshot, .captured)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func nativeCrashValidated<Value: Codable>(
|
|
178
|
+
_ object: Any,
|
|
179
|
+
maximumBytes: Int,
|
|
180
|
+
normalize: (Value) throws -> Value,
|
|
181
|
+
) throws -> Value {
|
|
182
|
+
do {
|
|
183
|
+
guard JSONSerialization.isValidJSONObject(object) else {
|
|
184
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
185
|
+
}
|
|
186
|
+
let source = try JSONSerialization.data(withJSONObject: object, options: [.sortedKeys])
|
|
187
|
+
guard source.count <= maximumBytes else {
|
|
188
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
189
|
+
}
|
|
190
|
+
let value = try normalize(JSONDecoder().decode(Value.self, from: source))
|
|
191
|
+
let normalized = try nativeCrashEncoded(value)
|
|
192
|
+
let normalizedObject = try JSONSerialization.jsonObject(with: normalized)
|
|
193
|
+
guard object as? NSDictionary == normalizedObject as? NSDictionary else {
|
|
194
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
195
|
+
}
|
|
196
|
+
return value
|
|
197
|
+
} catch let error as NativeCrashError {
|
|
198
|
+
throw error
|
|
199
|
+
} catch {
|
|
200
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
173
201
|
}
|
|
174
202
|
}
|
|
203
|
+
|
|
204
|
+
private func nativeCrashEncoded(_ value: some Encodable) throws -> Data {
|
|
205
|
+
let encoder = JSONEncoder()
|
|
206
|
+
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
207
|
+
return try encoder.encode(value)
|
|
208
|
+
}
|
|
@@ -165,6 +165,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
165
165
|
private let artifactIdentity: NativeArtifactIdentity?
|
|
166
166
|
private let context: TelemetryContext?
|
|
167
167
|
private let correlationState: NativeCrashContextState?
|
|
168
|
+
private let impact: IssueImpactEvidence?
|
|
168
169
|
private let breadcrumbs: [IssueBreadcrumb]?
|
|
169
170
|
private let breadcrumbsTruncated: Bool?
|
|
170
171
|
private let breadcrumbState: NativeCrashContextState?
|
|
@@ -182,6 +183,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
182
183
|
artifactIdentity: NativeArtifactIdentity?,
|
|
183
184
|
context: TelemetryContext?,
|
|
184
185
|
correlationState: NativeCrashContextState? = nil,
|
|
186
|
+
impact: IssueImpactEvidence? = nil,
|
|
185
187
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
186
188
|
breadcrumbsTruncated: Bool? = nil,
|
|
187
189
|
breadcrumbState: NativeCrashContextState? = nil,
|
|
@@ -198,6 +200,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
198
200
|
self.artifactIdentity = artifactIdentity
|
|
199
201
|
self.context = context
|
|
200
202
|
self.correlationState = correlationState
|
|
203
|
+
self.impact = impact
|
|
201
204
|
self.breadcrumbs = breadcrumbs
|
|
202
205
|
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
203
206
|
self.breadcrumbState = breadcrumbState
|
|
@@ -259,6 +262,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
259
262
|
exceptionChain: nativeCrashExceptionChain(for: issueException),
|
|
260
263
|
breadcrumbs: breadcrumbs,
|
|
261
264
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
265
|
+
evidence: impact.map { IssueDiagnosticEvidence(impact: $0) },
|
|
262
266
|
metadata: metadata,
|
|
263
267
|
context: context,
|
|
264
268
|
nativeStackFrames: nativeStackFrames,
|
|
@@ -301,7 +305,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
301
305
|
else {
|
|
302
306
|
return false
|
|
303
307
|
}
|
|
304
|
-
let legacyFields = ["exception", "exceptionChain", "breadcrumbs", "breadcrumbsTruncated", "context"]
|
|
308
|
+
let legacyFields = ["exception", "exceptionChain", "evidence", "breadcrumbs", "breadcrumbsTruncated", "context"]
|
|
305
309
|
for legacyField in legacyFields where actual[legacyField] == nil {
|
|
306
310
|
expected.removeValue(forKey: legacyField)
|
|
307
311
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStore.swift": "7c292dda68efc3de790daf44733b25e4392226793343c21ba5df4f51fc1b402b",
|
|
11
11
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStoreRecovery.swift": "79fee27975b6571954bf0b3692a50e60bbbcf53b5ab47f653a474db5c56d68ba",
|
|
12
12
|
"swift/logbrew-swift/Sources/LogBrew/EventEncoding.swift": "b88b6b2d17f7d0cee9ac1a1f34ef35b192cd33d213ef3c9e1c2d21cbcc304a97",
|
|
13
|
-
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnosticEvidence.swift": "
|
|
13
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnosticEvidence.swift": "1042b5a6283cf440529572844e5ad1f3078f4ba715133932ea305e075614a53c",
|
|
14
14
|
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "bbdfe6169961ff27b8f2cee63c7dcdeacf2e5cee6a3ca17f6a26b933f0adfc39",
|
|
15
15
|
"swift/logbrew-swift/Sources/LogBrew/IssueExceptionChain.swift": "10f81cbce95b47da0a44ef9e515b150e60d71dfcc8b04be24b037b1222a373a5",
|
|
16
16
|
"swift/logbrew-swift/Sources/LogBrew/LifecycleTrace.swift": "738cd3f129fac821a892404d1d42ee92bd806b6583d3dbb5379e88b14ab23c16",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTracer.swift": "0cdf08f92d6111c0dc2883b87837b81f69b5c2f9a8e5501780a114c23a741af7",
|
|
30
30
|
"swift/logbrew-swift/Sources/LogBrew/Validation.swift": "07ff5c2daeb829b1df4f50edb0b79727f9a81b1c9b5cde5f995f5ad6ce660aa7",
|
|
31
31
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashEngine.swift": "a94dfce653e3e7f381be42b6d2c4540f0be77e83777ecc53e06682cd2e569c5a",
|
|
32
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "
|
|
32
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "a56a2f6c6c7f1db900804a37df51aebba7b83dce14fa7816de5753c33409e4a5",
|
|
33
33
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashStorageDirectory.swift": "7cd566703cbf704dc99155451ee93dcace12c35c805abc09a6dcf23975cf43f9",
|
|
34
34
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeArtifactIdentity.swift": "3d785ad717dcf7302451531c18b5e1183983270a6cd78dd543a407e3facc6ae0",
|
|
35
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "
|
|
36
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCorrelation.swift": "
|
|
35
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "0b773a63987b6c694b044e56c0f81878f4f5e71eae3fdd126744cba3cf15aac1",
|
|
36
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCorrelation.swift": "e5a7c25448c83ca2ae149c650ecfc72c5193741291a6ff9623eabadad8a9c440",
|
|
37
37
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "ce191e46298d91d87b379528d610caa88b924b15a8dd912b4a164b51eb172a7a",
|
|
38
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "
|
|
38
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "70aa9700efd26d03b9656f1df96876de0b9eb769c03512ec53f667696773954f",
|
|
39
39
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashReplay.swift": "3eb3d93b32cf14de416f56fa30d6d65e6f268b5ec9d1a67081d7525c4efd4f80",
|
|
40
40
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "5a8150083f16d6b495c9c63434809dc5f11f13ebb23211f57151bad62040781b",
|
|
41
41
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdog.swift": "37b25904cd6576e20c40500be2c153156403b57ad7b4186a1b6b35b414145ecb",
|
package/lifecycle.js
CHANGED
|
@@ -1,121 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
createReactNativeSpanAttributes,
|
|
4
|
-
createReactNativeTraceContext,
|
|
5
|
-
getActiveLogBrewTrace,
|
|
6
|
-
getReactNativeContext
|
|
7
|
-
} from "./index.js";
|
|
1
|
+
import runtime from "./lifecycle.cjs";
|
|
8
2
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} =
|
|
14
|
-
const safeFromState = normalizeLifecycleState(fromState);
|
|
15
|
-
const safeToState = normalizeLifecycleState(toState ?? state);
|
|
16
|
-
const transition = [safeFromState, safeToState].filter(Boolean).join("->");
|
|
17
|
-
const spanName = name ?? `app_state:${transition || safeToState || safeFromState || "change"}`;
|
|
18
|
-
const activeTrace = trace ?? getActiveLogBrewTrace() ?? createReactNativeTraceContext();
|
|
19
|
-
return {
|
|
20
|
-
id: id ?? idFactory({ fromState: safeFromState, screen, toState: safeToState }),
|
|
21
|
-
timestamp: timestamp ?? now(),
|
|
22
|
-
attributes: createReactNativeSpanAttributes({
|
|
23
|
-
name: spanName,
|
|
24
|
-
status,
|
|
25
|
-
durationMs,
|
|
26
|
-
trace: activeTrace,
|
|
27
|
-
metadata: {
|
|
28
|
-
...getReactNativeContext({ platform, appState }),
|
|
29
|
-
source: "react-native.lifecycle",
|
|
30
|
-
appState: safeToState,
|
|
31
|
-
durationMs,
|
|
32
|
-
fromAppState: safeFromState,
|
|
33
|
-
screen,
|
|
34
|
-
sessionId,
|
|
35
|
-
toAppState: safeToState,
|
|
36
|
-
...metadata
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function captureReactNativeLifecycleSpan(client, input = {}) {
|
|
43
|
-
requireClient(client);
|
|
44
|
-
const event = createReactNativeLifecycleSpanEvent(input);
|
|
45
|
-
client.span(event.id, event.timestamp, event.attributes);
|
|
46
|
-
return event;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createAppStateLifecycleSpanListener(client, appState, {
|
|
50
|
-
captureInitialState = false, metadata = {}, now = () => new Date().toISOString(), nowMs = () => Date.now(),
|
|
51
|
-
onError, platform, screen, sessionId, trace
|
|
52
|
-
} = {}) {
|
|
53
|
-
requireClient(client);
|
|
54
|
-
if (!appState || typeof appState.addEventListener !== "function") {
|
|
55
|
-
throw new SdkError("configuration_error", "createAppStateLifecycleSpanListener requires AppState.addEventListener");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let previousState = normalizeLifecycleState(appState.currentState);
|
|
59
|
-
let previousChangedAtMs = nowMs();
|
|
60
|
-
if (captureInitialState && previousState !== undefined) {
|
|
61
|
-
captureReactNativeLifecycleSpan(client, {
|
|
62
|
-
appState, metadata, now, platform, screen, sessionId, toState: previousState, trace
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const subscription = appState.addEventListener("change", (nextState) => {
|
|
67
|
-
try {
|
|
68
|
-
const safeNextState = normalizeLifecycleState(nextState);
|
|
69
|
-
if (safeNextState === undefined) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const changedAtMs = nowMs();
|
|
73
|
-
const durationMs = previousState === undefined ? undefined : Math.max(0, changedAtMs - previousChangedAtMs);
|
|
74
|
-
captureReactNativeLifecycleSpan(client, {
|
|
75
|
-
appState, durationMs, fromState: previousState, metadata, now, platform, screen, sessionId,
|
|
76
|
-
timestamp: now(), toState: safeNextState, trace
|
|
77
|
-
});
|
|
78
|
-
previousState = safeNextState;
|
|
79
|
-
previousChangedAtMs = changedAtMs;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (typeof onError === "function") {
|
|
82
|
-
onError(error);
|
|
83
|
-
} else {
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return subscriptionRemover(subscription);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function requireClient(client) {
|
|
93
|
-
if (!client) {
|
|
94
|
-
throw new SdkError("configuration_error", "LogBrew React Native lifecycle helpers require a client");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function defaultLifecycleSpanEventId({ fromState, screen, toState }) {
|
|
99
|
-
return `evt_native_lifecycle_${slugify([screen, fromState, toState].filter(Boolean).join("_") || "app_state")}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function normalizeLifecycleState(state) {
|
|
103
|
-
return typeof state === "string" && state.trim() !== "" ? state.trim() : undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function subscriptionRemover(subscription) {
|
|
107
|
-
if (typeof subscription === "function") {
|
|
108
|
-
return subscription;
|
|
109
|
-
}
|
|
110
|
-
if (subscription && typeof subscription.remove === "function") {
|
|
111
|
-
return () => subscription.remove();
|
|
112
|
-
}
|
|
113
|
-
return () => {};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function slugify(value) {
|
|
117
|
-
return String(value)
|
|
118
|
-
.toLowerCase()
|
|
119
|
-
.replace(/[^a-z0-9]+/g, "_")
|
|
120
|
-
.replace(/^_+|_+$/g, "") || "event";
|
|
121
|
-
}
|
|
3
|
+
export const {
|
|
4
|
+
captureReactNativeLifecycleSpan,
|
|
5
|
+
createAppStateLifecycleSpanListener,
|
|
6
|
+
createReactNativeLifecycleSpanEvent
|
|
7
|
+
} = runtime;
|
package/native-bridge.js
CHANGED
|
@@ -1,125 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
getActiveLogBrewTrace,
|
|
4
|
-
getReactNativeTraceMetadata
|
|
5
|
-
} from "./index.js";
|
|
1
|
+
import runtime from "./native-bridge.cjs";
|
|
6
2
|
|
|
7
|
-
const
|
|
8
|
-
const RESERVED_TRACE_METADATA_KEYS = new Set([
|
|
9
|
-
"parentSpanId",
|
|
10
|
-
"spanId",
|
|
11
|
-
"traceFlags",
|
|
12
|
-
"traceId",
|
|
13
|
-
"traceSampled",
|
|
14
|
-
"traceparent"
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
export function createLogBrewNativeBridgeScope({
|
|
18
|
-
logger,
|
|
19
|
-
metadata = {},
|
|
20
|
-
screen,
|
|
21
|
-
sessionId,
|
|
22
|
-
source = DEFAULT_SCOPE_SOURCE,
|
|
23
|
-
trace = getActiveLogBrewTrace()
|
|
24
|
-
} = {}) {
|
|
25
|
-
const traceMetadata = getReactNativeTraceMetadata(trace);
|
|
26
|
-
return {
|
|
27
|
-
trace: Object.keys(traceMetadata).length === 0 ? undefined : {
|
|
28
|
-
parentSpanId: traceMetadata.parentSpanId,
|
|
29
|
-
spanId: traceMetadata.spanId,
|
|
30
|
-
traceFlags: traceMetadata.traceFlags,
|
|
31
|
-
traceId: traceMetadata.traceId,
|
|
32
|
-
traceSampled: traceMetadata.traceSampled
|
|
33
|
-
},
|
|
34
|
-
metadata: compactMetadata({
|
|
35
|
-
...metadata,
|
|
36
|
-
logger,
|
|
37
|
-
screen,
|
|
38
|
-
sessionId,
|
|
39
|
-
source
|
|
40
|
-
})
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function syncLogBrewNativeBridgeScope(nativeBridge, options = {}) {
|
|
45
|
-
const payload = createLogBrewNativeBridgeScope(options);
|
|
46
|
-
bridgeSync(nativeBridge)(payload);
|
|
47
|
-
return payload;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function clearLogBrewNativeBridgeScope(nativeBridge) {
|
|
51
|
-
bridgeClear(nativeBridge)();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function withLogBrewNativeBridgeScope(nativeBridge, options, callback) {
|
|
55
|
-
const resolvedOptions = typeof options === "function" ? {} : options ?? {};
|
|
56
|
-
const resolvedCallback = typeof options === "function" ? options : callback;
|
|
57
|
-
if (typeof resolvedCallback !== "function") {
|
|
58
|
-
throw new SdkError("configuration_error", "withLogBrewNativeBridgeScope requires a callback");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const payload = syncLogBrewNativeBridgeScope(nativeBridge, resolvedOptions);
|
|
62
|
-
try {
|
|
63
|
-
const result = resolvedCallback(payload);
|
|
64
|
-
if (result && typeof result.then === "function") {
|
|
65
|
-
return Promise.resolve(result).finally(() => clearLogBrewNativeBridgeScope(nativeBridge));
|
|
66
|
-
}
|
|
67
|
-
clearLogBrewNativeBridgeScope(nativeBridge);
|
|
68
|
-
return result;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
clearLogBrewNativeBridgeScope(nativeBridge);
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function bridgeSync(nativeBridge) {
|
|
76
|
-
if (typeof nativeBridge === "function") {
|
|
77
|
-
return nativeBridge;
|
|
78
|
-
}
|
|
79
|
-
if (nativeBridge && typeof nativeBridge.setLogBrewScope === "function") {
|
|
80
|
-
return nativeBridge.setLogBrewScope.bind(nativeBridge);
|
|
81
|
-
}
|
|
82
|
-
if (nativeBridge && typeof nativeBridge.syncLogBrewScope === "function") {
|
|
83
|
-
return nativeBridge.syncLogBrewScope.bind(nativeBridge);
|
|
84
|
-
}
|
|
85
|
-
throw new SdkError(
|
|
86
|
-
"configuration_error",
|
|
87
|
-
"LogBrew native bridge scope sync requires a function, setLogBrewScope, or syncLogBrewScope"
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function bridgeClear(nativeBridge) {
|
|
92
|
-
if (nativeBridge && typeof nativeBridge.clearLogBrewScope === "function") {
|
|
93
|
-
return nativeBridge.clearLogBrewScope.bind(nativeBridge);
|
|
94
|
-
}
|
|
95
|
-
if (nativeBridge && typeof nativeBridge.clearLogBrewTraceContext === "function") {
|
|
96
|
-
return nativeBridge.clearLogBrewTraceContext.bind(nativeBridge);
|
|
97
|
-
}
|
|
98
|
-
if (typeof nativeBridge === "function") {
|
|
99
|
-
return () => nativeBridge(undefined);
|
|
100
|
-
}
|
|
101
|
-
return () => {};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function compactMetadata(metadata) {
|
|
105
|
-
const compacted = {};
|
|
106
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
107
|
-
if (value === undefined) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (RESERVED_TRACE_METADATA_KEYS.has(key)) {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
114
|
-
compacted[key] = value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return compacted;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export default {
|
|
3
|
+
export const {
|
|
121
4
|
clearLogBrewNativeBridgeScope,
|
|
122
5
|
createLogBrewNativeBridgeScope,
|
|
123
6
|
syncLogBrewNativeBridgeScope,
|
|
124
7
|
withLogBrewNativeBridgeScope
|
|
125
|
-
};
|
|
8
|
+
} = runtime;
|
|
9
|
+
|
|
10
|
+
export default runtime.default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "React Native offline delivery, Apple native diagnostics, screen, error, trace, action, and network timeline helpers for LogBrew.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|