@logbrew/react-native 0.1.11 → 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 (61) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +106 -3
  3. package/apple-native-diagnostics.d.ts +52 -0
  4. package/apple-native-diagnostics.js +286 -0
  5. package/expo.cjs +46 -0
  6. package/expo.d.ts +16 -0
  7. package/expo.js +5 -0
  8. package/fatal-replay.cjs +12 -1
  9. package/global-errors.cjs +3 -1
  10. package/index.cjs +84 -2
  11. package/index.d.cts +4 -0
  12. package/index.d.ts +4 -0
  13. package/index.js +84 -2
  14. package/index.native.d.ts +4 -0
  15. package/index.native.js +14 -1
  16. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  17. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  18. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  19. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  20. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  21. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  22. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  57. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  58. package/package.json +42 -4
  59. package/promise-rejections.cjs +3 -1
  60. package/scripts/sync-apple-native-sources.mjs +93 -0
  61. package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
@@ -0,0 +1,331 @@
1
+ // Generated by scripts/sync-apple-native-sources.mjs.
2
+ // Edit the canonical Swift source, then regenerate this package boundary.
3
+ import Foundation
4
+
5
+ extension DeliveryEngine {
6
+ func timerFired(generation ownerGeneration: UInt64) {
7
+ let now = DispatchTime.now().uptimeNanoseconds
8
+ stateLock.lock()
9
+ guard generation == ownerGeneration else {
10
+ stateLock.unlock()
11
+ return
12
+ }
13
+ guard let deadline = nextWakeNanoseconds, now >= deadline else {
14
+ stateLock.unlock()
15
+ return
16
+ }
17
+ nextWakeNanoseconds = nil
18
+ let active = automaticTransport != nil && (state == .running || state == .retrying)
19
+ stateLock.unlock()
20
+ guard active else {
21
+ return
22
+ }
23
+ runAutomaticDelivery(generation: ownerGeneration)
24
+ }
25
+
26
+ func runAutomaticDelivery(generation currentGeneration: UInt64) {
27
+ flushLock.lock()
28
+ defer { flushLock.unlock() }
29
+
30
+ guard let transport = transportForAutomaticRun(generation: currentGeneration) else {
31
+ return
32
+ }
33
+ let prefix: FrozenPrefix
34
+ do {
35
+ guard let nextPrefix = try freezePrefix() else {
36
+ finishEmptyAutomaticRun(generation: currentGeneration)
37
+ return
38
+ }
39
+ prefix = nextPrefix
40
+ } catch {
41
+ pauseAfterInternalFailure(generation: currentGeneration)
42
+ return
43
+ }
44
+
45
+ let result = attempt(transport: transport, prefix: prefix)
46
+ finishAutomaticAttempt(result, prefix: prefix, generation: currentGeneration)
47
+ }
48
+
49
+ private func transportForAutomaticRun(generation currentGeneration: UInt64) -> (any Transport)? {
50
+ stateLock.lock()
51
+ defer { stateLock.unlock() }
52
+ guard generation == currentGeneration, state == .running || state == .retrying else {
53
+ return nil
54
+ }
55
+ return automaticTransport
56
+ }
57
+
58
+ private func finishEmptyAutomaticRun(generation currentGeneration: UInt64) {
59
+ stateLock.lock()
60
+ if generation == currentGeneration, state != .paused {
61
+ state = .running
62
+ }
63
+ stateLock.unlock()
64
+ }
65
+
66
+ private func finishAutomaticAttempt(
67
+ _ result: AttemptResult,
68
+ prefix: FrozenPrefix,
69
+ generation currentGeneration: UInt64,
70
+ ) {
71
+ if case .accepted = result {
72
+ finishAcceptedAutomaticAttempt(result, prefix: prefix, generation: currentGeneration)
73
+ return
74
+ }
75
+ stateLock.lock()
76
+ guard generation == currentGeneration || state == .shuttingDown else {
77
+ inFlight = false
78
+ stateLock.unlock()
79
+ return
80
+ }
81
+ inFlight = false
82
+ deliveryAttempts += result.attempts
83
+ let shouldSchedule = applyAutomaticResultLocked(result, prefix: prefix)
84
+ stateLock.unlock()
85
+ if shouldSchedule {
86
+ scheduleTimerUpdate()
87
+ }
88
+ }
89
+
90
+ private func finishAcceptedAutomaticAttempt(
91
+ _ result: AttemptResult,
92
+ prefix: FrozenPrefix,
93
+ generation currentGeneration: UInt64,
94
+ ) {
95
+ storageLock.lock()
96
+ stateLock.lock()
97
+ guard generation == currentGeneration || state == .shuttingDown else {
98
+ inFlight = false
99
+ stateLock.unlock()
100
+ storageLock.unlock()
101
+ return
102
+ }
103
+ let store = durableStore
104
+ stateLock.unlock()
105
+
106
+ let finalResult: AttemptResult
107
+ do {
108
+ try store?.acknowledge(body: prefix.body, eventRecordNames: prefix.durableRecordNames)
109
+ finalResult = result
110
+ } catch {
111
+ finalResult = .terminal(
112
+ reason: .storage,
113
+ attempts: result.attempts,
114
+ error: storageError(error),
115
+ )
116
+ }
117
+
118
+ stateLock.lock()
119
+ inFlight = false
120
+ deliveryAttempts += finalResult.attempts
121
+ let shouldSchedule = applyAutomaticResultLocked(finalResult, prefix: prefix)
122
+ stateLock.unlock()
123
+ storageLock.unlock()
124
+ if shouldSchedule {
125
+ scheduleTimerUpdate()
126
+ }
127
+ }
128
+
129
+ private func applyAutomaticResultLocked(_ result: AttemptResult, prefix: FrozenPrefix) -> Bool {
130
+ switch result {
131
+ case .accepted:
132
+ return applyAcceptedResultLocked(prefix)
133
+ case .retryable:
134
+ return applyRetryableResultLocked()
135
+ case let .terminal(reason, _, _):
136
+ applyTerminalResultLocked(reason: reason)
137
+ return false
138
+ }
139
+ }
140
+
141
+ private func applyAcceptedResultLocked(_ prefix: FrozenPrefix) -> Bool {
142
+ acknowledgeLocked(prefix)
143
+ acceptedEvents += prefix.count
144
+ consecutiveFailures = 0
145
+ retryAttempt = 0
146
+ lastOutcome = .accepted
147
+ guard state != .shuttingDown else {
148
+ return false
149
+ }
150
+ state = .running
151
+ return scheduleLiveQueueLocked()
152
+ }
153
+
154
+ private func applyRetryableResultLocked() -> Bool {
155
+ consecutiveFailures += 1
156
+ lastOutcome = .retryableFailure
157
+ guard state != .shuttingDown else {
158
+ return false
159
+ }
160
+ guard retryAttempt < maxRetries else {
161
+ state = .paused
162
+ pauseReason = .retryExhausted
163
+ return false
164
+ }
165
+ retryAttempt += 1
166
+ state = .retrying
167
+ return scheduleRetryLocked()
168
+ }
169
+
170
+ private func applyTerminalResultLocked(reason: DeliveryPauseReason) {
171
+ consecutiveFailures += 1
172
+ lastOutcome = .terminalFailure
173
+ if state != .shuttingDown {
174
+ state = .paused
175
+ pauseReason = reason
176
+ }
177
+ }
178
+
179
+ func scheduleCaptureLocked() -> Bool {
180
+ guard automaticTransport != nil, state == .running || state == .retrying else {
181
+ return false
182
+ }
183
+ if inFlight || frozenPrefix != nil {
184
+ return false
185
+ }
186
+ return scheduleLiveQueueLocked()
187
+ }
188
+
189
+ func scheduleLiveQueueLocked() -> Bool {
190
+ guard let options = automaticOptions, !queue.isEmpty, state == .running || state == .retrying else {
191
+ nextWakeNanoseconds = nil
192
+ return false
193
+ }
194
+ let delay: TimeInterval = queue.count >= options.threshold ? 0 : options.interval
195
+ return setWakeLocked(delay: delay, replaceLaterWake: true)
196
+ }
197
+
198
+ private func scheduleRetryLocked() -> Bool {
199
+ guard let options = automaticOptions else {
200
+ return false
201
+ }
202
+ let multiplier = pow(2, Double(max(0, retryAttempt - 1)))
203
+ let ceiling = min(options.retryBaseDelay * multiplier, options.maxRetryDelay)
204
+ let delay = Double.random(in: (ceiling / 2) ... ceiling)
205
+ return setWakeLocked(delay: delay, replaceLaterWake: true)
206
+ }
207
+
208
+ private func setWakeLocked(delay: TimeInterval, replaceLaterWake: Bool) -> Bool {
209
+ let delta = UInt64(min(delay * 1_000_000_000, Double(UInt64.max)))
210
+ let now = DispatchTime.now().uptimeNanoseconds
211
+ let deadline = now > UInt64.max - delta ? UInt64.max : now + delta
212
+ if let current = nextWakeNanoseconds, !replaceLaterWake || current <= deadline {
213
+ return false
214
+ }
215
+ nextWakeNanoseconds = deadline
216
+ return true
217
+ }
218
+
219
+ func scheduleTimerUpdate() {
220
+ let timerAndDelay: (DispatchSourceTimer, UInt64)? = withStateLock {
221
+ guard let timer = schedulerTimer, let deadline = nextWakeNanoseconds else {
222
+ return nil
223
+ }
224
+ let now = DispatchTime.now().uptimeNanoseconds
225
+ return (timer, deadline > now ? deadline - now : 0)
226
+ }
227
+ guard let (timer, delay) = timerAndDelay else {
228
+ return
229
+ }
230
+ timer.schedule(deadline: .now() + .nanoseconds(Int(min(delay, UInt64(Int.max)))), leeway: .milliseconds(1))
231
+ }
232
+
233
+ func prepareManualOperation() throws {
234
+ stateLock.lock()
235
+ if closed || state == .closed || state == .shuttingDown {
236
+ stateLock.unlock()
237
+ throw SdkError(code: "shutdown_error", message: "client is already shut down")
238
+ }
239
+ if state == .paused, pauseReason == .storage {
240
+ stateLock.unlock()
241
+ throw SdkError(code: "storage_corrupt", message: "durable delivery data requires explicit recovery")
242
+ }
243
+ nextWakeNanoseconds = nil
244
+ stateLock.unlock()
245
+ }
246
+
247
+ func rescheduleAfterManualOperation() {
248
+ stateLock.lock()
249
+ let shouldSchedule: Bool
250
+ if automaticTransport != nil, state != .paused, state != .shuttingDown, state != .closed {
251
+ state = .running
252
+ retryAttempt = 0
253
+ shouldSchedule = scheduleLiveQueueLocked()
254
+ } else {
255
+ shouldSchedule = false
256
+ }
257
+ stateLock.unlock()
258
+ if shouldSchedule {
259
+ scheduleTimerUpdate()
260
+ }
261
+ }
262
+
263
+ func ownedTransport() throws -> any Transport {
264
+ stateLock.lock()
265
+ defer { stateLock.unlock() }
266
+ guard let transport = automaticTransport else {
267
+ throw SdkError(code: "configuration_error", message: "automatic delivery is not running")
268
+ }
269
+ return transport
270
+ }
271
+
272
+ func beginShutdown() throws -> (timer: DispatchSourceTimer?, scheduler: DispatchQueue?) {
273
+ storageLock.lock()
274
+ defer { storageLock.unlock() }
275
+ stateLock.lock()
276
+ guard !closed, state != .closed, state != .shuttingDown else {
277
+ stateLock.unlock()
278
+ throw SdkError(code: "shutdown_error", message: "client is already shut down")
279
+ }
280
+ if state == .paused, pauseReason == .storage {
281
+ stateLock.unlock()
282
+ throw SdkError(code: "storage_corrupt", message: "durable delivery data requires explicit recovery")
283
+ }
284
+ generation &+= 1
285
+ state = .shuttingDown
286
+ closed = true
287
+ nextWakeNanoseconds = nil
288
+ let result = (schedulerTimer, schedulerQueue)
289
+ schedulerTimer = nil
290
+ schedulerQueue = nil
291
+ automaticTransport = nil
292
+ automaticOptions = nil
293
+ stateLock.unlock()
294
+ return result
295
+ }
296
+
297
+ func finishStopping(timer: DispatchSourceTimer?, scheduler: DispatchQueue?) {
298
+ timer?.setEventHandler {}
299
+ timer?.cancel()
300
+ if DispatchQueue.getSpecific(key: schedulerKey) == nil {
301
+ scheduler?.sync {}
302
+ }
303
+ }
304
+
305
+ private func pauseAfterInternalFailure(generation currentGeneration: UInt64) {
306
+ stateLock.lock()
307
+ if generation == currentGeneration, state != .shuttingDown {
308
+ state = .paused
309
+ pauseReason = .nonRetryable
310
+ lastOutcome = .terminalFailure
311
+ consecutiveFailures += 1
312
+ inFlight = false
313
+ }
314
+ stateLock.unlock()
315
+ }
316
+
317
+ func validate(_ options: AutomaticDeliveryOptions) throws {
318
+ guard options.interval.isFinite, options.interval > 0, options.interval <= Self.maxScheduleDelay else {
319
+ throw SdkError(code: "configuration_error", message: "automatic delivery interval is out of range")
320
+ }
321
+ guard (1 ... Self.maxQueuedEvents).contains(options.threshold) else {
322
+ throw SdkError(code: "configuration_error", message: "automatic delivery threshold is out of range")
323
+ }
324
+ guard options.retryBaseDelay.isFinite, options.retryBaseDelay > 0,
325
+ options.maxRetryDelay.isFinite, options.maxRetryDelay >= options.retryBaseDelay,
326
+ options.maxRetryDelay <= Self.maxScheduleDelay
327
+ else {
328
+ throw SdkError(code: "configuration_error", message: "automatic delivery retry delays are invalid")
329
+ }
330
+ }
331
+ }
@@ -0,0 +1,148 @@
1
+ // Generated by scripts/sync-apple-native-sources.mjs.
2
+ // Edit the canonical Swift source, then regenerate this package boundary.
3
+ import Foundation
4
+
5
+ extension DeliveryEngine {
6
+ func enableDurableDelivery(options: DurableDeliveryOptions) throws {
7
+ storageLock.lock()
8
+ defer { storageLock.unlock() }
9
+ let memoryQueue = try prepareDurableEnable()
10
+
11
+ do {
12
+ let store = try DurableDeliveryStore(parent: options.directory, sdk: sdk)
13
+ let recovered = try recoverDurableState(store: store, memoryQueue: memoryQueue)
14
+ installDurableState(recovered, store: store, parent: options.directory)
15
+ } catch let failure as DurableStoreFailure {
16
+ recordDurableEnableFailure(failure, parent: options.directory)
17
+ throw storageError(failure)
18
+ } catch {
19
+ throw storageError(error)
20
+ }
21
+ }
22
+
23
+ func purgeDurableDelivery() throws {
24
+ storageLock.lock()
25
+ defer { storageLock.unlock() }
26
+ let parent = try prepareDurablePurge()
27
+ guard let parent else {
28
+ return
29
+ }
30
+
31
+ do {
32
+ try DurableDeliveryStore.purge(parent: parent)
33
+ } catch {
34
+ recordStorageFailure()
35
+ throw storageError(error)
36
+ }
37
+ finishDurablePurge()
38
+ }
39
+
40
+ private func prepareDurableEnable() throws -> [QueuedEvent] {
41
+ stateLock.lock()
42
+ defer { stateLock.unlock() }
43
+ if closed || state == .closed || state == .shuttingDown {
44
+ throw SdkError(code: "shutdown_error", message: "client is already shut down")
45
+ }
46
+ if durableStore != nil {
47
+ throw SdkError(code: "configuration_error", message: "durable delivery is already enabled")
48
+ }
49
+ if automaticTransport != nil || inFlight {
50
+ throw SdkError(code: "configuration_error", message: "stop active delivery before enabling durability")
51
+ }
52
+ return queue
53
+ }
54
+
55
+ private func recoverDurableState(
56
+ store: DurableDeliveryStore,
57
+ memoryQueue: [QueuedEvent],
58
+ ) throws -> (queue: [QueuedEvent], prefix: FrozenPrefix?) {
59
+ let recovery = store.recovery()
60
+ if recovery.events.isEmpty {
61
+ let names = try store.appendExisting(memoryQueue.map { ($0.event, $0.encodedBytes) })
62
+ let queue = zip(memoryQueue, names).map { item, name in
63
+ QueuedEvent(event: item.event, encodedBytes: item.encodedBytes, durableRecordName: name)
64
+ }
65
+ return (queue, nil)
66
+ }
67
+ guard memoryQueue.isEmpty else {
68
+ throw DurableStoreFailure.owned
69
+ }
70
+ let queue = recovery.events.map {
71
+ QueuedEvent(event: $0.event, encodedBytes: $0.encodedBytes, durableRecordName: $0.recordName)
72
+ }
73
+ return try (queue, recoveredFrozenPrefix(recovery.prefix, queue: queue))
74
+ }
75
+
76
+ private func recoveredFrozenPrefix(
77
+ _ recovered: DurableDeliveryStore.RecoveredPrefix?,
78
+ queue: [QueuedEvent],
79
+ ) throws -> FrozenPrefix? {
80
+ guard let recovered else {
81
+ return nil
82
+ }
83
+ let count = recovered.eventRecordNames.count
84
+ let body = try encodeBatch(Array(queue.prefix(count)).map(\.event))
85
+ guard body == recovered.body else {
86
+ throw DurableStoreFailure.corrupt
87
+ }
88
+ return FrozenPrefix(
89
+ count: count,
90
+ bytes: recovered.encodedBytes,
91
+ body: recovered.body,
92
+ eventIDs: Array(queue.prefix(count)).map(\.event.id),
93
+ durableRecordNames: recovered.eventRecordNames,
94
+ )
95
+ }
96
+
97
+ private func installDurableState(
98
+ _ recovered: (queue: [QueuedEvent], prefix: FrozenPrefix?),
99
+ store: DurableDeliveryStore,
100
+ parent: URL,
101
+ ) {
102
+ stateLock.lock()
103
+ queue = recovered.queue
104
+ queuedBytes = recovered.queue.reduce(0) { $0 + $1.encodedBytes }
105
+ frozenPrefix = recovered.prefix
106
+ durableStore = store
107
+ durableParent = parent
108
+ if state == .paused, pauseReason == .storage {
109
+ state = .manual
110
+ pauseReason = .none
111
+ }
112
+ stateLock.unlock()
113
+ }
114
+
115
+ private func recordDurableEnableFailure(_ failure: DurableStoreFailure, parent: URL) {
116
+ stateLock.lock()
117
+ durableParent = parent
118
+ if failure == .corrupt {
119
+ state = .paused
120
+ pauseReason = .storage
121
+ lastOutcome = .terminalFailure
122
+ consecutiveFailures += 1
123
+ }
124
+ stateLock.unlock()
125
+ }
126
+
127
+ private func prepareDurablePurge() throws -> URL? {
128
+ stateLock.lock()
129
+ defer { stateLock.unlock() }
130
+ if automaticTransport != nil || inFlight || state == .shuttingDown || state == .closed {
131
+ throw SdkError(code: "configuration_error", message: "stop active delivery before purging durable data")
132
+ }
133
+ durableStore = nil
134
+ return durableParent
135
+ }
136
+
137
+ private func finishDurablePurge() {
138
+ stateLock.lock()
139
+ queue.removeAll()
140
+ queuedBytes = 0
141
+ frozenPrefix = nil
142
+ durableParent = nil
143
+ state = .manual
144
+ pauseReason = .none
145
+ inFlight = false
146
+ stateLock.unlock()
147
+ }
148
+ }