Skip to content

Commit ec25d79

Browse files
authored
openURL adjustments / standardization (#182)
* Fixes for openURL inconsistencies * Added test * isolated test to iOS * Made it more platform agnostic * Fixed protocol visibility * Fix linux build; update mac xcode. * test build * updated ci config * Added conversion method for UIKit * Update documentation
1 parent c47cf28 commit ec25d79

File tree

8 files changed

+111
-33
lines changed

8 files changed

+111
-33
lines changed

.github/workflows/swift.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- uses: maxim-lobanov/setup-xcode@v1
2222
with:
23-
xcode-version: '13.2.1'
23+
xcode-version: latest-stable
2424
- uses: actions/checkout@v2
2525
- uses: webfactory/[email protected]
2626
with:
@@ -34,9 +34,9 @@ jobs:
3434
needs: cancel_previous
3535
runs-on: ubuntu-latest
3636
steps:
37-
- uses: fwal/setup-swift@v1
37+
- uses: fwal/setup-swift@v1.21.0
3838
with:
39-
swift-version: "5.4"
39+
swift-version: "5.7.2"
4040
- uses: actions/checkout@v2
4141
- uses: webfactory/[email protected]
4242
with:
@@ -72,7 +72,7 @@ jobs:
7272
steps:
7373
- uses: maxim-lobanov/setup-xcode@v1
7474
with:
75-
xcode-version: '13.2.1'
75+
xcode-version: latest-stable
7676
- uses: actions/checkout@v2
7777
- uses: actions/cache@v2
7878
with:
@@ -91,7 +91,7 @@ jobs:
9191
steps:
9292
- uses: maxim-lobanov/setup-xcode@v1
9393
with:
94-
xcode-version: '13.2.1'
94+
xcode-version: latest-stable
9595
- uses: actions/checkout@v2
9696
- uses: actions/cache@v2
9797
with:
@@ -110,7 +110,7 @@ jobs:
110110
steps:
111111
- uses: maxim-lobanov/[email protected]
112112
with:
113-
xcode-version: '14.0.0'
113+
xcode-version: latest-stable
114114
- uses: actions/checkout@v2
115115
- uses: actions/cache@v2
116116
with:
@@ -149,7 +149,7 @@ jobs:
149149
steps:
150150
- uses: maxim-lobanov/setup-xcode@v1
151151
with:
152-
xcode-version: '13.2.1'
152+
xcode-version: latest-stable
153153
- uses: actions/checkout@v2
154154
- uses: actions/cache@v2
155155
with:

Sources/Segment/Analytics.swift

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ extension Analytics {
203203
public func manuallyEnableDestination(plugin: DestinationPlugin) {
204204
self.store.dispatch(action: System.AddDestinationToSettingsAction(key: plugin.key))
205205
}
206-
207206
}
208207

209208
extension Analytics {
@@ -259,3 +258,60 @@ extension Analytics {
259258
}
260259
}
261260
}
261+
262+
extension Analytics {
263+
/**
264+
Call openURL as needed or when instructed to by either UIApplicationDelegate or UISceneDelegate.
265+
This is necessary to track URL referrers across events. This method will also iterate
266+
any plugins that are watching for openURL events.
267+
268+
Example:
269+
```
270+
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
271+
let myStruct = MyStruct(options)
272+
analytics?.openURL(url, options: options)
273+
return true
274+
}
275+
```
276+
*/
277+
public func openURL<T: Codable>(_ url: URL, options: T? = nil) {
278+
guard let jsonProperties = try? JSON(with: options) else { return }
279+
guard let dict = jsonProperties.dictionaryValue else { return }
280+
openURL(url, options: dict)
281+
}
282+
283+
/**
284+
Call openURL as needed or when instructed to by either UIApplicationDelegate or UISceneDelegate.
285+
This is necessary to track URL referrers across events. This method will also iterate
286+
any plugins that are watching for openURL events.
287+
288+
Example:
289+
```
290+
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
291+
analytics?.openURL(url, options: options)
292+
return true
293+
}
294+
```
295+
*/
296+
public func openURL(_ url: URL, options: [String: Any] = [:]) {
297+
store.dispatch(action: UserInfo.SetReferrerAction(url: url))
298+
299+
// let any conforming plugins know
300+
apply { plugin in
301+
if let p = plugin as? OpeningURLs {
302+
p.openURL(url, options: options)
303+
}
304+
}
305+
306+
var jsonProperties: JSON? = nil
307+
if let json = try? JSON(options) {
308+
jsonProperties = json
309+
_ = try? jsonProperties?.add(value: url.absoluteString, forKey: "url")
310+
} else {
311+
if let json = try? JSON(["url": url.absoluteString]) {
312+
jsonProperties = json
313+
}
314+
}
315+
track(name: "Deep Link Opened", properties: jsonProperties)
316+
}
317+
}

Sources/Segment/Plugins/Context.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
import Foundation
99

