@rootnode-app/react-native 0.1.2 → 0.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.
- package/dist/index.d.ts +4 -2
- package/dist/index.js +152 -19
- package/package.json +4 -4
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:
|
|
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;
|
|
@@ -47,6 +47,8 @@ declare class RootnodeClient {
|
|
|
47
47
|
private postPushToken;
|
|
48
48
|
private makeStorage;
|
|
49
49
|
private makeContext;
|
|
50
|
+
/** Auto-track push opens: emits push_opened with the campaign id. */
|
|
51
|
+
private watchPushOpens;
|
|
50
52
|
private watchAppState;
|
|
51
53
|
}
|
|
52
54
|
export declare const Rootnode: RootnodeClient;
|
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
|
|
7
|
-
|
|
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
|
-
|
|
9
|
+
overrideLoader = l;
|
|
13
10
|
}
|
|
14
|
-
|
|
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
|
|
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');
|
|
17
66
|
}
|
|
18
67
|
catch {
|
|
19
|
-
onError?.(`[rootnode] optional module ${name} unavailable — feature disabled`);
|
|
20
68
|
return null;
|
|
21
69
|
}
|
|
22
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');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
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 =
|
|
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,
|
|
@@ -46,6 +154,7 @@ class RootnodeClient {
|
|
|
46
154
|
});
|
|
47
155
|
}
|
|
48
156
|
if (cfg.apiKey) {
|
|
157
|
+
this.watchPushOpens();
|
|
49
158
|
this.core = new core_1.RootnodeCore({
|
|
50
159
|
apiKey: cfg.apiKey,
|
|
51
160
|
host,
|
|
@@ -118,10 +227,10 @@ class RootnodeClient {
|
|
|
118
227
|
try {
|
|
119
228
|
if (!this.cfg.apiKey)
|
|
120
229
|
return false;
|
|
121
|
-
const Notifications =
|
|
230
|
+
const Notifications = warned(loadNotifications(), 'expo-notifications', this.cfg.onError);
|
|
122
231
|
if (!Notifications)
|
|
123
232
|
return false;
|
|
124
|
-
const Device =
|
|
233
|
+
const Device = loadDevice();
|
|
125
234
|
if (Device && Device.isDevice === false)
|
|
126
235
|
return false; // simulators get nothing
|
|
127
236
|
const perm = await Notifications.getPermissionsAsync();
|
|
@@ -176,10 +285,10 @@ class RootnodeClient {
|
|
|
176
285
|
}
|
|
177
286
|
}
|
|
178
287
|
makeStorage() {
|
|
179
|
-
const AsyncStorage =
|
|
288
|
+
const AsyncStorage = loadAsyncStorage()?.default;
|
|
180
289
|
if (AsyncStorage?.getItem)
|
|
181
290
|
return AsyncStorage;
|
|
182
|
-
const FS =
|
|
291
|
+
const FS = loadFileSystemLegacy() ?? loadFileSystem();
|
|
183
292
|
if (FS?.documentDirectory && FS.readAsStringAsync) {
|
|
184
293
|
const path = (k) => `${FS.documentDirectory}${k.replace(/[^\w.-]/g, '_')}.json`;
|
|
185
294
|
return {
|
|
@@ -199,10 +308,10 @@ class RootnodeClient {
|
|
|
199
308
|
return null; // memory-only queue — still works, just not restart-proof
|
|
200
309
|
}
|
|
201
310
|
makeContext() {
|
|
202
|
-
const RN =
|
|
203
|
-
const Device =
|
|
204
|
-
const App =
|
|
205
|
-
const Updates =
|
|
311
|
+
const RN = loadReactNative();
|
|
312
|
+
const Device = loadDevice();
|
|
313
|
+
const App = loadApplication();
|
|
314
|
+
const Updates = loadUpdates();
|
|
206
315
|
return () => {
|
|
207
316
|
const ctx = {};
|
|
208
317
|
try {
|
|
@@ -224,9 +333,33 @@ class RootnodeClient {
|
|
|
224
333
|
return ctx;
|
|
225
334
|
};
|
|
226
335
|
}
|
|
336
|
+
/** Auto-track push opens: emits push_opened with the campaign id. */
|
|
337
|
+
watchPushOpens() {
|
|
338
|
+
try {
|
|
339
|
+
const Notifications = loadNotifications();
|
|
340
|
+
if (!Notifications?.addNotificationResponseReceivedListener)
|
|
341
|
+
return;
|
|
342
|
+
Notifications.addNotificationResponseReceivedListener((res) => {
|
|
343
|
+
try {
|
|
344
|
+
const data = res?.notification?.request?.content?.data ?? {};
|
|
345
|
+
this.core?.capture('push_opened', {
|
|
346
|
+
...(data._rn_campaign ? { _rn_campaign: String(data._rn_campaign) } : {}),
|
|
347
|
+
...(typeof data.screen === 'string' ? { screen: data.screen } : {}),
|
|
348
|
+
});
|
|
349
|
+
void this.core?.flush();
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
/* tracking must never break the tap */
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
/* no notifications module = no open tracking */
|
|
358
|
+
}
|
|
359
|
+
}
|
|
227
360
|
watchAppState() {
|
|
228
361
|
try {
|
|
229
|
-
const RN =
|
|
362
|
+
const RN = warned(loadReactNative(), 'react-native', this.cfg.onError);
|
|
230
363
|
if (!RN?.AppState?.addEventListener)
|
|
231
364
|
return;
|
|
232
365
|
this.appStateSub = RN.AppState.addEventListener('change', (state) => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootnode-app/react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Rootnode for React Native / Expo apps: crash reporting, analytics, sessions and push registration
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Rootnode for React Native / Expo apps: crash reporting, analytics, sessions and push registration \u2014 one install, one init.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "Do\u011fan Akt\u0131",
|
|
7
7
|
"homepage": "https://rootnode.app/docs/integration.md",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -34,4 +34,4 @@
|
|
|
34
34
|
"vitest": "^2.1.9",
|
|
35
35
|
"esbuild": "^0.24.0"
|
|
36
36
|
}
|
|
37
|
-
}
|
|
37
|
+
}
|