mixpanel-react-native 3.1.2 → 3.2.0-beta.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.
Files changed (93) hide show
  1. package/.claude/settings.local.json +12 -1
  2. package/.github/dependabot.yml +7 -0
  3. package/.github/workflows/node.js.yml +24 -1
  4. package/.vscode/settings.json +2 -1
  5. package/MixpanelReactNative.podspec +1 -1
  6. package/Samples/MixpanelExample/ios/MixpanelExample.xcworkspace/contents.xcworkspacedata +10 -0
  7. package/Samples/MixpanelExample/ios/Podfile.lock +1996 -0
  8. package/Samples/MixpanelStarter/.bundle/config +2 -0
  9. package/Samples/MixpanelStarter/.env.example +4 -0
  10. package/Samples/MixpanelStarter/.eslintrc.js +4 -0
  11. package/Samples/MixpanelStarter/.prettierrc.js +5 -0
  12. package/Samples/MixpanelStarter/.watchmanconfig +1 -0
  13. package/Samples/MixpanelStarter/App.tsx +10 -0
  14. package/Samples/MixpanelStarter/CLAUDE.md +538 -0
  15. package/Samples/MixpanelStarter/Gemfile +16 -0
  16. package/Samples/MixpanelStarter/INTEGRATION_GUIDE.md +606 -0
  17. package/Samples/MixpanelStarter/README.md +406 -0
  18. package/Samples/MixpanelStarter/__tests__/MixpanelContext.test.tsx +63 -0
  19. package/Samples/MixpanelStarter/android/app/build.gradle +119 -0
  20. package/Samples/MixpanelStarter/android/app/debug.keystore +0 -0
  21. package/Samples/MixpanelStarter/android/app/proguard-rules.pro +10 -0
  22. package/Samples/MixpanelStarter/android/app/src/main/AndroidManifest.xml +27 -0
  23. package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainActivity.kt +22 -0
  24. package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainApplication.kt +27 -0
  25. package/Samples/MixpanelStarter/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  26. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  27. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  28. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  29. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  30. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  31. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  32. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  33. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  34. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  35. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  36. package/Samples/MixpanelStarter/android/app/src/main/res/values/strings.xml +3 -0
  37. package/Samples/MixpanelStarter/android/app/src/main/res/values/styles.xml +9 -0
  38. package/Samples/MixpanelStarter/android/build.gradle +21 -0
  39. package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  40. package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  41. package/Samples/MixpanelStarter/android/gradle.properties +44 -0
  42. package/Samples/MixpanelStarter/android/gradlew +251 -0
  43. package/Samples/MixpanelStarter/android/gradlew.bat +99 -0
  44. package/Samples/MixpanelStarter/android/settings.gradle +6 -0
  45. package/Samples/MixpanelStarter/app.json +4 -0
  46. package/Samples/MixpanelStarter/babel.config.js +14 -0
  47. package/Samples/MixpanelStarter/index.js +9 -0
  48. package/Samples/MixpanelStarter/ios/.xcode.env +11 -0
  49. package/Samples/MixpanelStarter/ios/MixpanelStarter/AppDelegate.swift +48 -0
  50. package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  51. package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/Contents.json +6 -0
  52. package/Samples/MixpanelStarter/ios/MixpanelStarter/Info.plist +55 -0
  53. package/Samples/MixpanelStarter/ios/MixpanelStarter/LaunchScreen.storyboard +47 -0
  54. package/Samples/MixpanelStarter/ios/MixpanelStarter/PrivacyInfo.xcprivacy +38 -0
  55. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/project.pbxproj +482 -0
  56. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/xcshareddata/xcschemes/MixpanelStarter.xcscheme +88 -0
  57. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcworkspace/contents.xcworkspacedata +10 -0
  58. package/Samples/MixpanelStarter/ios/Podfile +34 -0
  59. package/Samples/MixpanelStarter/ios/Podfile.lock +2839 -0
  60. package/Samples/MixpanelStarter/jest.config.js +3 -0
  61. package/Samples/MixpanelStarter/metro.config.js +42 -0
  62. package/Samples/MixpanelStarter/package-lock.json +12141 -0
  63. package/Samples/MixpanelStarter/package.json +51 -0
  64. package/Samples/MixpanelStarter/src/@types/env.d.ts +3 -0
  65. package/Samples/MixpanelStarter/src/App.tsx +83 -0
  66. package/Samples/MixpanelStarter/src/components/ActionButton.tsx +92 -0
  67. package/Samples/MixpanelStarter/src/components/ErrorBoundary.tsx +81 -0
  68. package/Samples/MixpanelStarter/src/components/EventTrackingLog.tsx +163 -0
  69. package/Samples/MixpanelStarter/src/components/FlagCard.tsx +199 -0
  70. package/Samples/MixpanelStarter/src/components/InfoCard.tsx +77 -0
  71. package/Samples/MixpanelStarter/src/components/TestResultDisplay.tsx +181 -0
  72. package/Samples/MixpanelStarter/src/constants/tracking.ts +77 -0
  73. package/Samples/MixpanelStarter/src/contexts/MixpanelContext.tsx +159 -0
  74. package/Samples/MixpanelStarter/src/screens/FeatureFlagsScreen.tsx +1011 -0
  75. package/Samples/MixpanelStarter/src/screens/HomeScreen.tsx +307 -0
  76. package/Samples/MixpanelStarter/src/screens/OnboardingScreen.tsx +253 -0
  77. package/Samples/MixpanelStarter/src/screens/SettingsScreen.tsx +316 -0
  78. package/Samples/MixpanelStarter/src/types/flags.types.ts +42 -0
  79. package/Samples/MixpanelStarter/src/types/mixpanel.types.ts +26 -0
  80. package/Samples/MixpanelStarter/tsconfig.json +13 -0
  81. package/__tests__/flags.test.js +730 -0
  82. package/__tests__/index.test.js +7 -3
  83. package/__tests__/jest_setup.js +18 -0
  84. package/android/build.gradle +1 -1
  85. package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +272 -2
  86. package/index.d.ts +64 -1
  87. package/index.js +42 -3
  88. package/ios/MixpanelReactNative.m +19 -1
  89. package/ios/MixpanelReactNative.swift +183 -5
  90. package/javascript/mixpanel-flags-js.js +463 -0
  91. package/javascript/mixpanel-flags.js +290 -0
  92. package/javascript/mixpanel-main.js +13 -1
  93. package/package.json +2 -2
