Skip to content

Commit d168655

Browse files
committed
Move reporting to own directory
Add event defintions Rename method for upload success Add standardization success method Device and locale become properties Reporting method for standardization failure Add Hashable conformance Support dispatching multiple events at the same time Naive non-thread safe implementation Revert "Add Hashable conformance" This reverts commit a4c8b53. Rename upload event to upload succeeded Add upload failed event Adjust upload succeeded event Include missing parameters upload failed event Change event type casing, individual files for events Fix reporter test reporting property Actually report upload failure Remove file Read off file size Use date in events Finangle duration and start time Consistent spelling of reporter methods Centralize checking for optOut in only one place Set correct version in correct place session UUID request extension own file extension Use Date for input standardization span Route every request through one chokepoint Upload failed test Shared test encoder Input standardization failed test Input standardization succeeded test Better organize expected json strings Use a shared instance Remove dupe file format
1 parent ea5bb97 commit d168655

11 files changed

+591
-123
lines changed

Sources/MuxUploadSDK/InternalUtilities/ChunkedFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class ChunkedFile {
9696
let fileSize = try fileManager.fileSizeOfItem(
9797
atPath: fileURL.path
9898
)
99-
99+
100100
guard let data = data else {
101101
// Called while already at the end of the file. We read zero bytes, "ending" at the end of the file
102102
return FileChunk(startByte: fileSize, endByte: fileSize, totalFileSize: fileSize, chunkData: Data(capacity: 0))
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// InputStandardizationFailedEvent.swift
3+
//
4+
5+
import Foundation
6+
7+
struct InputStandardizationFailedEvent: Codable {
8+
var type: String = "upload_input_standardization_failed"
9+
var sessionID: String
10+
var version: String = "1"
11+
var data: Data
12+
13+
struct Data: Codable {
14+
var appName: String?
15+
var appVersion: String?
16+
var deviceModel: String
17+
var errorDescription: String
18+
var inputDuration: Double
19+
var inputSize: UInt64
20+
var maximumResolution: String
21+
var nonStandardInputReasons: [String]
22+
var platformName: String
23+
var platformVersion: String
24+
var regionCode: String?
25+
var sdkVersion: String
26+
var standardizationStartTime: Date
27+
var standardizationEndTime: Date
28+
var uploadCanceled: Bool
29+
var uploadURL: URL
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// InputStandardizationSucceededEvent.swift
3+
//
4+
5+
import Foundation
6+
7+
struct InputStandardizationSucceededEvent: Codable {
8+
var type: String = "upload_input_standardization_succeeded"
9+
var sessionID: String
10+
var version: String = "1"
11+
var data: Data
12+
13+
struct Data: Codable {
14+
var appName: String?
15+
var appVersion: String?
16+
var deviceModel: String
17+
var inputDuration: Double
18+
var inputSize: UInt64
19+
var maximumResolution: String
20+
var nonStandardInputReasons: [String]
21+
var platformName: String
22+
var platformVersion: String
23+
var regionCode: String?
24+
var sdkVersion: String
25+
var standardizationStartTime: Date
26+
var standardizationEndTime: Date
27+
var uploadURL: URL
28+
}
29+
}

Sources/MuxUploadSDK/InternalUtilities/Reporting/Reporter.swift

Lines changed: 220 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ import Foundation
99
import UIKit
1010

1111
class Reporter: NSObject {
12+
13+
static let shared: Reporter = Reporter()
14+
1215
var session: URLSession?
13-
var pendingUploadEvent: UploadEvent?
16+
17+
var pendingEvents: [ObjectIdentifier: Codable] = [:]
1418

1519
var jsonEncoder: JSONEncoder
1620

21+
var sessionID: String = UUID().uuidString
22+
var url: URL
23+
1724
// TODO: Set these using dependency Injection
1825
var locale: Locale {
1926
Locale.current
@@ -33,83 +40,244 @@ class Reporter: NSObject {
3340
let jsonEncoder = JSONEncoder()
3441
jsonEncoder.keyEncodingStrategy = JSONEncoder.KeyEncodingStrategy.convertToSnakeCase
3542
jsonEncoder.outputFormatting = .sortedKeys
43+
jsonEncoder.dateEncodingStrategy = .iso8601
3644
self.jsonEncoder = jsonEncoder
3745

46+
// TODO: throwable initializer after NSObject super
47+
// is removed
48+
self.url = URL(
49+
string: "https://mobile.muxanalytics.com"
50+
)!
51+
3852
super.init()
3953

4054
let sessionConfig: URLSessionConfiguration = URLSessionConfiguration.default
4155
session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
4256
}
4357

44-
func report(
45-
startTime: TimeInterval,
46-
endTime: TimeInterval,
47-
fileSize: UInt64,
48-
videoDuration: Double,
58+
func send<Event: Codable>(
59+
event: Event,
60+
url: URL
61+
) {
62+
guard let httpBody = try? jsonEncoder.encode(event) else {
63+
return
64+
}
65+
66+
let request = NSMutableURLRequest.makeJSONPost(
67+
url: url,
68+
httpBody: httpBody
69+
)
70+
71+
guard let dataTask = session?.dataTask(
72+
with: request as URLRequest
73+
) else {
74+
return
75+
}
76+
77+
let taskID = ObjectIdentifier(dataTask)
78+
79+
pendingEvents[
80+
taskID
81+
] = event
82+
83+
dataTask.resume()
84+
}
85+
}
86+
87+
extension Reporter {
88+
func reportUploadSuccess(
89+
inputDuration: Double,
90+
inputSize: UInt64,
91+
options: UploadOptions,
92+
uploadEndTime: Date,
93+
uploadStartTime: Date,
4994
uploadURL: URL
5095
) -> Void {
51-
self.pendingUploadEvent = UploadEvent(
52-
startTime: startTime,
53-
endTime: endTime,
54-
fileSize: fileSize,
55-
videoDuration: videoDuration,
56-
uploadURL: uploadURL,
57-
sdkVersion: Version.versionString,
58-
osName: device.systemName,
59-
osVersion: device.systemVersion,
96+
97+
guard !options.eventTracking.optedOut else {
98+
return
99+
}
100+
101+
let data = UploadSucceededEvent.Data(
102+
appName: Bundle.main.appName,
103+
appVersion: Bundle.main.appVersion,
60104
deviceModel: device.model,
61-
appName: Bundle.main.bundleIdentifier,
105+
inputDuration: inputDuration,
106+
inputSize: inputSize,
107+
inputStandardizationEnabled: options.inputStandardization.isEnabled,
108+
platformName: device.systemName,
109+
platformVersion: device.systemVersion,
110+
regionCode: regionCode,
111+
sdkVersion: Version.versionString,
112+
uploadStartTime: uploadStartTime,
113+
uploadEndTime: uploadEndTime,
114+
uploadURL: uploadURL
115+
)
116+
117+
let event = UploadSucceededEvent(
118+
sessionID: sessionID,
119+
data: data
120+
)
121+
122+
send(
123+
event: event,
124+
url: url
125+
)
126+
}
127+
128+
func reportUploadFailure(
129+
errorDescription: String,
130+
inputDuration: Double,
131+
inputSize: UInt64,
132+
options: UploadOptions,
133+
uploadEndTime: Date,
134+
uploadStartTime: Date,
135+
uploadURL: URL
136+
) {
137+
guard !options.eventTracking.optedOut else {
138+
return
139+
}
140+
141+
let data = UploadFailedEvent.Data(
142+
appName: Bundle.main.appName,
62143
appVersion: Bundle.main.appVersion,
63-
regionCode: regionCode
144+
deviceModel: device.model,
145+
errorDescription: errorDescription,
146+
inputDuration: inputDuration,
147+
inputSize: inputSize,
148+
inputStandardizationEnabled: options.inputStandardization.isEnabled,
149+
platformName: device.systemName,
150+
platformVersion: device.systemVersion,
151+
regionCode: regionCode,
152+
sdkVersion: Version.versionString,
153+
uploadStartTime: uploadStartTime,
154+
uploadEndTime: uploadEndTime,
155+
uploadURL: url
64156
)
65157

66-
// FIXME: If this fails, an event without a payload
67-
// is sent which probably isn't what we want
68-
do {
69-
let httpBody = try serializePendingEvent()
70-
let request = self.generateRequest(
71-
url: URL(string: "https://mobile.muxanalytics.com")!,
72-
httpBody: httpBody
73-
)
74-
let dataTask = session?.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
75-
self.pendingUploadEvent = nil
76-
})
77-
dataTask?.resume()
78-
} catch _ as NSError {}
158+
let event = UploadFailedEvent(
159+
sessionID: sessionID,
160+
data: data
161+
)
162+
163+
send(
164+
event: event,
165+
url: url
166+
)
79167
}
80168

81-
func serializePendingEvent() throws -> Data {
82-
return try jsonEncoder.encode(pendingUploadEvent)
169+
func reportUploadInputStandardizationSuccess(
170+
inputDuration: Double,
171+
inputSize: UInt64,
172+
options: UploadOptions,
173+
nonStandardInputReasons: [String],
174+
standardizationEndTime: Date,
175+
standardizationStartTime: Date,
176+
uploadURL: URL
177+
) {
178+
guard !options.eventTracking.optedOut else {
179+
return
180+
}
181+
182+
let data = InputStandardizationSucceededEvent.Data(
183+
appName: Bundle.main.appName,
184+
appVersion: Bundle.main.appVersion,
185+
deviceModel: device.model,
186+
inputDuration: inputDuration,
187+
inputSize: inputSize,
188+
maximumResolution: options.inputStandardization.maximumResolution.description,
189+
nonStandardInputReasons: nonStandardInputReasons,
190+
platformName: device.systemName,
191+
platformVersion: device.systemVersion,
192+
regionCode: regionCode,
193+
sdkVersion: Version.versionString,
194+
standardizationStartTime: standardizationStartTime,
195+
standardizationEndTime: standardizationEndTime,
196+
uploadURL: uploadURL
197+
)
198+
199+
let event = InputStandardizationSucceededEvent(
200+
sessionID: sessionID,
201+
data: data
202+
)
203+
204+
send(
205+
event: event,
206+
url: url
207+
)
83208
}
84209

85-
private func generateRequest(
86-
url: URL,
87-
httpBody: Data
88-
) -> URLRequest {
89-
let request = NSMutableURLRequest(url: url,
90-
cachePolicy: .useProtocolCachePolicy,
91-
timeoutInterval: 10.0)
92-
request.httpMethod = "POST"
93-
request.setValue("application/json", forHTTPHeaderField: "Accept")
94-
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
95-
request.httpBody = httpBody
96-
97-
return request as URLRequest
210+
func reportUploadInputStandardizationFailure(
211+
errorDescription: String,
212+
inputDuration: Double,
213+
inputSize: UInt64,
214+
nonStandardInputReasons: [String],
215+
options: UploadOptions,
216+
standardizationEndTime: Date,
217+
standardizationStartTime: Date,
218+
uploadCanceled: Bool,
219+
uploadURL: URL
220+
) {
221+
guard !options.eventTracking.optedOut else {
222+
return
223+
}
224+
225+
let data = InputStandardizationFailedEvent.Data(
226+
appName: Bundle.main.appName,
227+
appVersion: Bundle.main.appVersion,
228+
deviceModel: device.model,
229+
errorDescription: errorDescription,
230+
inputDuration: inputDuration,
231+
inputSize: inputSize,
232+
maximumResolution: options.inputStandardization.maximumResolution.description,
233+
nonStandardInputReasons: nonStandardInputReasons,
234+
platformName: device.systemName,
235+
platformVersion: device.systemVersion,
236+
regionCode: regionCode,
237+
sdkVersion: Version.versionString,
238+
standardizationStartTime: standardizationStartTime,
239+
standardizationEndTime: standardizationEndTime,
240+
uploadCanceled: uploadCanceled,
241+
uploadURL: uploadURL
242+
)
243+
244+
let event = InputStandardizationFailedEvent(
245+
sessionID: sessionID,
246+
data: data
247+
)
248+
249+
send(
250+
event: event,
251+
url: url
252+
)
98253
}
99254
}
100255

101256
// TODO: Implement as a separate object so the URLSession
102257
// can become non-optional, which removes a bunch of edge cases
103258
extension Reporter: URLSessionDelegate, URLSessionTaskDelegate {
104259
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Swift.Void) {
105-
if(self.pendingUploadEvent != nil) {
106-
if let redirectUrl = request.url, let httpBody = try? serializePendingEvent() {
107-
let request = self.generateRequest(
108-
url: redirectUrl,
109-
httpBody: httpBody
110-
)
111-
completionHandler(request)
260+
if let pendingEvent = pendingEvents[ObjectIdentifier(task)], let redirectURL = request.url {
261+
guard let httpBody = try? jsonEncoder.encode(pendingEvent) else {
262+
completionHandler(nil)
263+
return
112264
}
265+
266+
// TODO: This can be URLRequest instead of NSMutableURLRequest
267+
// test URLRequest-based construction in case
268+
// for any weirdness
269+
let request = NSMutableURLRequest.makeJSONPost(
270+
url: redirectURL,
271+
httpBody: httpBody
272+
)
273+
274+
completionHandler(request as URLRequest)
113275
}
114276
}
277+
278+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
279+
pendingEvents[
280+
ObjectIdentifier(task)
281+
] = nil
282+
}
115283
}

0 commit comments

Comments
 (0)