@@ -9,11 +9,18 @@ import Foundation
9
9
import UIKit
10
10
11
11
class Reporter : NSObject {
12
+
13
+ static let shared : Reporter = Reporter ( )
14
+
12
15
var session : URLSession ?
13
- var pendingUploadEvent : UploadEvent ?
16
+
17
+ var pendingEvents : [ ObjectIdentifier : Codable ] = [ : ]
14
18
15
19
var jsonEncoder : JSONEncoder
16
20
21
+ var sessionID : String = UUID ( ) . uuidString
22
+ var url : URL
23
+
17
24
// TODO: Set these using dependency Injection
18
25
var locale : Locale {
19
26
Locale . current
@@ -33,83 +40,244 @@ class Reporter: NSObject {
33
40
let jsonEncoder = JSONEncoder ( )
34
41
jsonEncoder. keyEncodingStrategy = JSONEncoder . KeyEncodingStrategy. convertToSnakeCase
35
42
jsonEncoder. outputFormatting = . sortedKeys
43
+ jsonEncoder. dateEncodingStrategy = . iso8601
36
44
self . jsonEncoder = jsonEncoder
37
45
46
+ // TODO: throwable initializer after NSObject super
47
+ // is removed
48
+ self . url = URL (
49
+ string: " https://mobile.muxanalytics.com "
50
+ ) !
51
+
38
52
super. init ( )
39
53
40
54
let sessionConfig : URLSessionConfiguration = URLSessionConfiguration . default
41
55
session = URLSession ( configuration: sessionConfig, delegate: self , delegateQueue: nil )
42
56
}
43
57
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 ,
49
94
uploadURL: URL
50
95
) -> 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,
60
104
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,
62
143
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
64
156
)
65
157
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
+ )
79
167
}
80
168
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
+ )
83
208
}
84
209
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
+ )
98
253
}
99
254
}
100
255
101
256
// TODO: Implement as a separate object so the URLSession
102
257
// can become non-optional, which removes a bunch of edge cases
103
258
extension Reporter : URLSessionDelegate , URLSessionTaskDelegate {
104
259
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
112
264
}
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 )
113
275
}
114
276
}
277
+
278
+ func urlSession( _ session: URLSession , task: URLSessionTask , didCompleteWithError error: Error ? ) {
279
+ pendingEvents [
280
+ ObjectIdentifier ( task)
281
+ ] = nil
282
+ }
115
283
}
0 commit comments