@logbrew/react-native 0.1.17 → 0.1.18

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.
@@ -33,7 +33,7 @@ Pod::Spec.new do |spec|
33
33
  "ios/GeneratedAppleDiagnostics/**/*.swift"
34
34
  ]
35
35
  diagnostics.dependency "#{spec.name}/Core"
36
- diagnostics.dependency "KSCrash/Recording", "2.5.1"
36
+ diagnostics.dependency "KSCrash/Recording", "2.6.0"
37
37
  diagnostics.pod_target_xcconfig = {
38
38
  "DEFINES_MODULE" => "YES",
39
39
  "SWIFT_VERSION" => "5.0"
package/README.md CHANGED
@@ -335,7 +335,8 @@ the configured project does not replay an earlier project's records with the
335
335
  new project's key. The raw local KSCrash report can still contain sensitive
336
336
  process details, so apply the app's consent and retention policy to it. Hosted
337
337
  replay uploads only the bounded crash or hang issue described by the Swift
338
- `LogBrewCrash` contract. Status and error results expose fixed codes and bounded
338
+ `LogBrewCrash` contract, including its capture-time service, deployment, OS,
339
+ device-class, and application-version context. Status and error results expose fixed codes and bounded
339
340
  counters, not keys, paths, raw reports, or native error text. A custom endpoint
340
341
  must be a plain HTTPS path without embedded authentication, a query, or a fragment.
341
342
 
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.17";
19
+ const DEFAULT_SDK_VERSION = "0.1.18";
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;
package/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from "./metadata.js";
17
17
 
18
18
  const DEFAULT_SDK_NAME = "logbrew-react-native";
19
- const DEFAULT_SDK_VERSION = "0.1.17";
19
+ const DEFAULT_SDK_VERSION = "0.1.18";
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.17"
6
+ private static let sdkVersion = "0.1.18"
7
7
 
8
8
  private let lock = NSLock()
9
9
  private let replayQueue = DispatchQueue(label: "co.logbrew.react-native.apple-diagnostics-replay")
@@ -28,6 +28,10 @@ struct CrashReportSanitizer {
28
28
  mechanism: mechanism(error?["type"] as? String),
29
29
  nativeStackFrames: nativeStackFrames,
30
30
  artifactIdentity: artifactIdentity,
31
+ context: nativeCrashContext(
32
+ system: rawReport["system"] as? [String: Any],
33
+ artifactIdentity: artifactIdentity,
34
+ ),
31
35
  hangState: nil,
32
36
  hangDurationMs: nil,
33
37
  source: .engine(reportID: reportID),
@@ -70,6 +74,55 @@ struct CrashReportSanitizer {
70
74
  }
71
75
  }
72
76
 
77
+ func nativeCrashContext(
78
+ system: [String: Any]?,
79
+ artifactIdentity: NativeArtifactIdentity?,
80
+ ) -> TelemetryContext? {
81
+ let value = { (key: String) in crashContextValue(system?[key]) }
82
+ let operatingSystem = value("system_name").map {
83
+ TelemetryOperatingSystem(name: $0, version: value("system_version"), build: value("os_version"))
84
+ }
85
+ let family = value("model")
86
+ let model = value("machine")
87
+ let architecture = value("cpu_arch")
88
+ let device = family == nil && model == nil && architecture == nil
89
+ ? nil
90
+ : TelemetryDevice(family: family, model: model, architecture: architecture)
91
+ let applicationName = value("CFBundleName")
92
+ let applicationVersion = value("CFBundleShortVersionString")
93
+ let applicationBuild = value("CFBundleVersion")
94
+ let application = applicationName == nil && applicationVersion == nil && applicationBuild == nil
95
+ ? nil
96
+ : TelemetryApplication(name: applicationName, version: applicationVersion, build: applicationBuild)
97
+ guard artifactIdentity != nil || operatingSystem != nil || device != nil || application != nil else {
98
+ return nil
99
+ }
100
+ return TelemetryContext(resource: TelemetryResource(
101
+ service: artifactIdentity.map { TelemetryNamedVersion(name: $0.service) },
102
+ deployment: artifactIdentity.map {
103
+ TelemetryDeployment(environment: $0.environment, release: $0.release)
104
+ },
105
+ operatingSystem: operatingSystem,
106
+ device: device,
107
+ application: application,
108
+ ))
109
+ }
110
+
111
+ private func crashContextValue(_ rawValue: Any?) -> String? {
112
+ guard let value = rawValue as? String else {
113
+ return nil
114
+ }
115
+ let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
116
+ guard (1 ... 256).contains(normalized.utf8.count),
117
+ !normalized.unicodeScalars.contains(where: CharacterSet.controlCharacters.contains),
118
+ !normalized.contains("/"),
119
+ !normalized.contains("\\")
120
+ else {
121
+ return nil
122
+ }
123
+ return normalized
124
+ }
125
+
73
126
  struct NativeCrashTimestamp: Comparable {
74
127
  let normalized: String
75
128
  private let wholeSecond: String
@@ -1,6 +1,5 @@
1
1
  // Generated by scripts/sync-apple-native-sources.mjs.
2
2
  // Edit the canonical Swift source, then regenerate this package boundary.
3
- import Foundation
4
3
 
5
4
  func nativeCrashExceptionChain(for exception: IssueException) -> IssueExceptionChain {
6
5
  IssueExceptionChain(entries: [
@@ -14,31 +13,3 @@ func nativeCrashExceptionChain(for exception: IssueException) -> IssueExceptionC
14
13
  ),
15
14
  ])
16
15
  }
17
-
18
- func nativeCrashExceptionChainMatches(
19
- _ value: Any?,
20
- expected exception: IssueException,
21
- ) -> Bool {
22
- guard let value else {
23
- // Preserve retry identity for an event queued by an older SDK before chains existed.
24
- return true
25
- }
26
- guard let chain = value as? [String: Any] else {
27
- return false
28
- }
29
- guard let mechanism = exception.mechanism else {
30
- return false
31
- }
32
- let expected = [
33
- "entries": [[
34
- "id": 0,
35
- "relationship": "reported",
36
- "type": exception.type,
37
- "messageState": "not_captured",
38
- "mechanism": ["type": mechanism.type, "handled": mechanism.handled],
39
- "stackFramesState": "not_captured",
40
- ]],
41
- "truncated": false,
42
- ] as NSDictionary
43
- return chain as NSDictionary == expected
44
- }
@@ -163,6 +163,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
163
163
 
164
164
  private let nativeStackFrames: [NativeStackFrame]?
165
165
  private let artifactIdentity: NativeArtifactIdentity?
166
+ private let context: TelemetryContext?
166
167
  private let hangState: NativeHangIncidentState?
167
168
  private let hangDurationMs: Double?
168
169
  let source: NativeCrashRecordSource
@@ -175,6 +176,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
175
176
  mechanism: NativeCrashMechanism,
176
177
  nativeStackFrames: [NativeStackFrame]?,
177
178
  artifactIdentity: NativeArtifactIdentity?,
179
+ context: TelemetryContext?,
178
180
  hangState: NativeHangIncidentState?,
179
181
  hangDurationMs: Double? = nil,
180
182
  source: NativeCrashRecordSource,
@@ -186,6 +188,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
186
188
  self.mechanism = mechanism
187
189
  self.nativeStackFrames = nativeStackFrames
188
190
  self.artifactIdentity = artifactIdentity
191
+ self.context = context
189
192
  self.hangState = hangState
190
193
  self.hangDurationMs = hangDurationMs
191
194
  self.source = source
@@ -237,6 +240,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
237
240
  exception: issueException,
238
241
  exceptionChain: nativeCrashExceptionChain(for: issueException),
239
242
  metadata: metadata,
243
+ context: context,
240
244
  nativeStackFrames: nativeStackFrames,
241
245
  )
