Skip to content

Commit

Permalink
Merge 1806f73 into dd6df5f
Browse files Browse the repository at this point in the history
  • Loading branch information
wessmith committed Jul 27, 2020
2 parents dd6df5f + 1806f73 commit 5498edf
Show file tree
Hide file tree
Showing 11 changed files with 546 additions and 23 deletions.
20 changes: 20 additions & 0 deletions ButtonMerchant.xcodeproj/project.pbxproj
Expand Up @@ -117,6 +117,11 @@
DEA3F6BA2108F280009E720E /* MessageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA4AF05420868015002C3E0E /* MessageView.xib */; };
DEA3F6BB2108F286009E720E /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4AF05220867FFB002C3E0E /* MessageView.swift */; };
DEE61B2D20656A090039E47A /* XCTestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE61B2C20656A090039E47A /* XCTestExtensions.swift */; };
FB70030824CF2FDE0050E021 /* AppEventsRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB70030724CF2FDE0050E021 /* AppEventsRequestBody.swift */; };
FB70030B24CF46260050E021 /* AppEventRequestBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB70030924CF44620050E021 /* AppEventRequestBodyTests.swift */; };
FB70030F24CF48A90050E021 /* AppEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB70030E24CF48A90050E021 /* AppEvent.swift */; };
FB70031224CF4BDE0050E021 /* AppEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB70031024CF4BC60050E021 /* AppEventTests.swift */; };
FB70031424CF5EA50050E021 /* String+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB70031324CF5EA50050E021 /* String+Error.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -268,6 +273,11 @@
DE865FA2205304C700F4054D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
DEE61B2C20656A090039E47A /* XCTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestExtensions.swift; sourceTree = "<group>"; };
ECBD32D6815889EF663EBA5B /* Pods_ButtonMerchant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ButtonMerchant.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FB70030724CF2FDE0050E021 /* AppEventsRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEventsRequestBody.swift; sourceTree = "<group>"; };
FB70030924CF44620050E021 /* AppEventRequestBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEventRequestBodyTests.swift; sourceTree = "<group>"; };
FB70030E24CF48A90050E021 /* AppEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEvent.swift; sourceTree = "<group>"; };
FB70031024CF4BC60050E021 /* AppEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEventTests.swift; sourceTree = "<group>"; };
FB70031324CF5EA50050E021 /* String+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Error.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -399,6 +409,8 @@
DEE61B2B206569EA0039E47A /* TestExtensions */,
DA0FA2A1205C1EE8008296A6 /* TestObjects */,
607FACE91AFB9204008FA782 /* Supporting Files */,
FB70030924CF44620050E021 /* AppEventRequestBodyTests.swift */,
FB70031024CF4BC60050E021 /* AppEventTests.swift */,
);
path = UnitTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -462,6 +474,7 @@
DA756B8222B33762003397E3 /* URLAuthenticationChallengeExtensions.swift */,
DA756B8422B3378B003397E3 /* URLProtectionSpaceExtensions.swift */,
9E56CE5822B8049C00E75884 /* StringExtensions.swift */,
FB70031324CF5EA50050E021 /* String+Error.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -610,6 +623,8 @@
DAB8DFC32316EB3200E16619 /* NetworkError.swift */,
9E77202020605126005F740B /* Extensions */,
DE865FA42053073B00F4054D /* Supporting Files */,
FB70030724CF2FDE0050E021 /* AppEventsRequestBody.swift */,
FB70030E24CF48A90050E021 /* AppEvent.swift */,
);
path = Source;
sourceTree = "<group>";
Expand Down Expand Up @@ -1122,15 +1137,18 @@
DE175A1C20A09DEA005C97B9 /* Version.generated.swift in Sources */,
9EB1B0A0207AB43E00BE0A1A /* System.swift in Sources */,
DAE8B96F22AF5F0700D11AF9 /* TrustEvaluator.swift in Sources */,
FB70031424CF5EA50050E021 /* String+Error.swift in Sources */,
DE2F7450208F6552001E4BD6 /* ConfigurationError.swift in Sources */,
DCF4AD0422B421FB000DA3B2 /* ReportOrderBody.swift in Sources */,
FB70030F24CF48A90050E021 /* AppEvent.swift in Sources */,
DE865FA620530BCE00F4054D /* ButtonMerchant.swift in Sources */,
9E56CE5A22B812AE00E75884 /* StringExtensions.swift in Sources */,
9E2B4317206C1335009F2886 /* EncodableExtensions.swift in Sources */,
9E2B431B206C134A009F2886 /* PostInstallBody.swift in Sources */,
9EB1B0A4207AB68100BE0A1A /* CalendarExtensions.swift in Sources */,
DE1706DB20855B06009FF30B /* UIDeviceExtensions.swift in Sources */,
DA98C00622B29BA0002D1823 /* PEMCertificate.swift in Sources */,
FB70030824CF2FDE0050E021 /* AppEventsRequestBody.swift in Sources */,
DE1706E020855F4C009FF30B /* LocaleExtensions.swift in Sources */,
DA4C8C7922D3D909000E15A9 /* ReportOrderRequest.swift in Sources */,
9E77202220605506005F740B /* ButtonDefaults.swift in Sources */,
Expand Down Expand Up @@ -1176,6 +1194,7 @@
DE1706E920856417009FF30B /* TestAdIdManager.swift in Sources */,
DA29D892209CDC3100537806 /* URLSessionTests.swift in Sources */,
DA756B7922B2DB67003397E3 /* SessionDelegateTests.swift in Sources */,
FB70030B24CF46260050E021 /* AppEventRequestBodyTests.swift in Sources */,
9E4C496820616B040053E4CA /* ButtonDefaultsTests.swift in Sources */,
9E6F4341206C160C004242A1 /* TestClient.swift in Sources */,
9E4C496720616B040053E4CA /* CoreTests.swift in Sources */,
Expand All @@ -1194,6 +1213,7 @@
DA4AF05B20869E34002C3E0E /* TestNotificationCenter.swift in Sources */,
DA191A2622D5261100ED88B9 /* TestRetryPolicy.swift in Sources */,
DADE90C2209B9A630073144B /* TestError.swift in Sources */,
FB70031224CF4BDE0050E021 /* AppEventTests.swift in Sources */,
DE175A2120A0AFF6005C97B9 /* VersionTests.generated.swift in Sources */,
9E5475E4206D91A900947A1C /* TestURLSessionDataTask.swift in Sources */,
DE1706E5208563D7009FF30B /* TestLocale.swift in Sources */,
Expand Down
36 changes: 36 additions & 0 deletions Source/AppEvent.swift
@@ -0,0 +1,36 @@
//
// AppEvent.swift
//
// Copyright © 2020 Button, Inc. All rights reserved. (https://usebutton.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

