mixpanel-react-native 3.2.0-beta.3 → 3.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/CHANGELOG.md +0 -17
- package/README.md +10 -31
- package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +2 -272
- package/index.d.ts +1 -64
- package/index.js +8 -103
- package/ios/MixpanelReactNative.m +1 -19
- package/ios/MixpanelReactNative.swift +5 -183
- package/javascript/mixpanel-main.js +1 -21
- package/javascript/mixpanel-network.js +41 -86
- package/javascript/mixpanel-persistent.js +4 -36
- package/javascript/mixpanel-storage.js +2 -2
- package/package.json +38 -18
- package/.github/dependabot.yml +0 -7
- package/FEATURE_FLAGS_JS_MODE_FINDINGS.md +0 -119
- package/FEATURE_FLAGS_QUICKSTART.md +0 -399
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/android/gradlew +0 -172
- package/android/gradlew.bat +0 -84
- package/javascript/mixpanel-flags-js.js +0 -465
- package/javascript/mixpanel-flags.js +0 -710
package/CHANGELOG.md
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
#
|
|
2
2
|
|
|
3
|
-
## [v3.2.0-beta.3](https://github.com/mixpanel/mixpanel-react-native/tree/v3.2.0-beta.3) (2025-12-15)
|
|
4
|
-
|
|
5
|
-
### Features
|
|
6
|
-
|
|
7
|
-
- **Feature Flags**: Enable JavaScript mode support for Feature Flags
|
|
8
|
-
- Full support for Expo and React Native Web
|
|
9
|
-
- Runtime context updates via `updateContext()` (JavaScript mode only)
|
|
10
|
-
- Complete parity with native implementation
|
|
11
|
-
- Automatic fallback to JavaScript mode when native modules unavailable
|
|
12
|
-
- AsyncStorage-based caching for offline support
|
|
13
|
-
|
|
14
|
-
### Improvements
|
|
15
|
-
|
|
16
|
-
- Remove environment variable requirement for JavaScript mode flags
|
|
17
|
-
- Enhanced documentation with Expo-specific examples
|
|
18
|
-
- Improved test coverage for JavaScript mode
|
|
19
|
-
|
|
20
3
|
## [v3.1.3](https://github.com/mixpanel/mixpanel-react-native/tree/v3.1.3) (2025-12-15)
|
|
21
4
|
|
|
22
5
|
### Fixes
|
package/README.md
CHANGED
|
@@ -47,7 +47,15 @@ Mixpanel's React Native SDK is a wrapper around Mixpanel’s native iOS and Andr
|
|
|
47
47
|
npm install mixpanel-react-native
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
2.
|
|
50
|
+
2. Install AsyncStorage (required for data persistence):
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
npm install @react-native-async-storage/async-storage
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **Note:** Starting from v3.2.0, `@react-native-async-storage/async-storage` is a peer dependency. This allows your project to use either v1.x or v2.x, avoiding conflicts with frameworks like Expo 52+.
|
|
57
|
+
|
|
58
|
+
3. Under your application's ios folder, run
|
|
51
59
|
|
|
52
60
|
```
|
|
53
61
|
pod install
|
|
@@ -55,7 +63,7 @@ pod install
|
|
|
55
63
|
|
|
56
64
|
Please note: You do not need to update your Podfile to add Mixpanel.
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
4. Since Xcode 12.5, there is a known swift compile issue, please refer to this **[workaround](https://github.com/mixpanel/mixpanel-react-native/issues/43#issuecomment-829599732)**. However the compile issue has been resolved in Xcode 13.2.1+, there is no extra step required as long as you upgrade to Xcode 13.2.1+.
|
|
59
67
|
|
|
60
68
|
### 2. Initialize Mixpanel
|
|
61
69
|
|
|
@@ -115,35 +123,6 @@ const SampleApp = () => {
|
|
|
115
123
|
export default SampleApp;
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
### Feature Flags (Beta - 3.2.0-beta.1+)
|
|
119
|
-
|
|
120
|
-
Control features dynamically and run A/B tests with Mixpanel Feature Flags. **Native mode only** (iOS/Android) in beta.
|
|
121
|
-
|
|
122
|
-
#### Quick Start
|
|
123
|
-
|
|
124
|
-
```js
|
|
125
|
-
// Enable during initialization
|
|
126
|
-
await mixpanel.init(false, {}, 'https://api.mixpanel.com', true, {
|
|
127
|
-
enabled: true,
|
|
128
|
-
context: { platform: 'mobile' } // Optional targeting context
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Check if feature is enabled
|
|
132
|
-
if (mixpanel.flags.areFlagsReady()) {
|
|
133
|
-
const showNewUI = mixpanel.flags.isEnabledSync('new-feature', false);
|
|
134
|
-
const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### Key Methods
|
|
139
|
-
|
|
140
|
-
- `areFlagsReady()` - Check if flags are loaded
|
|
141
|
-
- `isEnabledSync(name, fallback)` - Check if feature is enabled
|
|
142
|
-
- `getVariantValueSync(name, fallback)` - Get variant value
|
|
143
|
-
- `getVariantSync(name, fallback)` - Get full variant object with metadata
|
|
144
|
-
|
|
145
|
-
All methods support async versions and snake_case aliases. See **[Feature Flags Quick Start Guide](FEATURE_FLAGS_QUICKSTART.md)** for complete documentation.
|
|
146
|
-
|
|
147
126
|
### Expo and React Native for Web support (3.0.2 and above)
|
|
148
127
|
|
|
149
128
|
Starting from version 3.0.2, we have introduced support for Expo, React Native for Web, and other platforms utilizing React Native that do not support iOS and Android directly.
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
package com.mixpanel.reactnative;
|
|
2
2
|
|
|
3
3
|
import com.mixpanel.android.mpmetrics.MixpanelAPI;
|
|
4
|
-
import com.mixpanel.android.mpmetrics.MixpanelOptions;
|
|
5
|
-
import com.mixpanel.android.mpmetrics.MixpanelFlagVariant;
|
|
6
|
-
import com.mixpanel.android.mpmetrics.FlagCompletionCallback;
|
|
7
4
|
|
|
8
5
|
import com.facebook.react.bridge.Promise;
|
|
9
6
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
@@ -12,10 +9,6 @@ import com.facebook.react.bridge.ReactMethod;
|
|
|
12
9
|
import com.facebook.react.bridge.ReadableArray;
|
|
13
10
|
import com.facebook.react.bridge.ReadableMap;
|
|
14
11
|
import com.facebook.react.bridge.Dynamic;
|
|
15
|
-
import com.facebook.react.bridge.WritableMap;
|
|
16
|
-
import com.facebook.react.bridge.WritableNativeMap;
|
|
17
|
-
import com.facebook.react.bridge.WritableArray;
|
|
18
|
-
import com.facebook.react.bridge.Callback;
|
|
19
12
|
|
|
20
13
|
import org.json.JSONArray;
|
|
21
14
|
import org.json.JSONException;
|
|
@@ -40,35 +33,10 @@ public class MixpanelReactNativeModule extends ReactContextBaseJavaModule {
|
|
|
40
33
|
|
|
41
34
|
|
|
42
35
|
@ReactMethod
|
|
43
|
-
public void initialize(String token, boolean trackAutomaticEvents, boolean optOutTrackingDefault, ReadableMap metadata, String serverURL, boolean useGzipCompression,
|
|
36
|
+
public void initialize(String token, boolean trackAutomaticEvents, boolean optOutTrackingDefault, ReadableMap metadata, String serverURL, boolean useGzipCompression, Promise promise) throws JSONException {
|
|
44
37
|
JSONObject mixpanelProperties = ReactNativeHelper.reactToJSON(metadata);
|
|
45
38
|
AutomaticProperties.setAutomaticProperties(mixpanelProperties);
|
|
46
|
-
|
|
47
|
-
// Handle feature flags options
|
|
48
|
-
boolean featureFlagsEnabled = false;
|
|
49
|
-
JSONObject featureFlagsContext = null;
|
|
50
|
-
|
|
51
|
-
if (featureFlagsOptions != null && featureFlagsOptions.hasKey("enabled")) {
|
|
52
|
-
featureFlagsEnabled = featureFlagsOptions.getBoolean("enabled");
|
|
53
|
-
|
|
54
|
-
if (featureFlagsOptions.hasKey("context")) {
|
|
55
|
-
featureFlagsContext = ReactNativeHelper.reactToJSON(featureFlagsOptions.getMap("context"));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Create Mixpanel instance with feature flags configuration
|
|
60
|
-
MixpanelOptions.Builder optionsBuilder = new MixpanelOptions.Builder()
|
|
61
|
-
.optOutTrackingDefault(optOutTrackingDefault)
|
|
62
|
-
.superProperties(mixpanelProperties)
|
|
63
|
-
.featureFlagsEnabled(featureFlagsEnabled);
|
|
64
|
-
|
|
65
|
-
if (featureFlagsContext != null) {
|
|
66
|
-
optionsBuilder.featureFlagsContext(featureFlagsContext);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
MixpanelOptions options = optionsBuilder.build();
|
|
70
|
-
|
|
71
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, trackAutomaticEvents, options);
|
|
39
|
+
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, optOutTrackingDefault, mixpanelProperties, null, trackAutomaticEvents);
|
|
72
40
|
instance.setServerURL(serverURL);
|
|
73
41
|
if (useGzipCompression) {
|
|
74
42
|
instance.setShouldGzipRequestPayload(true);
|
|
@@ -634,242 +602,4 @@ public class MixpanelReactNativeModule extends ReactContextBaseJavaModule {
|
|
|
634
602
|
promise.resolve(null);
|
|
635
603
|
}
|
|
636
604
|
}
|
|
637
|
-
|
|
638
|
-
// Feature Flags Methods
|
|
639
|
-
|
|
640
|
-
@ReactMethod
|
|
641
|
-
public void loadFlags(final String token, Promise promise) {
|
|
642
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
643
|
-
if (instance == null) {
|
|
644
|
-
promise.reject("Instance Error", "Failed to get Mixpanel instance");
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
synchronized (instance) {
|
|
648
|
-
instance.getFlags().loadFlags();
|
|
649
|
-
promise.resolve(null);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
654
|
-
public boolean areFlagsReadySync(final String token) {
|
|
655
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
656
|
-
if (instance == null) {
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
|
-
synchronized (instance) {
|
|
660
|
-
return instance.getFlags().areFlagsReady();
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
665
|
-
public WritableMap getVariantSync(final String token, String featureName, ReadableMap fallback) {
|
|
666
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
667
|
-
if (instance == null) {
|
|
668
|
-
return convertVariantToMap(fallback);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
synchronized (instance) {
|
|
672
|
-
MixpanelFlagVariant fallbackVariant = convertMapToVariant(fallback);
|
|
673
|
-
MixpanelFlagVariant variant = instance.getFlags().getVariantSync(featureName, fallbackVariant);
|
|
674
|
-
return convertVariantToWritableMap(variant);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Note: For getVariantValueSync, we'll return the full variant and extract value in JS
|
|
679
|
-
// React Native doesn't support returning Dynamic types from synchronous methods
|
|
680
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
681
|
-
public WritableMap getVariantValueSync(final String token, String featureName, Dynamic fallbackValue) {
|
|
682
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
683
|
-
|
|
684
|
-
WritableMap result = new WritableNativeMap();
|
|
685
|
-
if (instance == null) {
|
|
686
|
-
result.putString("type", "fallback");
|
|
687
|
-
// We'll handle the conversion in JavaScript
|
|
688
|
-
return result;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
synchronized (instance) {
|
|
692
|
-
Object value = instance.getFlags().getVariantValueSync(featureName, ReactNativeHelper.dynamicToObject(fallbackValue));
|
|
693
|
-
result.putString("type", "value");
|
|
694
|
-
|
|
695
|
-
// Convert value to appropriate type
|
|
696
|
-
if (value == null) {
|
|
697
|
-
result.putNull("value");
|
|
698
|
-
} else if (value instanceof String) {
|
|
699
|
-
result.putString("value", (String) value);
|
|
700
|
-
} else if (value instanceof Boolean) {
|
|
701
|
-
result.putBoolean("value", (Boolean) value);
|
|
702
|
-
} else if (value instanceof Integer) {
|
|
703
|
-
result.putInt("value", (Integer) value);
|
|
704
|
-
} else if (value instanceof Double) {
|
|
705
|
-
result.putDouble("value", (Double) value);
|
|
706
|
-
} else if (value instanceof Float) {
|
|
707
|
-
result.putDouble("value", ((Float) value).doubleValue());
|
|
708
|
-
} else if (value instanceof Long) {
|
|
709
|
-
result.putDouble("value", ((Long) value).doubleValue());
|
|
710
|
-
} else {
|
|
711
|
-
result.putString("value", value.toString());
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return result;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
719
|
-
public boolean isEnabledSync(final String token, String featureName, boolean fallbackValue) {
|
|
720
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
721
|
-
if (instance == null) {
|
|
722
|
-
return fallbackValue;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
synchronized (instance) {
|
|
726
|
-
return instance.getFlags().isEnabledSync(featureName, fallbackValue);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
@ReactMethod
|
|
731
|
-
public void getVariant(final String token, String featureName, ReadableMap fallback, final Promise promise) {
|
|
732
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
733
|
-
if (instance == null) {
|
|
734
|
-
promise.resolve(convertVariantToMap(fallback));
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
synchronized (instance) {
|
|
739
|
-
MixpanelFlagVariant fallbackVariant = convertMapToVariant(fallback);
|
|
740
|
-
instance.getFlags().getVariant(featureName, fallbackVariant, new FlagCompletionCallback<MixpanelFlagVariant>() {
|
|
741
|
-
@Override
|
|
742
|
-
public void onComplete(MixpanelFlagVariant variant) {
|
|
743
|
-
promise.resolve(convertVariantToWritableMap(variant));
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
@ReactMethod
|
|
750
|
-
public void getVariantValue(final String token, String featureName, Dynamic fallbackValue, final Promise promise) {
|
|
751
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
752
|
-
if (instance == null) {
|
|
753
|
-
promise.resolve(fallbackValue);
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
synchronized (instance) {
|
|
758
|
-
Object fallbackObj = ReactNativeHelper.dynamicToObject(fallbackValue);
|
|
759
|
-
instance.getFlags().getVariantValue(featureName, fallbackObj, new FlagCompletionCallback<Object>() {
|
|
760
|
-
@Override
|
|
761
|
-
public void onComplete(Object value) {
|
|
762
|
-
// Convert the value back to a format React Native can handle
|
|
763
|
-
if (value == null) {
|
|
764
|
-
promise.resolve(null);
|
|
765
|
-
} else if (value instanceof String) {
|
|
766
|
-
promise.resolve((String) value);
|
|
767
|
-
} else if (value instanceof Boolean) {
|
|
768
|
-
promise.resolve((Boolean) value);
|
|
769
|
-
} else if (value instanceof Number) {
|
|
770
|
-
promise.resolve(((Number) value).doubleValue());
|
|
771
|
-
} else if (value instanceof JSONObject) {
|
|
772
|
-
try {
|
|
773
|
-
WritableMap map = ReactNativeHelper.convertJsonToMap((JSONObject) value);
|
|
774
|
-
promise.resolve(map);
|
|
775
|
-
} catch (Exception e) {
|
|
776
|
-
promise.resolve(value.toString());
|
|
777
|
-
}
|
|
778
|
-
} else if (value instanceof JSONArray) {
|
|
779
|
-
try {
|
|
780
|
-
WritableArray array = ReactNativeHelper.convertJsonToArray((JSONArray) value);
|
|
781
|
-
promise.resolve(array);
|
|
782
|
-
} catch (Exception e) {
|
|
783
|
-
promise.resolve(value.toString());
|
|
784
|
-
}
|
|
785
|
-
} else {
|
|
786
|
-
promise.resolve(value.toString());
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
@ReactMethod
|
|
794
|
-
public void isEnabled(final String token, String featureName, boolean fallbackValue, final Promise promise) {
|
|
795
|
-
MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
|
|
796
|
-
if (instance == null) {
|
|
797
|
-
promise.resolve(fallbackValue);
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
synchronized (instance) {
|
|
802
|
-
instance.getFlags().isEnabled(featureName, fallbackValue, new FlagCompletionCallback<Boolean>() {
|
|
803
|
-
@Override
|
|
804
|
-
public void onComplete(Boolean isEnabled) {
|
|
805
|
-
promise.resolve(isEnabled);
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Helper methods for variant conversion
|
|
812
|
-
private MixpanelFlagVariant convertMapToVariant(ReadableMap map) {
|
|
813
|
-
if (map == null) {
|
|
814
|
-
return new MixpanelFlagVariant("", null);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
String key = map.hasKey("key") ? map.getString("key") : "";
|
|
818
|
-
Object value = map.hasKey("value") ? ReactNativeHelper.dynamicToObject(map.getDynamic("value")) : null;
|
|
819
|
-
String experimentID = map.hasKey("experimentID") ? map.getString("experimentID") : null;
|
|
820
|
-
Boolean isExperimentActive = map.hasKey("isExperimentActive") ? map.getBoolean("isExperimentActive") : null;
|
|
821
|
-
Boolean isQATester = map.hasKey("isQATester") ? map.getBoolean("isQATester") : null;
|
|
822
|
-
|
|
823
|
-
// Create variant with all properties using the full constructor
|
|
824
|
-
return new MixpanelFlagVariant(key, value, experimentID, isExperimentActive, isQATester);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
private WritableMap convertVariantToMap(ReadableMap source) {
|
|
828
|
-
WritableMap map = new WritableNativeMap();
|
|
829
|
-
if (source != null) {
|
|
830
|
-
map.merge(source);
|
|
831
|
-
}
|
|
832
|
-
return map;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
private WritableMap convertVariantToWritableMap(MixpanelFlagVariant variant) {
|
|
836
|
-
WritableMap map = new WritableNativeMap();
|
|
837
|
-
|
|
838
|
-
if (variant != null) {
|
|
839
|
-
map.putString("key", variant.key);
|
|
840
|
-
|
|
841
|
-
Object value = variant.value;
|
|
842
|
-
if (value == null) {
|
|
843
|
-
map.putNull("value");
|
|
844
|
-
} else if (value instanceof String) {
|
|
845
|
-
map.putString("value", (String) value);
|
|
846
|
-
} else if (value instanceof Boolean) {
|
|
847
|
-
map.putBoolean("value", (Boolean) value);
|
|
848
|
-
} else if (value instanceof Integer) {
|
|
849
|
-
map.putInt("value", (Integer) value);
|
|
850
|
-
} else if (value instanceof Double) {
|
|
851
|
-
map.putDouble("value", (Double) value);
|
|
852
|
-
} else if (value instanceof Float) {
|
|
853
|
-
map.putDouble("value", ((Float) value).doubleValue());
|
|
854
|
-
} else if (value instanceof Long) {
|
|
855
|
-
map.putDouble("value", ((Long) value).doubleValue());
|
|
856
|
-
} else {
|
|
857
|
-
// For complex objects, convert to string
|
|
858
|
-
map.putString("value", value.toString());
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// Add optional fields if they exist
|
|
862
|
-
if (variant.experimentID != null) {
|
|
863
|
-
map.putString("experimentID", variant.experimentID);
|
|
864
|
-
}
|
|
865
|
-
if (variant.isExperimentActive != null) {
|
|
866
|
-
map.putBoolean("isExperimentActive", variant.isExperimentActive);
|
|
867
|
-
}
|
|
868
|
-
if (variant.isQATester != null) {
|
|
869
|
-
map.putBoolean("isQATester", variant.isQATester);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
return map;
|
|
874
|
-
}
|
|
875
605
|
}
|
package/index.d.ts
CHANGED
|
@@ -7,69 +7,7 @@ export type MixpanelAsyncStorage = {
|
|
|
7
7
|
removeItem(key: string): Promise<void>;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export interface MixpanelFlagVariant {
|
|
11
|
-
key: string;
|
|
12
|
-
value: any;
|
|
13
|
-
experiment_id?: string | number; // Updated to match mixpanel-js format
|
|
14
|
-
is_experiment_active?: boolean; // Updated to match mixpanel-js format
|
|
15
|
-
is_qa_tester?: boolean; // Updated to match mixpanel-js format
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface FeatureFlagsOptions {
|
|
19
|
-
enabled?: boolean;
|
|
20
|
-
context?: {
|
|
21
|
-
[key: string]: any;
|
|
22
|
-
custom_properties?: {
|
|
23
|
-
[key: string]: any;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface UpdateContextOptions {
|
|
29
|
-
replace?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface Flags {
|
|
33
|
-
// Synchronous methods
|
|
34
|
-
loadFlags(): Promise<void>;
|
|
35
|
-
areFlagsReady(): boolean;
|
|
36
|
-
getVariantSync(featureName: string, fallback: MixpanelFlagVariant): MixpanelFlagVariant;
|
|
37
|
-
getVariantValueSync(featureName: string, fallbackValue: any): any;
|
|
38
|
-
isEnabledSync(featureName: string, fallbackValue?: boolean): boolean;
|
|
39
|
-
|
|
40
|
-
// Asynchronous methods with overloads for callback and Promise patterns
|
|
41
|
-
getVariant(featureName: string, fallback: MixpanelFlagVariant): Promise<MixpanelFlagVariant>;
|
|
42
|
-
getVariant(featureName: string, fallback: MixpanelFlagVariant, callback: (result: MixpanelFlagVariant) => void): void;
|
|
43
|
-
|
|
44
|
-
getVariantValue(featureName: string, fallbackValue: any): Promise<any>;
|
|
45
|
-
getVariantValue(featureName: string, fallbackValue: any, callback: (value: any) => void): void;
|
|
46
|
-
|
|
47
|
-
isEnabled(featureName: string, fallbackValue?: boolean): Promise<boolean>;
|
|
48
|
-
isEnabled(featureName: string, fallbackValue: boolean, callback: (isEnabled: boolean) => void): void;
|
|
49
|
-
|
|
50
|
-
// Context management (NEW - aligned with mixpanel-js)
|
|
51
|
-
// NOTE: Only available in JavaScript mode (Expo/React Native Web)
|
|
52
|
-
// In native mode, throws an error - context must be set during initialization
|
|
53
|
-
updateContext(newContext: MixpanelProperties, options?: UpdateContextOptions): Promise<void>;
|
|
54
|
-
|
|
55
|
-
// snake_case aliases (NEW - aligned with mixpanel-js)
|
|
56
|
-
are_flags_ready(): boolean;
|
|
57
|
-
get_variant(featureName: string, fallback: MixpanelFlagVariant): Promise<MixpanelFlagVariant>;
|
|
58
|
-
get_variant(featureName: string, fallback: MixpanelFlagVariant, callback: (result: MixpanelFlagVariant) => void): void;
|
|
59
|
-
get_variant_sync(featureName: string, fallback: MixpanelFlagVariant): MixpanelFlagVariant;
|
|
60
|
-
get_variant_value(featureName: string, fallbackValue: any): Promise<any>;
|
|
61
|
-
get_variant_value(featureName: string, fallbackValue: any, callback: (value: any) => void): void;
|
|
62
|
-
get_variant_value_sync(featureName: string, fallbackValue: any): any;
|
|
63
|
-
is_enabled(featureName: string, fallbackValue?: boolean): Promise<boolean>;
|
|
64
|
-
is_enabled(featureName: string, fallbackValue: boolean, callback: (isEnabled: boolean) => void): void;
|
|
65
|
-
is_enabled_sync(featureName: string, fallbackValue?: boolean): boolean;
|
|
66
|
-
// NOTE: Only available in JavaScript mode (Expo/React Native Web)
|
|
67
|
-
update_context(newContext: MixpanelProperties, options?: UpdateContextOptions): Promise<void>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
10
|
export class Mixpanel {
|
|
71
|
-
readonly flags: Flags;
|
|
72
|
-
|
|
73
11
|
constructor(token: string, trackAutoMaticEvents: boolean);
|
|
74
12
|
constructor(token: string, trackAutoMaticEvents: boolean, useNative: true);
|
|
75
13
|
constructor(
|
|
@@ -87,8 +25,7 @@ export class Mixpanel {
|
|
|
87
25
|
optOutTrackingDefault?: boolean,
|
|
88
26
|
superProperties?: MixpanelProperties,
|
|
89
27
|
serverURL?: string,
|
|
90
|
-
useGzipCompression?: boolean
|
|
91
|
-
featureFlagsOptions?: FeatureFlagsOptions
|
|
28
|
+
useGzipCompression?: boolean
|
|
92
29
|
): Promise<void>;
|
|
93
30
|
setServerURL(serverURL: string): void;
|
|
94
31
|
setLoggingEnabled(loggingEnabled: boolean): void;
|
package/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import {Platform, NativeModules} from "react-native";
|
|
|
4
4
|
import packageJson from "./package.json";
|
|
5
5
|
const {MixpanelReactNative} = NativeModules;
|
|
6
6
|
import MixpanelMain from "mixpanel-react-native/javascript/mixpanel-main"
|
|
7
|
-
import { MixpanelLogger } from "mixpanel-react-native/javascript/mixpanel-logger"
|
|
8
7
|
|
|
9
8
|
const DevicePlatform = {
|
|
10
9
|
Unknown: "Unknown",
|
|
@@ -47,8 +46,6 @@ export class Mixpanel {
|
|
|
47
46
|
}
|
|
48
47
|
this.token = token;
|
|
49
48
|
this.trackAutomaticEvents = trackAutomaticEvents;
|
|
50
|
-
this._flags = null; // Lazy-loaded flags instance
|
|
51
|
-
this.storage = storage; // Store for JavaScript mode
|
|
52
49
|
|
|
53
50
|
if (useNative && MixpanelReactNative) {
|
|
54
51
|
this.mixpanelImpl = MixpanelReactNative;
|
|
@@ -63,117 +60,27 @@ export class Mixpanel {
|
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
/**
|
|
66
|
-
*
|
|
63
|
+
* Initializes Mixpanel
|
|
67
64
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* (iOS/Android). JavaScript mode (Expo/React Native Web) support is planned for a future release.
|
|
73
|
-
*
|
|
74
|
-
* @return {Flags} an instance of Flags that provides access to feature flag operations
|
|
75
|
-
* @throws {Error} if accessed in JavaScript mode (when native modules are not available)
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* // Check if flags are ready
|
|
79
|
-
* if (mixpanel.flags.areFlagsReady()) {
|
|
80
|
-
* const isEnabled = mixpanel.flags.isEnabledSync('new-checkout', false);
|
|
81
|
-
* }
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* // Get a feature variant value
|
|
85
|
-
* const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
86
|
-
*
|
|
87
|
-
* @see Flags
|
|
88
|
-
*/
|
|
89
|
-
get flags() {
|
|
90
|
-
if (!this._flags) {
|
|
91
|
-
// Check if feature flags are enabled and warn if not
|
|
92
|
-
if (!this.featureFlagsOptions || !this.featureFlagsOptions.enabled) {
|
|
93
|
-
MixpanelLogger.warn(
|
|
94
|
-
this.token,
|
|
95
|
-
"Accessing feature flags API but flags are not enabled. " +
|
|
96
|
-
"Call init() with featureFlagsOptions.enabled = true to enable feature flags. " +
|
|
97
|
-
"Flag methods will return fallback values."
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
// Lazy load the Flags instance with proper dependencies
|
|
101
|
-
const Flags = require("./javascript/mixpanel-flags").Flags;
|
|
102
|
-
this._flags = new Flags(this.token, this.mixpanelImpl, this.storage);
|
|
103
|
-
}
|
|
104
|
-
return this._flags;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Initializes Mixpanel with optional configuration for tracking, super properties, and feature flags.
|
|
109
|
-
*
|
|
110
|
-
* <p>This method must be called before using any other Mixpanel functionality. It sets up
|
|
111
|
-
* the tracking environment, registers super properties, and optionally initializes feature flags.
|
|
112
|
-
*
|
|
113
|
-
* @param {boolean} [optOutTrackingDefault=false] Whether or not Mixpanel can start tracking by default.
|
|
114
|
-
* If true, no data will be tracked until optInTracking() is called. See optOutTracking()
|
|
115
|
-
* @param {object} [superProperties={}] A Map containing the key value pairs of the super properties to register.
|
|
116
|
-
* These properties will be sent with every event. Pass {} if no super properties needed.
|
|
117
|
-
* @param {string} [serverURL="https://api.mixpanel.com"] The base URL used for Mixpanel API requests.
|
|
118
|
-
* Use "https://api-eu.mixpanel.com" for EU data residency. See setServerURL()
|
|
119
|
-
* @param {boolean} [useGzipCompression=false] Whether to use gzip compression for network requests.
|
|
120
|
-
* Enabling this reduces bandwidth usage but adds slight CPU overhead.
|
|
121
|
-
* @param {object} [featureFlagsOptions={}] Feature flags configuration object with the following properties:
|
|
122
|
-
* @param {boolean} [featureFlagsOptions.enabled=false] Whether to enable feature flags functionality
|
|
123
|
-
* @param {object} [featureFlagsOptions.context={}] Context properties used for feature flag targeting.
|
|
124
|
-
* Can include user properties, device properties, or any custom properties for flag evaluation.
|
|
125
|
-
* Note: In native mode, context must be set during initialization and cannot be updated later.
|
|
126
|
-
* @returns {Promise<void>} A promise that resolves when initialization is complete
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* // Basic initialization
|
|
130
|
-
* const mixpanel = new Mixpanel('YOUR_TOKEN', true);
|
|
131
|
-
* await mixpanel.init();
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* // Initialize with feature flags enabled
|
|
135
|
-
* const mixpanel = new Mixpanel('YOUR_TOKEN', true);
|
|
136
|
-
* await mixpanel.init(false, {}, 'https://api.mixpanel.com', false, {
|
|
137
|
-
* enabled: true,
|
|
138
|
-
* context: {
|
|
139
|
-
* platform: 'mobile',
|
|
140
|
-
* app_version: '2.1.0'
|
|
141
|
-
* }
|
|
142
|
-
* });
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* // Initialize with EU data residency and super properties
|
|
146
|
-
* await mixpanel.init(
|
|
147
|
-
* false,
|
|
148
|
-
* { plan: 'premium', region: 'eu' },
|
|
149
|
-
* 'https://api-eu.mixpanel.com',
|
|
150
|
-
* true
|
|
151
|
-
* );
|
|
65
|
+
* @param {boolean} optOutTrackingDefault Optional Whether or not Mixpanel can start tracking by default. See optOutTracking()
|
|
66
|
+
* @param {object} superProperties Optional A Map containing the key value pairs of the super properties to register
|
|
67
|
+
* @param {string} serverURL Optional Set the base URL used for Mixpanel API requests. See setServerURL()
|
|
68
|
+
* @param {boolean} useGzipCompression Optional Set whether to use gzip compression for network requests. Defaults to false.
|
|
152
69
|
*/
|
|
153
70
|
async init(
|
|
154
71
|
optOutTrackingDefault = DEFAULT_OPT_OUT,
|
|
155
72
|
superProperties = {},
|
|
156
73
|
serverURL = "https://api.mixpanel.com",
|
|
157
|
-
useGzipCompression = false
|
|
158
|
-
featureFlagsOptions = {}
|
|
74
|
+
useGzipCompression = false
|
|
159
75
|
) {
|
|
160
|
-
// Store feature flags options for later use
|
|
161
|
-
this.featureFlagsOptions = featureFlagsOptions;
|
|
162
|
-
|
|
163
76
|
await this.mixpanelImpl.initialize(
|
|
164
77
|
this.token,
|
|
165
78
|
this.trackAutomaticEvents,
|
|
166
79
|
optOutTrackingDefault,
|
|
167
80
|
{...Helper.getMetaData(), ...superProperties},
|
|
168
81
|
serverURL,
|
|
169
|
-
useGzipCompression
|
|
170
|
-
featureFlagsOptions
|
|
82
|
+
useGzipCompression
|
|
171
83
|
);
|
|
172
|
-
|
|
173
|
-
// If flags are enabled AND we're in native mode, initialize them
|
|
174
|
-
if (featureFlagsOptions.enabled && this.mixpanelImpl === MixpanelReactNative) {
|
|
175
|
-
await this.flags.loadFlags();
|
|
176
|
-
}
|
|
177
84
|
}
|
|
178
85
|
|
|
179
86
|
/**
|
|
@@ -202,9 +109,7 @@ export class Mixpanel {
|
|
|
202
109
|
trackAutomaticEvents,
|
|
203
110
|
optOutTrackingDefault,
|
|
204
111
|
Helper.getMetaData(),
|
|
205
|
-
"https://api.mixpanel.com"
|
|
206
|
-
false,
|
|
207
|
-
{}
|
|
112
|
+
"https://api.mixpanel.com"
|
|
208
113
|
);
|
|
209
114
|
return new Mixpanel(token, trackAutomaticEvents);
|
|
210
115
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// MARK: - Mixpanel Instance
|
|
7
7
|
|
|
8
|
-
RCT_EXTERN_METHOD(initialize:(NSString *)token trackAutomaticEvents:(BOOL)trackAutomaticEvents optOutTrackingByDefault:(BOOL)optOutTrackingByDefault properties:(NSDictionary *)properties serverURL:(NSString *)serverURL useGzipCompression:(BOOL)useGzipCompression
|
|
8
|
+
RCT_EXTERN_METHOD(initialize:(NSString *)token trackAutomaticEvents:(BOOL)trackAutomaticEvents optOutTrackingByDefault:(BOOL)optOutTrackingByDefault properties:(NSDictionary *)properties serverURL:(NSString *)serverURL useGzipCompression:(BOOL)useGzipCompression resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
9
|
|
|
10
10
|
// Mark: - Settings
|
|
11
11
|
RCT_EXTERN_METHOD(setServerURL:(NSString *)token serverURL:(NSString *)serverURL resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -105,22 +105,4 @@ RCT_EXTERN_METHOD(groupRemovePropertyValue:(NSString *)token groupKey:(NSString
|
|
|
105
105
|
|
|
106
106
|
RCT_EXTERN_METHOD(groupUnionProperty:(NSString *)token groupKey:(NSString *)groupKey groupID:(id)groupID name:(NSString *)name values:(NSArray *)values resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
107
107
|
|
|
108
|
-
// MARK: - Feature Flags
|
|
109
|
-
|
|
110
|
-
RCT_EXTERN_METHOD(loadFlags:(NSString *)token resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
111
|
-
|
|
112
|
-
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(areFlagsReadySync:(NSString *)token)
|
|
113
|
-
|
|
114
|
-
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getVariantSync:(NSString *)token featureName:(NSString *)featureName fallback:(NSDictionary *)fallback)
|
|
115
|
-
|
|
116
|
-
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getVariantValueSync:(NSString *)token featureName:(NSString *)featureName fallbackValue:(id)fallbackValue)
|
|
117
|
-
|
|
118
|
-
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(isEnabledSync:(NSString *)token featureName:(NSString *)featureName fallbackValue:(BOOL)fallbackValue)
|
|
119
|
-
|
|
120
|
-
RCT_EXTERN_METHOD(getVariant:(NSString *)token featureName:(NSString *)featureName fallback:(NSDictionary *)fallback resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
121
|
-
|
|
122
|
-
RCT_EXTERN_METHOD(getVariantValue:(NSString *)token featureName:(NSString *)featureName fallbackValue:(id)fallbackValue resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
123
|
-
|
|
124
|
-
RCT_EXTERN_METHOD(isEnabled:(NSString *)token featureName:(NSString *)featureName fallbackValue:(BOOL)fallbackValue resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
125
|
-
|
|
126
108
|
@end
|