@mostly-good-metrics/react-native 0.1.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.
@@ -0,0 +1,261 @@
1
+ import { AppState, Platform } from 'react-native';
2
+ import { MostlyGoodMetrics as MGMClient, SystemEvents, SystemProperties } from '@mostly-good-metrics/javascript';
3
+ import { AsyncStorageEventStorage, persistence, getStorageType } from './storage';
4
+ // Use global to persist state across hot reloads
5
+ const g = globalThis;
6
+
7
+ // Initialize or restore state
8
+ if (!g.__MGM_RN_STATE__) {
9
+ g.__MGM_RN_STATE__ = {
10
+ appStateSubscription: null,
11
+ isConfigured: false,
12
+ currentAppState: AppState.currentState,
13
+ debugLogging: false,
14
+ lastLifecycleEvent: null
15
+ };
16
+ }
17
+ const state = g.__MGM_RN_STATE__;
18
+ const DEDUPE_INTERVAL_MS = 1000; // Ignore duplicate events within 1 second
19
+
20
+ function log(...args) {
21
+ if (state.debugLogging) {
22
+ console.log('[MostlyGoodMetrics]', ...args);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Track a lifecycle event with deduplication.
28
+ */
29
+ function trackLifecycleEvent(eventName, properties) {
30
+ const now = Date.now();
31
+
32
+ // Deduplicate events that fire multiple times in quick succession
33
+ if (state.lastLifecycleEvent && state.lastLifecycleEvent.name === eventName && now - state.lastLifecycleEvent.time < DEDUPE_INTERVAL_MS) {
34
+ log(`Skipping duplicate ${eventName} (${now - state.lastLifecycleEvent.time}ms ago)`);
35
+ return;
36
+ }
37
+ state.lastLifecycleEvent = {
38
+ name: eventName,
39
+ time: now
40
+ };
41
+ log(`Tracking lifecycle event: ${eventName}`);
42
+ MGMClient.track(eventName, properties);
43
+ }
44
+
45
+ /**
46
+ * Handle app state changes for lifecycle tracking.
47
+ */
48
+ function handleAppStateChange(nextAppState) {
49
+ if (!MGMClient.shared) return;
50
+ log(`AppState change: ${state.currentAppState} -> ${nextAppState}`);
51
+
52
+ // App came to foreground
53
+ if (state.currentAppState.match(/inactive|background/) && nextAppState === 'active') {
54
+ trackLifecycleEvent(SystemEvents.APP_OPENED);
55
+ }
56
+
57
+ // App went to background
58
+ if (state.currentAppState === 'active' && nextAppState.match(/inactive|background/)) {
59
+ trackLifecycleEvent(SystemEvents.APP_BACKGROUNDED);
60
+ // Flush events when going to background
61
+ MGMClient.flush().catch(e => log('Flush error:', e));
62
+ }
63
+ state.currentAppState = nextAppState;
64
+ }
65
+
66
+ /**
67
+ * Track app install or update events.
68
+ */
69
+ async function trackInstallOrUpdate(appVersion) {
70
+ if (!appVersion) return;
71
+ const previousVersion = await persistence.getAppVersion();
72
+ const isFirst = await persistence.isFirstLaunch();
73
+ if (isFirst) {
74
+ trackLifecycleEvent(SystemEvents.APP_INSTALLED, {
75
+ [SystemProperties.VERSION]: appVersion
76
+ });
77
+ await persistence.setAppVersion(appVersion);
78
+ } else if (previousVersion && previousVersion !== appVersion) {
79
+ trackLifecycleEvent(SystemEvents.APP_UPDATED, {
80
+ [SystemProperties.VERSION]: appVersion,
81
+ [SystemProperties.PREVIOUS_VERSION]: previousVersion
82
+ });
83
+ await persistence.setAppVersion(appVersion);
84
+ } else if (!previousVersion) {
85
+ await persistence.setAppVersion(appVersion);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * MostlyGoodMetrics React Native SDK
91
+ */
92
+ const MostlyGoodMetrics = {
93
+ /**
94
+ * Configure the SDK with an API key and optional settings.
95
+ */
96
+ configure(apiKey, config = {}) {
97
+ // Check both our state and the underlying JS SDK
98
+ if (state.isConfigured || MGMClient.isConfigured) {
99
+ log('Already configured, skipping');
100
+ return;
101
+ }
102
+ state.debugLogging = config.enableDebugLogging ?? false;
103
+ log('Configuring with options:', config);
104
+
105
+ // Create AsyncStorage-based storage
106
+ const storage = new AsyncStorageEventStorage(config.maxStoredEvents);
107
+
108
+ // Restore user ID from storage
109
+ persistence.getUserId().then(userId => {
110
+ if (userId) {
111
+ log('Restored user ID:', userId);
112
+ }
113
+ });
114
+
115
+ // Configure the JS SDK
116
+ // Disable its built-in lifecycle tracking since we handle it ourselves
117
+ MGMClient.configure({
118
+ apiKey,
119
+ ...config,
120
+ storage,
121
+ osVersion: getOSVersion(),
122
+ trackAppLifecycleEvents: false // We handle this with AppState
123
+ });
124
+ state.isConfigured = true;
125
+
126
+ // Set up React Native lifecycle tracking
127
+ if (config.trackAppLifecycleEvents !== false) {
128
+ log('Setting up lifecycle tracking, currentAppState:', state.currentAppState);
129
+
130
+ // Remove any existing listener (in case of hot reload)
131
+ if (state.appStateSubscription) {
132
+ state.appStateSubscription.remove();
133
+ state.appStateSubscription = null;
134
+ }
135
+
136
+ // Track initial app open
137
+ trackLifecycleEvent(SystemEvents.APP_OPENED);
138
+
139
+ // Track install/update
140
+ trackInstallOrUpdate(config.appVersion).catch(e => log('Install/update tracking error:', e));
141
+
142
+ // Subscribe to app state changes
143
+ state.appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
144
+ }
145
+ },
146
+ /**
147
+ * Track an event with optional properties.
148
+ */
149
+ track(name, properties) {
150
+ if (!state.isConfigured) {
151
+ console.warn('[MostlyGoodMetrics] SDK not configured. Call configure() first.');
152
+ return;
153
+ }
154
+
155
+ // Add React Native specific properties
156
+ const enrichedProperties = {
157
+ [SystemProperties.DEVICE_TYPE]: getDeviceType(),
158
+ $storage_type: getStorageType(),
159
+ ...properties
160
+ };
161
+ MGMClient.track(name, enrichedProperties);
162
+ },
163
+ /**
164
+ * Identify a user.
165
+ */
166
+ identify(userId) {
167
+ if (!state.isConfigured) {
168
+ console.warn('[MostlyGoodMetrics] SDK not configured. Call configure() first.');
169
+ return;
170
+ }
171
+ log('Identifying user:', userId);
172
+ MGMClient.identify(userId);
173
+ // Also persist to AsyncStorage for restoration
174
+ persistence.setUserId(userId).catch(e => log('Failed to persist user ID:', e));
175
+ },
176
+ /**
177
+ * Clear the current user identity.
178
+ */
179
+ resetIdentity() {
180
+ if (!state.isConfigured) return;
181
+ log('Resetting identity');
182
+ MGMClient.resetIdentity();
183
+ persistence.setUserId(null).catch(e => log('Failed to clear user ID:', e));
184
+ },
185
+ /**
186
+ * Manually flush pending events to the server.
187
+ */
188
+ flush() {
189
+ if (!state.isConfigured) return;
190
+ log('Flushing events');
191
+ MGMClient.flush().catch(e => log('Flush error:', e));
192
+ },
193
+ /**
194
+ * Start a new session with a fresh session ID.
195
+ */
196
+ startNewSession() {
197
+ if (!state.isConfigured) return;
198
+ log('Starting new session');
199
+ MGMClient.startNewSession();
200
+ },
201
+ /**
202
+ * Clear all pending events without sending them.
203
+ */
204
+ clearPendingEvents() {
205
+ if (!state.isConfigured) return;
206
+ log('Clearing pending events');
207
+ MGMClient.clearPendingEvents().catch(e => log('Clear error:', e));
208
+ },
209
+ /**
210
+ * Get the number of pending events.
211
+ */
212
+ async getPendingEventCount() {
213
+ if (!state.isConfigured) return 0;
214
+ return MGMClient.getPendingEventCount();
215
+ },
216
+ /**
217
+ * Clean up resources. Call when unmounting the app.
218
+ */
219
+ destroy() {
220
+ if (state.appStateSubscription) {
221
+ state.appStateSubscription.remove();
222
+ state.appStateSubscription = null;
223
+ }
224
+ MGMClient.reset();
225
+ state.isConfigured = false;
226
+ state.lastLifecycleEvent = null;
227
+ log('Destroyed');
228
+ }
229
+ };
230
+
231
+ /**
232
+ * Get device type based on platform.
233
+ */
234
+ function getDeviceType() {
235
+ if (Platform.OS === 'ios') {
236
+ // Could use react-native-device-info for more accuracy
237
+ return Platform.isPad ? 'tablet' : 'phone';
238
+ }
239
+ if (Platform.OS === 'android') {
240
+ return 'phone'; // Could detect tablet with dimensions
241
+ }
242
+ return 'unknown';
243
+ }
244
+
245
+ /**
246
+ * Get OS version based on platform.
247
+ */
248
+ function getOSVersion() {
249
+ const version = Platform.Version;
250
+ if (Platform.OS === 'ios') {
251
+ // iOS returns a string like "15.0"
252
+ return String(version);
253
+ }
254
+ if (Platform.OS === 'android') {
255
+ // Android returns SDK version number (e.g., 31 for Android 12)
256
+ return String(version);
257
+ }
258
+ return 'unknown';
259
+ }
260
+ export default MostlyGoodMetrics;
261
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["AppState","Platform","MostlyGoodMetrics","MGMClient","SystemEvents","SystemProperties","AsyncStorageEventStorage","persistence","getStorageType","g","globalThis","__MGM_RN_STATE__","appStateSubscription","isConfigured","currentAppState","currentState","debugLogging","lastLifecycleEvent","state","DEDUPE_INTERVAL_MS","log","args","console","trackLifecycleEvent","eventName","properties","now","Date","name","time","track","handleAppStateChange","nextAppState","shared","match","APP_OPENED","APP_BACKGROUNDED","flush","catch","e","trackInstallOrUpdate","appVersion","previousVersion","getAppVersion","isFirst","isFirstLaunch","APP_INSTALLED","VERSION","setAppVersion","APP_UPDATED","PREVIOUS_VERSION","configure","apiKey","config","enableDebugLogging","storage","maxStoredEvents","getUserId","then","userId","osVersion","getOSVersion","trackAppLifecycleEvents","remove","addEventListener","warn","enrichedProperties","DEVICE_TYPE","getDeviceType","$storage_type","identify","setUserId","resetIdentity","startNewSession","clearPendingEvents","getPendingEventCount","destroy","reset","OS","isPad","version","Version","String"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,152 @@
1
+ const STORAGE_KEY = 'mostlygoodmetrics_events';
2
+ const USER_ID_KEY = 'mostlygoodmetrics_user_id';
3
+ const APP_VERSION_KEY = 'mostlygoodmetrics_app_version';
4
+ const FIRST_LAUNCH_KEY = 'mostlygoodmetrics_installed';
5
+
6
+ // Try to import AsyncStorage, fall back to null if not available
7
+ let AsyncStorage = null;
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10
+ AsyncStorage = require('@react-native-async-storage/async-storage').default;
11
+ } catch {
12
+ // AsyncStorage not installed - will use in-memory storage
13
+ }
14
+
15
+ /**
16
+ * Returns the storage type being used.
17
+ */
18
+ export function getStorageType() {
19
+ return AsyncStorage ? 'persistent' : 'memory';
20
+ }
21
+
22
+ /**
23
+ * In-memory fallback storage when AsyncStorage is not available.
24
+ */
25
+ const memoryStorage = {};
26
+
27
+ /**
28
+ * Storage helpers that work with or without AsyncStorage.
29
+ */
30
+ async function getItem(key) {
31
+ if (AsyncStorage) {
32
+ try {
33
+ return await AsyncStorage.getItem(key);
34
+ } catch {
35
+ return memoryStorage[key] ?? null;
36
+ }
37
+ }
38
+ return memoryStorage[key] ?? null;
39
+ }
40
+ async function setItem(key, value) {
41
+ memoryStorage[key] = value;
42
+ if (AsyncStorage) {
43
+ try {
44
+ await AsyncStorage.setItem(key, value);
45
+ } catch {
46
+ // Fall back to memory storage (already set above)
47
+ }
48
+ }
49
+ }
50
+ async function removeItem(key) {
51
+ delete memoryStorage[key];
52
+ if (AsyncStorage) {
53
+ try {
54
+ await AsyncStorage.removeItem(key);
55
+ } catch {
56
+ // Already removed from memory
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Event storage for React Native.
63
+ * Uses AsyncStorage if available, otherwise falls back to in-memory storage.
64
+ */
65
+ export class AsyncStorageEventStorage {
66
+ events = null;
67
+ constructor(maxEvents = 10000) {
68
+ this.maxEvents = Math.max(maxEvents, 100);
69
+ }
70
+ async loadEvents() {
71
+ if (this.events !== null) {
72
+ return this.events;
73
+ }
74
+ try {
75
+ const stored = await getItem(STORAGE_KEY);
76
+ if (stored) {
77
+ this.events = JSON.parse(stored);
78
+ } else {
79
+ this.events = [];
80
+ }
81
+ } catch {
82
+ this.events = [];
83
+ }
84
+ return this.events;
85
+ }
86
+ async saveEvents() {
87
+ await setItem(STORAGE_KEY, JSON.stringify(this.events ?? []));
88
+ }
89
+ async store(event) {
90
+ const events = await this.loadEvents();
91
+ events.push(event);
92
+
93
+ // Trim oldest events if we exceed the limit
94
+ if (events.length > this.maxEvents) {
95
+ const excess = events.length - this.maxEvents;
96
+ events.splice(0, excess);
97
+ }
98
+ await this.saveEvents();
99
+ }
100
+ async fetchEvents(limit) {
101
+ const events = await this.loadEvents();
102
+ return events.slice(0, limit);
103
+ }
104
+ async removeEvents(count) {
105
+ const events = await this.loadEvents();
106
+ events.splice(0, count);
107
+ await this.saveEvents();
108
+ }
109
+ async eventCount() {
110
+ const events = await this.loadEvents();
111
+ return events.length;
112
+ }
113
+ async clear() {
114
+ this.events = [];
115
+ await removeItem(STORAGE_KEY);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Persistence helpers for user ID and app version.
121
+ */
122
+ export const persistence = {
123
+ async getUserId() {
124
+ return getItem(USER_ID_KEY);
125
+ },
126
+ async setUserId(userId) {
127
+ if (userId) {
128
+ await setItem(USER_ID_KEY, userId);
129
+ } else {
130
+ await removeItem(USER_ID_KEY);
131
+ }
132
+ },
133
+ async getAppVersion() {
134
+ return getItem(APP_VERSION_KEY);
135
+ },
136
+ async setAppVersion(version) {
137
+ if (version) {
138
+ await setItem(APP_VERSION_KEY, version);
139
+ } else {
140
+ await removeItem(APP_VERSION_KEY);
141
+ }
142
+ },
143
+ async isFirstLaunch() {
144
+ const hasLaunched = await getItem(FIRST_LAUNCH_KEY);
145
+ if (!hasLaunched) {
146
+ await setItem(FIRST_LAUNCH_KEY, 'true');
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+ };
152
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["STORAGE_KEY","USER_ID_KEY","APP_VERSION_KEY","FIRST_LAUNCH_KEY","AsyncStorage","require","default","getStorageType","memoryStorage","getItem","key","setItem","value","removeItem","AsyncStorageEventStorage","events","constructor","maxEvents","Math","max","loadEvents","stored","JSON","parse","saveEvents","stringify","store","event","push","length","excess","splice","fetchEvents","limit","slice","removeEvents","count","eventCount","clear","persistence","getUserId","setUserId","userId","getAppVersion","setAppVersion","version","isFirstLaunch","hasLaunched"],"sourceRoot":"../../src","sources":["storage.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,51 @@
1
+ import { type MGMConfiguration, type EventProperties } from '@mostly-good-metrics/javascript';
2
+ export type { MGMConfiguration, EventProperties };
3
+ export interface ReactNativeConfig extends Omit<MGMConfiguration, 'storage'> {
4
+ /**
5
+ * The app version string. Required for install/update tracking.
6
+ */
7
+ appVersion?: string;
8
+ }
9
+ /**
10
+ * MostlyGoodMetrics React Native SDK
11
+ */
12
+ declare const MostlyGoodMetrics: {
13
+ /**
14
+ * Configure the SDK with an API key and optional settings.
15
+ */
16
+ configure(apiKey: string, config?: Omit<ReactNativeConfig, "apiKey">): void;
17
+ /**
18
+ * Track an event with optional properties.
19
+ */
20
+ track(name: string, properties?: EventProperties): void;
21
+ /**
22
+ * Identify a user.
23
+ */
24
+ identify(userId: string): void;
25
+ /**
26
+ * Clear the current user identity.
27
+ */
28
+ resetIdentity(): void;
29
+ /**
30
+ * Manually flush pending events to the server.
31
+ */
32
+ flush(): void;
33
+ /**
34
+ * Start a new session with a fresh session ID.
35
+ */
36
+ startNewSession(): void;
37
+ /**
38
+ * Clear all pending events without sending them.
39
+ */
40
+ clearPendingEvents(): void;
41
+ /**
42
+ * Get the number of pending events.
43
+ */
44
+ getPendingEventCount(): Promise<number>;
45
+ /**
46
+ * Clean up resources. Call when unmounting the app.
47
+ */
48
+ destroy(): void;
49
+ };
50
+ export default MostlyGoodMetrics;
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAGrB,MAAM,iCAAiC,CAAC;AAGzC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAElD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC;IAC1E;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqGD;;GAEG;AACH,QAAA,MAAM,iBAAiB;IACrB;;OAEG;sBACe,MAAM,WAAU,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,GAAQ,IAAI;IAqD/E;;OAEG;gBACS,MAAM,eAAe,eAAe,GAAG,IAAI;IAgBvD;;OAEG;qBACc,MAAM,GAAG,IAAI;IAY9B;;OAEG;qBACc,IAAI;IAQrB;;OAEG;aACM,IAAI;IAOb;;OAEG;uBACgB,IAAI;IAOvB;;OAEG;0BACmB,IAAI;IAO1B;;OAEG;4BAC2B,OAAO,CAAC,MAAM,CAAC;IAK7C;;OAEG;eACQ,IAAI;CAUhB,CAAC;AAgCF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { IEventStorage, MGMEvent } from '@mostly-good-metrics/javascript';
2
+ /**
3
+ * Returns the storage type being used.
4
+ */
5
+ export declare function getStorageType(): 'persistent' | 'memory';
6
+ /**
7
+ * Event storage for React Native.
8
+ * Uses AsyncStorage if available, otherwise falls back to in-memory storage.
9
+ */
10
+ export declare class AsyncStorageEventStorage implements IEventStorage {
11
+ private maxEvents;
12
+ private events;
13
+ constructor(maxEvents?: number);
14
+ private loadEvents;
15
+ private saveEvents;
16
+ store(event: MGMEvent): Promise<void>;
17
+ fetchEvents(limit: number): Promise<MGMEvent[]>;
18
+ removeEvents(count: number): Promise<void>;
19
+ eventCount(): Promise<number>;
20
+ clear(): Promise<void>;
21
+ }
22
+ /**
23
+ * Persistence helpers for user ID and app version.
24
+ */
25
+ export declare const persistence: {
26
+ getUserId(): Promise<string | null>;
27
+ setUserId(userId: string | null): Promise<void>;
28
+ getAppVersion(): Promise<string | null>;
29
+ setAppVersion(version: string | null): Promise<void>;
30
+ isFirstLaunch(): Promise<boolean>;
31
+ };
32
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAgB/E;;GAEG;AACH,wBAAgB,cAAc,IAAI,YAAY,GAAG,QAAQ,CAExD;AA2CD;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,aAAa;IAC5D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAA2B;gBAE7B,SAAS,GAAE,MAAc;YAIvB,UAAU;YAmBV,UAAU;IAIlB,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK/C,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;iBACH,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;sBAIjB,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;qBAQ9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;2BAIhB,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;qBAQnC,OAAO,CAAC,OAAO,CAAC;CAQxC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@mostly-good-metrics/react-native",
3
+ "version": "0.1.0",
4
+ "description": "React Native SDK for MostlyGoodMetrics analytics",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/module/index",
7
+ "types": "lib/typescript/src/index.d.ts",
8
+ "react-native": "src/index",
9
+ "source": "src/index",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "!**/__tests__",
14
+ "!**/__fixtures__",
15
+ "!**/__mocks__"
16
+ ],
17
+ "scripts": {
18
+ "test": "jest",
19
+ "test:coverage": "jest --coverage",
20
+ "typescript": "tsc --noEmit",
21
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
22
+ "prepare": "bob build",
23
+ "clean": "del-cli lib"
24
+ },
25
+ "keywords": [
26
+ "react-native",
27
+ "ios",
28
+ "android",
29
+ "analytics",
30
+ "metrics",
31
+ "mostlygoodmetrics"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Mostly-Good-Metrics/mostly-good-metrics-react-native.git"
36
+ },
37
+ "author": "Josh Holtz",
38
+ "license": "MIT",
39
+ "bugs": {
40
+ "url": "https://github.com/Mostly-Good-Metrics/mostly-good-metrics-react-native/issues"
41
+ },
42
+ "homepage": "https://github.com/Mostly-Good-Metrics/mostly-good-metrics-react-native#readme",
43
+ "publishConfig": {
44
+ "registry": "https://registry.npmjs.org/"
45
+ },
46
+ "dependencies": {
47
+ "@mostly-good-metrics/javascript": "^0.1.0"
48
+ },
49
+ "devDependencies": {
50
+ "@react-native-async-storage/async-storage": "^1.21.0",
51
+ "@types/jest": "^30.0.0",
52
+ "@types/react": "^18.2.0",
53
+ "@types/react-native": "^0.72.0",
54
+ "del-cli": "^5.0.0",
55
+ "jest": "^30.2.0",
56
+ "react": "^18.2.0",
57
+ "react-native": "^0.73.0",
58
+ "react-native-builder-bob": "^0.23.0",
59
+ "ts-jest": "^29.4.6",
60
+ "typescript": "^5.0.0"
61
+ },
62
+ "peerDependencies": {
63
+ "@react-native-async-storage/async-storage": "*",
64
+ "react": "*",
65
+ "react-native": "*"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "@react-native-async-storage/async-storage": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "react-native-builder-bob": {
73
+ "source": "src",
74
+ "output": "lib",
75
+ "targets": [
76
+ "commonjs",
77
+ "module",
78
+ [
79
+ "typescript",
80
+ {
81
+ "project": "tsconfig.build.json"
82
+ }
83
+ ]
84
+ ]
85
+ }
86
+ }