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,290 @@
1
+ import { MixpanelFlagsJS } from './mixpanel-flags-js';
2
+
3
+ /**
4
+ * Flags class for managing Feature Flags functionality
5
+ * This class handles both native and JavaScript fallback implementations
6
+ */
7
+ export class Flags {
8
+ constructor(token, mixpanelImpl, storage) {
9
+ this.token = token;
10
+ this.mixpanelImpl = mixpanelImpl;
11
+ this.storage = storage;
12
+ this.isNativeMode = typeof mixpanelImpl.loadFlags === 'function';
13
+
14
+ // For JavaScript mode, create the JS implementation
15
+ if (!this.isNativeMode && storage) {
16
+ this.jsFlags = new MixpanelFlagsJS(token, mixpanelImpl, storage);
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Manually trigger a fetch of feature flags from the Mixpanel servers.
22
+ * This is usually automatic but can be called manually if needed.
23
+ */
24
+ async loadFlags() {
25
+ if (this.isNativeMode) {
26
+ return await this.mixpanelImpl.loadFlags(this.token);
27
+ } else if (this.jsFlags) {
28
+ return await this.jsFlags.loadFlags();
29
+ }
30
+ throw new Error("Feature flags are not initialized");
31
+ }
32
+
33
+ /**
34
+ * Check if feature flags have been fetched and are ready to use.
35
+ * @returns {boolean} True if flags are ready, false otherwise
36
+ */
37
+ areFlagsReady() {
38
+ if (this.isNativeMode) {
39
+ return this.mixpanelImpl.areFlagsReadySync(this.token);
40
+ } else if (this.jsFlags) {
41
+ return this.jsFlags.areFlagsReady();
42
+ }
43
+ return false;
44
+ }
45
+
46
+ /**
47
+ * Get a feature flag variant synchronously. Only works when flags are ready.
48
+ * @param {string} featureName - Name of the feature flag
49
+ * @param {object} fallback - Fallback variant if flag is not available
50
+ * @returns {object} The flag variant with key and value properties
51
+ */
52
+ getVariantSync(featureName, fallback) {
53
+ if (!this.areFlagsReady()) {
54
+ return fallback;
55
+ }
56
+
57
+ if (this.isNativeMode) {
58
+ return this.mixpanelImpl.getVariantSync(this.token, featureName, fallback);
59
+ } else if (this.jsFlags) {
60
+ return this.jsFlags.getVariantSync(featureName, fallback);
61
+ }
62
+ return fallback;
63
+ }
64
+
65
+ /**
66
+ * Get a feature flag variant value synchronously. Only works when flags are ready.
67
+ * @param {string} featureName - Name of the feature flag
68
+ * @param {any} fallbackValue - Fallback value if flag is not available
69
+ * @returns {any} The flag value
70
+ */
71
+ getVariantValueSync(featureName, fallbackValue) {
72
+ if (!this.areFlagsReady()) {
73
+ return fallbackValue;
74
+ }
75
+
76
+ if (this.isNativeMode) {
77
+ // Android returns a wrapped object due to React Native limitations
78
+ const result = this.mixpanelImpl.getVariantValueSync(this.token, featureName, fallbackValue);
79
+ if (result && typeof result === 'object' && 'type' in result) {
80
+ // Android wraps the response
81
+ return result.type === 'fallback' ? fallbackValue : result.value;
82
+ }
83
+ // iOS returns the value directly
84
+ return result;
85
+ } else if (this.jsFlags) {
86
+ return this.jsFlags.getVariantValueSync(featureName, fallbackValue);
87
+ }
88
+ return fallbackValue;
89
+ }
90
+
91
+ /**
92
+ * Check if a feature flag is enabled synchronously. Only works when flags are ready.
93
+ * @param {string} featureName - Name of the feature flag
94
+ * @param {boolean} fallbackValue - Fallback value if flag is not available
95
+ * @returns {boolean} True if enabled, false otherwise
96
+ */
97
+ isEnabledSync(featureName, fallbackValue = false) {
98
+ if (!this.areFlagsReady()) {
99
+ return fallbackValue;
100
+ }
101
+
102
+ if (this.isNativeMode) {
103
+ return this.mixpanelImpl.isEnabledSync(this.token, featureName, fallbackValue);
104
+ } else if (this.jsFlags) {
105
+ return this.jsFlags.isEnabledSync(featureName, fallbackValue);
106
+ }
107
+ return fallbackValue;
108
+ }
109
+
110
+ /**
111
+ * Get a feature flag variant asynchronously.
112
+ * Supports both callback and Promise patterns.
113
+ * @param {string} featureName - Name of the feature flag
114
+ * @param {object} fallback - Fallback variant if flag is not available
115
+ * @param {function} callback - Optional callback function
116
+ * @returns {Promise|void} Promise if no callback provided, void otherwise
117
+ */
118
+ getVariant(featureName, fallback, callback) {
119
+ // If callback provided, use callback pattern
120
+ if (typeof callback === 'function') {
121
+ if (this.isNativeMode) {
122
+ this.mixpanelImpl.getVariant(this.token, featureName, fallback)
123
+ .then(result => callback(result))
124
+ .catch(() => callback(fallback));
125
+ } else if (this.jsFlags) {
126
+ this.jsFlags.getVariant(featureName, fallback)
127
+ .then(result => callback(result))
128
+ .catch(() => callback(fallback));
129
+ } else {
130
+ callback(fallback);
131
+ }
132
+ return;
133
+ }
134
+
135
+ // Promise pattern
136
+ return new Promise((resolve) => {
137
+ if (this.isNativeMode) {
138
+ this.mixpanelImpl.getVariant(this.token, featureName, fallback)
139
+ .then(resolve)
140
+ .catch(() => resolve(fallback));
141
+ } else if (this.jsFlags) {
142
+ this.jsFlags.getVariant(featureName, fallback)
143
+ .then(resolve)
144
+ .catch(() => resolve(fallback));
145
+ } else {
146
+ resolve(fallback);
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Get a feature flag variant value asynchronously.
153
+ * Supports both callback and Promise patterns.
154
+ * @param {string} featureName - Name of the feature flag
155
+ * @param {any} fallbackValue - Fallback value if flag is not available
156
+ * @param {function} callback - Optional callback function
157
+ * @returns {Promise|void} Promise if no callback provided, void otherwise
158
+ */
159
+ getVariantValue(featureName, fallbackValue, callback) {
160
+ // If callback provided, use callback pattern
161
+ if (typeof callback === 'function') {
162
+ if (this.isNativeMode) {
163
+ this.mixpanelImpl.getVariantValue(this.token, featureName, fallbackValue)
164
+ .then(result => callback(result))
165
+ .catch(() => callback(fallbackValue));
166
+ } else if (this.jsFlags) {
167
+ this.jsFlags.getVariantValue(featureName, fallbackValue)
168
+ .then(result => callback(result))
169
+ .catch(() => callback(fallbackValue));
170
+ } else {
171
+ callback(fallbackValue);
172
+ }
173
+ return;
174
+ }
175
+
176
+ // Promise pattern
177
+ return new Promise((resolve) => {
178
+ if (this.isNativeMode) {
179
+ this.mixpanelImpl.getVariantValue(this.token, featureName, fallbackValue)
180
+ .then(resolve)
181
+ .catch(() => resolve(fallbackValue));
182
+ } else if (this.jsFlags) {
183
+ this.jsFlags.getVariantValue(featureName, fallbackValue)
184
+ .then(resolve)
185
+ .catch(() => resolve(fallbackValue));
186
+ } else {
187
+ resolve(fallbackValue);
188
+ }
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Check if a feature flag is enabled asynchronously.
194
+ * Supports both callback and Promise patterns.
195
+ * @param {string} featureName - Name of the feature flag
196
+ * @param {boolean} [fallbackValue=false] - Fallback value if flag is not available
197
+ * @param {function} [callback] - Optional callback function
198
+ * @returns {Promise<boolean>|void} Promise if no callback provided, void otherwise
199
+ */
200
+ isEnabled(featureName, fallbackValue = false, callback) {
201
+ // If callback provided, use callback pattern
202
+ if (typeof callback === 'function') {
203
+ if (this.isNativeMode) {
204
+ this.mixpanelImpl.isEnabled(this.token, featureName, fallbackValue)
205
+ .then(result => callback(result))
206
+ .catch(() => callback(fallbackValue));
207
+ } else if (this.jsFlags) {
208
+ this.jsFlags.isEnabled(featureName, fallbackValue)
209
+ .then(result => callback(result))
210
+ .catch(() => callback(fallbackValue));
211
+ } else {
212
+ callback(fallbackValue);
213
+ }
214
+ return;
215
+ }
216
+
217
+ // Promise pattern
218
+ return new Promise((resolve) => {
219
+ if (this.isNativeMode) {
220
+ this.mixpanelImpl.isEnabled(this.token, featureName, fallbackValue)
221
+ .then(resolve)
222
+ .catch(() => resolve(fallbackValue));
223
+ } else if (this.jsFlags) {
224
+ this.jsFlags.isEnabled(featureName, fallbackValue)
225
+ .then(resolve)
226
+ .catch(() => resolve(fallbackValue));
227
+ } else {
228
+ resolve(fallbackValue);
229
+ }
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Update the context used for feature flag evaluation
235
+ * Aligned with mixpanel-js API
236
+ *
237
+ * NOTE: This method is only available in JavaScript mode (Expo/React Native Web).
238
+ * In native mode, context must be set during initialization via FeatureFlagsOptions.
239
+ *
240
+ * @param {object} newContext - New context properties to add/update
241
+ * @param {object} options - Options object
242
+ * @param {boolean} options.replace - If true, replace entire context instead of merging
243
+ * @returns {Promise<void>}
244
+ */
245
+ async updateContext(newContext, options = { replace: false }) {
246
+ if (this.isNativeMode) {
247
+ throw new Error(
248
+ "updateContext() is not supported in native mode. " +
249
+ "Context must be set during initialization via FeatureFlagsOptions. " +
250
+ "This feature is only available in JavaScript mode (Expo/React Native Web)."
251
+ );
252
+ } else if (this.jsFlags) {
253
+ return await this.jsFlags.updateContext(newContext, options);
254
+ }
255
+ throw new Error("Feature flags are not initialized");
256
+ }
257
+
258
+ // snake_case aliases for API consistency with mixpanel-js
259
+ are_flags_ready() {
260
+ return this.areFlagsReady();
261
+ }
262
+
263
+ get_variant(featureName, fallback, callback) {
264
+ return this.getVariant(featureName, fallback, callback);
265
+ }
266
+
267
+ get_variant_sync(featureName, fallback) {
268
+ return this.getVariantSync(featureName, fallback);
269
+ }
270
+
271
+ get_variant_value(featureName, fallbackValue, callback) {
272
+ return this.getVariantValue(featureName, fallbackValue, callback);
273
+ }
274
+
275
+ get_variant_value_sync(featureName, fallbackValue) {
276
+ return this.getVariantValueSync(featureName, fallbackValue);
277
+ }
278
+
279
+ is_enabled(featureName, fallbackValue, callback) {
280
+ return this.isEnabled(featureName, fallbackValue, callback);
281
+ }
282
+
283
+ is_enabled_sync(featureName, fallbackValue) {
284
+ return this.isEnabledSync(featureName, fallbackValue);
285
+ }
286
+
287
+ update_context(newContext, options) {
288
+ return this.updateContext(newContext, options);
289
+ }
290
+ }
@@ -21,10 +21,17 @@ export default class MixpanelMain {
21
21
  trackAutomaticEvents = false,
22
22
  optOutTrackingDefault = false,
23
23
  superProperties = null,
24
- serverURL = "https://api.mixpanel.com"
24
+ serverURL = "https://api.mixpanel.com",
25
+ useGzipCompression = false,
26
+ featureFlagsOptions = {}
25
27
  ) {
26
28
  MixpanelLogger.log(token, `Initializing Mixpanel`);
27
29
 
30
+ // Store feature flags options for later use
31
+ this.featureFlagsOptions = featureFlagsOptions;
32
+ this.featureFlagsEnabled = featureFlagsOptions.enabled || false;
33
+ this.featureFlagsContext = featureFlagsOptions.context || {};
34
+
28
35
  await this.mixpanelPersistent.initializationCompletePromise(token);
29
36
  if (optOutTrackingDefault) {
30
37
  await this.optOutTracking(token);
@@ -37,6 +44,11 @@ export default class MixpanelMain {
37
44
  await this.registerSuperProperties(token, {
38
45
  ...superProperties,
39
46
  });
47
+
48
+ // Initialize feature flags if enabled
49
+ if (this.featureFlagsEnabled) {
50
+ MixpanelLogger.log(token, "Feature flags enabled during initialization");
51
+ }
40
52
  }
41
53
 
42
54
  getMetaData() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-react-native",
3
- "version": "3.1.2",
3
+ "version": "3.2.0-beta.0",
4
4
  "description": "Official React Native Tracking Library for Mixpanel Analytics",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -63,4 +63,4 @@
63
63
  "react-native-get-random-values": "^1.9.0",
64
64
  "uuid": "3.3.2"
65
65
  }
66
- }
66
+ }