@logbrew/react-native 0.1.10 → 0.1.12

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 (72) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +174 -4
  3. package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
  4. package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
  5. package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  6. package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  7. package/apple-native-diagnostics.d.ts +52 -0
  8. package/apple-native-diagnostics.js +286 -0
  9. package/expo.cjs +46 -0
  10. package/expo.d.ts +16 -0
  11. package/expo.js +5 -0
  12. package/fatal-replay.cjs +12 -1
  13. package/global-errors.cjs +3 -1
  14. package/index.cjs +86 -2
  15. package/index.d.cts +7 -0
  16. package/index.d.ts +7 -0
  17. package/index.js +86 -2
  18. package/index.native.d.ts +47 -1
  19. package/index.native.js +81 -1
  20. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  21. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  22. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  57. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  58. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  59. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  60. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  61. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  62. package/ios/LBRNEventRecordStore.h +29 -0
  63. package/ios/LBRNEventRecordStore.m +746 -0
  64. package/ios/LBRNFatalStoreModule.mm +159 -14
  65. package/ios/LBRNPrivateStorage.h +7 -0
  66. package/ios/LBRNPrivateStorage.m +38 -0
  67. package/package.json +47 -4
  68. package/persistent-delivery.native.js +282 -0
  69. package/promise-rejections.cjs +3 -1
  70. package/scripts/sync-apple-native-sources.mjs +93 -0
  71. package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
  72. package/src/NativeLogBrewFatalStore.ts +9 -0
