@logbrew/react-native 0.1.22 → 0.1.24

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 (37) hide show
  1. package/LogBrewReactNative.podspec +1 -0
  2. package/README.md +70 -7
  3. package/android/CMakeLists.txt +7 -0
  4. package/android/build.gradle +12 -0
  5. package/android/src/main/cpp/android_diagnostics.cpp +363 -0
  6. package/android/src/main/java/co/logbrew/reactnative/AndroidDiagnosticsRuntime.java +214 -0
  7. package/android/src/main/java/co/logbrew/reactnative/AndroidNativeDiagnostics.java +363 -0
  8. package/android/src/main/java/co/logbrew/reactnative/AndroidNativeSignalStore.java +233 -0
  9. package/android/src/main/java/co/logbrew/reactnative/AndroidParentDirectorySync.java +12 -12
  10. package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +15 -5
  11. package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +127 -156
  12. package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +17 -17
  13. package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +17 -17
  14. package/android-native-diagnostics.d.ts +29 -0
  15. package/android-native-diagnostics.js +176 -0
  16. package/fatal-replay.cjs +35 -3
  17. package/global-errors.d.cts +3 -3
  18. package/global-errors.d.ts +3 -3
  19. package/global-errors.native.js +1 -12
  20. package/index.cjs +27 -5
  21. package/index.native.d.ts +4 -0
  22. package/index.native.js +29 -4
  23. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +2 -10
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/OperationTrace.swift +56 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +48 -56
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +6 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +0 -1
  29. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +5 -4
  30. package/ios/LBRNFatalStoreModule.mm +34 -86
  31. package/package.json +17 -5
  32. package/persistent-delivery.native.js +4 -3
  33. package/resource-fetch.js +5 -463
  34. package/src/NativeLogBrewFatalStore.ts +6 -4
  35. package/android/src/main/java/co/logbrew/reactnative/FatalRecordStore.java +0 -623
  36. package/ios/LBRNFatalRecordStore.h +0 -25
  37. package/ios/LBRNFatalRecordStore.m +0 -542
