@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.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # MostlyGoodMetrics React Native SDK
2
+
3
+ A lightweight React Native SDK for tracking analytics events with [MostlyGoodMetrics](https://mostlygoodmetrics.com).
4
+
5
+ ## Requirements
6
+
7
+ - React Native 0.71+
8
+ - Expo SDK 49+ (if using Expo)
9
+
10
+ ## Installation
11
+
12
+ ### Expo (Recommended)
13
+
14
+ ```bash
15
+ npm install @mostly-good-metrics/react-native
16
+ ```
17
+
18
+ That's it! Expo includes AsyncStorage by default, so events persist across app restarts.
19
+
20
+ ### Bare React Native
21
+
22
+ ```bash
23
+ npm install @mostly-good-metrics/react-native @react-native-async-storage/async-storage
24
+ cd ios && pod install
25
+ ```
26
+
27
+ **Note:** AsyncStorage is optional. Without it, events are stored in memory only (lost on app restart).
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Initialize the SDK
32
+
33
+ Initialize once at app startup:
34
+
35
+ ```typescript
36
+ import MostlyGoodMetrics from '@mostly-good-metrics/react-native';
37
+
38
+ MostlyGoodMetrics.configure('mgm_proj_your_api_key');
39
+ ```
40
+
41
+ ### 2. Track Events
42
+
43
+ ```typescript
44
+ // Simple event
45
+ MostlyGoodMetrics.track('button_clicked');
46
+
47
+ // Event with properties
48
+ MostlyGoodMetrics.track('purchase_completed', {
49
+ product_id: 'SKU123',
50
+ price: 29.99,
51
+ currency: 'USD',
52
+ });
53
+ ```
54
+
55
+ ### 3. Identify Users
56
+
57
+ ```typescript
58
+ // Set user identity
59
+ MostlyGoodMetrics.identify('user_123');
60
+
61
+ // Reset identity (e.g., on logout)
62
+ MostlyGoodMetrics.resetIdentity();
63
+ ```
64
+
65
+ That's it! Events are automatically batched and sent.
66
+
67
+ ## Configuration Options
68
+
69
+ For more control, pass a configuration object:
70
+
71
+ ```typescript
72
+ import { version } from './package.json'; // Or use expo-constants
73
+
74
+ MostlyGoodMetrics.configure('mgm_proj_your_api_key', {
75
+ baseURL: 'https://mostlygoodmetrics.com',
76
+ environment: 'production',
77
+ appVersion: version, // Required for install/update tracking
78
+ maxBatchSize: 100,
79
+ flushInterval: 30,
80
+ maxStoredEvents: 10000,
81
+ enableDebugLogging: __DEV__,
82
+ trackAppLifecycleEvents: true,
83
+ });
84
+ ```
85
+
86
+ | Option | Default | Description |
87
+ |--------|---------|-------------|
88
+ | `baseURL` | `https://mostlygoodmetrics.com` | API endpoint |
89
+ | `environment` | `"production"` | Environment name |
90
+ | `appVersion` | - | App version string (required for install/update tracking) |
91
+ | `maxBatchSize` | `100` | Events per batch (1-1000) |
92
+ | `flushInterval` | `30` | Auto-flush interval in seconds |
93
+ | `maxStoredEvents` | `10000` | Max cached events |
94
+ | `enableDebugLogging` | `false` | Enable console output |
95
+ | `trackAppLifecycleEvents` | `true` | Auto-track lifecycle events |
96
+
97
+ ## Automatic Events
98
+
99
+ When `trackAppLifecycleEvents` is enabled (default), the SDK automatically tracks:
100
+
101
+ | Event | When | Properties |
102
+ |-------|------|------------|
103
+ | `$app_installed` | First launch after install | `$version` |
104
+ | `$app_updated` | First launch after version change | `$version`, `$previous_version` |
105
+ | `$app_opened` | App became active (foreground) | - |
106
+ | `$app_backgrounded` | App resigned active (background) | - |
107
+
108
+ ## Automatic Properties
109
+
110
+ The SDK automatically includes these properties with every event:
111
+
112
+ | Property | Description |
113
+ |----------|-------------|
114
+ | `$device_type` | Device type (`phone`, `tablet`) |
115
+ | `$storage_type` | Storage type (`persistent` or `memory`) |
116
+
117
+ Additionally, `osVersion` and `appVersion` (if configured) are included at the event level.
118
+
119
+ ## Event Naming
120
+
121
+ Event names must:
122
+ - Start with a letter (or `$` for system events)
123
+ - Contain only alphanumeric characters and underscores
124
+ - Be 255 characters or less
125
+
126
+ ```typescript
127
+ // Valid
128
+ MostlyGoodMetrics.track('button_clicked');
129
+ MostlyGoodMetrics.track('PurchaseCompleted');
130
+ MostlyGoodMetrics.track('step_1_completed');
131
+
132
+ // Invalid (will be ignored)
133
+ MostlyGoodMetrics.track('123_event'); // starts with number
134
+ MostlyGoodMetrics.track('event-name'); // contains hyphen
135
+ MostlyGoodMetrics.track('event name'); // contains space
136
+ ```
137
+
138
+ ## Properties
139
+
140
+ Events support various property types:
141
+
142
+ ```typescript
143
+ MostlyGoodMetrics.track('checkout', {
144
+ string_prop: 'value',
145
+ int_prop: 42,
146
+ double_prop: 3.14,
147
+ bool_prop: true,
148
+ null_prop: null,
149
+ list_prop: ['a', 'b', 'c'],
150
+ nested: {
151
+ key: 'value',
152
+ },
153
+ });
154
+ ```
155
+
156
+ **Limits:**
157
+ - String values: truncated to 1000 characters
158
+ - Nesting depth: max 3 levels
159
+ - Total properties size: max 10KB
160
+
161
+ ## Manual Flush
162
+
163
+ Events are automatically flushed periodically and when the app backgrounds. You can also trigger a manual flush:
164
+
165
+ ```typescript
166
+ MostlyGoodMetrics.flush();
167
+ ```
168
+
169
+ To check pending events:
170
+
171
+ ```typescript
172
+ const count = await MostlyGoodMetrics.getPendingEventCount();
173
+ console.log(`${count} events pending`);
174
+ ```
175
+
176
+ ## Automatic Behavior
177
+
178
+ The SDK automatically:
179
+
180
+ - **Persists events** to AsyncStorage, surviving app restarts
181
+ - **Batches events** for efficient network usage
182
+ - **Flushes on interval** (default: every 30 seconds)
183
+ - **Flushes on background** when the app goes to background
184
+ - **Retries on failure** for network errors (events are preserved)
185
+ - **Persists user ID** across app launches
186
+ - **Generates session IDs** per app launch
187
+
188
+ ## Debug Logging
189
+
190
+ Enable debug logging to see SDK activity:
191
+
192
+ ```typescript
193
+ MostlyGoodMetrics.configure('mgm_proj_your_api_key', {
194
+ enableDebugLogging: true,
195
+ });
196
+ ```
197
+
198
+ Output example:
199
+ ```
200
+ [MostlyGoodMetrics] Configuring with options: {...}
201
+ [MostlyGoodMetrics] Setting up lifecycle tracking
202
+ [MostlyGoodMetrics] App opened (foreground)
203
+ [MostlyGoodMetrics] Flushing events
204
+ ```
205
+
206
+ ## Running the Example
207
+
208
+ ```bash
209
+ cd example
210
+ npm install
211
+ ```
212
+
213
+ For iOS:
214
+
215
+ ```bash
216
+ cd ios && pod install && cd ..
217
+ npm run ios
218
+ ```
219
+
220
+ For Android:
221
+
222
+ ```bash
223
+ npm run android
224
+ ```
225
+
226
+ ## License
227
+
228
+ MIT
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _javascript = require("@mostly-good-metrics/javascript");
9
+ var _storage = require("./storage");
10
+ // Use global to persist state across hot reloads
11
+ const g = globalThis;
12
+
13
+ // Initialize or restore state
14
+ if (!g.__MGM_RN_STATE__) {
15
+ g.__MGM_RN_STATE__ = {
16
+ appStateSubscription: null,
17
+ isConfigured: false,
18
+ currentAppState: _reactNative.AppState.currentState,
19
+ debugLogging: false,
20
+ lastLifecycleEvent: null
21
+ };
22
+ }
23
+ const state = g.__MGM_RN_STATE__;
24
+ const DEDUPE_INTERVAL_MS = 1000; // Ignore duplicate events within 1 second
25
+
26
+ function log(...args) {
27
+ if (state.debugLogging) {
28
+ console.log('[MostlyGoodMetrics]', ...args);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Track a lifecycle event with deduplication.
34
+ */
35
+ function trackLifecycleEvent(eventName, properties) {
36
+ const now = Date.now();
37
+
38
+ // Deduplicate events that fire multiple times in quick succession
39
+ if (state.lastLifecycleEvent && state.lastLifecycleEvent.name === eventName && now - state.lastLifecycleEvent.time < DEDUPE_INTERVAL_MS) {
40
+ log(`Skipping duplicate ${eventName} (${now - state.lastLifecycleEvent.time}ms ago)`);
41
+ return;
42
+ }
43
+ state.lastLifecycleEvent = {
44
+ name: eventName,
45
+ time: now
46
+ };
47
+ log(`Tracking lifecycle event: ${eventName}`);
48
+ _javascript.MostlyGoodMetrics.track(eventName, properties);
49
+ }
50
+
51
+ /**
52
+ * Handle app state changes for lifecycle tracking.
53
+ */
54
+ function handleAppStateChange(nextAppState) {
55
+ if (!_javascript.MostlyGoodMetrics.shared) return;
56
+ log(`AppState change: ${state.currentAppState} -> ${nextAppState}`);
57
+
58
+ // App came to foreground
59
+ if (state.currentAppState.match(/inactive|background/) && nextAppState === 'active') {
60
+ trackLifecycleEvent(_javascript.SystemEvents.APP_OPENED);
61
+ }
62
+
63
+ // App went to background
64
+ if (state.currentAppState === 'active' && nextAppState.match(/inactive|background/)) {
65
+ trackLifecycleEvent(_javascript.SystemEvents.APP_BACKGROUNDED);
66
+ // Flush events when going to background
67
+ _javascript.MostlyGoodMetrics.flush().catch(e => log('Flush error:', e));
68
+ }
69
+ state.currentAppState = nextAppState;
70
+ }
71
+
72
+ /**
73
+ * Track app install or update events.
74
+ */
75
+ async function trackInstallOrUpdate(appVersion) {
76
+ if (!appVersion) return;
77
+ const previousVersion = await _storage.persistence.getAppVersion();
78
+ const isFirst = await _storage.persistence.isFirstLaunch();
79
+ if (isFirst) {
80
+ trackLifecycleEvent(_javascript.SystemEvents.APP_INSTALLED, {
81
+ [_javascript.SystemProperties.VERSION]: appVersion
82
+ });
83
+ await _storage.persistence.setAppVersion(appVersion);
84
+ } else if (previousVersion && previousVersion !== appVersion) {
85
+ trackLifecycleEvent(_javascript.SystemEvents.APP_UPDATED, {
86
+ [_javascript.SystemProperties.VERSION]: appVersion,
87
+ [_javascript.SystemProperties.PREVIOUS_VERSION]: previousVersion
88
+ });
89
+ await _storage.persistence.setAppVersion(appVersion);
90
+ } else if (!previousVersion) {
91
+ await _storage.persistence.setAppVersion(appVersion);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * MostlyGoodMetrics React Native SDK
97
+ */
98
+ const MostlyGoodMetrics = {
99
+ /**
100
+ * Configure the SDK with an API key and optional settings.
101
+ */
102
+ configure(apiKey, config = {}) {
103
+ // Check both our state and the underlying JS SDK
104
+ if (state.isConfigured || _javascript.MostlyGoodMetrics.isConfigured) {
105
+ log('Already configured, skipping');
106
+ return;
107
+ }
108
+ state.debugLogging = config.enableDebugLogging ?? false;
109
+ log('Configuring with options:', config);
110
+
111
+ // Create AsyncStorage-based storage
112
+ const storage = new _storage.AsyncStorageEventStorage(config.maxStoredEvents);
113
+
114
+ // Restore user ID from storage
115
+ _storage.persistence.getUserId().then(userId => {
116
+ if (userId) {
117
+ log('Restored user ID:', userId);
118
+ }
119
+ });
120
+
121
+ // Configure the JS SDK
122
+ // Disable its built-in lifecycle tracking since we handle it ourselves
123
+ _javascript.MostlyGoodMetrics.configure({
124
+ apiKey,
125
+ ...config,
126
+ storage,
127
+ osVersion: getOSVersion(),
128
+ trackAppLifecycleEvents: false // We handle this with AppState
129
+ });
130
+ state.isConfigured = true;
131
+
132
+ // Set up React Native lifecycle tracking
133
+ if (config.trackAppLifecycleEvents !== false) {
134
+ log('Setting up lifecycle tracking, currentAppState:', state.currentAppState);
135
+
136
+ // Remove any existing listener (in case of hot reload)
137
+ if (state.appStateSubscription) {
138
+ state.appStateSubscription.remove();
139
+ state.appStateSubscription = null;
140
+ }
141
+
142
+ // Track initial app open
143
+ trackLifecycleEvent(_javascript.SystemEvents.APP_OPENED);
144
+
145
+ // Track install/update
146
+ trackInstallOrUpdate(config.appVersion).catch(e => log('Install/update tracking error:', e));
147
+
148
+ // Subscribe to app state changes
149
+ state.appStateSubscription = _reactNative.AppState.addEventListener('change', handleAppStateChange);
150
+ }
151
+ },
152
+ /**
153
+ * Track an event with optional properties.
154
+ */
155
+ track(name, properties) {
156
+ if (!state.isConfigured) {
157
+ console.warn('[MostlyGoodMetrics] SDK not configured. Call configure() first.');
158
+ return;
159
+ }
160
+
161
+ // Add React Native specific properties
162
+ const enrichedProperties = {
163
+ [_javascript.SystemProperties.DEVICE_TYPE]: getDeviceType(),
164
+ $storage_type: (0, _storage.getStorageType)(),
165
+ ...properties
166
+ };
167
+ _javascript.MostlyGoodMetrics.track(name, enrichedProperties);
168
+ },
169
+ /**
170
+ * Identify a user.
171
+ */
172
+ identify(userId) {
173
+ if (!state.isConfigured) {
174
+ console.warn('[MostlyGoodMetrics] SDK not configured. Call configure() first.');
175
+ return;
176
+ }
177
+ log('Identifying user:', userId);
178
+ _javascript.MostlyGoodMetrics.identify(userId);
179
+ // Also persist to AsyncStorage for restoration
180
+ _storage.persistence.setUserId(userId).catch(e => log('Failed to persist user ID:', e));
181
+ },
182
+ /**
183
+ * Clear the current user identity.
184
+ */
185
+ resetIdentity() {
186
+ if (!state.isConfigured) return;
187
+ log('Resetting identity');
188
+ _javascript.MostlyGoodMetrics.resetIdentity();
189
+ _storage.persistence.setUserId(null).catch(e => log('Failed to clear user ID:', e));
190
+ },
191
+ /**
192
+ * Manually flush pending events to the server.
193
+ */
194
+ flush() {
195
+ if (!state.isConfigured) return;
196
+ log('Flushing events');
197
+ _javascript.MostlyGoodMetrics.flush().catch(e => log('Flush error:', e));
198
+ },
199
+ /**
200
+ * Start a new session with a fresh session ID.
201
+ */
202
+ startNewSession() {
203
+ if (!state.isConfigured) return;
204
+ log('Starting new session');
205
+ _javascript.MostlyGoodMetrics.startNewSession();
206
+ },
207
+ /**
208
+ * Clear all pending events without sending them.
209
+ */
210
+ clearPendingEvents() {
211
+ if (!state.isConfigured) return;
212
+ log('Clearing pending events');
213
+ _javascript.MostlyGoodMetrics.clearPendingEvents().catch(e => log('Clear error:', e));
214
+ },
215
+ /**
216
+ * Get the number of pending events.
217
+ */
218
+ async getPendingEventCount() {
219
+ if (!state.isConfigured) return 0;
220
+ return _javascript.MostlyGoodMetrics.getPendingEventCount();
221
+ },
222
+ /**
223
+ * Clean up resources. Call when unmounting the app.
224
+ */
225
+ destroy() {
226
+ if (state.appStateSubscription) {
227
+ state.appStateSubscription.remove();
228
+ state.appStateSubscription = null;
229
+ }
230
+ _javascript.MostlyGoodMetrics.reset();
231
+ state.isConfigured = false;
232
+ state.lastLifecycleEvent = null;
233
+ log('Destroyed');
234
+ }
235
+ };
236
+
237
+ /**
238
+ * Get device type based on platform.
239
+ */
240
+ function getDeviceType() {
241
+ if (_reactNative.Platform.OS === 'ios') {
242
+ // Could use react-native-device-info for more accuracy
243
+ return _reactNative.Platform.isPad ? 'tablet' : 'phone';
244
+ }
245
+ if (_reactNative.Platform.OS === 'android') {
246
+ return 'phone'; // Could detect tablet with dimensions
247
+ }
248
+ return 'unknown';
249
+ }
250
+
251
+ /**
252
+ * Get OS version based on platform.
253
+ */
254
+ function getOSVersion() {
255
+ const version = _reactNative.Platform.Version;
256
+ if (_reactNative.Platform.OS === 'ios') {
257
+ // iOS returns a string like "15.0"
258
+ return String(version);
259
+ }
260
+ if (_reactNative.Platform.OS === 'android') {
261
+ // Android returns SDK version number (e.g., 31 for Android 12)
262
+ return String(version);
263
+ }
264
+ return 'unknown';
265
+ }
266
+ var _default = exports.default = MostlyGoodMetrics;
267
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_javascript","_storage","g","globalThis","__MGM_RN_STATE__","appStateSubscription","isConfigured","currentAppState","AppState","currentState","debugLogging","lastLifecycleEvent","state","DEDUPE_INTERVAL_MS","log","args","console","trackLifecycleEvent","eventName","properties","now","Date","name","time","MGMClient","track","handleAppStateChange","nextAppState","shared","match","SystemEvents","APP_OPENED","APP_BACKGROUNDED","flush","catch","e","trackInstallOrUpdate","appVersion","previousVersion","persistence","getAppVersion","isFirst","isFirstLaunch","APP_INSTALLED","SystemProperties","VERSION","setAppVersion","APP_UPDATED","PREVIOUS_VERSION","MostlyGoodMetrics","configure","apiKey","config","enableDebugLogging","storage","AsyncStorageEventStorage","maxStoredEvents","getUserId","then","userId","osVersion","getOSVersion","trackAppLifecycleEvents","remove","addEventListener","warn","enrichedProperties","DEVICE_TYPE","getDeviceType","$storage_type","getStorageType","identify","setUserId","resetIdentity","startNewSession","clearPendingEvents","getPendingEventCount","destroy","reset","Platform","OS","isPad","version","Version","String","_default","exports","default"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AsyncStorageEventStorage = void 0;
7
+ exports.getStorageType = getStorageType;
8
+ exports.persistence = void 0;
9
+ const STORAGE_KEY = 'mostlygoodmetrics_events';
10
+ const USER_ID_KEY = 'mostlygoodmetrics_user_id';
11
+ const APP_VERSION_KEY = 'mostlygoodmetrics_app_version';
12
+ const FIRST_LAUNCH_KEY = 'mostlygoodmetrics_installed';
13
+
14
+ // Try to import AsyncStorage, fall back to null if not available
15
+ let AsyncStorage = null;
16
+ try {
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ AsyncStorage = require('@react-native-async-storage/async-storage').default;
19
+ } catch {
20
+ // AsyncStorage not installed - will use in-memory storage
21
+ }
22
+
23
+ /**
24
+ * Returns the storage type being used.
25
+ */
26
+ function getStorageType() {
27
+ return AsyncStorage ? 'persistent' : 'memory';
28
+ }
29
+
30
+ /**
31
+ * In-memory fallback storage when AsyncStorage is not available.
32
+ */
33
+ const memoryStorage = {};
34
+
35
+ /**
36
+ * Storage helpers that work with or without AsyncStorage.
37
+ */
38
+ async function getItem(key) {
39
+ if (AsyncStorage) {
40
+ try {
41
+ return await AsyncStorage.getItem(key);
42
+ } catch {
43
+ return memoryStorage[key] ?? null;
44
+ }
45
+ }
46
+ return memoryStorage[key] ?? null;
47
+ }
48
+ async function setItem(key, value) {
49
+ memoryStorage[key] = value;
50
+ if (AsyncStorage) {
51
+ try {
52
+ await AsyncStorage.setItem(key, value);
53
+ } catch {
54
+ // Fall back to memory storage (already set above)
55
+ }
56
+ }
57
+ }
58
+ async function removeItem(key) {
59
+ delete memoryStorage[key];
60
+ if (AsyncStorage) {
61
+ try {
62
+ await AsyncStorage.removeItem(key);
63
+ } catch {
64
+ // Already removed from memory
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Event storage for React Native.
71
+ * Uses AsyncStorage if available, otherwise falls back to in-memory storage.
72
+ */
73
+ class AsyncStorageEventStorage {
74
+ events = null;
75
+ constructor(maxEvents = 10000) {
76
+ this.maxEvents = Math.max(maxEvents, 100);
77
+ }
78
+ async loadEvents() {
79
+ if (this.events !== null) {
80
+ return this.events;
81
+ }
82
+ try {
83
+ const stored = await getItem(STORAGE_KEY);
84
+ if (stored) {
85
+ this.events = JSON.parse(stored);
86
+ } else {
87
+ this.events = [];
88
+ }
89
+ } catch {
90
+ this.events = [];
91
+ }
92
+ return this.events;
93
+ }
94
+ async saveEvents() {
95
+ await setItem(STORAGE_KEY, JSON.stringify(this.events ?? []));
96
+ }
97
+ async store(event) {
98
+ const events = await this.loadEvents();
99
+ events.push(event);
100
+
101
+ // Trim oldest events if we exceed the limit
102
+ if (events.length > this.maxEvents) {
103
+ const excess = events.length - this.maxEvents;
104
+ events.splice(0, excess);
105
+ }
106
+ await this.saveEvents();
107
+ }
108
+ async fetchEvents(limit) {
109
+ const events = await this.loadEvents();
110
+ return events.slice(0, limit);
111
+ }
112
+ async removeEvents(count) {
113
+ const events = await this.loadEvents();
114
+ events.splice(0, count);
115
+ await this.saveEvents();
116
+ }
117
+ async eventCount() {
118
+ const events = await this.loadEvents();
119
+ return events.length;
120
+ }
121
+ async clear() {
122
+ this.events = [];
123
+ await removeItem(STORAGE_KEY);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Persistence helpers for user ID and app version.
129
+ */
130
+ exports.AsyncStorageEventStorage = AsyncStorageEventStorage;
131
+ const persistence = exports.persistence = {
132
+ async getUserId() {
133
+ return getItem(USER_ID_KEY);
134
+ },
135
+ async setUserId(userId) {
136
+ if (userId) {
137
+ await setItem(USER_ID_KEY, userId);
138
+ } else {
139
+ await removeItem(USER_ID_KEY);
140
+ }
141
+ },
142
+ async getAppVersion() {
143
+ return getItem(APP_VERSION_KEY);
144
+ },
145
+ async setAppVersion(version) {
146
+ if (version) {
147
+ await setItem(APP_VERSION_KEY, version);
148
+ } else {
149
+ await removeItem(APP_VERSION_KEY);
150
+ }
151
+ },
152
+ async isFirstLaunch() {
153
+ const hasLaunched = await getItem(FIRST_LAUNCH_KEY);
154
+ if (!hasLaunched) {
155
+ await setItem(FIRST_LAUNCH_KEY, 'true');
156
+ return true;
157
+ }
158
+ return false;
159
+ }
160
+ };
161
+ //# 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","exports","persistence","getUserId","setUserId","userId","getAppVersion","setAppVersion","version","isFirstLaunch","hasLaunched"],"sourceRoot":"../../src","sources":["storage.ts"],"mappings":"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","ignoreList":[]}