242
246
  }
@@ -254,57 +258,13 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
254
258
  guard event["type"] as? String == "issue",
255
259
  event["timestamp"] as? String == timestamp,
256
260
  let attributes = event["attributes"] as? [String: Any],
257
- attributes["title"] as? String == issueAttributes.title,
258
- attributes["level"] as? String == issueAttributes.level.canonicalValue,
259
- attributes["message"] == nil,
260
- exceptionMatches(attributes["exception"]),
261
- nativeCrashExceptionChainMatches(
262
- attributes["exceptionChain"],
263
- expected: issueException,
264
- ),
265
- attributes["stackFrames"] == nil,
266
- attributes["breadcrumbs"] == nil,
267
- attributes["breadcrumbsTruncated"] == nil,
268
- attributes["context"] == nil,
269
- let metadata = attributes["metadata"] as? [String: Any],
270
- metadata as NSDictionary == expectedMetadata,
271
- nativeStackFramesMatch(attributes["nativeStackFrames"])
261
+ attributesMatch(attributes)
272
262
  else {
273
263
  return .collision
274
264
  }
275
265
  return .matching
276
266
  }
277
267
 
278
- private var expectedMetadata: NSDictionary {
279
- var metadata: [String: Any] = [
280
- "crash.mechanism": mechanism.name,
281
- "crash.replayed": true,
282
- ]
283
- if let artifactIdentity {
284
- metadata["projectId"] = artifactIdentity.projectId
285
- metadata["release"] = artifactIdentity.release
286
- metadata["environment"] = artifactIdentity.environment
287
- metadata["service"] = artifactIdentity.service
288
- }
289
- if let hangState {
290
- metadata["crash.handled"] = hangState == .recovered
291
- }
292
- if let hangDurationMs {
293
- metadata["durationMs"] = hangDurationMs
294
- }
295
- return metadata as NSDictionary
296
- }
297
-
298
- private var expectedException: NSDictionary {
299
- [
300
- "type": hangState == nil ? "AppleNativeCrash" : "AppleNativeHang",
301
- "mechanism": [
302
- "type": mechanism.name,
303
- "handled": hangState == .recovered,
304
- ],
305
- ] as NSDictionary
306
- }
307
-
308
268
  private var issueException: IssueException {
309
269
  IssueException(
310
270
  type: hangState == nil ? "AppleNativeCrash" : "AppleNativeHang",
@@ -315,31 +275,16 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
315
275
  )
316
276
  }
