@rootnode-app/react-native 0.1.1 → 0.1.3

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 CHANGED
@@ -8,8 +8,10 @@ queueing and sessions, person properties, push registration.
8
8
  npx expo install @rootnode-app/react-native
9
9
  ```
10
10
 
11
- > Includes a native crash engine — ship it with a new build (EAS), not an OTA
12
- > update.
11
+ > One command — the native engines arrive automatically (npm installs peer
12
+ > dependencies since v7). If expo-doctor reports a version mismatch with your
13
+ > Expo SDK, run `npx expo install --fix` once. Includes a native crash
14
+ > engine: ship with a new build (EAS), not an OTA update.
13
15
 
14
16
  ```tsx
15
17
  import { Rootnode } from '@rootnode-app/react-native';
package/dist/index.d.ts CHANGED
@@ -21,8 +21,8 @@ export interface RootnodeConfig {
21
21
  onError?: (message: string) => void;
22
22
  }
23
23
  type Loader = (name: string) => any;
24
- /** Test seam: swap module loading (vitest has no RN runtime). */
25
- export declare function _setLoader(l: Loader): void;
24
+ /** Test seam: vitest has no RN runtime. null restores the real requires. */
25
+ export declare function _setLoader(l: Loader | null): void;
26
26
  declare class RootnodeClient {
27
27
  private core;
28
28
  private sentry;
package/dist/index.js CHANGED
@@ -3,23 +3,131 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RootnodeCore = exports.Rootnode = void 0;
4
4
  exports._setLoader = _setLoader;
5
5
  const core_1 = require("./core");
6
- let load = (name) => {
7
- // eslint-disable-next-line @typescript-eslint/no-require-imports
8
- return require(name);
9
- };
10
- /** Test seam: swap module loading (vitest has no RN runtime). */
6
+ let overrideLoader = null;
7
+ /** Test seam: vitest has no RN runtime. null restores the real requires. */
11
8
  function _setLoader(l) {
12
- load = l;
9
+ overrideLoader = l;
13
10
  }
14
- function tryLoad(name, onError) {
11
+ /**
12
+ * Every native module gets its own LITERAL require inside its own try/catch —
13
+ * Metro only resolves static names, and only treats a require as optional
14
+ * when the call sits in a try block. A require call with a variable name ships
15
+ * a bundle where every native feature silently dies (Foneria, 0.1.2).
16
+ */
17
+ function viaOverride(name) {
18
+ if (!overrideLoader)
19
+ return { hit: false, mod: null };
15
20
  try {
16
- return load(name);
21
+ return { hit: true, mod: overrideLoader(name) };
22
+ }
23
+ catch {
24
+ return { hit: true, mod: null };
25
+ }
26
+ }
27
+ function loadSentry() {
28
+ const o = viaOverride('@sentry/react-native');
29
+ if (o.hit)
30
+ return o.mod;
31
+ try {
32
+ return require('@sentry/react-native');
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ function loadNotifications() {
39
+ const o = viaOverride('expo-notifications');
40
+ if (o.hit)
41
+ return o.mod;
42
+ try {
43
+ return require('expo-notifications');
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ function loadDevice() {
50
+ const o = viaOverride('expo-device');
51
+ if (o.hit)
52
+ return o.mod;
53
+ try {
54
+ return require('expo-device');
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ function loadReactNative() {
61
+ const o = viaOverride('react-native');
62
+ if (o.hit)
63
+ return o.mod;
64
+ try {
65
+ return require('react-native');
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ function loadAsyncStorage() {
72
+ const o = viaOverride('@react-native-async-storage/async-storage');
73
+ if (o.hit)
74
+ return o.mod;
75
+ try {
76
+ return require('@react-native-async-storage/async-storage');
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ function loadFileSystemLegacy() {
83
+ const o = viaOverride('expo-file-system/legacy');
84
+ if (o.hit)
85
+ return o.mod;
86
+ try {
87
+ return require('expo-file-system/legacy');
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ function loadFileSystem() {
94
+ const o = viaOverride('expo-file-system');
95
+ if (o.hit)
96
+ return o.mod;
97
+ try {
98
+ return require('expo-file-system');
17
99
  }
18
100
  catch {
19
- onError?.(`[rootnode] optional module ${name} unavailable — feature disabled`);
20
101
  return null;
21
102
  }
22
103
  }
104
+ function loadApplication() {
105
+ const o = viaOverride('expo-application');
106
+ if (o.hit)
107
+ return o.mod;
108
+ try {
109
+ return require('expo-application');
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ function loadUpdates() {
116
+ const o = viaOverride('expo-updates');
117
+ if (o.hit)
118
+ return o.mod;
119
+ try {
120
+ return require('expo-updates');
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ function warned(mod, name, onError) {
127
+ if (mod == null)
128
+ onError?.(`[rootnode] optional module ${name} unavailable — feature disabled`);
129
+ return mod;
130
+ }
23
131
  class RootnodeClient {
24
132
  constructor() {
25
133
  this.core = null;
@@ -37,7 +145,7 @@ class RootnodeClient {
37
145
  ? `https://${cfg.dsn.match(/^https?:\/\/[^@]+@([^/]+)\//)[1]}`
38
146
  : 'https://rootnode.app');
39
147
  if (cfg.dsn) {
40
- this.sentry = tryLoad('@sentry/react-native', cfg.onError);
148
+ this.sentry = warned(loadSentry(), '@sentry/react-native', cfg.onError);
41
149
  this.sentry?.init?.({
42
150
  dsn: cfg.dsn,
43
151
  environment: cfg.environment,
@@ -118,10 +226,10 @@ class RootnodeClient {
118
226
  try {
119
227
  if (!this.cfg.apiKey)
120
228
  return false;
121
- const Notifications = tryLoad('expo-notifications', this.cfg.onError);
229
+ const Notifications = warned(loadNotifications(), 'expo-notifications', this.cfg.onError);
122
230
  if (!Notifications)
123
231
  return false;
124
- const Device = tryLoad('expo-device', this.cfg.onError);
232
+ const Device = loadDevice();
125
233
  if (Device && Device.isDevice === false)
126
234
  return false; // simulators get nothing
127
235
  const perm = await Notifications.getPermissionsAsync();
@@ -176,10 +284,10 @@ class RootnodeClient {
176
284
  }
177
285
  }
178
286
  makeStorage() {
179
- const AsyncStorage = tryLoad('@react-native-async-storage/async-storage', undefined)?.default;
287
+ const AsyncStorage = loadAsyncStorage()?.default;
180
288
  if (AsyncStorage?.getItem)
181
289
  return AsyncStorage;
182
- const FS = tryLoad('expo-file-system/legacy', undefined) ?? tryLoad('expo-file-system', undefined);
290
+ const FS = loadFileSystemLegacy() ?? loadFileSystem();
183
291
  if (FS?.documentDirectory && FS.readAsStringAsync) {
184
292
  const path = (k) => `${FS.documentDirectory}${k.replace(/[^\w.-]/g, '_')}.json`;
185
293
  return {
@@ -199,10 +307,10 @@ class RootnodeClient {
199
307
  return null; // memory-only queue — still works, just not restart-proof
200
308
  }
201
309
  makeContext() {
202
- const RN = tryLoad('react-native', undefined);
203
- const Device = tryLoad('expo-device', undefined);
204
- const App = tryLoad('expo-application', undefined);
205
- const Updates = tryLoad('expo-updates', undefined);
310
+ const RN = loadReactNative();
311
+ const Device = loadDevice();
312
+ const App = loadApplication();
313
+ const Updates = loadUpdates();
206
314
  return () => {
207
315
  const ctx = {};
208
316
  try {
@@ -226,7 +334,7 @@ class RootnodeClient {
226
334
  }
227
335
  watchAppState() {
228
336
  try {
229
- const RN = tryLoad('react-native', this.cfg.onError);
337
+ const RN = warned(loadReactNative(), 'react-native', this.cfg.onError);
230
338
  if (!RN?.AppState?.addEventListener)
231
339
  return;
232
340
  this.appStateSub = RN.AppState.addEventListener('change', (state) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnode-app/react-native",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Rootnode for React Native / Expo apps: crash reporting, analytics, sessions and push registration — one install, one init.",
5
5
  "license": "MIT",
6
6
  "author": "Doğan Aktı",
@@ -19,19 +19,19 @@
19
19
  "scripts": {
20
20
  "build": "tsc -p tsconfig.build.json",
21
21
  "test": "vitest run",
22
- "prepublishOnly": "npm run test && npm run build"
22
+ "prepublishOnly": "npm run test && npm run build && npm run verify:bundle",
23
+ "verify:bundle": "esbuild dist/index.js --bundle --platform=browser --outfile=/dev/null --external:react-native --external:@sentry/react-native --external:expo-device --external:expo-notifications --external:expo-file-system --external:expo-file-system/legacy --external:expo-application --external:expo-updates --external:@react-native-async-storage/async-storage --log-level=error"
23
24
  },
24
- "dependencies": {
25
+ "peerDependencies": {
26
+ "react-native": "*",
25
27
  "@sentry/react-native": ">=5.0.0",
26
28
  "expo-device": "*",
27
29
  "expo-notifications": "*"
28
30
  },
29
- "peerDependencies": {
30
- "react-native": "*"
31
- },
32
31
  "devDependencies": {
33
32
  "@types/node": "^22.20.5",
34
33
  "typescript": "^5.9.3",
35
- "vitest": "^2.1.9"
34
+ "vitest": "^2.1.9",
35
+ "esbuild": "^0.24.0"
36
36
  }
37
37
  }