import UIKit

internal struct AppEvent: Codable {
let name: String
let value: [String: String]?
let attributionToken: String?

enum CodingKeys: String, CodingKey {
case name, value
case attributionToken = "promotion_source_token"
}
}
30 changes: 30 additions & 0 deletions Source/AppEventsRequestBody.swift
@@ -0,0 +1,30 @@
//
// AppEventRequest.swift
//
// Copyright © 2020 Button, Inc. All rights reserved. (https://usebutton.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

import Foundation

internal struct AppEventsRequestBody: Codable {
let ifa: String?
let events: [AppEvent]
}
17 changes: 17 additions & 0 deletions Source/Client.swift
Expand Up @@ -30,6 +30,7 @@ internal enum Service: String {
case postInstall = "v1/web/deferred-deeplink"
case activity = "v1/activity/order"
case order = "v1/app/order"
case appEvents = "v1/app/events"

static var baseURL = "https://api.usebutton.com/"

Expand All @@ -45,6 +46,7 @@ internal protocol ClientType: class {
func fetchPostInstallURL(parameters: [String: Any], _ completion: @escaping (URL?, String?) -> Void)
func trackOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?)
func reportOrder(orderRequest: ReportOrderRequestType, _ completion: ((Error?) -> Void)?)
func reportEvents(_ events: [AppEvent], ifa: String?, _ completion: ((Error?) -> Void)?)
init(session: URLSessionType, userAgent: UserAgentType, defaults: ButtonDefaultsType)
}

