Skip to content

Commit

Permalink
Create user agent on main queue (#175)
Browse files Browse the repository at this point in the history
* Create user agent on main queue
Refactor duplicate code into common method

* Removed custom asyncMain function

* Added a line to the CHANGELOG
  • Loading branch information
minitrue22 authored and brototyp committed Oct 10, 2017
1 parent 6f8aabb commit 71baff6
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 35 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Unreleased
* **improvement** The PiwikTracker is now save to create in a background thread. [#175](https://github.com/piwik/piwik-sdk-ios/pull/175)

## 4.3.0
* **feature** Added the ability to send custom events with custom tracking parameters. [#153](https://github.com/piwik/piwik-sdk-ios/issues/153)
* **bugfix** Fixed a crash when initializing a new tracker. [#162](https://github.com/piwik/piwik-sdk-ios/issues/162)
Expand Down
4 changes: 4 additions & 0 deletions PiwikTracker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
1F6F0CDE1E61E377008170FC /* TrackerFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6F0CDA1E61E377008170FC /* TrackerFixtures.swift */; };
1F6F0CDF1E61E377008170FC /* TrackerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6F0CDB1E61E377008170FC /* TrackerSpec.swift */; };
1F6F0CE11E61E4F3008170FC /* URLSessionDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6F0CE01E61E4F3008170FC /* URLSessionDispatcher.swift */; };
1F7C667F1F8C096F0066CC64 /* MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7C667E1F8C096F0066CC64 /* MainThread.swift */; };
1F80856F1E6B4B9800A61AAF /* Locale+HttpAcceptLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F80856E1E6B4B9800A61AAF /* Locale+HttpAcceptLanguage.swift */; };
1FCA6D451DBE0B2F0033F01C /* PiwikTracker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCA6D3B1DBE0B2F0033F01C /* PiwikTracker.framework */; };
1FCA6D4C1DBE0B2F0033F01C /* PiwikTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCA6D3E1DBE0B2F0033F01C /* PiwikTracker.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -61,6 +62,7 @@
1F6F0CDA1E61E377008170FC /* TrackerFixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackerFixtures.swift; sourceTree = "<group>"; };
1F6F0CDB1E61E377008170FC /* TrackerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackerSpec.swift; sourceTree = "<group>"; };
1F6F0CE01E61E4F3008170FC /* URLSessionDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDispatcher.swift; sourceTree = "<group>"; };
1F7C667E1F8C096F0066CC64 /* MainThread.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainThread.swift; sourceTree = "<group>"; };
1F80856E1E6B4B9800A61AAF /* Locale+HttpAcceptLanguage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Locale+HttpAcceptLanguage.swift"; sourceTree = "<group>"; };
1FCA6D3B1DBE0B2F0033F01C /* PiwikTracker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PiwikTracker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1FCA6D3E1DBE0B2F0033F01C /* PiwikTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PiwikTracker.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -157,6 +159,7 @@
1F38EBF71EE568D10021FBF8 /* Logger.swift */,
1FDC917D1F1A65150046F506 /* Application.swift */,
1FDC917E1F1A65150046F506 /* Device.swift */,
1F7C667E1F8C096F0066CC64 /* MainThread.swift */,
1FCA6D3F1DBE0B2F0033F01C /* Info.plist */,
1FCA6D3E1DBE0B2F0033F01C /* PiwikTracker.h */,
);
Expand Down Expand Up @@ -375,6 +378,7 @@
1F092C1A1E26B44500394B30 /* Dispatcher.swift in Sources */,
1F38EBF81EE568D10021FBF8 /* Logger.swift in Sources */,
1FCFF0241F82C7A50038BC17 /* CustomDimension.swift in Sources */,
1F7C667F1F8C096F0066CC64 /* MainThread.swift in Sources */,
1F80856F1E6B4B9800A61AAF /* Locale+HttpAcceptLanguage.swift in Sources */,
1F0A15CE1E6335D800FEAE72 /* Visitor.swift in Sources */,
1FDC917F1F1A65150046F506 /* Application.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions PiwikTracker/MainThread.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

func assetMainThread(_ file: StaticString = #file, line: UInt = #line) {
assert(Thread.isMainThread, "\(file):\(line) must run on the main thread!")
}
2 changes: 2 additions & 0 deletions PiwikTracker/PiwikTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ final public class PiwikTracker: NSObject {
/// - baseURL: The url of the piwik server. This url has to end in `piwik.php`.
/// - userAgent: An optional parameter for custom user agent.
convenience public init(siteId: String, baseURL: URL, userAgent: String? = nil) {
assert(baseURL.absoluteString.hasSuffix("piwik.php"), "The baseURL is expected to end in piwik.php")

let queue = MemoryQueue()
let dispatcher = URLSessionDispatcher(baseURL: baseURL, userAgent: userAgent)
self.init(siteId: siteId, queue: queue, dispatcher: dispatcher)
Expand Down
70 changes: 36 additions & 34 deletions PiwikTracker/URLSessionDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,44 @@ final class URLSessionDispatcher: Dispatcher {
let session: URLSession
let baseURL: URL

var userAgent: String? = {
#if os(OSX)
let webView = WebView(frame: .zero)
let currentUserAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") ?? ""
#elseif os(iOS)
let webView = UIWebView(frame: .zero)
let currentUserAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") ?? ""
#elseif os(tvOS)
let currentUserAgent = ""
#endif
return currentUserAgent.appending(" PiwikTracker SDK URLSessionDispatcher")
}()
private(set) var userAgent: String?

/// Generate a URLSessionDispatcher instance
///
/// - Parameters:
/// - baseURL: The url of the piwik server. This url has to end in `piwik.php`.
/// - userAgent: An optional parameter for custom user agent.
init(baseURL: URL, userAgent: String? = nil) {
if !baseURL.absoluteString.hasSuffix("piwik.php") {
fatalError("The baseURL is expected to end in piwik.php")
}
init(baseURL: URL, userAgent: String? = nil) {
self.baseURL = baseURL
self.timeout = 5
self.session = URLSession.shared
if (userAgent != nil) {
self.userAgent = userAgent
DispatchQueue.main.async {
self.userAgent = userAgent ?? URLSessionDispatcher.defaultUserAgent()
}
}

private static func defaultUserAgent() -> String {
assetMainThread()
#if os(OSX)
let webView = WebView(frame: .zero)
let currentUserAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") ?? ""
#elseif os(iOS)
let webView = UIWebView(frame: .zero)
let currentUserAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") ?? ""
#elseif os(tvOS)
let currentUserAgent = ""
#endif
return currentUserAgent.appending(" PiwikTracker SDK URLSessionDispatcher")
}

func send(event: Event, success: @escaping ()->(), failure: @escaping (_ error: Error)->()) {
let url = baseURL.setting(event.queryItems)
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: timeout)
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
request.httpMethod = "GET"
let request = buildRequest(baseURL: url, method: "GET")
send(request: request, success: success, failure: failure)
}

func send(events: [Event], success: @escaping ()->(), failure: @escaping (_ error: Error)->()) {
let eventsAsQueryItems = events.map({ event in event.queryItems })
let eventsAsQueryItems = events.map({ $0.queryItems })
let serializedEvents = eventsAsQueryItems.map({ items in
items.flatMap({ item in
guard let value = item.value,
Expand All @@ -67,14 +65,19 @@ final class URLSessionDispatcher: Dispatcher {
failure(error)
return
}
var request = URLRequest(url: baseURL, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: timeout)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
request.httpMethod = "POST"
request.httpBody = jsonBody
let request = buildRequest(baseURL: baseURL, method: "POST", contentType: "application/json; charset=utf-8", body: jsonBody)
send(request: request, success: success, failure: failure)
}

private func buildRequest(baseURL: URL, method: String, contentType: String? = nil, body: Data? = nil) -> URLRequest {
var request = URLRequest(url: baseURL, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: timeout)
request.httpMethod = method
body.map { request.httpBody = $0 }
contentType.map { request.setValue($0, forHTTPHeaderField: "Content-Type") }
userAgent.map { request.setValue($0, forHTTPHeaderField: "User-Agent") }
return request
}

private func send(request: URLRequest, success: @escaping ()->(), failure: @escaping (_ error: Error)->()) {
let task = session.dataTask(with: request) { data, response, error in
// should we check the response?
Expand All @@ -93,7 +96,7 @@ final class URLSessionDispatcher: Dispatcher {
fileprivate extension Event {
var queryItems: [URLQueryItem] {
get {
var items = [
let items = [
URLQueryItem(name: "idsite", value: siteId),
URLQueryItem(name: "rec", value: "1"),
// Visitor
Expand All @@ -115,7 +118,6 @@ fileprivate extension Event {
URLQueryItem(name: "m", value: DateFormatter.minuteDateFormatter.string(from: date)),
URLQueryItem(name: "s", value: DateFormatter.secondsDateFormatter.string(from: date)),


//screen resolution
URLQueryItem(name: "res", value:String(format: "%1.0fx%1.0f", screenResolution.width, screenResolution.height)),

Expand All @@ -124,11 +126,11 @@ fileprivate extension Event {
URLQueryItem(name: "e_n", value: eventName),
URLQueryItem(name: "e_v", value: eventValue != nil ? "\(eventValue!)" : nil),

].filter({ $0.value != nil }) // remove the items that lack the value
for dimension in dimensions {
items.append(URLQueryItem(name: "dimension\(dimension.index)", value: dimension.value))
}
return items + customTrackingParameters.map({ key, value in return URLQueryItem(name: key, value: value) })
].filter { $0.value != nil }

let dimensionItems = dimensions.map { URLQueryItem(name: "dimension\($0.index)", value: $0.value) }
let customItems = customTrackingParameters.map { return URLQueryItem(name: $0.key, value: $0.value) }
return items + dimensionItems + customItems
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ PiwikTracker.shared?.logger = DefaultLogger(minLevel: .error)
You can also write your own `Logger` and send the logs whereever you want. Just write a new class/struct an let it conform to the `Logger` protocol.

### Custom User Agent
The `PiwikTracker` will create a default user agent derived from the WKWebView user agent. This is why you should always instantiate and configure the `PiwikTracker` on the main thread.
The `PiwikTracker` will create a default user agent derived from the WKWebView user agent.
You can instantiate the `PiwikTracker` using your own user agent.

```
Expand Down

0 comments on commit 71baff6

Please sign in to comment.