@@ -0,0 +1,330 @@
1
+ // Generated by scripts/sync-apple-native-sources.mjs.
2
+ // Edit the canonical Swift source, then regenerate this package boundary.
3
+ import Darwin
4
+ import Foundation
5
+
6
+ extension DurableDeliveryStore {
7
+ static func loadRecovery(from directory: URL, sdk: SDKInfo, fileManager: FileManager) throws -> Recovery {
8
+ let children = try recoveryChildren(in: directory, fileManager: fileManager)
9
+ var events: [(UInt64, RecoveredEvent)] = []
10
+ var prefix: RecoveredPrefix?
11
+
12
+ for child in children where child.lastPathComponent != lockName {
13
+ let values = try resourceValues(for: child)
14
+ guard values.isSymbolicLink != true, values.isRegularFile == true else {
15
+ throw DurableStoreFailure.corrupt
16
+ }
17
+ try hardenOwnedFile(child, fileManager: fileManager)
18
+ if child.lastPathComponent == prefixName {
19
+ guard prefix == nil else {
20
+ throw DurableStoreFailure.corrupt
21
+ }
22
+ prefix = try recoverPrefix(from: child, values: values, sdk: sdk)
23
+ } else {
24
+ try events.append(recoverEvent(from: child, values: values, sdk: sdk))
25
+ }
26
+ }
27
+
28
+ events.sort { $0.0 < $1.0 }
29
+ let recovered = events.map(\.1)
30
+ try validateRecovery(events: recovered, prefix: prefix)
31
+ return Recovery(events: recovered, prefix: prefix)
32
+ }
33
+
34
+ static func validateParent(_ parent: URL, fileManager: FileManager) throws {
35
+ guard parent.isFileURL else {
36
+ throw DurableStoreFailure.invalidLocation
37
+ }
38
+ let values = try resourceValues(for: parent)
39
+ guard values.isSymbolicLink != true, values.isDirectory == true else {
40
+ throw DurableStoreFailure.invalidLocation
41
+ }
42
+ let permissions = try permissions(at: parent, fileManager: fileManager)
43
+ guard permissions & 0o022 == 0 else {
44
+ throw DurableStoreFailure.invalidLocation
45
+ }
46
+ }
47
+
48
+ static func prepareOwnedDirectory(_ directory: URL, fileManager: FileManager) throws {
49
+ if fileManager.fileExists(atPath: directory.path) {
50
+ try validateDirectory(directory, fileManager: fileManager)
51
+ } else {
52
+ do {
53
+ try fileManager.createDirectory(
54
+ at: directory,
55
+ withIntermediateDirectories: false,
56
+ attributes: ownedDirectoryAttributes,
57
+ )
58
+ } catch {
59
+ throw DurableStoreFailure.inputOutput
60
+ }
61
+ }
62
+ do {
63
+ try fileManager.setAttributes(ownedDirectoryAttributes, ofItemAtPath: directory.path)
64
+ var values = URLResourceValues()
65
+ values.isExcludedFromBackup = true
66
+ var mutableDirectory = directory
67
+ try mutableDirectory.setResourceValues(values)
68
+ } catch {
69
+ throw DurableStoreFailure.inputOutput
70
+ }
71
+ }
72
+
73
+ static func validateDirectory(_ directory: URL, fileManager: FileManager) throws {
74
+ let values = try resourceValues(for: directory)
75
+ guard values.isSymbolicLink != true, values.isDirectory == true else {
76
+ throw DurableStoreFailure.invalidLocation
77
+ }
78
+ let permissions = try permissions(at: directory, fileManager: fileManager)
79
+ guard permissions & 0o077 == 0 else {
80
+ throw DurableStoreFailure.invalidLocation
81
+ }
82
+ }
83
+
84
+ static func acquireLock(in directory: URL, fileManager: FileManager) throws -> FileHandle {
85
+ let lock = directory.appendingPathComponent(lockName, isDirectory: false)
86
+ if !fileManager.fileExists(atPath: lock.path) {
87
+ guard fileManager.createFile(atPath: lock.path, contents: Data(), attributes: ownedFileAttributes) else {
88
+ throw DurableStoreFailure.inputOutput
89
+ }
90
+ }
91
+ do {
92
+ let values = try resourceValues(for: lock)
93
+ guard values.isSymbolicLink != true, values.isRegularFile == true else {
94
+ throw DurableStoreFailure.invalidLocation
95
+ }
96
+ try hardenOwnedFile(lock, fileManager: fileManager)
97
+ let handle = try FileHandle(forUpdating: lock)
98
+ guard flock(handle.fileDescriptor, LOCK_EX | LOCK_NB) == 0 else {
99
+ try? handle.close()
100
+ throw DurableStoreFailure.owned
101
+ }
102
+ return handle
103
+ } catch let failure as DurableStoreFailure {
104
+ throw failure
105
+ } catch {
106
+ throw DurableStoreFailure.inputOutput
107
+ }
108
+ }
109
+
110
+ static func releaseLock(_ handle: FileHandle) {
111
+ _ = flock(handle.fileDescriptor, LOCK_UN)
112
+ try? handle.close()
113
+ }
114
+
115
+ static func resourceValues(for url: URL) throws -> URLResourceValues {
116
+ do {
117
+ return try url.resourceValues(forKeys: [
118
+ .fileSizeKey,
119
+ .isDirectoryKey,
120
+ .isRegularFileKey,
121
+ .isSymbolicLinkKey,
122
+ ])
123
+ } catch {
124
+ throw DurableStoreFailure.inputOutput
125
+ }
126
+ }
127
+
128
+ static func permissions(at url: URL, fileManager: FileManager) throws -> Int {
129
+ do {
130
+ let attributes = try fileManager.attributesOfItem(atPath: url.path)
131
+ guard let permissions = attributes[.posixPermissions] as? NSNumber else {
132
+ throw DurableStoreFailure.invalidLocation
133
+ }
134
+ return permissions.intValue
135
+ } catch let failure as DurableStoreFailure {
136
+ throw failure
137
+ } catch {
138
+ throw DurableStoreFailure.inputOutput
139
+ }
140
+ }
141
+
142
+ static func hardenOwnedFile(_ file: URL, fileManager: FileManager) throws {
143
+ do {
144
+ try fileManager.setAttributes(ownedFileAttributes, ofItemAtPath: file.path)
145
+ } catch {
146
+ throw DurableStoreFailure.inputOutput
147
+ }
148
+ }
149
+
150
+ static var ownedDirectoryAttributes: [FileAttributeKey: Any] {
151
+ var attributes: [FileAttributeKey: Any] = [.posixPermissions: 0o700]
152
+ #if os(iOS) || os(tvOS) || os(watchOS)
153
+ attributes[.protectionKey] = FileProtectionType.completeUntilFirstUserAuthentication
154
+ #endif
155
+ return attributes
156
+ }
157
+
158
+ static var ownedFileAttributes: [FileAttributeKey: Any] {
159
+ var attributes: [FileAttributeKey: Any] = [.posixPermissions: 0o600]
160
+ #if os(iOS) || os(tvOS) || os(watchOS)
161
+ attributes[.protectionKey] = FileProtectionType.completeUntilFirstUserAuthentication
162
+ #endif
163
+ return attributes
164
+ }
165
+
166
+ static func eventName(sequence: UInt64) -> String {
167
+ String(format: "event-%020llu.json", sequence)
168
+ }
169
+
170
+ static func sequence(from recovered: RecoveredEvent) throws -> UInt64 {
171
+ try sequence(from: recovered.recordName)
172
+ }
173
+
174
+ static func sequence(from name: String) throws -> UInt64 {
175
+ let prefix = "event-"
176
+ let suffix = ".json"
177
+ guard name.hasPrefix(prefix), name.hasSuffix(suffix) else {
178
+ throw DurableStoreFailure.corrupt
179
+ }
180
+ let start = name.index(name.startIndex, offsetBy: prefix.count)
181
+ let end = name.index(name.endIndex, offsetBy: -suffix.count)
182
+ let digits = String(name[start ..< end])
183
+ guard digits.count == 20,
184
+ digits.allSatisfy(\.isNumber),
185
+ let sequence = UInt64(digits),
186
+ eventName(sequence: sequence) == name
187
+ else {
188
+ throw DurableStoreFailure.corrupt
189
+ }
190
+ return sequence
191
+ }
192
+
193
+ static func isOwnedName(_ name: String) -> Bool {
194
+ name == prefixName || (try? sequence(from: name)) != nil
195
+ }
196
+
197
+ static func encode(_ value: some Encodable) throws -> Data {
198
+ do {
199
+ let encoder = JSONEncoder()
200
+ encoder.outputFormatting = [.sortedKeys]
201
+ return try encoder.encode(value)
202
+ } catch {
203
+ throw DurableStoreFailure.inputOutput
204
+ }
205
+ }
206
+
207
+ static func encodeEvent(_ event: Event) throws -> Data {
208
+ try encode(event)
209
+ }
210
+
211
+ static func checksum(_ data: Data) -> String {
212
+ var hash: UInt64 = 14_695_981_039_346_656_037
213
+ for byte in data {
214
+ hash ^= UInt64(byte)
215
+ hash &*= 1_099_511_628_211
216
+ }
217
+ return String(format: "%016llx", hash)
218
+ }
219
+
220
+ private static func recoveryChildren(in directory: URL, fileManager: FileManager) throws -> [URL] {
221
+ do {
222
+ return try fileManager.contentsOfDirectory(
223
+ at: directory,
224
+ includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey, .isSymbolicLinkKey],
225
+ options: [],
226
+ )
227
+ } catch {
228
+ throw DurableStoreFailure.inputOutput
229
+ }
230
+ }
231
+
232
+ private static func recoverPrefix(
233
+ from file: URL,
234
+ values: URLResourceValues,
235
+ sdk: SDKInfo,
236
+ ) throws -> RecoveredPrefix {
237
+ guard let size = values.fileSize, size <= maxPrefixRecordBytes else {
238
+ throw DurableStoreFailure.corrupt
239
+ }
240
+ let record = try decodePrefixRecord(from: file)
241
+ guard record.version == version, record.sdk == sdk else {
242
+ throw DurableStoreFailure.corrupt
243
+ }
244
+ return RecoveredPrefix(
245
+ body: record.body,
246
+ eventRecordNames: record.eventRecordNames,
247
+ encodedBytes: record.encodedBytes,
248
+ )
249
+ }
250
+
251
+ private static func recoverEvent(
252
+ from file: URL,
253
+ values: URLResourceValues,
254
+ sdk: SDKInfo,
255
+ ) throws -> (UInt64, RecoveredEvent) {
256
+ let name = file.lastPathComponent
257
+ let sequence = try sequence(from: name)
258
+ guard let size = values.fileSize, size <= maxEventRecordBytes else {
259
+ throw DurableStoreFailure.corrupt
260
+ }
261
+ let record = try decodeEventRecord(from: file)
262
+ let encodedEvent = try encodeEvent(record.event)
263
+ guard record.version == version,
264
+ record.sequence == sequence,
265
+ record.sdk == sdk,
266
+ record.encodedBytes == encodedEvent.count,
267
+ record.checksum == checksum(encodedEvent)
268
+ else {
269
+ throw DurableStoreFailure.corrupt
270
+ }
271
+ return (
272
+ sequence,
273
+ RecoveredEvent(event: record.event, encodedBytes: record.encodedBytes, recordName: name),
274
+ )
275
+ }
276
+
277
+ private static func validateRecovery(events: [RecoveredEvent], prefix: RecoveredPrefix?) throws {
278
+ guard events.count <= DeliveryEngine.maxQueuedEvents,
279
+ events.reduce(0, { $0 + $1.encodedBytes }) <= DeliveryEngine.maxQueuedBytes
280
+ else {
281
+ throw DurableStoreFailure.corrupt
282
+ }
283
+ guard let prefix else {
284
+ return
285
+ }
286
+ let frozenEvents = events.prefix(prefix.eventRecordNames.count)
287
+ guard !prefix.eventRecordNames.isEmpty,
288
+ prefix.eventRecordNames.count <= DeliveryEngine.maxRequestEvents,
289
+ prefix.body.count <= DeliveryEngine.maxRequestBytes,
290
+ frozenEvents.map(\.recordName) == prefix.eventRecordNames,
291
+ frozenEvents.reduce(0, { $0 + $1.encodedBytes }) == prefix.encodedBytes
292
+ else {
293
+ throw DurableStoreFailure.corrupt
294
+ }
295
+ }
296
+
297
+ private static func decodeEventRecord(from file: URL) throws -> EventRecord {
298
+ do {
299
+ return try JSONDecoder().decode(EventRecord.self, from: read(file, maxBytes: maxEventRecordBytes))
300
+ } catch let failure as DurableStoreFailure {
301
+ throw failure
302
+ } catch {
303
+ throw DurableStoreFailure.corrupt
304
+ }
305
+ }
306
+
307
+ private static func decodePrefixRecord(from file: URL) throws -> PrefixRecord {
308
+ do {
309
+ return try JSONDecoder().decode(PrefixRecord.self, from: read(file, maxBytes: maxPrefixRecordBytes))
310
+ } catch let failure as DurableStoreFailure {
311
+ throw failure
312
+ } catch {
313
+ throw DurableStoreFailure.corrupt
314
+ }
315
+ }
316
+
317
+ private static func read(_ file: URL, maxBytes: Int) throws -> Data {
318
+ do {
319
+ let data = try Data(contentsOf: file, options: [.mappedIfSafe])
320
+ guard data.count <= maxBytes else {
321
+ throw DurableStoreFailure.corrupt
322
+ }
323
+ return data
324
+ } catch let failure as DurableStoreFailure {
325
+ throw failure
326
+ } catch {
327
+ throw DurableStoreFailure.corrupt
328
+ }
329
+ }
330
+ }
@@ -0,0 +1,114 @@
1
+ // Generated by scripts/sync-apple-native-sources.mjs.
2
+ // Edit the canonical Swift source, then regenerate this package boundary.
3
+ struct SDKInfo: Codable, Equatable {
4
+ let name: String
5
+ let language: String
6
+ let version: String
7
+ }
8
+
9
+ struct EventBatch: Encodable {
10
+ let sdk: SDKInfo
11
+ let events: [Event]
12
+ }
13
+
14
+ struct Event: Codable, Equatable {
15
+ let type: String
16
+ let timestamp: String
17
+ let id: String
18
+ let attributes: EventAttributes
19
+
20
+ private enum CodingKeys: String, CodingKey {
21
+ case type
22
+ case timestamp
23
+ case id
24
+ case attributes
25
+ }
26
+
27
+ init(type: String, timestamp: String, id: String, attributes: EventAttributes) {
28
+ self.type = type
29
+ self.timestamp = timestamp
30
+ self.id = id
31
+ self.attributes = attributes
32
+ }
33
+
34
+ func encode(to encoder: Encoder) throws {
35
+ var container = encoder.container(keyedBy: CodingKeys.self)
36
+ try container.encode(type, forKey: .type)
37
+ try container.encode(timestamp, forKey: .timestamp)
38
+ try container.encode(id, forKey: .id)
39
+ switch attributes {
40
+ case let .release(value):
41
+ try container.encode(value, forKey: .attributes)
42
+ case let .environment(value):
43
+ try container.encode(value, forKey: .attributes)
44
+ case let .issue(value):
45
+ try container.encode(value, forKey: .attributes)
46
+ case let .log(value):
47
+ try container.encode(value, forKey: .attributes)
48
+ case let .span(value):
49
+ try container.encode(value, forKey: .attributes)
50
+ case let .action(value):
51
+ try container.encode(value, forKey: .attributes)
52
+ case let .metric(value):
53
+ try container.encode(value, forKey: .attributes)
54
+ }
55
+ }
56
+
57
+ init(from decoder: Decoder) throws {
58
+ let container = try decoder.container(keyedBy: CodingKeys.self)
59
+ type = try container.decode(String.self, forKey: .type)
60
+ timestamp = try container.decode(String.self, forKey: .timestamp)
61
+ id = try container.decode(String.self, forKey: .id)
62
+ switch type {
63
+ case "release":
64
+ attributes = try .release(container.decode(ReleaseAttributes.self, forKey: .attributes))
65
+ case "environment":
66
+ attributes = try .environment(container.decode(EnvironmentAttributes.self, forKey: .attributes))
67
+ case "issue":
68
+ attributes = try .issue(container.decode(IssueAttributes.self, forKey: .attributes))
69
+ case "log":
70
+ attributes = try .log(container.decode(LogAttributes.self, forKey: .attributes))
71
+ case "span":
72
+ attributes = try .span(container.decode(SpanAttributes.self, forKey: .attributes))
73
+ case "action":
74
+ attributes = try .action(container.decode(ActionAttributes.self, forKey: .attributes))
75
+ case "metric":
76
+ attributes = try .metric(container.decode(MetricAttributes.self, forKey: .attributes))
77
+ default:
78
+ throw DecodingError.dataCorruptedError(
79
+ forKey: .type,
80
+ in: container,
81
+ debugDescription: "unsupported event type",
82
+ )
83
+ }
84
+ }
85
+ }
86
+
87
+ enum EventAttributes: Equatable {
88
+ case release(ReleaseAttributes)
89
+ case environment(EnvironmentAttributes)
90
+ case issue(IssueAttributes)
91
+ case log(LogAttributes)
92
+ case span(SpanAttributes)
93
+ case action(ActionAttributes)
94
+ case metric(MetricAttributes)
95
+
96
+ var eventType: String {
97
+ switch self {
98
+ case .release:
99
+ "release"
100
+ case .environment:
101
+ "environment"
102
+ case .issue:
103
+ "issue"
104
+ case .log:
105
+ "log"
106
+ case .span:
107
+ "span"
108
+ case .action:
109
+ "action"
110
+ case .metric:
111
+ "metric"
112
+ }
113
+ }
114
+ }