mixpanel-react-native 3.2.0-beta.2 → 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 +16 -0
- package/MixpanelReactNative.podspec +1 -1
- 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 -101
- package/ios/MixpanelReactNative.m +1 -19
- package/ios/MixpanelReactNative.swift +5 -183
- package/javascript/mixpanel-config.js +9 -5
- package/javascript/mixpanel-main.js +1 -13
- package/javascript/mixpanel-persistent.js +4 -4
- package/javascript/mixpanel-storage.js +2 -2
- package/package.json +38 -17
- package/.github/dependabot.yml +0 -7
- package/FEATURE_FLAGS_QUICKSTART.md +0 -348
- 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 -463
- package/javascript/mixpanel-flags.js +0 -670
|
@@ -18,39 +18,16 @@ open class MixpanelReactNative: NSObject {
|
|
|
18
18
|
properties: [String: Any],
|
|
19
19
|
serverURL: String,
|
|
20
20
|
useGzipCompression: Bool = false,
|
|
21
|
-
featureFlagsOptions: [String: Any]?,
|
|
22
21
|
resolver resolve: RCTPromiseResolveBlock,
|
|
23
22
|
rejecter reject: RCTPromiseRejectBlock) -> Void {
|
|
24
23
|
let autoProps = properties // copy
|
|
25
24
|
AutomaticProperties.setAutomaticProperties(autoProps)
|
|
26
25
|
let propsProcessed = MixpanelTypeHandler.processProperties(properties: autoProps)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if let flagsOptions = featureFlagsOptions {
|
|
33
|
-
featureFlagsEnabled = flagsOptions["enabled"] as? Bool ?? false
|
|
34
|
-
featureFlagsContext = flagsOptions["context"] as? [String: Any]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create MixpanelOptions with all configuration including feature flags
|
|
38
|
-
let options = MixpanelOptions(
|
|
39
|
-
token: token,
|
|
40
|
-
flushInterval: Constants.DEFAULT_FLUSH_INTERVAL,
|
|
41
|
-
instanceName: token,
|
|
42
|
-
trackAutomaticEvents: trackAutomaticEvents,
|
|
43
|
-
optOutTrackingByDefault: optOutTrackingByDefault,
|
|
44
|
-
useUniqueDistinctId: false,
|
|
45
|
-
superProperties: propsProcessed,
|
|
46
|
-
serverURL: serverURL,
|
|
47
|
-
proxyServerConfig: nil,
|
|
48
|
-
useGzipCompression: useGzipCompression,
|
|
49
|
-
featureFlagsEnabled: featureFlagsEnabled,
|
|
50
|
-
featureFlagsContext: featureFlagsContext ?? [:]
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
Mixpanel.initialize(options: options)
|
|
26
|
+
Mixpanel.initialize(token: token, trackAutomaticEvents: trackAutomaticEvents, flushInterval: Constants.DEFAULT_FLUSH_INTERVAL,
|
|
27
|
+
instanceName: token, optOutTrackingByDefault: optOutTrackingByDefault,
|
|
28
|
+
superProperties: propsProcessed,
|
|
29
|
+
serverURL: serverURL,
|
|
30
|
+
useGzipCompression: useGzipCompression)
|
|
54
31
|
resolve(true)
|
|
55
32
|
}
|
|
56
33
|
|
|
@@ -483,159 +460,4 @@ open class MixpanelReactNative: NSObject {
|
|
|
483
460
|
return Mixpanel.getInstance(name: token)
|
|
484
461
|
}
|
|
485
462
|
|
|
486
|
-
// MARK: - Feature Flags
|
|
487
|
-
|
|
488
|
-
@objc
|
|
489
|
-
func loadFlags(_ token: String,
|
|
490
|
-
resolver resolve: RCTPromiseResolveBlock,
|
|
491
|
-
rejecter reject: RCTPromiseRejectBlock) -> Void {
|
|
492
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
493
|
-
let flags = instance.flags else {
|
|
494
|
-
resolve(nil)
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
flags.loadFlags()
|
|
498
|
-
resolve(nil)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
@objc
|
|
502
|
-
func areFlagsReadySync(_ token: String) -> NSNumber {
|
|
503
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token) else {
|
|
504
|
-
NSLog("[Mixpanel - areFlagsReadySync: instance is nil for token: \(token)]")
|
|
505
|
-
return NSNumber(value: false)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
guard let flags = instance.flags else {
|
|
509
|
-
NSLog("[Mixpanel - areFlagsReadySync: flags is nil")
|
|
510
|
-
return NSNumber(value: false)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
let ready = flags.areFlagsReady()
|
|
514
|
-
NSLog("[Mixpanel - areFlagsReadySync: flags ready = \(ready)")
|
|
515
|
-
return NSNumber(value: ready)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
@objc
|
|
519
|
-
func getVariantSync(_ token: String,
|
|
520
|
-
featureName: String,
|
|
521
|
-
fallback: [String: Any]) -> [String: Any] {
|
|
522
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
523
|
-
let flags = instance.flags else {
|
|
524
|
-
return fallback
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
let fallbackVariant = convertDictToVariant(fallback)
|
|
528
|
-
let variant = flags.getVariantSync(featureName, fallback: fallbackVariant)
|
|
529
|
-
return convertVariantToDict(variant)
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
@objc
|
|
533
|
-
func getVariantValueSync(_ token: String,
|
|
534
|
-
featureName: String,
|
|
535
|
-
fallbackValue: Any) -> Any {
|
|
536
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
537
|
-
let flags = instance.flags else {
|
|
538
|
-
return fallbackValue
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return flags.getVariantValueSync(featureName, fallbackValue: fallbackValue) ?? fallbackValue
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
@objc
|
|
545
|
-
func isEnabledSync(_ token: String,
|
|
546
|
-
featureName: String,
|
|
547
|
-
fallbackValue: Bool) -> NSNumber {
|
|
548
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
549
|
-
let flags = instance.flags else {
|
|
550
|
-
return NSNumber(value: fallbackValue)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
let enabled = flags.isEnabledSync(featureName, fallbackValue: fallbackValue)
|
|
554
|
-
return NSNumber(value: enabled)
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
@objc
|
|
558
|
-
func getVariant(_ token: String,
|
|
559
|
-
featureName: String,
|
|
560
|
-
fallback: [String: Any],
|
|
561
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
562
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
563
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
564
|
-
let flags = instance.flags else {
|
|
565
|
-
resolve(fallback)
|
|
566
|
-
return
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
let fallbackVariant = convertDictToVariant(fallback)
|
|
570
|
-
flags.getVariant(featureName, fallback: fallbackVariant) { variant in
|
|
571
|
-
resolve(self.convertVariantToDict(variant))
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
@objc
|
|
576
|
-
func getVariantValue(_ token: String,
|
|
577
|
-
featureName: String,
|
|
578
|
-
fallbackValue: Any,
|
|
579
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
580
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
581
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
582
|
-
let flags = instance.flags else {
|
|
583
|
-
resolve(fallbackValue)
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
flags.getVariantValue(featureName, fallbackValue: fallbackValue) { value in
|
|
588
|
-
resolve(value)
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
@objc
|
|
593
|
-
func isEnabled(_ token: String,
|
|
594
|
-
featureName: String,
|
|
595
|
-
fallbackValue: Bool,
|
|
596
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
597
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
598
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
599
|
-
let flags = instance.flags else {
|
|
600
|
-
resolve(fallbackValue)
|
|
601
|
-
return
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
flags.isEnabled(featureName, fallbackValue: fallbackValue) { isEnabled in
|
|
605
|
-
resolve(isEnabled)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Helper methods for variant conversion
|
|
610
|
-
private func convertDictToVariant(_ dict: [String: Any]) -> MixpanelFlagVariant {
|
|
611
|
-
let key = dict["key"] as? String ?? ""
|
|
612
|
-
let value = dict["value"] ?? NSNull()
|
|
613
|
-
let experimentID = dict["experimentID"] as? String
|
|
614
|
-
let isExperimentActive = dict["isExperimentActive"] as? Bool
|
|
615
|
-
let isQATester = dict["isQATester"] as? Bool
|
|
616
|
-
|
|
617
|
-
return MixpanelFlagVariant(
|
|
618
|
-
key: key,
|
|
619
|
-
value: value,
|
|
620
|
-
isExperimentActive: isExperimentActive,
|
|
621
|
-
isQATester: isQATester,
|
|
622
|
-
experimentID: experimentID
|
|
623
|
-
)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private func convertVariantToDict(_ variant: MixpanelFlagVariant) -> [String: Any] {
|
|
627
|
-
var dict: [String: Any] = [
|
|
628
|
-
"key": variant.key,
|
|
629
|
-
"value": variant.value ?? NSNull()
|
|
630
|
-
]
|
|
631
|
-
|
|
632
|
-
if let experimentID = variant.experimentID {
|
|
633
|
-
dict["experimentID"] = experimentID
|
|
634
|
-
}
|
|
635
|
-
dict["isExperimentActive"] = variant.isExperimentActive
|
|
636
|
-
dict["isQATester"] = variant.isQATester
|
|
637
|
-
|
|
638
|
-
return dict
|
|
639
|
-
}
|
|
640
|
-
|
|
641
463
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
defaultServerURL,
|
|
5
5
|
} from "./mixpanel-constants";
|
|
6
6
|
|
|
7
|
-
import {MixpanelLogger} from "./mixpanel-logger";
|
|
7
|
+
import { MixpanelLogger } from "./mixpanel-logger";
|
|
8
8
|
|
|
9
9
|
export class MixpanelConfig {
|
|
10
10
|
static instance;
|
|
@@ -65,10 +65,14 @@ export class MixpanelConfig {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
getUseIpAddressForGeolocation(token) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
68
|
+
if (
|
|
69
|
+
this._config[token] &&
|
|
70
|
+
"useIpAddressForGeolocation" in this._config[token]
|
|
71
|
+
) {
|
|
72
|
+
return this._config[token].useIpAddressForGeolocation;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
setFlushBatchSize(token, batchSize) {
|
|
@@ -21,17 +21,10 @@ export default class MixpanelMain {
|
|
|
21
21
|
trackAutomaticEvents = false,
|
|
22
22
|
optOutTrackingDefault = false,
|
|
23
23
|
superProperties = null,
|
|
24
|
-
serverURL = "https://api.mixpanel.com"
|
|
25
|
-
useGzipCompression = false,
|
|
26
|
-
featureFlagsOptions = {}
|
|
24
|
+
serverURL = "https://api.mixpanel.com"
|
|
27
25
|
) {
|
|
28
26
|
MixpanelLogger.log(token, `Initializing Mixpanel`);
|
|
29
27
|
|
|
30
|
-
// Store feature flags options for later use
|
|
31
|
-
this.featureFlagsOptions = featureFlagsOptions;
|
|
32
|
-
this.featureFlagsEnabled = featureFlagsOptions.enabled || false;
|
|
33
|
-
this.featureFlagsContext = featureFlagsOptions.context || {};
|
|
34
|
-
|
|
35
28
|
await this.mixpanelPersistent.initializationCompletePromise(token);
|
|
36
29
|
if (optOutTrackingDefault) {
|
|
37
30
|
await this.optOutTracking(token);
|
|
@@ -44,11 +37,6 @@ export default class MixpanelMain {
|
|
|
44
37
|
await this.registerSuperProperties(token, {
|
|
45
38
|
...superProperties,
|
|
46
39
|
});
|
|
47
|
-
|
|
48
|
-
// Initialize feature flags if enabled
|
|
49
|
-
if (this.featureFlagsEnabled) {
|
|
50
|
-
MixpanelLogger.log(token, "Feature flags enabled during initialization");
|
|
51
|
-
}
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
getMetaData() {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import "react-native-get-random-values"; // Polyfill for crypto.getRandomValues
|
|
13
13
|
import { AsyncStorageAdapter } from "./mixpanel-storage";
|
|
14
|
-
import
|
|
14
|
+
import { v4 as uuidv4 } from "uuid";
|
|
15
15
|
import { MixpanelLogger } from "mixpanel-react-native/javascript/mixpanel-logger";
|
|
16
16
|
|
|
17
17
|
export class MixpanelPersistent {
|
|
@@ -42,7 +42,7 @@ export class MixpanelPersistent {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async initializationCompletePromise(token) {
|
|
45
|
-
Promise.all([
|
|
45
|
+
await Promise.all([
|
|
46
46
|
this.loadIdentity(token),
|
|
47
47
|
this.loadSuperProperties(token),
|
|
48
48
|
this.loadTimeEvents(token),
|
|
@@ -67,8 +67,8 @@ export class MixpanelPersistent {
|
|
|
67
67
|
this._identity[token].deviceId = storageToken;
|
|
68
68
|
|
|
69
69
|
if (!this._identity[token].deviceId) {
|
|
70
|
-
// Generate device ID using
|
|
71
|
-
this._identity[token].deviceId =
|
|
70
|
+
// Generate device ID using uuidv4() with polyfilled crypto.getRandomValues
|
|
71
|
+
this._identity[token].deviceId = uuidv4();
|
|
72
72
|
await this.storageAdapter.setItem(
|
|
73
73
|
getDeviceIdKey(token),
|
|
74
74
|
this._identity[token].deviceId
|
|
@@ -12,9 +12,9 @@ export class AsyncStorageAdapter {
|
|
|
12
12
|
}
|
|
13
13
|
} catch {
|
|
14
14
|
console.error(
|
|
15
|
-
"[
|
|
15
|
+
"[Mixpanel] AsyncStorage not available. Install @react-native-async-storage/async-storage (^1.15.0 or ^2.0.0), or provide a custom storage implementation. See: https://github.com/mixpanel/mixpanel-react-native#readme"
|
|
16
16
|
);
|
|
17
|
-
console.error("[Mixpanel] Falling back to in-memory storage");
|
|
17
|
+
console.error("[Mixpanel] Falling back to in-memory storage. Data will not persist across app restarts.");
|
|
18
18
|
this.storage = new InMemoryStorage();
|
|
19
19
|
}
|
|
20
20
|
} else {
|
package/package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixpanel-react-native",
|
|
3
|
-
"version": "3.2.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Official React Native Tracking Library for Mixpanel Analytics",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"index.d.ts",
|
|
9
|
+
"javascript/",
|
|
10
|
+
"ios/",
|
|
11
|
+
"android/src/",
|
|
12
|
+
"android/build.gradle",
|
|
13
|
+
"MixpanelReactNative.podspec",
|
|
14
|
+
"react-native.config.js",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE.md",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
20
|
"test": "jest"
|
|
8
21
|
},
|
|
@@ -31,17 +44,17 @@
|
|
|
31
44
|
"mp_lib": "react-native"
|
|
32
45
|
},
|
|
33
46
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@babel/
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
47
|
+
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
48
|
+
"@babel/core": "^7.26.0",
|
|
49
|
+
"@babel/runtime": "^7.26.0",
|
|
50
|
+
"@react-native-community/eslint-config": "^3.2.0",
|
|
51
|
+
"babel-jest": "^29.7.0",
|
|
52
|
+
"eslint": "^8.57.0",
|
|
53
|
+
"jest": "^29.7.0",
|
|
40
54
|
"jest-fetch-mock": "^3.0.3",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"react-
|
|
44
|
-
"react-test-renderer": "16.13.1"
|
|
55
|
+
"metro-react-native-babel-preset": "^0.77.0",
|
|
56
|
+
"react-native": "^0.76.0",
|
|
57
|
+
"react-test-renderer": "^18.3.1"
|
|
45
58
|
},
|
|
46
59
|
"jest": {
|
|
47
60
|
"modulePathIgnorePatterns": [
|
|
@@ -55,13 +68,21 @@
|
|
|
55
68
|
],
|
|
56
69
|
"verbose": true,
|
|
57
70
|
"preset": "react-native",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
71
|
+
"transformIgnorePatterns": [
|
|
72
|
+
"node_modules/(?!(@react-native|react-native|react-native-get-random-values|uuid)/)"
|
|
73
|
+
],
|
|
74
|
+
"testEnvironment": "node"
|
|
61
75
|
},
|
|
62
76
|
"dependencies": {
|
|
63
|
-
"@react-native-async-storage/async-storage": "^1.21.0",
|
|
64
77
|
"react-native-get-random-values": "^1.9.0",
|
|
65
|
-
"uuid": "
|
|
78
|
+
"uuid": "^9.0.1"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@react-native-async-storage/async-storage": "^1.15.0 || ^2.0.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependenciesMeta": {
|
|
84
|
+
"@react-native-async-storage/async-storage": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
66
87
|
}
|
|
67
|
-
}
|
|
88
|
+
}
|