@@ -0,0 +1,316 @@
1
+ import React, {useState, useEffect} from 'react';
2
+ import {View, Text, StyleSheet, ScrollView, Switch, Alert} from 'react-native';
3
+ import {useMixpanel} from '../contexts/MixpanelContext';
4
+ import {ActionButton} from '../components/ActionButton';
5
+ import {InfoCard} from '../components/InfoCard';
6
+ import {Events} from '../constants/tracking';
7
+
8
+ export const SettingsScreen: React.FC = () => {
9
+ const {mixpanel, isInitialized, track, reset, flush} = useMixpanel();
10
+ const [trackingEnabled, setTrackingEnabled] = useState(true);
11
+ const [distinctId, setDistinctId] = useState<string>('');
12
+ const [loading, setLoading] = useState(false);
13
+ const [sdkVersion, setSdkVersion] = useState<string>('');
14
+
15
+ useEffect(() => {
16
+ // Track screen view
17
+ if (isInitialized) {
18
+ track(Events.SCREEN_VIEWED, {
19
+ screen_name: 'Settings',
20
+ timestamp: new Date().toISOString(),
21
+ });
22
+ }
23
+ }, [isInitialized, track]);
24
+
25
+ useEffect(() => {
26
+ // Get current settings
27
+ const fetchSettings = async () => {
28
+ if (mixpanel && isInitialized) {
29
+ try {
30
+ // Get distinct ID
31
+ const id = await mixpanel.getDistinctId();
32
+ setDistinctId(id);
33
+
34
+ // Check opt-out status
35
+ const hasOptedOut = await mixpanel.hasOptedOutTracking();
36
+ setTrackingEnabled(!hasOptedOut);
37
+ } catch (error) {
38
+ console.error('Failed to fetch settings:', error);
39
+ }
40
+ }
41
+ };
42
+ fetchSettings();
43
+ }, [mixpanel, isInitialized]);
44
+
45
+ const handleToggleTracking = async (value: boolean) => {
46
+ setLoading(true);
47
+ try {
48
+ if (value) {
49
+ // Opt in to tracking
50
+ await mixpanel?.optInTracking();
51
+ track(Events.TRACKING_OPTED_IN, {
52
+ timestamp: new Date().toISOString(),
53
+ });
54
+ Alert.alert(
55
+ 'Tracking Enabled',
56
+ 'Analytics tracking has been enabled. Your events will now be sent to Mixpanel.',
57
+ );
58
+ } else {
59
+ // Track opt-out event before opting out
60
+ track(Events.TRACKING_OPTED_OUT, {
61
+ timestamp: new Date().toISOString(),
62
+ });
63
+ // Flush events before opting out
64
+ await mixpanel?.flush();
65
+ // Opt out of tracking
66
+ await mixpanel?.optOutTracking();
67
+ Alert.alert(
68
+ 'Tracking Disabled',
69
+ 'Analytics tracking has been disabled. No events will be sent until you opt back in.',
70
+ );
71
+ }
72
+ setTrackingEnabled(value);
73
+ } catch (error) {
74
+ console.error('Failed to toggle tracking:', error);
75
+ Alert.alert('Error', 'Failed to update tracking preference');
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ };
80
+
81
+ const handleResetData = () => {
82
+ Alert.alert(
83
+ 'Reset All Data',
84
+ 'This will clear your distinct ID, user profile, and all local data. Are you sure?',
85
+ [
86
+ {text: 'Cancel', style: 'cancel'},
87
+ {
88
+ text: 'Reset',
89
+ style: 'destructive',
90
+ onPress: async () => {
91
+ setLoading(true);
92
+ try {
93
+ // Track reset event before resetting
94
+ track(Events.DATA_RESET, {
95
+ timestamp: new Date().toISOString(),
96
+ });
97
+
98
+ // Flush events to ensure reset event is sent
99
+ await flush();
100
+
101
+ // Reset all data
102
+ reset();
103
+
104
+ Alert.alert(
105
+ 'Data Reset',
106
+ 'All your data has been cleared. You now have a new anonymous ID.',
107
+ );
108
+
109
+ // Fetch new distinct ID
110
+ const newId = await mixpanel?.getDistinctId();
111
+ if (newId) {
112
+ setDistinctId(newId);
113
+ }
114
+ } catch (error) {
115
+ console.error('Failed to reset data:', error);
116
+ Alert.alert('Error', 'Failed to reset data');
117
+ } finally {
118
+ setLoading(false);
119
+ }
120
+ },
121
+ },
122
+ ],
123
+ );
124
+ };
125
+
126
+ const handleFlushEvents = async () => {
127
+ setLoading(true);
128
+ try {
129
+ // Track flush event
130
+ track(Events.EVENTS_FLUSHED, {
131
+ timestamp: new Date().toISOString(),
132
+ });
133
+
134
+ // Flush all queued events
135
+ await flush();
136
+
137
+ Alert.alert(
138
+ 'Events Flushed',
139
+ 'All queued events have been sent to Mixpanel immediately.',
140
+ );
141
+ } catch (error) {
142
+ console.error('Failed to flush events:', error);
143
+ Alert.alert('Error', 'Failed to flush events');
144
+ } finally {
145
+ setLoading(false);
146
+ }
147
+ };
148
+
149
+ const handleViewDistinctId = () => {
150
+ Alert.alert('Your Distinct ID', distinctId || 'Loading...');
151
+ };
152
+
153
+ return (
154
+ <ScrollView style={styles.container} contentContainerStyle={styles.content}>
155
+ <View style={styles.header}>
156
+ <Text style={styles.title}>Settings</Text>
157
+ <Text style={styles.subtitle}>Privacy controls and data management</Text>
158
+ </View>
159
+
160
+ <InfoCard title="Your Distinct ID" content={distinctId || 'Loading...'} />
161
+
162
+ <View style={styles.section}>
163
+ <Text style={styles.sectionTitle}>Privacy Controls</Text>
164
+
165
+ <View style={styles.preferenceRow}>
166
+ <View style={styles.preferenceLabel}>
167
+ <Text style={styles.preferenceTitle}>Analytics Tracking</Text>
168
+ <Text style={styles.preferenceSubtitle}>
169
+ {trackingEnabled
170
+ ? 'Events are being sent to Mixpanel'
171
+ : 'Tracking is disabled (GDPR compliant)'}
172
+ </Text>
173
+ </View>
174
+ <Switch
175
+ value={trackingEnabled}
176
+ onValueChange={handleToggleTracking}
177
+ disabled={!isInitialized || loading}
178
+ />
179
+ </View>
180
+
181
+ <ActionButton
182
+ title="Reset All Data"
183
+ onPress={handleResetData}
184
+ variant="danger"
185
+ disabled={!isInitialized || loading}
186
+ loading={loading}
187
+ style={styles.actionButton}
188
+ />
189
+ </View>
190
+
191
+ <View style={styles.section}>
192
+ <Text style={styles.sectionTitle}>Developer Tools</Text>
193
+
194
+ <ActionButton
195
+ title="Flush Events Now"
196
+ onPress={handleFlushEvents}
197
+ variant="secondary"
198
+ disabled={!isInitialized || loading}
199
+ loading={loading}
200
+ style={styles.actionButton}
201
+ />
202
+
203
+ <ActionButton
204
+ title="View Distinct ID"
205
+ onPress={handleViewDistinctId}
206
+ variant="secondary"
207
+ disabled={!isInitialized}
208
+ style={styles.actionButton}
209
+ />
210
+ </View>
211
+
212
+ <View style={styles.section}>
213
+ <Text style={styles.sectionTitle}>About</Text>
214
+
215
+ <InfoCard
216
+ title="SDK Information"
217
+ content={{
218
+ 'SDK Version': '3.1.2',
219
+ 'Implementation Mode': 'Native (iOS: Swift, Android: Java)',
220
+ 'Tracking Status': trackingEnabled ? 'Enabled' : 'Disabled',
221
+ }}
222
+ />
223
+ </View>
224
+
225
+ <InfoCard
226
+ title="What's Happening?"
227
+ content={`Privacy Controls:
228
+ • optInTracking() / optOutTracking() controls data collection
229
+ • Complies with GDPR and privacy regulations
230
+ • hasOptedOutTracking() checks current status
231
+ • When opted out, no events are sent to Mixpanel
232
+
233
+ Data Reset:
234
+ • reset() clears all local data and user identity
235
+ • Generates a new anonymous distinct ID
236
+ • Use this for logout or "forget me" functionality
237
+ • Previous events remain in Mixpanel (not deleted)
238
+
239
+ Manual Flush:
240
+ • flush() immediately sends all queued events
241
+ • Useful before app termination or user logout
242
+ • Events are normally flushed automatically every 60s
243
+ • Ensures data is sent even if app is force-closed
244
+
245
+ Distinct ID:
246
+ • Every user has a unique distinct ID
247
+ • Anonymous users get an auto-generated UUID
248
+ • Identified users get their custom ID (email, etc.)
249
+ • Used to associate events with individual users`}
250
+ style={styles.infoCard}
251
+ />
252
+ </ScrollView>
253
+ );
254
+ };
255
+
256
+ const styles = StyleSheet.create({
257
+ container: {
258
+ flex: 1,
259
+ backgroundColor: '#fff',
260
+ },
261
+ content: {
262
+ padding: 20,
263
+ },
264
+ header: {
265
+ marginBottom: 20,
266
+ },
267
+ title: {
268
+ fontSize: 24,
269
+ fontWeight: 'bold',
270
+ color: '#333',
271
+ marginBottom: 8,
272
+ },
273
+ subtitle: {
274
+ fontSize: 16,
275
+ color: '#666',
276
+ },
277
+ section: {
278
+ marginTop: 20,
279
+ marginBottom: 10,
280
+ },
281
+ sectionTitle: {
282
+ fontSize: 18,
283
+ fontWeight: '600',
284
+ color: '#333',
285
+ marginBottom: 16,
286
+ },
287
+ preferenceRow: {
288
+ flexDirection: 'row',
289
+ justifyContent: 'space-between',
290
+ alignItems: 'center',
291
+ paddingVertical: 12,
292
+ paddingHorizontal: 15,
293
+ backgroundColor: '#f9f9f9',
294
+ borderRadius: 8,
295
+ marginBottom: 16,
296
+ },
297
+ preferenceLabel: {
298
+ flex: 1,
299
+ },
300
+ preferenceTitle: {
301
+ fontSize: 16,
302
+ fontWeight: '500',
303
+ color: '#333',
304
+ marginBottom: 4,
305
+ },
306
+ preferenceSubtitle: {
307
+ fontSize: 13,
308
+ color: '#666',
309
+ },
310
+ actionButton: {
311
+ marginBottom: 12,
312
+ },
313
+ infoCard: {
314
+ marginTop: 10,
315
+ },
316
+ });
@@ -0,0 +1,42 @@
1
+ export interface MixpanelFlagVariant {
2
+ key: string;
3
+ value: any;
4
+ experimentID?: string;
5
+ isExperimentActive?: boolean;
6
+ isQATester?: boolean;
7
+ }
8
+
9
+ export interface TestResult {
10
+ id: string;
11
+ timestamp: Date;
12
+ method: string;
13
+ flagName: string;
14
+ fallback: any;
15
+ result: any;
16
+ resultType: string;
17
+ executionTime: number;
18
+ usedFallback: boolean;
19
+ error?: string;
20
+ }
21
+
22
+ export interface TrackedEvent {
23
+ id: string;
24
+ timestamp: Date;
25
+ eventName: string;
26
+ properties: Record<string, any>;
27
+ }
28
+
29
+ export type TestMode = 'sync' | 'async' | 'edge' | 'coercion';
30
+
31
+ export type ValueType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null' | 'undefined';
32
+
33
+ export interface FlagInfo {
34
+ key: string;
35
+ value: any;
36
+ valueType: ValueType;
37
+ variantKey: string;
38
+ experimentID?: string;
39
+ isExperimentActive?: boolean;
40
+ isQATester?: boolean;
41
+ lastAccessed?: Date;
42
+ }
@@ -0,0 +1,26 @@
1
+ import {Mixpanel} from 'mixpanel-react-native';
2
+
3
+ export interface MixpanelContextValue {
4
+ mixpanel: Mixpanel | null;
5
+ isInitialized: boolean;
6
+ isLoading: boolean;
7
+ error: Error | null;
8
+
9
+ // Convenience methods
10
+ track: (eventName: string, properties?: Record<string, any>) => void;
11
+ identify: (distinctId: string) => void;
12
+ alias: (alias: string, distinctId?: string) => Promise<void>;
13
+ reset: () => void;
14
+ flush: () => Promise<void>;
15
+ }
16
+
17
+ export interface UserProfile {
18
+ $email?: string;
19
+ $name?: string;
20
+ signup_date?: string;
21
+ [key: string]: any;
22
+ }
23
+
24
+ export interface EventProperties {
25
+ [key: string]: any;
26
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@react-native/typescript-config",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "baseUrl": ".",
6
+ "paths": {
7
+ "@env": ["src/@types/env.d.ts"]
8
+ },
9
+ "typeRoots": ["./node_modules/@types", "./src/@types"]
10
+ },
11
+ "include": ["**/*.ts", "**/*.tsx"],
12
+ "exclude": ["**/node_modules", "**/Pods"]
13
+ }