317
277
 
318
- private func exceptionMatches(_ value: Any?) -> Bool {
319
- guard let value else {
320
- // Preserve exact retry identity for an event queued by an older SDK before typed exceptions existed.
321
- return true
322
- }
323
- guard let exception = value as? [String: Any] else {
324
- return false
325
- }
326
- return exception as NSDictionary == expectedException
327
- }
328
-
329
- private func nativeStackFramesMatch(_ value: Any?) -> Bool {
330
- guard let nativeStackFrames else {
331
- return value == nil
332
- }
333
- guard let frames = value as? [[String: Any]], frames.count == nativeStackFrames.count else {
278
+ private func attributesMatch(_ actual: [String: Any]) -> Bool {
279
+ guard let encoded = try? JSONEncoder().encode(issueAttributes),
280
+ var expected = try? JSONSerialization.jsonObject(with: encoded) as? [String: Any]
281
+ else {
334
282
  return false
335
283
  }
336
- return zip(frames, nativeStackFrames).allSatisfy { frame, expected in
337
- frame as NSDictionary == [
338
- "imageUuid": expected.imageUuid,
339
- "architecture": expected.architecture.rawValue,
340
- "instructionOffset": expected.instructionOffset,
341
- ] as NSDictionary
284
+ for legacyField in ["exception", "exceptionChain", "context"] where actual[legacyField] == nil {
285
+ expected.removeValue(forKey: legacyField)
342
286
  }
287
+ return actual as NSDictionary == expected as NSDictionary
343
288
  }
344
289
  }
345
290
 
@@ -93,6 +93,7 @@ struct NativeHangIncident: Codable, Equatable {
93
93
  mechanism: .deadlock,
94
94
  nativeStackFrames: nativeStackFrames,
95
95
  artifactIdentity: artifactIdentity.validatedIdentity(),
96
+ context: nil,
96
97
  hangState: state,
97
98
  hangDurationMs: durationMs,
98
99
  source: .hang(eventID: eventID),
@@ -29,14 +29,14 @@
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": "7a46035fcaa72dd7504b4e274a92070d73dd1c45fe20e008474340d6d7c44ede",
32
- "swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "a872ad63532999e74af1e9685a9382d0e02b93c6f9b942b371d47f56f8b961c0",
32
+ "swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "8798c8900ea28a85d6909183dc8435bc08da96573b89310d26ed178217fbc04b",
33
33
  "swift/logbrew-swift/Sources/LogBrewCrash/CrashStorageDirectory.swift": "7cd566703cbf704dc99155451ee93dcace12c35c805abc09a6dcf23975cf43f9",
34
34
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeArtifactIdentity.swift": "3d785ad717dcf7302451531c18b5e1183983270a6cd78dd543a407e3facc6ae0",
35
35
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "e9ab5f1850a096f40592a025d16c5ffa2fdff0222004a37056898cf1cb4b0dd9",
36
- "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "08819652ce0796bee9f502de1202d236887afbcb7794d4db9adc5b6c2928ca2c",
37
- "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "fe0db58c4ad348a2601d984cdf097147f0b2093baad64861aef48cc052697d9c",
36
+ "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "ce191e46298d91d87b379528d610caa88b924b15a8dd912b4a164b51eb172a7a",
37
+ "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "7c1d028bea143c6ab34ebddaaaab64aeb69d4d950ec7a42264c567e672991748",
38
38
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashReplay.swift": "3eb3d93b32cf14de416f56fa30d6d65e6f268b5ec9d1a67081d7525c4efd4f80",
39
- "swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "02413ca7e003917faadcc9aa612b5d98897c75b39f67c3c845948dd9349a9d8f",
39
+ "swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "5a8150083f16d6b495c9c63434809dc5f11f13ebb23211f57151bad62040781b",
40
40
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdog.swift": "37b25904cd6576e20c40500be2c153156403b57ad7b4186a1b6b35b414145ecb",
41
41
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdogContracts.swift": "59f51554d86c326122bb2bdce47c5a7e01996bd61f2b371eba23855032487b9f",
42
42
  "swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdogSystem.swift": "b87e3db246691d5b0719898cdc02ba068836647b1ac9339a73cf866eabe235ae",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/react-native",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
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",