@bigbinary/neeto-commons-rn 2.1.0 → 2.2.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"bugsnag.d.ts","sourceRoot":"","sources":["../../../src/config/bugsnag.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAStE,eAAO,MAAM,mBAAmB,GAC9B,KAAK,eAAe,EACpB,QAAQ,MAAM,EAGd,eAAe,MAAM,SAkBtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,YAsB5B,CAAC"}
1
+ {"version":3,"file":"bugsnag.d.ts","sourceRoot":"","sources":["../../../src/config/bugsnag.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAUtE,eAAO,MAAM,mBAAmB,GAC9B,KAAK,eAAe,EACpB,QAAQ,MAAM,EAGd,eAAe,MAAM,SAkBtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,YA2B5B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const SAMPLE_STORAGE_KEY = "LOG_ROCKET_SAMPLED";
2
+ export declare const DEFAULT_SAMPLE_RATE = 10;
3
+ export declare const SAMPLE_DECISION_TTL_MS: number;
4
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/initializers/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,sBAAsB,QAA2B,CAAC"}
@@ -1,3 +1,3 @@
1
- import { logRocket } from "./logRocket";
2
- export { logRocket };
1
+ import { getLogRocketSessionURL, identifyLogRocketUser, initLogRocket, shutdownLogRocket } from "./logRocket";
2
+ export { getLogRocketSessionURL, identifyLogRocketUser, initLogRocket, shutdownLogRocket, };
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/initializers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/initializers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,iBAAiB,GAClB,CAAC"}
@@ -1,2 +1,12 @@
1
- export declare const logRocket: () => void;
1
+ interface LogRocketUser {
2
+ userId: string;
3
+ name: string;
4
+ email: string;
5
+ subdomain: string;
6
+ }
7
+ export declare const getLogRocketSessionURL: () => string | null;
8
+ export declare const initLogRocket: () => Promise<void>;
9
+ export declare const identifyLogRocketUser: ({ userId, name, email, subdomain, }: LogRocketUser) => Promise<void>;
10
+ export declare const shutdownLogRocket: () => void;
11
+ export {};
2
12
  //# sourceMappingURL=logRocket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logRocket.d.ts","sourceRoot":"","sources":["../../../src/initializers/logRocket.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS,YAmBrB,CAAC"}
1
+ {"version":3,"file":"logRocket.d.ts","sourceRoot":"","sources":["../../../src/initializers/logRocket.ts"],"names":[],"mappings":"AAiBA,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD,eAAO,MAAM,sBAAsB,qBAAmB,CAAC;AA0BvD,eAAO,MAAM,aAAa,qBAyBzB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAU,qCAKzC,aAAa,kBAYf,CAAC;AAEF,eAAO,MAAM,iBAAiB,YAU7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/neeto-commons-rn",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "license": "UNLICENSED",
5
5
  "main": "./src/index.ts",
6
6
  "react-native": "./src/index.ts",
@@ -6,6 +6,7 @@ import {
6
6
  isNetworkError,
7
7
  } from "./axios/networkErrorHandler";
8
8
 
9
+ import { getLogRocketSessionURL } from "../initializers/logRocket";
9
10
  import { Store } from "../store";
10
11
 
11
12
  export const bugsnagErrorHandler = (
@@ -50,6 +51,11 @@ export const configureBugsnag = () => {
50
51
  return false;
51
52
  }
52
53
 
54
+ const sessionURL = getLogRocketSessionURL();
55
+ if (sessionURL) {
56
+ event.addMetadata("LogRocket", { sessionURL });
57
+ }
58
+
53
59
  return true;
54
60
  },
55
61
  plugins: [new BugsnagPluginReactNavigation()],
@@ -0,0 +1,3 @@
1
+ export const SAMPLE_STORAGE_KEY = "LOG_ROCKET_SAMPLED";
2
+ export const DEFAULT_SAMPLE_RATE = 10;
3
+ export const SAMPLE_DECISION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
@@ -1,3 +1,13 @@
1
- import { logRocket } from "./logRocket";
1
+ import {
2
+ getLogRocketSessionURL,
3
+ identifyLogRocketUser,
4
+ initLogRocket,
5
+ shutdownLogRocket,
6
+ } from "./logRocket";
2
7
 
3
- export { logRocket };
8
+ export {
9
+ getLogRocketSessionURL,
10
+ identifyLogRocketUser,
11
+ initLogRocket,
12
+ shutdownLogRocket,
13
+ };
@@ -1,25 +1,116 @@
1
1
  import LogRocket from "@logrocket/react-native";
2
2
  import Config from "react-native-config";
3
3
 
4
- import { Store } from "../store";
5
-
6
- export const logRocket = () => {
7
- if (!Config.LOG_ROCKET_API_KEY || __DEV__) return;
8
-
9
- const { isLoggedIn, userId, name, email, subdomain } = Store.getState().auth;
10
-
11
- LogRocket.init(Config.LOG_ROCKET_API_KEY);
12
-
13
- if (!isLoggedIn) {
14
- //End the current session gracefully during logout
15
- LogRocket.shutdown();
16
- // Reinitialize LogRocket to capture the login screen after logout
17
- LogRocket.init(Config.LOG_ROCKET_API_KEY);
18
- } else {
19
- LogRocket.identify(userId, {
20
- name,
21
- email,
22
- organization: subdomain,
23
- });
4
+ import {
5
+ DEFAULT_SAMPLE_RATE,
6
+ SAMPLE_DECISION_TTL_MS,
7
+ SAMPLE_STORAGE_KEY,
8
+ } from "./constants";
9
+
10
+ import { getItem, setItem } from "../helpers/asyncStore";
11
+
12
+ interface SampleDecision {
13
+ sampled: boolean;
14
+ rate: number;
15
+ rolledAt: number;
16
+ }
17
+
18
+ interface LogRocketUser {
19
+ userId: string;
20
+ name: string;
21
+ email: string;
22
+ subdomain: string;
23
+ }
24
+
25
+ let initialized = false;
26
+ let initPromise: Promise<void> | null = null;
27
+ let sessionURL: string | null = null;
28
+ // Bumped by shutdownLogRocket so an init still awaiting the sampling roll
29
+ // knows it was cancelled and must not start recording.
30
+ let generation = 0;
31
+
32
+ // Null until LogRocket's backend accepts the session (1-5s after init), and
33
+ // null on unsampled devices — callers must handle the absence.
34
+ export const getLogRocketSessionURL = () => sessionURL;
35
+
36
+ // 0 falls back to the default: turning recording off is done by removing or
37
+ // rotating the API key, never through the sample rate.
38
+ const getSampleRate = () =>
39
+ parseInt(Config.LOG_ROCKET_SAMPLE_RATE ?? "", 10) || DEFAULT_SAMPLE_RATE;
40
+
41
+ // A device keeps its decision until the TTL passes or the rate changes, so
42
+ // the sampled population rotates instead of locking in the first 10% forever.
43
+ const shouldSample = async () => {
44
+ const rate = getSampleRate();
45
+ const stored = await getItem<SampleDecision>(SAMPLE_STORAGE_KEY);
46
+
47
+ const isCurrent =
48
+ stored !== null &&
49
+ stored.rate === rate &&
50
+ Date.now() - stored.rolledAt < SAMPLE_DECISION_TTL_MS;
51
+
52
+ if (isCurrent) return stored.sampled;
53
+
54
+ const sampled = Math.random() * 100 < rate;
55
+ await setItem(SAMPLE_STORAGE_KEY, { sampled, rate, rolledAt: Date.now() });
56
+
57
+ return sampled;
58
+ };
59
+
60
+ export const initLogRocket = async () => {
61
+ const apiKey = Config.LOG_ROCKET_API_KEY;
62
+ if (initialized || __DEV__ || !apiKey) return;
63
+
64
+ initPromise ??= (async () => {
65
+ try {
66
+ const startedAs = generation;
67
+ if (!(await shouldSample()) || startedAs !== generation) return;
68
+
69
+ LogRocket.init(apiKey);
70
+ initialized = true;
71
+ LogRocket.getSessionURL(url => {
72
+ // The backend accepts the session 1-5s after init; by then a logout
73
+ // may have ended it, and its URL must not resurface.
74
+ if (startedAs === generation) {
75
+ sessionURL = url;
76
+ }
77
+ });
78
+ } catch {
79
+ // Recording is expendable; a LogRocket failure must never reject the
80
+ // promise that initLogRocket and identifyLogRocketUser await.
81
+ }
82
+ })();
83
+
84
+ await initPromise;
85
+ };
86
+
87
+ export const identifyLogRocketUser = async ({
88
+ userId,
89
+ name,
90
+ email,
91
+ subdomain,
92
+ }: LogRocketUser) => {
93
+ // Callers invoke this right after initLogRocket(), often in the same tick;
94
+ // waiting on the pending init keeps that pattern from dropping the identity.
95
+ if (initPromise) await initPromise;
96
+
97
+ if (!initialized) return;
98
+
99
+ try {
100
+ LogRocket.identify(userId, { name, email, organization: subdomain });
101
+ } catch {
102
+ // Fire-and-forget callers must never see a rejection from LogRocket.
24
103
  }
25
104
  };
105
+
106
+ export const shutdownLogRocket = () => {
107
+ generation += 1;
108
+ initPromise = null;
109
+ // The replay ends here, so an error after logout must not link to it.
110
+ sessionURL = null;
111
+
112
+ if (!initialized) return;
113
+
114
+ LogRocket.shutdown();
115
+ initialized = false;
116
+ };