@logbrew/react-native 0.1.9 → 0.1.11
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 +111 -2
- package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
- package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
- package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- package/global-errors.cjs +4 -2
- package/global-errors.d.cts +63 -0
- package/global-errors.d.ts +63 -0
- package/global-errors.js +2 -1
- package/global-errors.native.js +63 -2
- package/index.cjs +2 -0
- package/index.d.cts +3 -0
- package/index.d.ts +3 -0
- package/index.js +2 -0
- package/index.native.d.ts +47 -2
- package/index.native.js +70 -2
- package/ios/LBRNEventRecordStore.h +29 -0
- package/ios/LBRNEventRecordStore.m +746 -0
- package/ios/LBRNFatalStoreModule.mm +159 -14
- package/ios/LBRNPrivateStorage.h +7 -0
- package/ios/LBRNPrivateStorage.m +38 -0
- package/package.json +7 -2
- package/persistent-delivery.native.js +282 -0
- package/promise-rejections.cjs +128 -4
- package/src/NativeLogBrewFatalStore.ts +9 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#import "LBRNFatalStoreModule.h"
|
|
2
2
|
|
|
3
|
+
#import "LBRNEventRecordStore.h"
|
|
3
4
|
#import "LBRNFatalRecordStore.h"
|
|
5
|
+
#import "LBRNPrivateStorage.h"
|
|
6
|
+
|
|
7
|
+
#import <CommonCrypto/CommonDigest.h>
|
|
8
|
+
#import <TargetConditionals.h>
|
|
4
9
|
|
|
5
10
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
6
11
|
#import <LogBrewReactNativeSpec/LogBrewReactNativeSpec.h>
|
|
@@ -11,6 +16,51 @@ static NSDictionary *LBRNStorageError(void)
|
|
|
11
16
|
return @{ @"status" : @"storage_error" };
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
static BOOL LBRNPrepareProtectedDirectory(NSURL *directoryURL)
|
|
20
|
+
{
|
|
21
|
+
NSError *writeError = nil;
|
|
22
|
+
if (![directoryURL setResourceValue:@YES
|
|
23
|
+
forKey:NSURLIsExcludedFromBackupKey
|
|
24
|
+
error:&writeError]
|
|
25
|
+
|| writeError != nil) {
|
|
26
|
+
return NO;
|
|
27
|
+
}
|
|
28
|
+
#if TARGET_OS_IPHONE
|
|
29
|
+
writeError = nil;
|
|
30
|
+
if (![directoryURL setResourceValue:NSFileProtectionCompleteUntilFirstUserAuthentication
|
|
31
|
+
forKey:NSURLFileProtectionKey
|
|
32
|
+
error:&writeError]
|
|
33
|
+
|| writeError != nil) {
|
|
34
|
+
return NO;
|
|
35
|
+
}
|
|
36
|
+
#endif
|
|
37
|
+
NSNumber *excluded = nil;
|
|
38
|
+
NSError *readError = nil;
|
|
39
|
+
return [directoryURL getResourceValue:&excluded
|
|
40
|
+
forKey:NSURLIsExcludedFromBackupKey
|
|
41
|
+
error:&readError]
|
|
42
|
+
&& readError == nil && excluded.boolValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static NSString *LBRNQueueHash(NSString *queueKey)
|
|
46
|
+
{
|
|
47
|
+
if (![queueKey isKindOfClass:[NSString class]]
|
|
48
|
+
|| [queueKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length == 0) {
|
|
49
|
+
return nil;
|
|
50
|
+
}
|
|
51
|
+
NSData *data = [queueKey dataUsingEncoding:NSUTF8StringEncoding];
|
|
52
|
+
if (data.length == 0 || data.length > 4096) {
|
|
53
|
+
return nil;
|
|
54
|
+
}
|
|
55
|
+
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
|
56
|
+
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
|
|
57
|
+
NSMutableString *value = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
|
58
|
+
for (NSUInteger index = 0; index < CC_SHA256_DIGEST_LENGTH; index += 1) {
|
|
59
|
+
[value appendFormat:@"%02x", digest[index]];
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
14
64
|
static NSDictionary *LBRNNormalizeRecord(NSDictionary *record)
|
|
15
65
|
{
|
|
16
66
|
if (![record isKindOfClass:[NSDictionary class]]) {
|
|
@@ -44,6 +94,8 @@ static NSDictionary *LBRNNormalizeRecord(NSDictionary *record)
|
|
|
44
94
|
<NativeLogBrewFatalStoreSpec>
|
|
45
95
|
#endif
|
|
46
96
|
@property (nonatomic, nullable) LBRNFatalRecordStore *store;
|
|
97
|
+
@property (nonatomic, nullable) NSURL *eventStoreParentURL;
|
|
98
|
+
@property (nonatomic) NSMutableDictionary<NSString *, LBRNEventRecordStore *> *eventStores;
|
|
47
99
|
@end
|
|
48
100
|
|
|
49
101
|
@implementation LBRNFatalStoreModule
|
|
@@ -62,23 +114,13 @@ RCT_EXPORT_MODULE(LogBrewFatalStore)
|
|
|
62
114
|
NSURL *baseURL =
|
|
63
115
|
[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
|
64
116
|
inDomains:NSUserDomainMask].firstObject;
|
|
65
|
-
if (baseURL != nil) {
|
|
117
|
+
if (baseURL != nil && LBRNPreparePrivateRootDirectory(baseURL)) {
|
|
118
|
+
_eventStoreParentURL = baseURL;
|
|
119
|
+
_eventStores = [NSMutableDictionary dictionary];
|
|
66
120
|
NSURL *directoryURL = [baseURL URLByAppendingPathComponent:@"LogBrewFatalJS"
|
|
67
121
|
isDirectory:YES];
|
|
68
122
|
LBRNFatalDirectoryPreparation directoryPreparation = ^BOOL(NSURL *preparedURL) {
|
|
69
|
-
|
|
70
|
-
if (![preparedURL setResourceValue:@YES
|
|
71
|
-
forKey:NSURLIsExcludedFromBackupKey
|
|
72
|
-
error:&writeError]
|
|
73
|
-
|| writeError != nil) {
|
|
74
|
-
return NO;
|
|
75
|
-
}
|
|
76
|
-
NSNumber *excluded = nil;
|
|
77
|
-
NSError *readError = nil;
|
|
78
|
-
return [preparedURL getResourceValue:&excluded
|
|
79
|
-
forKey:NSURLIsExcludedFromBackupKey
|
|
80
|
-
error:&readError]
|
|
81
|
-
&& readError == nil && excluded.boolValue;
|
|
123
|
+
return LBRNPrepareProtectedDirectory(preparedURL);
|
|
82
124
|
};
|
|
83
125
|
_store = [[LBRNFatalRecordStore alloc]
|
|
84
126
|
initWithDirectoryURL:directoryURL
|
|
@@ -88,6 +130,30 @@ RCT_EXPORT_MODULE(LogBrewFatalStore)
|
|
|
88
130
|
return self;
|
|
89
131
|
}
|
|
90
132
|
|
|
133
|
+
- (nullable LBRNEventRecordStore *)eventStoreForQueueKey:(NSString *)queueKey
|
|
134
|
+
{
|
|
135
|
+
NSString *queueHash = LBRNQueueHash(queueKey);
|
|
136
|
+
if (queueHash == nil || self.eventStoreParentURL == nil) {
|
|
137
|
+
return nil;
|
|
138
|
+
}
|
|
139
|
+
@synchronized(self) {
|
|
140
|
+
LBRNEventRecordStore *existing = self.eventStores[queueHash];
|
|
141
|
+
if (existing != nil) {
|
|
142
|
+
return existing;
|
|
143
|
+
}
|
|
144
|
+
NSURL *directoryURL = [self.eventStoreParentURL
|
|
145
|
+
URLByAppendingPathComponent:[@"LogBrewEventsV1-" stringByAppendingString:queueHash]
|
|
146
|
+
isDirectory:YES];
|
|
147
|
+
LBRNEventRecordStore *created = [[LBRNEventRecordStore alloc]
|
|
148
|
+
initWithDirectoryURL:directoryURL
|
|
149
|
+
directoryPreparation:^BOOL(NSURL *preparedURL) {
|
|
150
|
+
return LBRNPrepareProtectedDirectory(preparedURL);
|
|
151
|
+
}];
|
|
152
|
+
self.eventStores[queueHash] = created;
|
|
153
|
+
return created;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
91
157
|
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(writeFatalRecord:(NSDictionary *)record)
|
|
92
158
|
{
|
|
93
159
|
if (self.store == nil) {
|
|
@@ -136,6 +202,85 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(discardFatalRecord)
|
|
|
136
202
|
}
|
|
137
203
|
}
|
|
138
204
|
|
|
205
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(loadEventRecords:(NSString *)queueKey)
|
|
206
|
+
{
|
|
207
|
+
LBRNEventRecordStore *eventStore = [self eventStoreForQueueKey:queueKey];
|
|
208
|
+
if (eventStore == nil) {
|
|
209
|
+
return LBRNStorageError();
|
|
210
|
+
}
|
|
211
|
+
@try {
|
|
212
|
+
return [eventStore loadRecords];
|
|
213
|
+
} @catch (__unused NSException *exception) {
|
|
214
|
+
return LBRNStorageError();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(appendEventRecord:(NSString *)queueKey
|
|
219
|
+
serializedEvent:(NSString *)serializedEvent
|
|
220
|
+
eventBytes:(double)eventBytes)
|
|
221
|
+
{
|
|
222
|
+
LBRNEventRecordStore *eventStore = [self eventStoreForQueueKey:queueKey];
|
|
223
|
+
if (eventStore == nil) {
|
|
224
|
+
return LBRNStorageError();
|
|
225
|
+
}
|
|
226
|
+
@try {
|
|
227
|
+
return [eventStore appendSerializedEvent:serializedEvent eventBytes:@(eventBytes)];
|
|
228
|
+
} @catch (__unused NSException *exception) {
|
|
229
|
+
return LBRNStorageError();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(acknowledgeEventRecords:(NSString *)queueKey
|
|
234
|
+
count:(double)count)
|
|
235
|
+
{
|
|
236
|
+
LBRNEventRecordStore *eventStore = [self eventStoreForQueueKey:queueKey];
|
|
237
|
+
if (eventStore == nil) {
|
|
238
|
+
return LBRNStorageError();
|
|
239
|
+
}
|
|
240
|
+
@try {
|
|
241
|
+
return [eventStore acknowledgeRecordCount:@(count)];
|
|
242
|
+
} @catch (__unused NSException *exception) {
|
|
243
|
+
return LBRNStorageError();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(purgeEventRecords:(NSString *)queueKey)
|
|
248
|
+
{
|
|
249
|
+
LBRNEventRecordStore *eventStore = [self eventStoreForQueueKey:queueKey];
|
|
250
|
+
if (eventStore == nil) {
|
|
251
|
+
return LBRNStorageError();
|
|
252
|
+
}
|
|
253
|
+
@try {
|
|
254
|
+
return [eventStore purgeRecords];
|
|
255
|
+
} @catch (__unused NSException *exception) {
|
|
256
|
+
return LBRNStorageError();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(closeEventStore:(NSString *)queueKey)
|
|
261
|
+
{
|
|
262
|
+
NSString *queueHash = LBRNQueueHash(queueKey);
|
|
263
|
+
if (queueHash == nil) {
|
|
264
|
+
return LBRNStorageError();
|
|
265
|
+
}
|
|
266
|
+
LBRNEventRecordStore *eventStore = nil;
|
|
267
|
+
@synchronized(self) {
|
|
268
|
+
eventStore = self.eventStores[queueHash];
|
|
269
|
+
}
|
|
270
|
+
if (eventStore == nil) {
|
|
271
|
+
return @{ @"status" : @"closed" };
|
|
272
|
+
}
|
|
273
|
+
@try {
|
|
274
|
+
return [eventStore closeStore];
|
|
275
|
+
} @catch (__unused NSException *exception) {
|
|
276
|
+
return LBRNStorageError();
|
|
277
|
+
} @finally {
|
|
278
|
+
@synchronized(self) {
|
|
279
|
+
[self.eventStores removeObjectForKey:queueHash];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
139
284
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
140
285
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
141
286
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#import "LBRNPrivateStorage.h"
|
|
2
|
+
|
|
3
|
+
#import <errno.h>
|
|
4
|
+
#import <fcntl.h>
|
|
5
|
+
#import <sys/stat.h>
|
|
6
|
+
#import <unistd.h>
|
|
7
|
+
|
|
8
|
+
BOOL LBRNPreparePrivateRootDirectory(NSURL *directoryURL)
|
|
9
|
+
{
|
|
10
|
+
if (!directoryURL.isFileURL) {
|
|
11
|
+
return NO;
|
|
12
|
+
}
|
|
13
|
+
const char *path = directoryURL.fileSystemRepresentation;
|
|
14
|
+
struct stat pathInfo;
|
|
15
|
+
if (lstat(path, &pathInfo) != 0) {
|
|
16
|
+
if (errno != ENOENT || mkdir(path, 0700) != 0) {
|
|
17
|
+
return NO;
|
|
18
|
+
}
|
|
19
|
+
} else if (!S_ISDIR(pathInfo.st_mode) || S_ISLNK(pathInfo.st_mode)) {
|
|
20
|
+
return NO;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
int directoryFD = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
|
|
24
|
+
if (directoryFD < 0) {
|
|
25
|
+
return NO;
|
|
26
|
+
}
|
|
27
|
+
struct stat openedInfo;
|
|
28
|
+
BOOL prepared = fstat(directoryFD, &openedInfo) == 0
|
|
29
|
+
&& S_ISDIR(openedInfo.st_mode)
|
|
30
|
+
&& fchmod(directoryFD, 0700) == 0
|
|
31
|
+
&& lstat(path, &pathInfo) == 0
|
|
32
|
+
&& S_ISDIR(pathInfo.st_mode)
|
|
33
|
+
&& !S_ISLNK(pathInfo.st_mode)
|
|
34
|
+
&& openedInfo.st_dev == pathInfo.st_dev
|
|
35
|
+
&& openedInfo.st_ino == pathInfo.st_ino;
|
|
36
|
+
close(directoryFD);
|
|
37
|
+
return prepared;
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/react-native",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "React Native screen, error, trace, action, and network timeline helpers for LogBrew.",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"description": "React Native offline delivery, screen, error, trace, action, and network timeline helpers for LogBrew.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"module": "./index.js",
|
|
@@ -138,6 +138,7 @@
|
|
|
138
138
|
"global-errors.cjs",
|
|
139
139
|
"promise-rejections.cjs",
|
|
140
140
|
"fatal-replay.cjs",
|
|
141
|
+
"persistent-delivery.native.js",
|
|
141
142
|
"global-errors.js",
|
|
142
143
|
"global-errors.native.js",
|
|
143
144
|
"global-errors.d.ts",
|
|
@@ -169,6 +170,10 @@
|
|
|
169
170
|
"ios/LBRNFatalRecordStore.m",
|
|
170
171
|
"ios/LBRNFatalStoreModule.h",
|
|
171
172
|
"ios/LBRNFatalStoreModule.mm",
|
|
173
|
+
"ios/LBRNPrivateStorage.h",
|
|
174
|
+
"ios/LBRNPrivateStorage.m",
|
|
175
|
+
"ios/LBRNEventRecordStore.h",
|
|
176
|
+
"ios/LBRNEventRecordStore.m",
|
|
172
177
|
"react-native.config.js",
|
|
173
178
|
"src",
|
|
174
179
|
"README.md",
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { SdkError } from "@logbrew/sdk";
|
|
2
|
+
import { NativeModules, TurboModuleRegistry } from "react-native";
|
|
3
|
+
|
|
4
|
+
const ACTIVE_QUEUE_KEYS = new Set();
|
|
5
|
+
const MAX_NATIVE_QUEUE_BYTES = 4 * 1024 * 1024;
|
|
6
|
+
const MAX_NATIVE_QUEUE_EVENTS = 1000;
|
|
7
|
+
const NATIVE_STORE_METHODS = [
|
|
8
|
+
"acknowledgeEventRecords",
|
|
9
|
+
"appendEventRecord",
|
|
10
|
+
"closeEventStore",
|
|
11
|
+
"loadEventRecords",
|
|
12
|
+
"purgeEventRecords"
|
|
13
|
+
];
|
|
14
|
+
const PERSISTENT_QUEUE_MODES = new Set(["auto", "disabled", "required"]);
|
|
15
|
+
|
|
16
|
+
export function resolveReactNativePersistentEventStore({
|
|
17
|
+
authKey,
|
|
18
|
+
eventStore,
|
|
19
|
+
hasExplicitPersistentQueue = false,
|
|
20
|
+
maxQueueBytes,
|
|
21
|
+
maxQueueSize,
|
|
22
|
+
persistentQueue = "auto"
|
|
23
|
+
}) {
|
|
24
|
+
validatePersistentQueueMode(persistentQueue);
|
|
25
|
+
if (eventStore !== undefined) {
|
|
26
|
+
if (hasExplicitPersistentQueue) {
|
|
27
|
+
throw new SdkError(
|
|
28
|
+
"configuration_error",
|
|
29
|
+
"eventStore and persistentQueue are mutually exclusive"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return { eventStore, abort() {}, release() {} };
|
|
33
|
+
}
|
|
34
|
+
if (persistentQueue === "disabled") {
|
|
35
|
+
return { eventStore: undefined, abort() {}, release() {} };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nativeStore = defaultNativeStore();
|
|
39
|
+
if (!nativeStore) {
|
|
40
|
+
if (persistentQueue === "required") {
|
|
41
|
+
throw new SdkError(
|
|
42
|
+
"configuration_error",
|
|
43
|
+
"React Native persistent queue requires the linked LogBrew native module"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return { eventStore: undefined, abort() {}, release() {} };
|
|
47
|
+
}
|
|
48
|
+
validateNativeQueueLimits({ maxQueueBytes, maxQueueSize });
|
|
49
|
+
if (ACTIVE_QUEUE_KEYS.has(authKey)) {
|
|
50
|
+
throw new SdkError(
|
|
51
|
+
"configuration_error",
|
|
52
|
+
"React Native persistent queue already has an active client for this key"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ACTIVE_QUEUE_KEYS.add(authKey);
|
|
57
|
+
let released = false;
|
|
58
|
+
const release = () => {
|
|
59
|
+
if (!released) {
|
|
60
|
+
released = true;
|
|
61
|
+
ACTIVE_QUEUE_KEYS.delete(authKey);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const nativeEventStore = createNativeEventStore(nativeStore, authKey, release);
|
|
65
|
+
return {
|
|
66
|
+
eventStore: nativeEventStore,
|
|
67
|
+
abort() {
|
|
68
|
+
try {
|
|
69
|
+
nativeEventStore.close();
|
|
70
|
+
} catch {
|
|
71
|
+
release();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
release
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function purgeReactNativePersistentQueue({ apiKey, clientKey } = {}) {
|
|
79
|
+
const authKey = clientKey ?? apiKey;
|
|
80
|
+
if (typeof authKey !== "string" || authKey.trim() === "") {
|
|
81
|
+
throw new SdkError(
|
|
82
|
+
"configuration_error",
|
|
83
|
+
"purgeLogBrewReactNativePersistentQueue requires clientKey or apiKey"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (ACTIVE_QUEUE_KEYS.has(authKey)) {
|
|
87
|
+
throw new SdkError(
|
|
88
|
+
"persistence_error",
|
|
89
|
+
"cannot purge a React Native persistent queue while its client is active"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const nativeStore = defaultNativeStore();
|
|
93
|
+
if (!nativeStore) {
|
|
94
|
+
throw new SdkError(
|
|
95
|
+
"configuration_error",
|
|
96
|
+
"React Native persistent queue requires the linked LogBrew native module"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
let failure;
|
|
100
|
+
try {
|
|
101
|
+
requireStatus(
|
|
102
|
+
"purge",
|
|
103
|
+
callNative(nativeStore, "purgeEventRecords", authKey),
|
|
104
|
+
"purged"
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
failure = error;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
requireStatus(
|
|
111
|
+
"close",
|
|
112
|
+
callNative(nativeStore, "closeEventStore", authKey),
|
|
113
|
+
"closed"
|
|
114
|
+
);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
failure ??= error;
|
|
117
|
+
}
|
|
118
|
+
if (failure) {
|
|
119
|
+
throw failure;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createNativeEventStore(nativeStore, authKey, release) {
|
|
124
|
+
return {
|
|
125
|
+
load() {
|
|
126
|
+
const result = requireStatus(
|
|
127
|
+
"load",
|
|
128
|
+
callNative(nativeStore, "loadEventRecords", authKey),
|
|
129
|
+
"loaded"
|
|
130
|
+
);
|
|
131
|
+
if (!Array.isArray(result.records)) {
|
|
132
|
+
throw persistenceFailure("load");
|
|
133
|
+
}
|
|
134
|
+
return result.records.map((record) => {
|
|
135
|
+
if (!record
|
|
136
|
+
|| Array.isArray(record)
|
|
137
|
+
|| typeof record !== "object"
|
|
138
|
+
|| typeof record.serializedEvent !== "string"
|
|
139
|
+
|| !Number.isSafeInteger(record.eventBytes)
|
|
140
|
+
|| record.eventBytes <= 0) {
|
|
141
|
+
throw persistenceFailure("load");
|
|
142
|
+
}
|
|
143
|
+
let event;
|
|
144
|
+
try {
|
|
145
|
+
event = JSON.parse(record.serializedEvent);
|
|
146
|
+
} catch {
|
|
147
|
+
throw persistenceFailure("load");
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
event,
|
|
151
|
+
eventBytes: record.eventBytes,
|
|
152
|
+
serializedEvent: record.serializedEvent
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
append(record) {
|
|
157
|
+
requireStatus(
|
|
158
|
+
"append",
|
|
159
|
+
callNative(
|
|
160
|
+
nativeStore,
|
|
161
|
+
"appendEventRecord",
|
|
162
|
+
authKey,
|
|
163
|
+
record.serializedEvent,
|
|
164
|
+
record.eventBytes
|
|
165
|
+
),
|
|
166
|
+
"appended"
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
acknowledge(count) {
|
|
170
|
+
requireStatus(
|
|
171
|
+
"acknowledge",
|
|
172
|
+
callNative(nativeStore, "acknowledgeEventRecords", authKey, count),
|
|
173
|
+
"acknowledged"
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
purge() {
|
|
177
|
+
requireStatus(
|
|
178
|
+
"purge",
|
|
179
|
+
callNative(nativeStore, "purgeEventRecords", authKey),
|
|
180
|
+
"purged"
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
close() {
|
|
184
|
+
try {
|
|
185
|
+
requireStatus(
|
|
186
|
+
"close",
|
|
187
|
+
callNative(nativeStore, "closeEventStore", authKey),
|
|
188
|
+
"closed"
|
|
189
|
+
);
|
|
190
|
+
} finally {
|
|
191
|
+
release();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function defaultNativeStore() {
|
|
198
|
+
try {
|
|
199
|
+
const nativeStore = TurboModuleRegistry?.get?.("LogBrewFatalStore")
|
|
200
|
+
?? NativeModules?.LogBrewFatalStore;
|
|
201
|
+
return NATIVE_STORE_METHODS.every((method) => typeof nativeStore?.[method] === "function")
|
|
202
|
+
? nativeStore
|
|
203
|
+
: undefined;
|
|
204
|
+
} catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function validateNativeQueueLimits({ maxQueueBytes, maxQueueSize }) {
|
|
210
|
+
if (maxQueueBytes !== undefined
|
|
211
|
+
&& (!Number.isSafeInteger(maxQueueBytes)
|
|
212
|
+
|| maxQueueBytes <= 0
|
|
213
|
+
|| maxQueueBytes > MAX_NATIVE_QUEUE_BYTES)) {
|
|
214
|
+
throw new SdkError(
|
|
215
|
+
"configuration_error",
|
|
216
|
+
`persistent React Native maxQueueBytes must be at most ${MAX_NATIVE_QUEUE_BYTES}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (maxQueueSize !== undefined
|
|
220
|
+
&& (!Number.isSafeInteger(maxQueueSize)
|
|
221
|
+
|| maxQueueSize <= 0
|
|
222
|
+
|| maxQueueSize > MAX_NATIVE_QUEUE_EVENTS)) {
|
|
223
|
+
throw new SdkError(
|
|
224
|
+
"configuration_error",
|
|
225
|
+
`persistent React Native maxQueueSize must be at most ${MAX_NATIVE_QUEUE_EVENTS}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function validatePersistentQueueMode(mode) {
|
|
231
|
+
if (!PERSISTENT_QUEUE_MODES.has(mode)) {
|
|
232
|
+
throw new SdkError(
|
|
233
|
+
"configuration_error",
|
|
234
|
+
"persistentQueue must be auto, required, or disabled"
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function callNative(nativeStore, method, ...args) {
|
|
240
|
+
if (typeof nativeStore?.[method] !== "function") {
|
|
241
|
+
throw persistenceFailure(methodName(method));
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
return nativeStore[method](...args);
|
|
245
|
+
} catch {
|
|
246
|
+
throw persistenceFailure(methodName(method));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function requireStatus(operation, result, expected) {
|
|
251
|
+
if (!result
|
|
252
|
+
|| Array.isArray(result)
|
|
253
|
+
|| typeof result !== "object"
|
|
254
|
+
|| result.status !== expected) {
|
|
255
|
+
throw persistenceFailure(operation);
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function methodName(method) {
|
|
261
|
+
switch (method) {
|
|
262
|
+
case "loadEventRecords":
|
|
263
|
+
return "load";
|
|
264
|
+
case "appendEventRecord":
|
|
265
|
+
return "append";
|
|
266
|
+
case "acknowledgeEventRecords":
|
|
267
|
+
return "acknowledge";
|
|
268
|
+
case "purgeEventRecords":
|
|
269
|
+
return "purge";
|
|
270
|
+
case "closeEventStore":
|
|
271
|
+
return "close";
|
|
272
|
+
default:
|
|
273
|
+
return "operation";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function persistenceFailure(operation) {
|
|
278
|
+
return new SdkError(
|
|
279
|
+
"persistence_error",
|
|
280
|
+
`React Native persistent queue ${operation} failed`
|
|
281
|
+
);
|
|
282
|
+
}
|