@emit-vision/sdk-react-native 0.0.0-stage → 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/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,133 @@
1
- # Temporary Holding Version
1
+ # @emit-vision/sdk-react-native
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ ## Consent
74
+
75
+ Pass `consentRequired: true` to `initRN`, then record the user's choice:
76
+
77
+ ```tsx
78
+ import { grantConsent, declineConsent } from "@emit-vision/sdk-react-native";
79
+
80
+ <Button title="Accept" onPress={() => grantConsent()} />
81
+ <Button title="Decline" onPress={() => declineConsent()} />
82
+ ```
83
+
84
+ When you pass `asyncStorage` to `initRN`, the choice is saved through it, so a
85
+ grant or a decline survives app restarts and the user isn't asked again.
86
+
87
+ **Without `asyncStorage` (and without a `consentStorage`), the choice is kept
88
+ in memory only and is forgotten when the app restarts.**
89
+
90
+ To persist consent somewhere other than AsyncStorage, pass a `consentStorage`
91
+ to `initRN`. It takes precedence over `asyncStorage` for consent. `getItem`
92
+ must answer synchronously; writes may be async:
93
+
94
+ ```ts
95
+ import type { ConsentStorage } from "@emit-vision/sdk-js";
96
+
97
+ const consentStorage: ConsentStorage = {
98
+ getItem: (key) => cache.get(key) ?? null,
99
+ setItem: (key, value) => {
100
+ cache.set(key, value);
101
+ return persist(key, value);
102
+ },
103
+ removeItem: (key) => {
104
+ cache.delete(key);
105
+ return unpersist(key);
106
+ },
107
+ };
108
+
109
+ await initRN({ dsn, consentRequired: true, consentStorage });
110
+ ```
111
+
112
+ See the [consent model](https://emitvision.com/docs/sdk-js#consent) for the
113
+ states and what is sent in each.
114
+
115
+ ## Notes
116
+
117
+ - Page-view auto-capture is disabled — there is no DOM in React Native.
118
+ - Unhandled promise rejections are not automatically captured (different
119
+ runtime guarantees than the browser); track manually with `captureError`.
120
+ - No optional peer package? Implement `AsyncStorageLike`/`NetInfoLike`
121
+ yourself and pass it to `initRN` — see the full reference for an example.
122
+
123
+ ## Documentation
124
+
125
+ - [sdk-react-native reference](https://emitvision.com/docs/sdk-react-native) — full API reference and guides
126
+ - [Getting started](https://emitvision.com/docs/introduction)
127
+ - [Installation & configuration](https://emitvision.com/docs/installation)
128
+ - [Browser SDK](https://emitvision.com/docs/sdk-js) · [React SDK](https://emitvision.com/docs/sdk-react)
129
+ - [Troubleshooting](https://emitvision.com/docs/troubleshooting)
130
+
131
+ ## License
132
+
133
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,53 @@
1
+ import { EmitVisionOptions, captureEvent, captureError, identify, group, consent, grantConsent, declineConsent, getConsentState, onConsentChange, flush, setContext, setTags } from '@emit-vision/sdk-js';
2
+ export { CaptureOptions, ConsentState, EmitVisionOptions, EmitVisionUser, captureError, captureEvent, consent, declineConsent, flush, getConsentState, grantConsent, group, identify, onConsentChange, 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
+ removeItem?(key: string): Promise<void>;
8
+ };
9
+ type NetInfoState = {
10
+ isConnected: boolean | null;
11
+ };
12
+ type AppStateLike = {
13
+ addEventListener(type: "change", handler: (state: string) => void): {
14
+ remove(): void;
15
+ };
16
+ };
17
+ type ErrorUtilsLike = {
18
+ setGlobalHandler(handler: (error: Error, isFatal?: boolean) => void): void;
19
+ getGlobalHandler(): ((error: Error, isFatal?: boolean) => void) | null;
20
+ };
21
+
22
+ type RNInitOptions = EmitVisionOptions & {
23
+ asyncStorage?: AsyncStorageLike;
24
+ netInfo?: {
25
+ addEventListener(listener: (state: NetInfoState) => void): (() => void) | {
26
+ remove(): void;
27
+ } | void;
28
+ };
29
+ appState?: AppStateLike;
30
+ errorUtils?: ErrorUtilsLike;
31
+ };
32
+ type RNClient = {
33
+ close(): void;
34
+ };
35
+ declare function initRN(options: RNInitOptions): Promise<RNClient>;
36
+
37
+ type EmitVisionRNApi = {
38
+ captureEvent: typeof captureEvent;
39
+ captureError: typeof captureError;
40
+ identify: typeof identify;
41
+ group: typeof group;
42
+ consent: typeof consent;
43
+ grantConsent: typeof grantConsent;
44
+ declineConsent: typeof declineConsent;
45
+ getConsentState: typeof getConsentState;
46
+ onConsentChange: typeof onConsentChange;
47
+ flush: typeof flush;
48
+ setContext: typeof setContext;
49
+ setTags: typeof setTags;
50
+ };
51
+ declare function useEmitVision(): EmitVisionRNApi;
52
+
53
+ 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,165 @@
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
+ grantConsent as grantConsent2,
9
+ declineConsent as declineConsent2,
10
+ getConsentState as getConsentState2,
11
+ onConsentChange as onConsentChange2,
12
+ flush as flush3,
13
+ setContext as setContext2,
14
+ setTags as setTags2
15
+ } from "@emit-vision/sdk-js";
16
+
17
+ // src/client-rn.ts
18
+ import {
19
+ consentStorageKey,
20
+ init,
21
+ flush,
22
+ captureError
23
+ } from "@emit-vision/sdk-js";
24
+
25
+ // src/consent-storage.ts
26
+ async function createConsentStorage(asyncStorage, key) {
27
+ const cache = /* @__PURE__ */ new Map();
28
+ try {
29
+ const stored = await asyncStorage.getItem(key);
30
+ if (stored) cache.set(key, stored);
31
+ } catch {
32
+ }
33
+ return {
34
+ getItem: (k) => cache.get(k) ?? null,
35
+ setItem: (k, v) => {
36
+ cache.set(k, v);
37
+ return asyncStorage.setItem(k, v);
38
+ },
39
+ removeItem: (k) => {
40
+ cache.delete(k);
41
+ return asyncStorage.removeItem?.(k);
42
+ }
43
+ };
44
+ }
45
+
46
+ // src/client-rn.ts
47
+ var ANON_ID_KEY = "__ev_anon_id";
48
+ async function getOrCreateAnonId(storage) {
49
+ try {
50
+ const stored = await storage.getItem(ANON_ID_KEY);
51
+ if (stored) return stored;
52
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2) + Date.now().toString(36);
53
+ await storage.setItem(ANON_ID_KEY, id);
54
+ return id;
55
+ } catch {
56
+ return void 0;
57
+ }
58
+ }
59
+ async function initRN(options) {
60
+ const { asyncStorage, netInfo, appState, errorUtils, ...sdkOptions } = options;
61
+ const sessionId = sdkOptions.sessionId ?? (asyncStorage ? await getOrCreateAnonId(asyncStorage) : void 0);
62
+ const consentStorage = sdkOptions.consentStorage ?? (asyncStorage ? await createConsentStorage(
63
+ asyncStorage,
64
+ consentStorageKey(sdkOptions.apiKey ?? "", sdkOptions.projectId)
65
+ ) : void 0);
66
+ const client = init({
67
+ ...sdkOptions,
68
+ sessionId,
69
+ consentStorage,
70
+ autoCapture: {
71
+ ...sdkOptions.autoCapture,
72
+ pageViews: false
73
+ }
74
+ });
75
+ let appStateSub;
76
+ if (appState) {
77
+ appStateSub = appState.addEventListener("change", (nextState) => {
78
+ if (nextState === "inactive" || nextState === "background") {
79
+ void flush().catch(() => void 0);
80
+ }
81
+ });
82
+ }
83
+ let prevErrorHandler = null;
84
+ if (errorUtils && options.autoCapture?.errors !== false) {
85
+ prevErrorHandler = errorUtils.getGlobalHandler();
86
+ errorUtils.setGlobalHandler((error, isFatal) => {
87
+ captureError(error, {
88
+ context: { isFatal: isFatal ?? false, source: "ErrorUtils" }
89
+ });
90
+ prevErrorHandler?.(error, isFatal);
91
+ });
92
+ }
93
+ let netInfoUnsub;
94
+ if (netInfo) {
95
+ const sub = netInfo.addEventListener((state) => {
96
+ if (state.isConnected) {
97
+ void flush().catch(() => void 0);
98
+ }
99
+ });
100
+ if (typeof sub === "function") {
101
+ netInfoUnsub = sub;
102
+ } else if (sub && typeof sub.remove === "function") {
103
+ netInfoUnsub = () => sub.remove();
104
+ }
105
+ }
106
+ return {
107
+ close() {
108
+ appStateSub?.remove();
109
+ netInfoUnsub?.();
110
+ if (errorUtils && prevErrorHandler !== null) {
111
+ errorUtils.setGlobalHandler(prevErrorHandler);
112
+ }
113
+ client.close();
114
+ }
115
+ };
116
+ }
117
+
118
+ // src/useEmitVision.ts
119
+ import {
120
+ captureEvent,
121
+ captureError as captureError2,
122
+ identify,
123
+ group,
124
+ consent,
125
+ grantConsent,
126
+ declineConsent,
127
+ getConsentState,
128
+ onConsentChange,
129
+ flush as flush2,
130
+ setContext,
131
+ setTags
132
+ } from "@emit-vision/sdk-js";
133
+ var api = {
134
+ captureEvent,
135
+ captureError: captureError2,
136
+ identify,
137
+ group,
138
+ consent,
139
+ grantConsent,
140
+ declineConsent,
141
+ getConsentState,
142
+ onConsentChange,
143
+ flush: flush2,
144
+ setContext,
145
+ setTags
146
+ };
147
+ function useEmitVision() {
148
+ return api;
149
+ }
150
+ export {
151
+ captureError3 as captureError,
152
+ captureEvent2 as captureEvent,
153
+ consent2 as consent,
154
+ declineConsent2 as declineConsent,
155
+ flush3 as flush,
156
+ getConsentState2 as getConsentState,
157
+ grantConsent2 as grantConsent,
158
+ group2 as group,
159
+ identify2 as identify,
160
+ initRN,
161
+ onConsentChange2 as onConsentChange,
162
+ setContext2 as setContext,
163
+ setTags2 as setTags,
164
+ useEmitVision
165
+ };
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@emit-vision/sdk-react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
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.5.0"
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
  }