@@ -1,542 +0,0 @@
1
- #import "LBRNFatalRecordStore.h"
2
-
3
- #import <errno.h>
4
- #import <fcntl.h>
5
- #import <math.h>
6
- #import <stdint.h>
7
- #import <sys/stat.h>
8
- #import <unistd.h>
9
-
10
- NSString *const LBRNFatalRecordFileName = @"fatal-js-v1.record";
11
- NSString *const LBRNFatalRecordTemporaryFileName = @"fatal-js-v1.tmp";
12
-
13
- static const NSUInteger LBRNMaximumRecordBytes = 16 * 1024;
14
- static const NSUInteger LBRNMaximumFrames = 24;
15
- static const NSUInteger LBRNMaximumFilenameBytes = 512;
16
- static const NSUInteger LBRNMaximumIdentifierBytes = 96;
17
- static const int32_t LBRNMaximumCounter = INT32_MAX;
18
-
19
- @interface LBRNFatalRecordStore ()
20
- @property (nonatomic, readonly) NSURL *directoryURL;
21
- @property (nonatomic, readonly, copy)
22
- LBRNFatalDirectoryPreparation directoryPreparation;
23
- @end
24
-
25
- @implementation LBRNFatalRecordStore
26
-
27
- - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
28
- {
29
- return [self initWithDirectoryURL:directoryURL
30
- directoryPreparation:^BOOL(__unused NSURL *preparedURL) {
31
- return NO;
32
- }];
33
- }
34
-
35
- - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
36
- directoryPreparation:(LBRNFatalDirectoryPreparation)directoryPreparation
37
- {
38
- self = [super init];
39
- if (self != nil) {
40
- _directoryURL = directoryURL;
41
- _directoryPreparation = [directoryPreparation copy];
42
- }
43
- return self;
44
- }
45
-
46
- - (NSDictionary *)writeRecord:(NSDictionary *)record
47
- {
48
- @synchronized(self) {
49
- NSDictionary *validated = [self validatedRecord:record requireZeroCounters:YES];
50
- if (validated == nil) {
51
- return @{ @"status" : @"invalid_record" };
52
- }
53
-
54
- int directoryFD = [self openPrivateDirectory];
55
- if (directoryFD < 0) {
56
- return @{ @"status" : @"storage_error" };
57
- }
58
- @try {
59
- BOOL directoryPrepared = NO;
60
- @try {
61
- directoryPrepared = self.directoryPreparation(self.directoryURL);
62
- } @catch (__unused NSException *exception) {
63
- directoryPrepared = NO;
64
- }
65
- if (!directoryPrepared || ![self directoryFDMatchesDirectoryURL:directoryFD]) {
66
- return @{ @"status" : @"storage_error" };
67
- }
68
-
69
- NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
70
- NSString *status = existing[@"status"];
71
- if ([status isEqualToString:@"storage_error"]) {
72
- return existing;
73
- }
74
- if ([status isEqualToString:@"pending"]) {
75
- NSMutableDictionary *preserved = [existing[@"record"] mutableCopy];
76
- int32_t dropped = [preserved[@"droppedRecords"] intValue];
77
- if (dropped < LBRNMaximumCounter) {
78
- dropped += 1;
79
- }
80
- preserved[@"droppedRecords"] = @(dropped);
81
- if (![self atomicallyWriteRecord:preserved directoryFD:directoryFD]) {
82
- return @{ @"status" : @"storage_error" };
83
- }
84
- return @{
85
- @"status" : @"dropped_pending",
86
- @"recordId" : preserved[@"id"],
87
- @"droppedRecords" : @(dropped),
88
- };
89
- }
90
-
91
- NSMutableDictionary *stored = [validated mutableCopy];
92
- BOOL recoveredCorruption = [status isEqualToString:@"corrupt_discarded"];
93
- if (recoveredCorruption) {
94
- stored[@"corruptRecords"] = @1;
95
- }
96
- if (![self atomicallyWriteRecord:stored directoryFD:directoryFD]) {
97
- return @{ @"status" : @"storage_error" };
98
- }
99
- return @{
100
- @"status" : recoveredCorruption ? @"stored_after_corruption" : @"stored",
101
- @"recordId" : stored[@"id"],
102
- @"corruptRecords" : stored[@"corruptRecords"],
103
- };
104
- } @finally {
105
- close(directoryFD);
106
- }
107
- }
108
- }
109
-
110
- - (NSDictionary *)readRecord
111
- {
112
- @synchronized(self) {
113
- int directoryFD = [self openPrivateDirectory];
114
- if (directoryFD < 0) {
115
- return @{ @"status" : @"storage_error" };
116
- }
117
- @try {
118
- return [self readRecordFromDirectory:directoryFD];
119
- } @finally {
120
- close(directoryFD);
121
- }
122
- }
123
- }
124
-
125
- - (NSDictionary *)acknowledgeRecordId:(NSString *)recordId
126
- {
127
- @synchronized(self) {
128
- if (![self validIdentifier:recordId]) {
129
- return @{ @"status" : @"id_mismatch" };
130
- }
131
- int directoryFD = [self openPrivateDirectory];
132
- if (directoryFD < 0) {
133
- return @{ @"status" : @"storage_error" };
134
- }
135
- @try {
136
- NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
137
- if (![existing[@"status"] isEqualToString:@"pending"]) {
138
- return existing;
139
- }
140
- NSDictionary *record = existing[@"record"];
141
- if (![record[@"id"] isEqualToString:recordId]) {
142
- return @{
143
- @"status" : @"id_mismatch",
144
- @"recordId" : record[@"id"],
145
- };
146
- }
147
- if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
148
- || fsync(directoryFD) != 0) {
149
- return @{ @"status" : @"storage_error" };
150
- }
151
- return @{
152
- @"status" : @"acknowledged",
153
- @"recordId" : recordId,
154
- };
155
- } @finally {
156
- close(directoryFD);
157
- }
158
- }
159
- }
160
-
161
- - (NSDictionary *)discardRecord
162
- {
163
- @synchronized(self) {
164
- int directoryFD = [self openPrivateDirectory];
165
- if (directoryFD < 0) {
166
- return @{ @"status" : @"storage_error" };
167
- }
168
- @try {
169
- NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
170
- if (![existing[@"status"] isEqualToString:@"pending"]) {
171
- return existing;
172
- }
173
- NSDictionary *record = existing[@"record"];
174
- if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
175
- || fsync(directoryFD) != 0) {
176
- return @{ @"status" : @"storage_error" };
177
- }
178
- return @{
179
- @"status" : @"discarded",
180
- @"recordId" : record[@"id"],
181
- };
182
- } @finally {
183
- close(directoryFD);
184
- }
185
- }
186
- }
187
-
188
- - (int)openPrivateDirectory
189
- {
190
- if (!self.directoryURL.isFileURL) {
191
- return -1;
192
- }
193
- const char *path = self.directoryURL.fileSystemRepresentation;
194
- struct stat info;
195
- if (lstat(path, &info) != 0) {
196
- if (errno != ENOENT || mkdir(path, 0700) != 0) {
197
- return -1;
198
- }
199
- } else if (!S_ISDIR(info.st_mode) || S_ISLNK(info.st_mode)) {
200
- return -1;
201
- }
202
-
203
- int directoryFD = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
204
- if (directoryFD < 0) {
205
- return -1;
206
- }
207
- if (fstat(directoryFD, &info) != 0 || !S_ISDIR(info.st_mode)
208
- || fchmod(directoryFD, 0700) != 0) {
209
- close(directoryFD);
210
- return -1;
211
- }
212
- return directoryFD;
213
- }
214
-
215
- - (BOOL)directoryFDMatchesDirectoryURL:(int)directoryFD
216
- {
217
- struct stat openedInfo;
218
- struct stat pathInfo;
219
- if (fstat(directoryFD, &openedInfo) != 0
220
- || lstat(self.directoryURL.fileSystemRepresentation, &pathInfo) != 0) {
221
- return NO;
222
- }
223
- return S_ISDIR(openedInfo.st_mode) && S_ISDIR(pathInfo.st_mode)
224
- && !S_ISLNK(pathInfo.st_mode) && openedInfo.st_dev == pathInfo.st_dev
225
- && openedInfo.st_ino == pathInfo.st_ino;
226
- }
227
-
228
- - (NSDictionary *)readRecordFromDirectory:(int)directoryFD
229
- {
230
- if (![self removeStaleTemporaryFile:directoryFD]) {
231
- return @{ @"status" : @"storage_error" };
232
- }
233
-
234
- struct stat pathInfo;
235
- if (fstatat(
236
- directoryFD,
237
- LBRNFatalRecordFileName.UTF8String,
238
- &pathInfo,
239
- AT_SYMLINK_NOFOLLOW)
240
- != 0) {
241
- return errno == ENOENT ? @{ @"status" : @"empty" }
242
- : @{ @"status" : @"storage_error" };
243
- }
244
- if (!S_ISREG(pathInfo.st_mode) || S_ISLNK(pathInfo.st_mode)) {
245
- return @{ @"status" : @"storage_error" };
246
- }
247
- if (pathInfo.st_size < 0 || (uint64_t)pathInfo.st_size > LBRNMaximumRecordBytes) {
248
- return [self discardCorruptRecord:directoryFD];
249
- }
250
-
251
- int fileFD = openat(
252
- directoryFD,
253
- LBRNFatalRecordFileName.UTF8String,
254
- O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
255
- if (fileFD < 0) {
256
- return @{ @"status" : @"storage_error" };
257
- }
258
- NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)pathInfo.st_size];
259
- BOOL readSucceeded = YES;
260
- NSUInteger offset = 0;
261
- while (offset < data.length) {
262
- ssize_t count =
263
- read(fileFD, (uint8_t *)data.mutableBytes + offset, data.length - offset);
264
- if (count <= 0) {
265
- readSucceeded = NO;
266
- break;
267
- }
268
- offset += (NSUInteger)count;
269
- }
270
- struct stat openedInfo;
271
- if (fstat(fileFD, &openedInfo) != 0 || openedInfo.st_dev != pathInfo.st_dev
272
- || openedInfo.st_ino != pathInfo.st_ino || !S_ISREG(openedInfo.st_mode)) {
273
- readSucceeded = NO;
274
- }
275
- close(fileFD);
276
- if (!readSucceeded) {
277
- return @{ @"status" : @"storage_error" };
278
- }
279
-
280
- NSError *error = nil;
281
- id object = [NSPropertyListSerialization propertyListWithData:data
282
- options:NSPropertyListImmutable
283
- format:nil
284
- error:&error];
285
- NSDictionary *record =
286
- [object isKindOfClass:[NSDictionary class]]
287
- ? [self validatedRecord:(NSDictionary *)object requireZeroCounters:NO]
288
- : nil;
289
- if (record == nil) {
290
- return [self discardCorruptRecord:directoryFD];
291
- }
292
- return @{
293
- @"status" : @"pending",
294
- @"record" : record,
295
- };
296
- }
297
-
298
- - (NSDictionary *)discardCorruptRecord:(int)directoryFD
299
- {
300
- if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
301
- || fsync(directoryFD) != 0) {
302
- return @{ @"status" : @"storage_error" };
303
- }
304
- return @{
305
- @"status" : @"corrupt_discarded",
306
- @"corruptRecords" : @1,
307
- };
308
- }
309
-
310
- - (BOOL)removeStaleTemporaryFile:(int)directoryFD
311
- {
312
- struct stat info;
313
- if (fstatat(
314
- directoryFD,
315
- LBRNFatalRecordTemporaryFileName.UTF8String,
316
- &info,
317
- AT_SYMLINK_NOFOLLOW)
318
- != 0) {
319
- return errno == ENOENT;
320
- }
321
- if (!S_ISREG(info.st_mode) || S_ISLNK(info.st_mode)) {
322
- return NO;
323
- }
324
- return unlinkat(directoryFD, LBRNFatalRecordTemporaryFileName.UTF8String, 0) == 0;
325
- }
326
-
327
- - (BOOL)atomicallyWriteRecord:(NSDictionary *)record directoryFD:(int)directoryFD
328
- {
329
- NSError *serializationError = nil;
330
- NSData *data =
331
- [NSPropertyListSerialization dataWithPropertyList:record
332
- format:NSPropertyListBinaryFormat_v1_0
333
- options:0
334
- error:&serializationError];
335
- if (data == nil || data.length == 0 || data.length > LBRNMaximumRecordBytes) {
336
- return NO;
337
- }
338
- if (![self removeStaleTemporaryFile:directoryFD]) {
339
- return NO;
340
- }
341
-
342
- int fileFD = openat(
343
- directoryFD,
344
- LBRNFatalRecordTemporaryFileName.UTF8String,
345
- O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW,
346
- 0600);
347
- if (fileFD < 0) {
348
- return NO;
349
- }
350
- BOOL succeeded = fchmod(fileFD, 0600) == 0;
351
- NSUInteger offset = 0;
352
- while (succeeded && offset < data.length) {
353
- ssize_t count =
354
- write(fileFD, (const uint8_t *)data.bytes + offset, data.length - offset);
355
- if (count <= 0) {
356
- succeeded = NO;
357
- } else {
358
- offset += (NSUInteger)count;
359
- }
360
- }
361
- if (succeeded) {
362
- succeeded = fsync(fileFD) == 0;
363
- }
364
- if (close(fileFD) != 0) {
365
- succeeded = NO;
366
- }
367
- if (succeeded) {
368
- succeeded = renameat(
369
- directoryFD,
370
- LBRNFatalRecordTemporaryFileName.UTF8String,
371
- directoryFD,
372
- LBRNFatalRecordFileName.UTF8String)
373
- == 0;
374
- }
375
- if (succeeded) {
376
- succeeded = fsync(directoryFD) == 0;
377
- }
378
- if (!succeeded) {
379
- unlinkat(directoryFD, LBRNFatalRecordTemporaryFileName.UTF8String, 0);
380
- }
381
- return succeeded;
382
- }
383
-
384
- - (nullable NSDictionary *)validatedRecord:(NSDictionary *)record
385
- requireZeroCounters:(BOOL)requireZeroCounters
386
- {
387
- NSSet *expectedKeys = [NSSet setWithArray:@[
388
- @"schemaVersion",
389
- @"id",
390
- @"timestamp",
391
- @"errorName",
392
- @"stackFrames",
393
- @"droppedRecords",
394
- @"corruptRecords",
395
- ]];
396
- if (![expectedKeys isEqualToSet:[NSSet setWithArray:record.allKeys]]
397
- || ![record[@"schemaVersion"] isEqual:@1]
398
- || ![self validIdentifier:record[@"id"]]
399
- || ![self validTimestamp:record[@"timestamp"]]
400
- || ![self validErrorName:record[@"errorName"]]
401
- || ![record[@"stackFrames"] isKindOfClass:[NSArray class]]) {
402
- return nil;
403
- }
404
-
405
- NSArray *frames = record[@"stackFrames"];
406
- if (frames.count > LBRNMaximumFrames) {
407
- return nil;
408
- }
409
- NSMutableArray *validatedFrames = [NSMutableArray arrayWithCapacity:frames.count];
410
- for (id candidate in frames) {
411
- if (![candidate isKindOfClass:[NSDictionary class]]) {
412
- return nil;
413
- }
414
- NSDictionary *frame = candidate;
415
- NSSet *frameKeys =
416
- [NSSet setWithArray:@[ @"filename", @"line", @"column" ]];
417
- if (![frameKeys isEqualToSet:[NSSet setWithArray:frame.allKeys]]
418
- || ![self validFilename:frame[@"filename"]]
419
- || ![self validPositiveInteger:frame[@"line"]]
420
- || ![self validPositiveInteger:frame[@"column"]]) {
421
- return nil;
422
- }
423
- [validatedFrames addObject:@{
424
- @"filename" : frame[@"filename"],
425
- @"line" : frame[@"line"],
426
- @"column" : frame[@"column"],
427
- }];
428
- }
429
-
430
- NSNumber *dropped = record[@"droppedRecords"];
431
- NSNumber *corrupt = record[@"corruptRecords"];
432
- if (![self validCounter:dropped] || ![self validCounter:corrupt]
433
- || (requireZeroCounters && (dropped.intValue != 0 || corrupt.intValue != 0))) {
434
- return nil;
435
- }
436
- return @{
437
- @"schemaVersion" : @1,
438
- @"id" : record[@"id"],
439
- @"timestamp" : record[@"timestamp"],
440
- @"errorName" : record[@"errorName"],
441
- @"stackFrames" : validatedFrames,
442
- @"droppedRecords" : @(dropped.intValue),
443
- @"corruptRecords" : @(corrupt.intValue),
444
- };
445
- }
446
-
447
- - (BOOL)validIdentifier:(id)value
448
- {
449
- if (![value isKindOfClass:[NSString class]]
450
- || [(NSString *)value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > LBRNMaximumIdentifierBytes) {
451
- return NO;
452
- }
453
- NSRegularExpression *pattern =
454
- [NSRegularExpression regularExpressionWithPattern:@"^evt_rn_fatal_[a-z0-9]+(?:_[a-z0-9]+)*$"
455
- options:0
456
- error:nil];
457
- NSString *identifier = value;
458
- return [pattern firstMatchInString:identifier
459
- options:0
460
- range:NSMakeRange(0, identifier.length)]
461
- != nil;
462
- }
463
-
464
- - (BOOL)validTimestamp:(id)value
465
- {
466
- if (![value isKindOfClass:[NSString class]]) {
467
- return NO;
468
- }
469
- NSString *timestamp = value;
470
- if (timestamp.length < 20 || timestamp.length > 35) {
471
- return NO;
472
- }
473
- NSRegularExpression *pattern =
474
- [NSRegularExpression regularExpressionWithPattern:
475
- @"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?Z$"
476
- options:0
477
- error:nil];
478
- return [pattern firstMatchInString:timestamp
479
- options:0
480
- range:NSMakeRange(0, timestamp.length)]
481
- != nil;
482
- }
483
-
484
- - (BOOL)validErrorName:(id)value
485
- {
486
- static NSSet *allowedNames;
487
- static dispatch_once_t onceGuard;
488
- dispatch_once(&onceGuard, ^{
489
- allowedNames = [NSSet setWithArray:@[
490
- @"Error",
491
- @"EvalError",
492
- @"RangeError",
493
- @"ReferenceError",
494
- @"SyntaxError",
495
- @"TypeError",
496
- @"URIError",
497
- ]];
498
- });
499
- return [value isKindOfClass:[NSString class]] && [allowedNames containsObject:value];
500
- }
501
-
502
- - (BOOL)validFilename:(id)value
503
- {
504
- if (![value isKindOfClass:[NSString class]]) {
505
- return NO;
506
- }
507
- NSString *filename = value;
508
- if (filename.length == 0
509
- || [filename lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > LBRNMaximumFilenameBytes
510
- || [filename hasPrefix:@"/"] || [filename containsString:@"\\"]
511
- || [filename containsString:@"://"] || [filename containsString:@"?"]
512
- || [filename containsString:@"#"]) {
513
- return NO;
514
- }
515
- for (NSString *component in [filename componentsSeparatedByString:@"/"]) {
516
- if ([component isEqualToString:@".."]) {
517
- return NO;
518
- }
519
- }
520
- NSCharacterSet *control = [NSCharacterSet controlCharacterSet];
521
- return [filename rangeOfCharacterFromSet:control].location == NSNotFound;
522
- }
523
-
524
- - (BOOL)validPositiveInteger:(id)value
525
- {
526
- if (![value isKindOfClass:[NSNumber class]]) {
527
- return NO;
528
- }
529
- double number = [value doubleValue];
530
- return isfinite(number) && number >= 1 && number <= INT32_MAX && floor(number) == number;
531
- }
532
-
533
- - (BOOL)validCounter:(id)value
534
- {
535
- if (![value isKindOfClass:[NSNumber class]]) {
536
- return NO;
537
- }
538
- double number = [value doubleValue];
539
- return isfinite(number) && number >= 0 && number <= LBRNMaximumCounter && floor(number) == number;
540
- }
541
-
542
- @end