* Defer automatic events initialization
Delay automaticEvents.initializeEvents by dispatching to DispatchQueue.main.async to run on the next run loop iteration. This avoids interfering with SwiftUI's accent color setup when Mixpanel.initialize() is called from a SwiftUI App's init (see #522). Uses a weak self capture to avoid retain cycles.
* Drain main run loop before tracking queue
Call waitForAsyncTasks() at the start of waitForTrackingQueue to drain the main run loop so any deferred initialization (e.g. automatic events) is dispatched to the tracking queue before synchronizing. Adds a clarifying comment and prevents races in tests that rely on tracking/ network queue state.
- Fix for copilot review comment on pull/712
* Fix for tests in PR
drains the main run loop only once per test right after init, ensuring initializeEvents() has dispatched before the test proceeds
* added code for communcation bridge
* removed the excess logging
* removed the MixpanelBridgeEvent class
* updated eventBridge code
* moved notifyListeners to track call
* use AsyncStream
* Merge branch 'master' into feature/communcation-bridge
* moved the bridge code to shared module
* Update Package.swift
* Use mixpanel-swift-common package
Replace mixpanel-swift-shared with mixpanel-swift-common across the project. Updated Package.swift (dependency URL and product name), changed import in Sources/Track.swift, and modified Mixpanel.xcodeproj entries to reference MixpanelSwiftCommon (XCRemoteSwiftPackageReference, XCSwiftPackageProductDependency, framework entries and groups). Also bumped the package requirement to upToNextMajor with minimumVersion 2.0.0.
* Update Package.swift
* Update Package.swift
* Lower mixpanel-swift-common min version and remove duplicate
Reduce the XCRemoteSwiftPackageReference minimumVersion for mixpanel-swift-common from 2.0.0 to 1.0.0 in Mixpanel.xcodeproj to allow older compatible releases. Also remove a duplicated flagManager.checkFirstTimeEvents(...) block in Sources/Track.swift to prevent the same first-time event check from running twice.
* Add MixpanelEventBridgeTests covering bridge notification on track and opt-out behavior
Agent-Logs-Url: https://github.com/mixpanel/mixpanel-swift/sessions/fa45ba33-c2bf-4811-9e91-829afdc858bf
Co-authored-by: ketanmixpanel <188901560+ketanmixpanel@users.noreply.github.com>
* Add MixpanelEventBridgeTests.swift to Mac test directory
Agent-Logs-Url: https://github.com/mixpanel/mixpanel-swift/sessions/fa45ba33-c2bf-4811-9e91-829afdc858bf
Co-authored-by: ketanmixpanel <188901560+ketanmixpanel@users.noreply.github.com>
* Add MixpanelEventBridgeTests.swift to tvOS test target
Agent-Logs-Url: https://github.com/mixpanel/mixpanel-swift/sessions/47b76ca2-86a7-4c28-9265-b72264c0d548
Co-authored-by: ketanmixpanel <188901560+ketanmixpanel@users.noreply.github.com>
* Update MixpanelEventBridgeTests.swift
* added pod dependency
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ketanmixpanel <188901560+ketanmixpanel@users.noreply.github.com>
* Add OpenFeature provider
Implement an OpenFeature provider for the Mixpanel Swift SDK as a
separate SPM target (MixpanelOpenFeature). The provider wraps the
MixpanelFlags protocol and bridges flag evaluations to the OpenFeature
FeatureProvider interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix macOS tests to use renamed instanceName parameter
The MixpanelPersistence API was renamed from token/apiToken to
instanceName to support multiple instances under the same token,
but the macOS test files were never updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add missing OpenFeature provider tests for consistency across SDKs
Adds context no-op verification, variant key passthrough, SDK exception
handling, and empty variant key edge case tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Implement OpenFeature context forwarding in initialize and onContextSet
Add setContext method to MixpanelFlags protocol and FeatureFlagManager to
support dynamic context updates with flag re-fetch. Update the OpenFeature
provider to forward evaluation context during initialize() and
onContextSet() using replace mode, awaiting flag fetch completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add GENERAL error handling and normalize targetingKey
Wrap resolve() in do/catch to convert unexpected errors to
OpenFeatureError.generalError. Rename targeting_key to targetingKey
(camelCase) in context forwarding for cross-SDK consistency per spec.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Simplify OpenFeature provider and remove dead code
Remove unnecessary do/catch wrapper with unreachable dead code in
resolve method. Remove ~50-line duplicate mock class. De-duplicate
completion handler append in _fetchFlagsIfNeeded. Remove unnecessary
weak self captures. Fix redundant nil-coalescing in test base classes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improved update context variable storage
* Return defaults with error codes instead of throwing exceptions
Evaluators now catch errors from resolve() and return default values
with specific error codes (.providerNotReady, .flagNotFound, .typeMismatch)
instead of throwing exceptions. This provides a more graceful experience
when flags are accessed before they are ready.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use ERROR reason code for failed evaluations to match JS provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove OpenFeature provider (moved to mixpanel-swift-openfeature)
The OpenFeature provider has been extracted into its own standalone
package at https://github.com/mixpanel/mixpanel-swift-openfeature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix flag context not loaded from options on init
The init(serverURL:) initializer sets flagContext to empty, and
MixpanelInstance sets the delegate afterward. The context from
FeatureFlagOptions was never populated into flagContext, causing
the first flag fetch to send no context and receive empty flags.
Fix by populating flagContext from the delegate's options in a
didSet observer on the delegate property.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert platform version requirements raised for OpenFeature provider
The OpenFeature provider has been moved to its own repo
(mixpanel-swift-openfeature), so the platform minimums can return to
their original values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add loadFlags(completion:) to support async flag loading callbacks
Adds a completion handler variant of loadFlags() to the MixpanelFlags
protocol and FeatureFlagManager implementation. The completion is called
with true on success and false on failure. The existing no-arg loadFlags()
now delegates to loadFlags(completion: nil).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add loadFlags(completion:) to MockMixpanelFlags for protocol conformance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add support to get all feature flag assignments
* Fix test class conformity
* cleanup
---------
Co-authored-by: Kwame Efah <kwameefah@h7d9gc72q4-kwameefah.corp.mixpanel.com>
* Add FlagOptions with loadOnFirstForeground to fix initialize/identify race
When calling Mixpanel.initialize immediately followed by identify, both
can trigger concurrent flag fetches causing inconsistent flag evaluation.
This adds a FlagOptions struct with a loadOnFirstForeground option that
lets customers disable the automatic flag load at init time, call
identify first, then manually trigger loadFlags(). The new flagsOptions
parameter on MixpanelOptions groups flag config together while keeping
full backward compatibility with existing featureFlagsEnabled and
featureFlagsContext parameters.
Usage:
let options = MixpanelOptions(
token: "TOKEN",
flagsOptions: FlagOptions(enabled: true, loadOnFirstForeground: false)
)
let mp = Mixpanel.initialize(options: options)
mp.identify(distinctId: "user123")
mp.flags.loadFlags()
https://claude.ai/code/session_01234twgpsuvC2SP67zKDNru
* Clarify FlagOptions docs: identify() already triggers loadFlags()
identify() calls loadFlags() internally when the distinctId changes,
so an explicit loadFlags() call is only needed when re-identifying
with the same persisted distinctId.
https://claude.ai/code/session_01234twgpsuvC2SP67zKDNru
* deprecate old options
* test update
* Fix CI test failures: replace timer waits with predicate expectations
Timer-based waits (0.2-0.3s) expire before .utility QoS dispatches
complete on resource-constrained CI runners. Replace with
XCTNSPredicateExpectation that polls observable state and fulfills
immediately when ready, with 10s timeout for CI headroom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* rename fields
* rename to featureFlagOptions
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add deviceIdProvider closure for custom device ID generation
Adds a `deviceIdProvider: (() -> String)?` property to MixpanelOptions
that allows customers to supply their own device ID generation logic.
Key behaviors:
- Provider called on init (if no persisted identity) and after reset()/optOutTracking()
- Return same value = persistent device ID (never resets)
- Return different value = ephemeral device ID (resets each time)
- SDK logs warning when provider would replace existing persisted anonymousId
- Empty string from provider falls back to default UUID/IDFV behavior
This gives customers full control over device ID persistence and reset
semantics without needing separate parameters on reset() and optOutTracking().
Includes:
- 13 unit tests covering all behaviors
- Comprehensive documentation in docs/device-id-provider.md
* Address PR review feedback for deviceIdProvider
- Change log level from warn to error for identity mismatch
- Use defaultDeviceId() in unarchive to avoid calling provider twice
- Add whitespace trimming to provider result
- Update documentation table with "Value Used?" column
- Add testIdentifyWithDeviceIdProvider test case
* Add deviceIdProvider QA helpers to MixpanelDemo
- Add persistent and ephemeral device ID provider examples in AppDelegate
- Enhance Reset button with before/after console logging
- Default to no provider (standard SDK behavior) for production
* Make deviceIdProvider return optional String for graceful error handling
Change type from `(() -> String)?` to `(() -> String?)?` so customers can
return nil to signal "use SDK default" when their device ID source fails.
- Update MixpanelOptions type signature and documentation
- Handle optional return in defaultDeviceId() with nil-coalescing
- Update warning message to mention nil or empty string
- Add failingDeviceIdProvider test option in demo app
- Add testProviderReturningNilFallsBackToDefault test
* Add Original ID Merge compatibility note to deviceIdProvider docs
Document the footgun with persistent device IDs + Original ID Merge
on shared devices, where createAlias() can incorrectly merge
identities of different users.
* Add thread-safety warning to deviceIdProvider documentation
The provider closure is called while holding internal locks, so blocking
operations (Keychain, network, UserDefaults) can cause deadlocks. Update
docs and examples to recommend caching the device ID at app launch and
returning the cached value from the provider.
- Add Thread Safety note to MixpanelOptions.swift doc comment
- Update docs/device-id-provider.md with caching pattern
- Revise Best Practices to emphasize non-blocking providers
- Update MixpanelDemo to demonstrate caching pattern