10+
public protocol OpeningURLs {
11+
func openURL(_ url: URL, options: [String : Any])
12+
}
13+
14+
extension OpeningURLs {
15+
func openURL(_ url: URL, options: [String : Any]) {}
16+
}
17+
1018
public class Context: PlatformPlugin {
1119
public let type: PluginType = .before
1220
public weak var analytics: Analytics?
@@ -25,6 +33,10 @@ public class Context: PlatformPlugin {
2533
// add instanceId to the context
2634
context["instanceId"] = instanceId
2735

36+
if let userInfo: UserInfo = analytics?.store.currentState(), let referrer = userInfo.referrer {
37+
context["referrer"] = ["url": referrer.absoluteString]
38+
}
39+
2840
// if this event came in with context data already
2941
// let it take precedence over our values.
3042
if let eventContext = workingEvent.context?.dictionaryValue {

Sources/Segment/Plugins/Platforms/iOS/iOSDelegation.swift

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,21 @@ extension Analytics {
8080
p.continueUserActivity(activity)
8181
}
8282
}
83-
}
84-
}
85-
86-
// MARK: - Opening a URL
87-
88-
public protocol OpeningURLs {
89-
func openURL(_ url: URL, options: [UIApplication.OpenURLOptionsKey : Any])
90-
}
91-
92-
extension OpeningURLs {
93-
func openURL(_ url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) {}
94-
}
95-
96-
extension Analytics {
97-
public func openURL(_ url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) {
98-
apply { plugin in
99-
if let p = plugin as? OpeningURLs {
100-
p.openURL(url, options: options)
83+
84+
if activity.activityType == NSUserActivityTypeBrowsingWeb {
85+
if let url = activity.webpageURL {
86+
openURL(url, options: ["title": activity.title ?? ""])
10187
}
10288
}
10389
}
90+
91+
public func openURL(_ url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) {
92+
var converted: [String: Any] = [:]
93+
for (key, value) in options {
94+
converted[String(describing:key)] = value
95+
}
96+
openURL(url, options: converted)
97+
}
10498
}
10599

106100
#endif

Sources/Segment/State.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,26 +85,27 @@ struct UserInfo: Codable, State {
8585
let anonymousId: String
8686
let userId: String?
8787
let traits: JSON?
88+
let referrer: URL?
8889

8990
struct ResetAction: Action {
9091
func reduce(state: UserInfo) -> UserInfo {
91-
return UserInfo(anonymousId: UUID().uuidString, userId: nil, traits: nil)
92+
return UserInfo(anonymousId: UUID().uuidString, userId: nil, traits: nil, referrer: nil)
9293
}
9394
}
9495

9596
struct SetUserIdAction: Action {
9697
let userId: String
9798

9899
func reduce(state: UserInfo) -> UserInfo {
99-
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: state.traits)
100+
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: state.traits, referrer: state.referrer)
100101
}
101102
}
102103

103104
struct SetTraitsAction: Action {
104105
let traits: JSON?
105106

106107
func reduce(state: UserInfo) -> UserInfo {
107-
return UserInfo(anonymousId: state.anonymousId, userId: state.userId, traits: traits)
108+
return UserInfo(anonymousId: state.anonymousId, userId: state.userId, traits: traits, referrer: state.referrer)
108109
}
109110
}
110111

@@ -113,15 +114,23 @@ struct UserInfo: Codable, State {
113114
let traits: JSON?
114115

115116
func reduce(state: UserInfo) -> UserInfo {
116-
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: traits)
117+
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: traits, referrer: state.referrer)
117118
}
118119
}
119120

120121
struct SetAnonymousIdAction: Action {
121122
let anonymousId: String
122123

123124
func reduce(state: UserInfo) -> UserInfo {
124-
return UserInfo(anonymousId: anonymousId, userId: state.userId, traits: state.traits)
125+
return UserInfo(anonymousId: anonymousId, userId: state.userId, traits: state.traits, referrer: state.referrer)
126+
}
127+
}
128+
129+
struct SetReferrerAction: Action {
130+
let url: URL
131+
132+
func reduce(state: UserInfo) -> UserInfo {
133+
return UserInfo(anonymousId: state.anonymousId, userId: state.userId, traits: state.traits, referrer: url)
125134
}
126135
}
127136
}
@@ -150,6 +159,6 @@ extension UserInfo {
150159
if let existingId: String = storage.read(.anonymousId) {
151160
anonymousId = existingId
152161
}
153-
return UserInfo(anonymousId: anonymousId, userId: userId, traits: traits)
162+
return UserInfo(anonymousId: anonymousId, userId: userId, traits: traits, referrer: nil)
154163
}
155164
}

Tests/Segment-Tests/Analytics_Tests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ final class Analytics_Tests: XCTestCase {
125125

126126
waitUntilStarted(analytics: analytics)
127127

128+
// add a referrer
129+
analytics.openURL(URL(string: "https://google.com")!)
130+
128131
analytics.track(name: "token check")
129132

130133
let trackEvent: TrackEvent? = outputReader.lastEvent as? TrackEvent
@@ -138,6 +141,9 @@ final class Analytics_Tests: XCTestCase {
138141
XCTAssertNotNil(context?["timezone"], "timezone missing!")
139142
XCTAssertNotNil(context?["library"], "library missing!")
140143
XCTAssertNotNil(context?["device"], "device missing!")
144+
145+
let referrer = context?["referrer"] as! [String: Any]
146+
XCTAssertEqual(referrer["url"] as! String, "https://google.com")
141147

142148
// this key not present on watchOS (doesn't have webkit)
143149
#if !os(watchOS)

Tests/Segment-Tests/JSON_Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class JSONTests: XCTestCase {
3838

3939
func testJSONBasic() throws {
4040
let traits = try? JSON(["email": "[email protected]"])
41-
let userInfo = UserInfo(anonymousId: "1234", userId: "brandon", traits: traits)
41+
let userInfo = UserInfo(anonymousId: "1234", userId: "brandon", traits: traits, referrer: nil)
4242

4343
let encoder = JSONEncoder()
4444
encoder.outputFormatting = .prettyPrinted

test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blah

0 commit comments

Comments
 (0)