@logbrew/react-native 0.1.10 → 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.
@@ -9,10 +9,15 @@ import com.facebook.react.bridge.ReadableType;
9
9
  import com.facebook.react.bridge.WritableArray;
10
10
  import com.facebook.react.bridge.WritableMap;
11
11
  import java.io.File;
12
+ import java.nio.charset.StandardCharsets;
13
+ import java.security.MessageDigest;
14
+ import java.security.NoSuchAlgorithmException;
12
15
  import java.util.ArrayList;
13
16
  import java.util.Arrays;
17
+ import java.util.HashMap;
14
18
  import java.util.HashSet;
15
19
  import java.util.List;
20
+ import java.util.Map;
16
21
  import java.util.Set;
17
22
 
18
23
  final class FatalStoreModuleImpl {
@@ -32,9 +37,12 @@ final class FatalStoreModuleImpl {
32
37
  new HashSet<>(Arrays.asList("filename", "line", "column"));
33
38
 
34
39
  private final FatalRecordStore store;
40
+ private final File eventStoreParent;
41
+ private final Map<String, EventRecordStore> eventStores = new HashMap<>();
35
42
 
36
43
  FatalStoreModuleImpl(ReactApplicationContext context) {
37
44
  File root = context.getNoBackupFilesDir();
45
+ eventStoreParent = root;
38
46
  store =
39
47
  root == null
40
48
  ? null
@@ -43,6 +51,66 @@ final class FatalStoreModuleImpl {
43
51
  new AndroidParentDirectorySync());
44
52
  }
45
53
 
54
+ WritableMap loadEventRecords(String queueKey) {
55
+ try {
56
+ EventRecordStore eventStore = eventStore(queueKey);
57
+ return eventStore == null ? status("storage_error") : eventResultMap(eventStore.load());
58
+ } catch (RuntimeException error) {
59
+ return status("storage_error");
60
+ }
61
+ }
62
+
63
+ WritableMap appendEventRecord(String queueKey, String serializedEvent, double eventBytes) {
64
+ try {
65
+ EventRecordStore eventStore = eventStore(queueKey);
66
+ Integer byteCount = integer(eventBytes);
67
+ return eventStore == null || byteCount == null
68
+ ? status("storage_error")
69
+ : eventResultMap(eventStore.append(serializedEvent, byteCount));
70
+ } catch (RuntimeException error) {
71
+ return status("storage_error");
72
+ }
73
+ }
74
+
75
+ WritableMap acknowledgeEventRecords(String queueKey, double count) {
76
+ try {
77
+ EventRecordStore eventStore = eventStore(queueKey);
78
+ Integer recordCount = integer(count);
79
+ return eventStore == null || recordCount == null
80
+ ? status("storage_error")
81
+ : eventResultMap(eventStore.acknowledge(recordCount));
82
+ } catch (RuntimeException error) {
83
+ return status("storage_error");
84
+ }
85
+ }
86
+
87
+ WritableMap purgeEventRecords(String queueKey) {
88
+ try {
89
+ EventRecordStore eventStore = eventStore(queueKey);
90
+ return eventStore == null ? status("storage_error") : eventResultMap(eventStore.purge());
91
+ } catch (RuntimeException error) {
92
+ return status("storage_error");
93
+ }
94
+ }
95
+
96
+ synchronized WritableMap closeEventStore(String queueKey) {
97
+ String queueHash = queueHash(queueKey);
98
+ if (queueHash == null) {
99
+ return status("storage_error");
100
+ }
101
+ EventRecordStore eventStore = eventStores.get(queueHash);
102
+ if (eventStore == null) {
103
+ return status("closed");
104
+ }
105
+ try {
106
+ return eventResultMap(eventStore.close());
107
+ } catch (RuntimeException error) {
108
+ return status("storage_error");
109
+ } finally {
110
+ eventStores.remove(queueHash);
111
+ }
112
+ }
113
+
46
114
  WritableMap writeFatalRecord(ReadableMap input) {
47
115
  if (store == null) {
48
116
  return status("storage_error");
@@ -227,4 +295,65 @@ final class FatalStoreModuleImpl {
227
295
  output.putString("status", value);
228
296
  return output;
229
297
  }
298
+
299
+ private synchronized EventRecordStore eventStore(String queueKey) {
300
+ String queueHash = queueHash(queueKey);
301
+ if (eventStoreParent == null || queueHash == null) {
302
+ return null;
303
+ }
304
+ EventRecordStore existing = eventStores.get(queueHash);
305
+ if (existing != null) {
306
+ return existing;
307
+ }
308
+ EventRecordStore created =
309
+ new EventRecordStore(
310
+ new File(eventStoreParent, "logbrew-events-v1-" + queueHash),
311
+ new AndroidParentDirectorySync());
312
+ eventStores.put(queueHash, created);
313
+ return created;
314
+ }
315
+
316
+ private static String queueHash(String queueKey) {
317
+ if (queueKey == null || queueKey.trim().isEmpty()) {
318
+ return null;
319
+ }
320
+ byte[] keyBytes = queueKey.getBytes(StandardCharsets.UTF_8);
321
+ if (keyBytes.length == 0 || keyBytes.length > 4096) {
322
+ return null;
323
+ }
324
+ try {
325
+ byte[] digest = MessageDigest.getInstance("SHA-256").digest(keyBytes);
326
+ StringBuilder value = new StringBuilder(digest.length * 2);
327
+ for (byte item : digest) {
328
+ value.append(String.format(java.util.Locale.ROOT, "%02x", item & 0xff));
329
+ }
330
+ return value.toString();
331
+ } catch (NoSuchAlgorithmException impossible) {
332
+ return null;
333
+ }
334
+ }
335
+
336
+ private static Integer integer(double value) {
337
+ return Double.isFinite(value)
338
+ && value >= 0
339
+ && value <= Integer.MAX_VALUE
340
+ && value == Math.rint(value)
341
+ ? (int) value
342
+ : null;
343
+ }
344
+
345
+ private static WritableMap eventResultMap(EventRecordStore.Result result) {
346
+ WritableMap output = status(result.status);
347
+ if ("loaded".equals(result.status)) {
348
+ WritableArray records = Arguments.createArray();
349
+ for (EventRecordStore.Record record : result.records) {
350
+ WritableMap value = Arguments.createMap();
351
+ value.putString("serializedEvent", record.serializedEvent);
352
+ value.putInt("eventBytes", record.eventBytes);
353
+ records.pushMap(value);
354
+ }
355
+ output.putArray("records", records);
356
+ }
357
+ return output;
358
+ }
230
359
  }
@@ -36,4 +36,30 @@ final class FatalStoreModule extends NativeLogBrewFatalStoreSpec {
36
36
  public WritableMap discardFatalRecord() {
37
37
  return implementation.discardFatalRecord();
38
38
  }
39
+
40
+ @Override
41
+ public WritableMap loadEventRecords(String queueKey) {
42
+ return implementation.loadEventRecords(queueKey);
43
+ }
44
+
45
+ @Override
46
+ public WritableMap appendEventRecord(
47
+ String queueKey, String serializedEvent, double eventBytes) {
48
+ return implementation.appendEventRecord(queueKey, serializedEvent, eventBytes);
49
+ }
50
+
51
+ @Override
52
+ public WritableMap acknowledgeEventRecords(String queueKey, double count) {
53
+ return implementation.acknowledgeEventRecords(queueKey, count);
54
+ }
55
+
56
+ @Override
57
+ public WritableMap purgeEventRecords(String queueKey) {
58
+ return implementation.purgeEventRecords(queueKey);
59
+ }
60
+
61
+ @Override
62
+ public WritableMap closeEventStore(String queueKey) {
63
+ return implementation.closeEventStore(queueKey);
64
+ }
39
65
  }
@@ -38,4 +38,30 @@ final class FatalStoreModule extends ReactContextBaseJavaModule {
38
38
  public WritableMap discardFatalRecord() {
39
39
  return implementation.discardFatalRecord();
40
40
  }
41
+
42
+ @ReactMethod(isBlockingSynchronousMethod = true)
43
+ public WritableMap loadEventRecords(String queueKey) {
44
+ return implementation.loadEventRecords(queueKey);
45
+ }
46
+
47
+ @ReactMethod(isBlockingSynchronousMethod = true)
48
+ public WritableMap appendEventRecord(
49
+ String queueKey, String serializedEvent, double eventBytes) {
50
+ return implementation.appendEventRecord(queueKey, serializedEvent, eventBytes);
51
+ }
52
+
53
+ @ReactMethod(isBlockingSynchronousMethod = true)
54
+ public WritableMap acknowledgeEventRecords(String queueKey, double count) {
55
+ return implementation.acknowledgeEventRecords(queueKey, count);
56
+ }
57
+
58
+ @ReactMethod(isBlockingSynchronousMethod = true)
59
+ public WritableMap purgeEventRecords(String queueKey) {
60
+ return implementation.purgeEventRecords(queueKey);
61
+ }
62
+
63
+ @ReactMethod(isBlockingSynchronousMethod = true)
64
+ public WritableMap closeEventStore(String queueKey) {
65
+ return implementation.closeEventStore(queueKey);
66
+ }
41
67
  }
package/index.cjs CHANGED
@@ -29,6 +29,7 @@ function createLogBrewReactNativeClient({
29
29
  clientKey,
30
30
  deliveryIntervalMs,
31
31
  deliveryQueueThreshold,
32
+ eventStore,
32
33
  maxBatchBytes,
33
34
  maxBatchEvents,
34
35
  maxQueueBytes,
@@ -48,6 +49,7 @@ function createLogBrewReactNativeClient({
48
49
  automaticDelivery,
49
50
  deliveryIntervalMs,
50
51
  deliveryQueueThreshold,
52
+ eventStore,
51
53
  maxBatchBytes,
52
54
  maxBatchEvents,
53
55
  maxQueueBytes,
package/index.d.cts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  DeliveryHealthSnapshot,
5
5
  DroppedEvent,
6
6
  EnvironmentAttributes,
7
+ EventStore,
7
8
  IssueAttributes,
8
9
  LogAttributes,
9
10
  LogBrewClient,
@@ -37,6 +38,8 @@ export type CreateLogBrewReactNativeClientConfig = {
37
38
  clientKey?: string;
38
39
  deliveryIntervalMs?: number;
39
40
  deliveryQueueThreshold?: number;
41
+ /** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
42
+ eventStore?: EventStore;
40
43
  maxBatchBytes?: number;
41
44
  maxBatchEvents?: number;
42
45
  maxQueueBytes?: number;
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  DeliveryHealthSnapshot,
5
5
  DroppedEvent,
6
6
  EnvironmentAttributes,
7
+ EventStore,
7
8
  IssueAttributes,
8
9
  LogAttributes,
9
10
  LogBrewClient,
@@ -37,6 +38,8 @@ export type CreateLogBrewReactNativeClientConfig = {
37
38
  clientKey?: string;
38
39
  deliveryIntervalMs?: number;
39
40
  deliveryQueueThreshold?: number;
41
+ /** Advanced synchronous persistence seam. React Native apps normally use the native entry's persistentQueue mode. */
42
+ eventStore?: EventStore;
40
43
  maxBatchBytes?: number;
41
44
  maxBatchEvents?: number;
42
45
  maxQueueBytes?: number;
package/index.js CHANGED
@@ -29,6 +29,7 @@ export function createLogBrewReactNativeClient({
29
29
  clientKey,
30
30
  deliveryIntervalMs,
31
31
  deliveryQueueThreshold,
32
+ eventStore,
32
33
  maxBatchBytes,
33
34
  maxBatchEvents,
34
35
  maxQueueBytes,
@@ -48,6 +49,7 @@ export function createLogBrewReactNativeClient({
48
49
  automaticDelivery,
49
50
  deliveryIntervalMs,
50
51
  deliveryQueueThreshold,
52
+ eventStore,
51
53
  maxBatchBytes,
52
54
  maxBatchEvents,
53
55
  maxQueueBytes,
package/index.native.d.ts CHANGED
@@ -1,5 +1,47 @@
1
- export * from "./index";
1
+ import type { EventStore, LogBrewClient } from "@logbrew/sdk";
2
+ import type { CreateLogBrewReactNativeClientConfig } from "./index.js";
3
+
4
+ export * from "./index.js";
2
5
  export {
3
6
  installLogBrewReactNativeGlobalErrorHandler,
4
7
  installLogBrewReactNativePromiseRejectionTracker
5
8
  } from "./global-errors";
9
+
10
+ export type ReactNativePersistentQueueMode = "auto" | "required" | "disabled";
11
+
12
+ export type CreateNativeLogBrewReactNativeClientConfig =
13
+ Omit<CreateLogBrewReactNativeClientConfig, "eventStore"> & ({
14
+ /**
15
+ * `auto` uses the linked app-private native queue when available and otherwise uses memory.
16
+ * `required` fails client creation when the native queue is unavailable. `disabled` uses memory.
17
+ */
18
+ persistentQueue?: ReactNativePersistentQueueMode;
19
+ eventStore?: undefined;
20
+ } | {
21
+ /** Advanced app-owned synchronous persistence adapter. Mutually exclusive with persistentQueue. */
22
+ eventStore: EventStore;
23
+ persistentQueue?: never;
24
+ });
25
+
26
+ export declare function createLogBrewReactNativeClient(
27
+ config: CreateNativeLogBrewReactNativeClientConfig
28
+ ): LogBrewClient;
29
+
30
+ export declare function createDefaultLogBrewReactNativeClient(
31
+ config: CreateNativeLogBrewReactNativeClientConfig
32
+ ): LogBrewClient;
33
+
34
+ export declare function purgeLogBrewReactNativePersistentQueue(
35
+ config: { apiKey?: string; clientKey?: string }
36
+ ): void;
37
+
38
+ declare const defaultExport: Omit<
39
+ typeof import("./index.js").default,
40
+ "createLogBrewReactNativeClient" | "createDefaultLogBrewReactNativeClient"
41
+ > & {
42
+ createLogBrewReactNativeClient: typeof createLogBrewReactNativeClient;
43
+ createDefaultLogBrewReactNativeClient: typeof createDefaultLogBrewReactNativeClient;
44
+ purgeLogBrewReactNativePersistentQueue: typeof purgeLogBrewReactNativePersistentQueue;
45
+ };
46
+
47
+ export default defaultExport;
package/index.native.js CHANGED
@@ -6,9 +6,14 @@ import {
6
6
  captureReactNativeNetwork,
7
7
  captureScreenView,
8
8
  createAppStateListener,
9
- createLogBrewReactNativeClient,
9
+ createLogBrewReactNativeClient as createPlatformNeutralClient,
10
10
  getReactNativeContext
11
11
  } from "./index.js";
12
+ import baseDefault from "./index.js";
13
+ import {
14
+ purgeReactNativePersistentQueue,
15
+ resolveReactNativePersistentEventStore
16
+ } from "./persistent-delivery.native.js";
12
17
  export {
13
18
  installLogBrewReactNativeGlobalErrorHandler,
14
19
  installLogBrewReactNativePromiseRejectionTracker
@@ -16,6 +21,52 @@ export {
16
21
 
17
22
  export * from "./index.js";
18
23
 
24
+ export function createLogBrewReactNativeClient(config = {}) {
25
+ const input = config !== null && typeof config === "object" ? config : {};
26
+ const {
27
+ apiKey,
28
+ clientKey,
29
+ eventStore,
30
+ maxQueueBytes,
31
+ maxQueueSize,
32
+ persistentQueue = "auto",
33
+ ...forwarded
34
+ } = input;
35
+ const hasExplicitPersistentQueue = Object.prototype.hasOwnProperty.call(
36
+ input,
37
+ "persistentQueue"
38
+ ) && input.persistentQueue !== undefined;
39
+ const authKey = clientKey ?? apiKey;
40
+ if (typeof authKey !== "string" || authKey.trim() === "") {
41
+ return createPlatformNeutralClient(input);
42
+ }
43
+ const resolved = resolveReactNativePersistentEventStore({
44
+ authKey,
45
+ eventStore,
46
+ maxQueueBytes,
47
+ maxQueueSize,
48
+ persistentQueue,
49
+ hasExplicitPersistentQueue
50
+ });
51
+ try {
52
+ return createPlatformNeutralClient({
53
+ ...forwarded,
54
+ apiKey,
55
+ clientKey,
56
+ eventStore: resolved.eventStore,
57
+ maxQueueBytes,
58
+ maxQueueSize
59
+ });
60
+ } catch (error) {
61
+ resolved.abort();
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ export function purgeLogBrewReactNativePersistentQueue(config = {}) {
67
+ purgeReactNativePersistentQueue(config);
68
+ }
69
+
19
70
  export function createDefaultLogBrewReactNativeClient(config = {}) {
20
71
  return createLogBrewReactNativeClient(config);
21
72
  }
@@ -70,3 +121,19 @@ export function createDefaultAppStateListener(client, options = {}) {
70
121
  ...options
71
122
  });
72
123
  }
124
+
125
+ const defaultExport = {
126
+ ...baseDefault,
127
+ captureDefaultAppStateChange,
128
+ captureDefaultReactNativeAction,
129
+ captureDefaultReactNativeError,
130
+ captureDefaultReactNativeNetwork,
131
+ captureDefaultScreenView,
132
+ createDefaultAppStateListener,
133
+ createDefaultLogBrewReactNativeClient,
134
+ createLogBrewReactNativeClient,
135
+ getDefaultReactNativeContext,
136
+ purgeLogBrewReactNativePersistentQueue
137
+ };
138
+
139
+ export default defaultExport;
@@ -0,0 +1,29 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ FOUNDATION_EXPORT NSString *const LBRNEventRecordPrefix;
6
+ FOUNDATION_EXPORT NSString *const LBRNEventRecordSuffix;
7
+ FOUNDATION_EXPORT NSString *const LBRNEventMarkerPrefix;
8
+ FOUNDATION_EXPORT NSString *const LBRNEventMarkerSuffix;
9
+
10
+ typedef BOOL (^LBRNEventDirectoryPreparation)(NSURL *directoryURL);
11
+
12
+ @interface LBRNEventRecordStore : NSObject
13
+
14
+ - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL;
15
+ - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
16
+ directoryPreparation:(LBRNEventDirectoryPreparation)directoryPreparation
17
+ NS_DESIGNATED_INITIALIZER;
18
+ - (instancetype)init NS_UNAVAILABLE;
19
+
20
+ - (NSDictionary *)loadRecords;
21
+ - (NSDictionary *)appendSerializedEvent:(NSString *)serializedEvent
22
+ eventBytes:(NSNumber *)eventBytes;
23
+ - (NSDictionary *)acknowledgeRecordCount:(NSNumber *)count;
24
+ - (NSDictionary *)purgeRecords;
25
+ - (NSDictionary *)closeStore;
26
+
27
+ @end
28
+
29
+ NS_ASSUME_NONNULL_END