Expand Down Expand Up @@ -90,6 +92,21 @@ internal final class Client: ClientType {
orderRequest.report(request, with: session, completion)
}

func reportEvents(_ events: [AppEvent], ifa: String?, _ completion: ((Error?) -> Void)?) {
guard events.count > 0 else {
if let completion = completion {
completion("No events to report")
}
return
}
let body = AppEventsRequestBody(ifa: ifa, events: events)
let request = urlRequest(url: Service.appEvents.url, parameters: body.dictionaryRepresentation)
enqueueRequest(request: request) { _, error in
if let completion = completion {
completion(error)
}
}
}
}

internal extension Client {
Expand Down
83 changes: 60 additions & 23 deletions Source/Core.swift
Expand Up @@ -90,33 +90,67 @@ final internal class Core: CoreType {

/**
Checks and persists Button attribution in all passed URLs.
- Parameter url: The URL to be inspected.
*/
func trackIncomingURL(_ url: URL) {
guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = urlComponents.queryItems else {
return
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return
}

if let queryItem = queryItems.first(where: {$0.name == "btn_ref"}),
let newAttributionToken = queryItem.value {

let oldAttributionToken = attributionToken
attributionToken = newAttributionToken

var isNewToken = true
if let oldAttributionToken = oldAttributionToken {
isNewToken = oldAttributionToken != newAttributionToken
}
if isNewToken {
notificationCenter.post(name: Notification.Name.Button.AttributionTokenDidChange,
object: nil,
userInfo: [Notification.Key.NewToken: newAttributionToken])

let queryItems = urlComponents.queryItems
urlComponents.queryItems = nil
urlComponents.fragment = nil

var incomingToken: String?
if let queryItems = queryItems {
var allowedQueryItems = [URLQueryItem]()
queryItems.forEach { item in
switch item.name {
case "btn_ref":
incomingToken = item.value
updateAttributionIfNeeded(token: incomingToken)
allowedQueryItems.append(item)
case "from_landing",
"from_tracking",
_ where item.name.hasPrefix("btn_"):
allowedQueryItems.append(item)
default:
break
}
}
urlComponents.queryItems = allowedQueryItems
}

guard let filteredURL = urlComponents.url else {
return
}

let event = AppEvent(name: "btn:deeplink-opened",
value: [ "url": filteredURL.absoluteString],
attributionToken: incomingToken)
client.reportEvents([event], ifa: system.advertisingId, nil)
}


private func updateAttributionIfNeeded(token: String?) {
guard let incomingToken = token else {
return
}

let oldAttributionToken = attributionToken
attributionToken = incomingToken

var isNewToken = true
if let oldAttributionToken = oldAttributionToken {
isNewToken = oldAttributionToken != incomingToken
}
if isNewToken {
notificationCenter.post(name: Notification.Name.Button.AttributionTokenDidChange,
object: nil,
userInfo: [Notification.Key.NewToken: incomingToken])
}
}

/**
Checks *once only* for a post-install url if installed < 12 hours ago.
*/
Expand All @@ -135,11 +169,14 @@ final internal class Core: CoreType {

let postInstallBody = PostInstallBody(system: system, applicationId: appId)
let parameters = postInstallBody.dictionaryRepresentation

client.fetchPostInstallURL(parameters: parameters) { [weak self] url, attributionToken in
if let token = attributionToken {
self?.buttonDefaults.attributionToken = token
guard self?.buttonDefaults.attributionToken == nil,
let token = attributionToken else {
completion(nil, nil)
return
}
self?.buttonDefaults.attributionToken = token
completion(url, nil)
}
}
Expand Down
26 changes: 26 additions & 0 deletions Source/Extensions/String+Error.swift
@@ -0,0 +1,26 @@
//
// String+Error.swift
//
// Copyright © 2020 Button, Inc. All rights reserved. (https://usebutton.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

/// Allow a String to be an error for simple use cases.
extension String: Error {}

0 comments on commit 5498edf

Please sign in to comment.