Skip to content

Commit

Permalink
headers and query item capability added to proxy server
Browse files Browse the repository at this point in the history
  • Loading branch information
mohitanand-cred committed Dec 12, 2023
1 parent c633688 commit 918d3ad
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 21 deletions.
13 changes: 8 additions & 5 deletions Sources/Flush.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ class Flush: AppLifecycle {
flushIntervalReadWriteLock = DispatchQueue(label: "com.mixpanel.flush_interval.lock", qos: .utility, attributes: .concurrent, autoreleaseFrequency: .workItem)
}

func flushQueue(_ queue: Queue, type: FlushType) {
func flushQueue(_ queue: Queue, type: FlushType, headers: [String: String], queryItems: [URLQueryItem]) {
if flushRequest.requestNotAllowed() {
return
}
flushQueueInBatches(queue, type: type)
flushQueueInBatches(queue, type: type, headers: headers, queryItems: queryItems)
}

func startFlushTimer() {
Expand Down Expand Up @@ -95,7 +95,7 @@ class Flush: AppLifecycle {
}
}

func flushQueueInBatches(_ queue: Queue, type: FlushType) {
func flushQueueInBatches(_ queue: Queue, type: FlushType, headers: [String: String], queryItems: [URLQueryItem]) {
var mutableQueue = queue
while !mutableQueue.isEmpty {
let batchSize = min(mutableQueue.count, flushBatchSize)
Expand All @@ -115,8 +115,10 @@ class Flush: AppLifecycle {
}
#endif // os(iOS)
let success = flushRequest.sendRequest(requestData,
type: type,
useIP: useIPAddressForGeoLocation)
type: type,
useIP: useIPAddressForGeoLocation,
headers: headers,
queryItems: queryItems)
#if os(iOS)
if !MixpanelInstance.isiOSAppExtension() {
delegate?.updateNetworkActivityIndicator(false)
Expand Down Expand Up @@ -156,3 +158,4 @@ class Flush: AppLifecycle {
}

}

13 changes: 10 additions & 3 deletions Sources/FlushRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ class FlushRequest: Network {

func sendRequest(_ requestData: String,
type: FlushType,
useIP: Bool) -> Bool {
useIP: Bool,
headers: [String: String],
queryItems: [URLQueryItem] = []) -> Bool {

let responseParser: (Data) -> Int? = { data in
let response = String(data: data, encoding: String.Encoding.utf8)
Expand All @@ -31,12 +33,16 @@ class FlushRequest: Network {
return nil
}

let resourceHeaders: [String: String] = headers.merging(["Content-Type": "application/json"]) {(_,new) in new}

let ipString = useIP ? "1" : "0"
var resourceQueryItems: [URLQueryItem] = [URLQueryItem(name: "ip", value: ipString)]
resourceQueryItems.append(contentsOf: queryItems)
let resource = Network.buildResource(path: type.rawValue,
method: .post,
requestBody: requestData.data(using: .utf8),
queryItems: [URLQueryItem(name: "ip", value: ipString)],
headers: ["Content-Type": "application/json"],
queryItems: resourceQueryItems,
headers: resourceHeaders,
parse: responseParser)
var result = false
let semaphore = DispatchSemaphore(value: 0)
Expand Down Expand Up @@ -97,3 +103,4 @@ class FlushRequest: Network {
}

}

93 changes: 84 additions & 9 deletions Sources/Mixpanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,49 @@ open class Mixpanel {
superProperties: superProperties,
serverURL: serverURL)
}

/**
Initializes an instance of the API with the given project token.

Returns a new Mixpanel instance API object. This allows you to create more than one instance
of the API object, which is convenient if you'd like to send data to more than
one Mixpanel project from a single app.

- parameter token: your project token
- parameter trackAutomaticEvents: Whether or not to collect common mobile events
- parameter flushInterval: Optional. Interval to run background flushing
- parameter instanceName: Optional. The name you want to uniquely identify the Mixpanel Instance.
It is useful when you want more than one Mixpanel instance under the same project token.
- parameter optOutTrackingByDefault: Optional. Whether or not to be opted out from tracking by default
- parameter useUniqueDistinctId: Optional. whether or not to use the unique device identifier as the distinct_id
- parameter superProperties: Optional. Super properties dictionary to register during initialization
- parameter proxyServerConfig: Optional. Setup for proxy server.

- important: If you have more than one Mixpanel instance, it is beneficial to initialize
the instances with an instanceName. Then they can be reached by calling getInstance with name.

- returns: returns a mixpanel instance if needed to keep throughout the project.
You can always get the instance by calling getInstance(name)
*/

@discardableResult
open class func initialize(token apiToken: String,
trackAutomaticEvents: Bool,
flushInterval: Double = 60,
instanceName: String? = nil,
optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false,
superProperties: Properties? = nil,
proxyServerConfig: ProxyServerConfig) -> MixpanelInstance {
return MixpanelManager.sharedInstance.initialize(token: apiToken,
flushInterval: flushInterval,
instanceName: ((instanceName != nil) ? instanceName! : apiToken),
trackAutomaticEvents: trackAutomaticEvents,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
proxyServerConfig: proxyServerConfig)
}
#else
/**
Initializes an instance of the API with the given project token (MAC OS ONLY).
Expand Down Expand Up @@ -97,6 +140,24 @@ open class Mixpanel {
superProperties: superProperties,
serverURL: serverURL)
}

@discardableResult
open class func initialize(token apiToken: String,
flushInterval: Double = 60,
instanceName: String? = nil,
optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false,
superProperties: Properties? = nil,
proxyServerConfig: ProxyServerConfig) -> MixpanelInstance {
return MixpanelManager.sharedInstance.initialize(token: apiToken,
flushInterval: flushInterval,
instanceName: ((instanceName != nil) ? instanceName! : apiToken),
trackAutomaticEvents: false,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
proxyServerConfig: proxyServerConfig)
}
#endif // os(OSX)

/**
Expand Down Expand Up @@ -176,22 +237,35 @@ final class MixpanelManager {
optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false,
superProperties: Properties? = nil,
serverURL: String? = nil
serverURL: String? = nil,
proxyServerConfig: ProxyServerConfig? = nil
) -> MixpanelInstance {
instanceQueue.sync {
var instance: MixpanelInstance?
if let instance = instances[instanceName] {
mainInstance = instance
return
}
instance = MixpanelInstance(apiToken: apiToken,
flushInterval: flushInterval,
name: instanceName,
trackAutomaticEvents: trackAutomaticEvents,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
serverURL: serverURL)

if let proxyServerConfig = proxyServerConfig {
instance = MixpanelInstance(apiToken: apiToken,
flushInterval: flushInterval,
name: instanceName,
trackAutomaticEvents: trackAutomaticEvents,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
proxyServerConfig: proxyServerConfig)
} else {
instance = MixpanelInstance(apiToken: apiToken,
flushInterval: flushInterval,
name: instanceName,
trackAutomaticEvents: trackAutomaticEvents,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
serverURL: serverURL)
}
readWriteLock.write {
instances[instanceName] = instance!
mainInstance = instance!
Expand Down Expand Up @@ -245,3 +319,4 @@ final class MixpanelManager {
}

}

77 changes: 73 additions & 4 deletions Sources/MixpanelInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ import CoreTelephony
#endif // os(iOS)

private let devicePrefix = "$device:"

/**
* Delegate protocol for updating the Proxy Server API's network behavior.
*/
public protocol MixpanelProxyServerDelegate: AnyObject {
/**
Asks the delegate to return API resource items like query params & headers for proxy Server.

- parameter mixpanel: The mixpanel instance

- returns: return ServerProxyResource to give custom headers and query params.
*/
func mixpanelResourceForProxyServer(_ mixpanel: MixpanelInstance) -> ServerProxyResource?
}

/**
* Delegate protocol for controlling the Mixpanel API's network behavior.
*/
Expand All @@ -44,6 +59,21 @@ protocol AppLifecycle {
func applicationWillResignActive()
}

public struct ProxyServerConfig {
public init?(serverUrl: String, delegate: MixpanelProxyServerDelegate? = nil) {
/// check if proxy server is not same as default mixpanel API
/// if same, then fail the initializer
/// this is to avoid case where client might inadvertently use headers intended for the proxy server
/// on Mixpanel's default server, leading to unexpected behavior.
guard serverUrl != BasePath.DefaultMixpanelAPI else { return nil }
self.serverUrl = serverUrl
self.delegate = delegate
}

let serverUrl: String
let delegate: MixpanelProxyServerDelegate?
}

/// The class that represents the Mixpanel Instance
open class MixpanelInstance: CustomDebugStringConvertible, FlushDelegate, AEDelegate {

Expand Down Expand Up @@ -141,6 +171,10 @@ open class MixpanelInstance: CustomDebugStringConvertible, FlushDelegate, AEDele
}
}

/// The a MixpanelProxyServerDelegate object that gives config control over Proxy Server's network activity.
open weak var proxyServerDelegate: MixpanelProxyServerDelegate?


open var debugDescription: String {
return "Mixpanel(\n"
+ " Token: \(apiToken),\n"
Expand Down Expand Up @@ -226,9 +260,39 @@ open class MixpanelInstance: CustomDebugStringConvertible, FlushDelegate, AEDele
#if os(iOS) || os(tvOS)
let automaticEvents = AutomaticEvents()
#endif
init(apiToken: String?, flushInterval: Double, name: String, trackAutomaticEvents: Bool, optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false, superProperties: Properties? = nil,
serverURL: String? = nil) {

convenience init(
apiToken: String?,
flushInterval: Double,
name: String,
trackAutomaticEvents: Bool,
optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false,
superProperties: Properties? = nil,
proxyServerConfig: ProxyServerConfig
) {
self.init(apiToken: apiToken,
flushInterval: flushInterval,
name: name,
trackAutomaticEvents: trackAutomaticEvents,
optOutTrackingByDefault: optOutTrackingByDefault,
useUniqueDistinctId: useUniqueDistinctId,
superProperties: superProperties,
serverURL: proxyServerConfig.serverUrl)
self.proxyServerDelegate = proxyServerConfig.delegate
}


init(
apiToken: String?,
flushInterval: Double,
name: String,
trackAutomaticEvents: Bool,
optOutTrackingByDefault: Bool = false,
useUniqueDistinctId: Bool = false,
superProperties: Properties? = nil,
serverURL: String? = nil
) {
if let apiToken = apiToken, !apiToken.isEmpty {
self.apiToken = apiToken
}
Expand Down Expand Up @@ -987,7 +1051,11 @@ extension MixpanelInstance {
if hasOptedOutTracking() {
return
}
self.flushInstance.flushQueue(queue, type: type)
let proxyServerResource = proxyServerDelegate?.mixpanelResourceForProxyServer(self)
let headers: [String: String] = proxyServerResource?.headers ?? [:]
let queryItems = proxyServerResource?.queryItems ?? []

self.flushInstance.flushQueue(queue, type: type, headers: headers, queryItems: queryItems)
}

func flushSuccess(type: FlushType, ids: [Int32]) {
Expand Down Expand Up @@ -1530,3 +1598,4 @@ extension MixpanelInstance {
}

}

11 changes: 11 additions & 0 deletions Sources/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ enum Reason {
case other(Error)
}

public struct ServerProxyResource {
public init(queryItems: [URLQueryItem]? = nil, headers: [String : String]) {
self.queryItems = queryItems
self.headers = headers
}

let queryItems: [URLQueryItem]?
let headers: [String: String]
}

class Network {

let basePathIdentifier: String
Expand Down Expand Up @@ -192,3 +202,4 @@ class Network {
}
}
}

0 comments on commit 918d3ad

Please sign in to comment.