@emit-vision/sdk-react-native 0.0.0-stage โ 0.1.1
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/LICENSE +21 -0
- package/README.md +90 -2
- package/dist/index.d.ts +48 -0
- package/dist/index.js +120 -0
- package/package.json +46 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emit Vision
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,91 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @emit-vision/sdk-react-native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native helper layer for `@emit-vision/sdk-js`. Wraps the browser SDK with
|
|
4
|
+
React Native equivalents for storage, connectivity, and error handling โ
|
|
5
|
+
works with both bare React Native and Expo.
|
|
6
|
+
|
|
7
|
+
๐ **[Full sdk-react-native reference โ](https://emitvision.com/docs/sdk-react-native)**
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @emit-vision/sdk-react-native @emit-vision/sdk-js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Optional (recommended):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @react-native-async-storage/async-storage @react-native-community/netinfo
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useEffect } from "react";
|
|
25
|
+
import { AppState, ErrorUtils } from "react-native";
|
|
26
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
27
|
+
import NetInfo from "@react-native-community/netinfo";
|
|
28
|
+
import { initRN } from "@emit-vision/sdk-react-native";
|
|
29
|
+
|
|
30
|
+
export function App() {
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
let client: Awaited<ReturnType<typeof initRN>> | undefined;
|
|
33
|
+
|
|
34
|
+
initRN({
|
|
35
|
+
dsn: process.env.EXPO_PUBLIC_EMIT_VISION_DSN,
|
|
36
|
+
environment: process.env.NODE_ENV,
|
|
37
|
+
asyncStorage: AsyncStorage,
|
|
38
|
+
appState: AppState,
|
|
39
|
+
netInfo: NetInfo,
|
|
40
|
+
errorUtils: ErrorUtils,
|
|
41
|
+
}).then((c) => {
|
|
42
|
+
client = c;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
client?.close();
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return <Router />;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
All of `asyncStorage`, `appState`, `netInfo`, and `errorUtils` are optional โ
|
|
55
|
+
pass only the platform APIs you want wired in.
|
|
56
|
+
|
|
57
|
+
## useEmitVision()
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { useEmitVision } from "@emit-vision/sdk-react-native";
|
|
61
|
+
|
|
62
|
+
function SaveButton() {
|
|
63
|
+
const { captureEvent } = useEmitVision();
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<TouchableOpacity onPress={() => captureEvent("settings_saved")}>
|
|
67
|
+
<Text>Save</Text>
|
|
68
|
+
</TouchableOpacity>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- Page-view auto-capture is disabled โ there is no DOM in React Native.
|
|
76
|
+
- Unhandled promise rejections are not automatically captured (different
|
|
77
|
+
runtime guarantees than the browser); track manually with `captureError`.
|
|
78
|
+
- No optional peer package? Implement `AsyncStorageLike`/`NetInfoLike`
|
|
79
|
+
yourself and pass it to `initRN` โ see the full reference for an example.
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- [sdk-react-native reference](https://emitvision.com/docs/sdk-react-native) โ full API reference and guides
|
|
84
|
+
- [Getting started](https://emitvision.com/docs/introduction)
|
|
85
|
+
- [Installation & configuration](https://emitvision.com/docs/installation)
|
|
86
|
+
- [Browser SDK](https://emitvision.com/docs/sdk-js) ยท [React SDK](https://emitvision.com/docs/sdk-react)
|
|
87
|
+
- [Troubleshooting](https://emitvision.com/docs/troubleshooting)
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT โ see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EmitVisionOptions, captureEvent, captureError, identify, group, consent, flush, setContext, setTags } from '@emit-vision/sdk-js';
|
|
2
|
+
export { CaptureOptions, EmitVisionOptions, EmitVisionUser, captureError, captureEvent, consent, flush, group, identify, setContext, setTags } from '@emit-vision/sdk-js';
|
|
3
|
+
|
|
4
|
+
type AsyncStorageLike = {
|
|
5
|
+
getItem(key: string): Promise<string | null>;
|
|
6
|
+
setItem(key: string, value: string): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
type NetInfoState = {
|
|
9
|
+
isConnected: boolean | null;
|
|
10
|
+
};
|
|
11
|
+
type AppStateLike = {
|
|
12
|
+
addEventListener(type: "change", handler: (state: string) => void): {
|
|
13
|
+
remove(): void;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type ErrorUtilsLike = {
|
|
17
|
+
setGlobalHandler(handler: (error: Error, isFatal?: boolean) => void): void;
|
|
18
|
+
getGlobalHandler(): ((error: Error, isFatal?: boolean) => void) | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type RNInitOptions = EmitVisionOptions & {
|
|
22
|
+
asyncStorage?: AsyncStorageLike;
|
|
23
|
+
netInfo?: {
|
|
24
|
+
addEventListener(listener: (state: NetInfoState) => void): (() => void) | {
|
|
25
|
+
remove(): void;
|
|
26
|
+
} | void;
|
|
27
|
+
};
|
|
28
|
+
appState?: AppStateLike;
|
|
29
|
+
errorUtils?: ErrorUtilsLike;
|
|
30
|
+
};
|
|
31
|
+
type RNClient = {
|
|
32
|
+
close(): void;
|
|
33
|
+
};
|
|
34
|
+
declare function initRN(options: RNInitOptions): Promise<RNClient>;
|
|
35
|
+
|
|
36
|
+
type EmitVisionRNApi = {
|
|
37
|
+
captureEvent: typeof captureEvent;
|
|
38
|
+
captureError: typeof captureError;
|
|
39
|
+
identify: typeof identify;
|
|
40
|
+
group: typeof group;
|
|
41
|
+
consent: typeof consent;
|
|
42
|
+
flush: typeof flush;
|
|
43
|
+
setContext: typeof setContext;
|
|
44
|
+
setTags: typeof setTags;
|
|
45
|
+
};
|
|
46
|
+
declare function useEmitVision(): EmitVisionRNApi;
|
|
47
|
+
|
|
48
|
+
export { type AppStateLike, type AsyncStorageLike, type EmitVisionRNApi, type ErrorUtilsLike, type NetInfoState, type RNClient, type RNInitOptions, initRN, useEmitVision };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
captureEvent as captureEvent2,
|
|
4
|
+
captureError as captureError3,
|
|
5
|
+
identify as identify2,
|
|
6
|
+
group as group2,
|
|
7
|
+
consent as consent2,
|
|
8
|
+
flush as flush3,
|
|
9
|
+
setContext as setContext2,
|
|
10
|
+
setTags as setTags2
|
|
11
|
+
} from "@emit-vision/sdk-js";
|
|
12
|
+
|
|
13
|
+
// src/client-rn.ts
|
|
14
|
+
import {
|
|
15
|
+
init,
|
|
16
|
+
flush,
|
|
17
|
+
captureError
|
|
18
|
+
} from "@emit-vision/sdk-js";
|
|
19
|
+
var ANON_ID_KEY = "__ev_anon_id";
|
|
20
|
+
async function getOrCreateAnonId(storage) {
|
|
21
|
+
try {
|
|
22
|
+
const stored = await storage.getItem(ANON_ID_KEY);
|
|
23
|
+
if (stored) return stored;
|
|
24
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
25
|
+
await storage.setItem(ANON_ID_KEY, id);
|
|
26
|
+
return id;
|
|
27
|
+
} catch {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function initRN(options) {
|
|
32
|
+
const { asyncStorage, netInfo, appState, errorUtils, ...sdkOptions } = options;
|
|
33
|
+
const sessionId = sdkOptions.sessionId ?? (asyncStorage ? await getOrCreateAnonId(asyncStorage) : void 0);
|
|
34
|
+
const client = init({
|
|
35
|
+
...sdkOptions,
|
|
36
|
+
sessionId,
|
|
37
|
+
autoCapture: {
|
|
38
|
+
...sdkOptions.autoCapture,
|
|
39
|
+
pageViews: false
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
let appStateSub;
|
|
43
|
+
if (appState) {
|
|
44
|
+
appStateSub = appState.addEventListener("change", (nextState) => {
|
|
45
|
+
if (nextState === "inactive" || nextState === "background") {
|
|
46
|
+
void flush().catch(() => void 0);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
let prevErrorHandler = null;
|
|
51
|
+
if (errorUtils && options.autoCapture?.errors !== false) {
|
|
52
|
+
prevErrorHandler = errorUtils.getGlobalHandler();
|
|
53
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
54
|
+
captureError(error, {
|
|
55
|
+
context: { isFatal: isFatal ?? false, source: "ErrorUtils" }
|
|
56
|
+
});
|
|
57
|
+
prevErrorHandler?.(error, isFatal);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
let netInfoUnsub;
|
|
61
|
+
if (netInfo) {
|
|
62
|
+
const sub = netInfo.addEventListener((state) => {
|
|
63
|
+
if (state.isConnected) {
|
|
64
|
+
void flush().catch(() => void 0);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (typeof sub === "function") {
|
|
68
|
+
netInfoUnsub = sub;
|
|
69
|
+
} else if (sub && typeof sub.remove === "function") {
|
|
70
|
+
netInfoUnsub = () => sub.remove();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
close() {
|
|
75
|
+
appStateSub?.remove();
|
|
76
|
+
netInfoUnsub?.();
|
|
77
|
+
if (errorUtils && prevErrorHandler !== null) {
|
|
78
|
+
errorUtils.setGlobalHandler(prevErrorHandler);
|
|
79
|
+
}
|
|
80
|
+
client.close();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/useEmitVision.ts
|
|
86
|
+
import {
|
|
87
|
+
captureEvent,
|
|
88
|
+
captureError as captureError2,
|
|
89
|
+
identify,
|
|
90
|
+
group,
|
|
91
|
+
consent,
|
|
92
|
+
flush as flush2,
|
|
93
|
+
setContext,
|
|
94
|
+
setTags
|
|
95
|
+
} from "@emit-vision/sdk-js";
|
|
96
|
+
var api = {
|
|
97
|
+
captureEvent,
|
|
98
|
+
captureError: captureError2,
|
|
99
|
+
identify,
|
|
100
|
+
group,
|
|
101
|
+
consent,
|
|
102
|
+
flush: flush2,
|
|
103
|
+
setContext,
|
|
104
|
+
setTags
|
|
105
|
+
};
|
|
106
|
+
function useEmitVision() {
|
|
107
|
+
return api;
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
captureError3 as captureError,
|
|
111
|
+
captureEvent2 as captureEvent,
|
|
112
|
+
consent2 as consent,
|
|
113
|
+
flush3 as flush,
|
|
114
|
+
group2 as group,
|
|
115
|
+
identify2 as identify,
|
|
116
|
+
initRN,
|
|
117
|
+
setContext2 as setContext,
|
|
118
|
+
setTags2 as setTags,
|
|
119
|
+
useEmitVision
|
|
120
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emit-vision/sdk-react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "React Native SDK for emit-vision analytics and error tracking.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Emit Vision",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=18.0.0",
|
|
29
|
+
"react-native": ">=0.73.0",
|
|
30
|
+
"@react-native-async-storage/async-storage": ">=2.0.0",
|
|
31
|
+
"@react-native-community/netinfo": ">=11.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@react-native-async-storage/async-storage": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"@react-native-community/netinfo": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@emit-vision/sdk-js": "^0.4.3"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup --config tsup.config.ts",
|
|
46
|
+
"test": "vitest run --config vitest.config.ts",
|
|
47
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
48
|
+
}
|
|
6
49
|
}
|