Skip to content

Commit 15d1467

Browse files
committed
Example Network Blocker flush policy
1 parent 4c7aab4 commit 15d1467

File tree

6 files changed

+135
-2
lines changed

6 files changed

+135
-2
lines changed

Examples/apps/MacExample/MacExample.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
464B2D5C29F9DF3C00471ABF /* NetBlockerFlushPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464B2D5B29F9DF3C00471ABF /* NetBlockerFlushPolicy.swift */; };
1011
4663C738267A926B00ADDD1A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4663C737267A926B00ADDD1A /* AppDelegate.swift */; };
1112
4663C73A267A926B00ADDD1A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4663C739267A926B00ADDD1A /* ViewController.swift */; };
1213
4663C73C267A926C00ADDD1A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4663C73B267A926C00ADDD1A /* Assets.xcassets */; };
@@ -16,6 +17,7 @@
1617
/* End PBXBuildFile section */
1718

1819
/* Begin PBXFileReference section */
20+
464B2D5B29F9DF3C00471ABF /* NetBlockerFlushPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetBlockerFlushPolicy.swift; path = ../../../tasks/NetBlockerFlushPolicy.swift; sourceTree = "<group>"; };
1921
4663C734267A926B00ADDD1A /* MacExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MacExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
2022
4663C737267A926B00ADDD1A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
2123
4663C739267A926B00ADDD1A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -59,6 +61,7 @@
5961
4663C736267A926B00ADDD1A /* MacExample */ = {
6062
isa = PBXGroup;
6163
children = (
64+
464B2D5B29F9DF3C00471ABF /* NetBlockerFlushPolicy.swift */,
6265
4663C737267A926B00ADDD1A /* AppDelegate.swift */,
6366
4663C739267A926B00ADDD1A /* ViewController.swift */,
6467
4663C73B267A926C00ADDD1A /* Assets.xcassets */,
@@ -157,6 +160,7 @@
157160
buildActionMask = 2147483647;
158161
files = (
159162
4663C73A267A926B00ADDD1A /* ViewController.swift in Sources */,
163+
464B2D5C29F9DF3C00471ABF /* NetBlockerFlushPolicy.swift in Sources */,
160164
4663C738267A926B00ADDD1A /* AppDelegate.swift in Sources */,
161165
46E73DA926F53C570021042C /* NotificationTracking.swift in Sources */,
162166
);

Examples/apps/MacExample/MacExample/AppDelegate.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,26 @@ import Segment
1212
class AppDelegate: NSObject, NSApplicationDelegate {
1313

1414
var analytics: Analytics? = nil
15-
15+
let blockerFlushPolicy = NetBlockerFlushPolicy()
1616

1717
func applicationDidFinishLaunching(_ aNotification: Notification) {
1818
// Insert code here to initialize your application
1919

2020
let configuration = Configuration(writeKey: "<WRITE KEY>")
2121
.trackApplicationLifecycleEvents(true)
2222
.flushInterval(10)
23+
.flushAt(1)
24+
.errorHandler { error in
25+
NetBlockerFlushPolicy.networkBlockedHandler(error: error, blockerPolicy: self.blockerFlushPolicy)
26+
}
2327

2428
analytics = Analytics(configuration: configuration)
29+
analytics?.add(flushPolicy: blockerFlushPolicy)
30+
31+
Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { _ in
32+
self.analytics?.track(name: "test little snitch")
33+
})
34+
2535
//analytics?.add(plugin: NotificationTracking())
2636
}
2737

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// NetBlockerFlushPolicy.swift
3+
//
4+
//
5+
// Created by Brandon Sneed on 4/26/23.
6+
//
7+
8+
import Foundation
9+
10+
// NOTE: You can see this task in use in the MacExample application.
11+
12+
// MIT License
13+
//
14+
// Copyright (c) 2023 Segment
15+
//
16+
// Permission is hereby granted, free of charge, to any person obtaining a copy
17+
// of this software and associated documentation files (the "Software"), to deal
18+
// in the Software without restriction, including without limitation the rights
19+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20+
// copies of the Software, and to permit persons to whom the Software is
21+
// furnished to do so, subject to the following conditions:
22+
//
23+
// The above copyright notice and this permission notice shall be included in all
24+
// copies or substantial portions of the Software.
25+
//
26+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32+
// SOFTWARE.
33+
34+
import Foundation
35+
import Segment
36+
37+
public class NetBlockerFlushPolicy: FlushPolicy {
38+
public var type = PluginType.utility
39+
public weak var analytics: Segment.Analytics?
40+
41+
static func networkBlockedHandler(error: Error, blockerPolicy: NetBlockerFlushPolicy) {
42+
switch error {
43+
case AnalyticsError.networkUnknown(let error):
44+
if let e = error as? URLError {
45+
if e.code == URLError.networkConnectionLost {
46+
// Little Snitch might be running..
47+
// lets disable analytics for now.
48+
blockerPolicy.analytics?.enabled = false
49+
print("The network appears to be blocked. Disabling Analytics.")
50+
}
51+
}
52+
default:
53+
break
54+
}
55+
}
56+
57+
public func configure(analytics: Segment.Analytics) {
58+
// if we've already been configured, exit.
59+
guard self.analytics == nil else { return }
60+
61+
self.analytics = analytics
62+
// add our utility plugin portion of our policy ...
63+
// that way we can try to enable analytics when the app comes back to the foreground.
64+
self.analytics?.add(plugin: self)
65+
}
66+
67+
public func shouldFlush() -> Bool {
68+
// if we're enabled, then flush. if we don't know if we're enabled
69+
// it's probably because our analytics pointer became nil somehow, so
70+
// to prevent unexpected catastrophe, assume true.
71+
return self.analytics?.enabled ?? true
72+
}
73+
74+
public func updateState(event: Segment.RawEvent) {
75+
// do nothing
76+
}
77+
78+
public func reset() {
79+
// if we're told to reset.. lets try again.
80+
analytics?.enabled = true
81+
}
82+
}
83+
84+
extension NetBlockerFlushPolicy: UtilityPlugin {
85+
// we can be a utility plugin as well to get lifecycle events to act on
86+
// see `type` defined in the main class above.
87+
}
88+
89+
#if os(macOS)
90+
extension NetBlockerFlushPolicy: macOSLifecycle {
91+
public func applicationDidBecomeActive() {
92+
// try to turn back on analytics and see if it's allowed ... if net is still
93+
// blocked, analytics will be disabled again.
94+
analytics?.enabled = true
95+
print("Turning analytics back on ...")
96+
}
97+
}
98+
#endif
99+
100+
#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
101+
import UIKit
102+
extension NetBlockerFlushPolicy: iOSLifecycle {
103+
public func applicationDidBecomeActive(application: UIApplication?) {
104+
// try to turn back on analytics and see if it's allowed ... if net is still
105+
// blocked, analytics will be disabled again.
106+
analytics?.enabled = true
107+
print("Turning analytics back on ...")
108+
}
109+
}
110+
#endif

Sources/Segment/Analytics.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ extension Analytics {
168168
/// Tells this instance of Analytics to flush any queued events up to Segment.com. This command will also
169169
/// be sent to each plugin present in the system.
170170
public func flush() {
171+
// only flush if we're enabled.
172+
guard enabled == true else { return }
173+
171174
apply { plugin in
172175
if let p = plugin as? EventPlugin {
173176
p.flush()

Sources/Segment/Plugins/SegmentDestination.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ public class SegmentDestination: DestinationPlugin, Subscriber {
116116
guard let storage = self.storage else { return }
117117
guard let analytics = self.analytics else { return }
118118
guard let httpClient = self.httpClient else { return }
119+
120+
// don't flush if analytics is disabled.
121+
guard analytics.enabled == true else { return }
119122

120123
// Read events from file system
121124
guard let data = storage.read(Storage.Constants.events) else { return }

Sources/Segment/Utilities/HTTPClient.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ enum HTTPClientErrors: Error {
1414
case badSession
1515
case failedToOpenBatch
1616
case statusCode(code: Int)
17+
case unknown(error: Error)
1718
}
1819

1920
public class HTTPClient {
@@ -53,6 +54,7 @@ public class HTTPClient {
5354
@discardableResult
5455
func startBatchUpload(writeKey: String, batch: URL, completion: @escaping (_ result: Result<Bool, Error>) -> Void) -> URLSessionDataTask? {
5556
guard let uploadURL = segmentURL(for: apiHost, path: "/b") else {
57+
self.analytics?.reportInternalError(HTTPClientErrors.failedToOpenBatch)
5658
completion(.failure(HTTPClientErrors.failedToOpenBatch))
5759
return nil
5860
}
@@ -62,7 +64,8 @@ public class HTTPClient {
6264
let dataTask = session.uploadTask(with: urlRequest, fromFile: batch) { [weak self] (data, response, error) in
6365
if let error = error {
6466
self?.analytics?.log(message: "Error uploading request \(error.localizedDescription).")
65-
completion(.failure(error))
67+
self?.analytics?.reportInternalError(AnalyticsError.networkUnknown(error))
68+
completion(.failure(HTTPClientErrors.unknown(error: error)))
6669
} else if let httpResponse = response as? HTTPURLResponse {
6770
switch (httpResponse.statusCode) {
6871
case 1..<300:

0 commit comments

Comments
 (0)