-
Notifications
You must be signed in to change notification settings - Fork 10
/
KlaviyoAPI.swift
142 lines (116 loc) · 4.32 KB
/
KlaviyoAPI.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//
// KlaviyoAPI.swift
//
//
// Created by Noah Durell on 11/8/22.
//
import AnyCodable
import Foundation
@_spi(KlaviyoPrivate)
public func setKlaviyoAPIURL(url: String) {
environment.analytics.apiURL = url
}
struct KlaviyoAPI {
struct KlaviyoRequest: Equatable, Codable {
public let apiKey: String
public let endpoint: KlaviyoEndpoint
public var uuid = environment.analytics.uuid().uuidString
}
enum KlaviyoAPIError: Error {
case httpError(Int, Data)
case rateLimitError
case missingOrInvalidResponse(URLResponse?)
case networkError(Error)
case internalError(String)
case internalRequestError(Error)
case unknownError(Error)
case dataEncodingError(KlaviyoRequest)
case invalidData
}
// For internal testing use only
static var requestStarted: (KlaviyoRequest) -> Void = { _ in }
static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in }
static var requestFailed: (KlaviyoRequest, Error, Double) -> Void = { _, _, _ in }
static var requestRateLimited: (KlaviyoRequest) -> Void = { _ in }
static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in }
var send: (KlaviyoRequest) async -> Result<Data, KlaviyoAPIError> = { request in
let start = Date()
var urlRequest: URLRequest
do {
urlRequest = try request.urlRequest()
} catch {
requestFailed(request, error, 0.0)
return .failure(.internalRequestError(error))
}
requestStarted(request)
var response: URLResponse
var data: Data
do {
(data, response) = try await environment.analytics.networkSession().data(urlRequest)
} catch {
requestFailed(request, error, 0.0)
return .failure(KlaviyoAPIError.networkError(error))
}
let end = Date()
let duration = end.timeIntervalSince(start)
guard let httpResponse = response as? HTTPURLResponse else {
return .failure(.missingOrInvalidResponse(response))
}
if httpResponse.statusCode == 429 {
requestRateLimited(request)
return .failure(KlaviyoAPIError.rateLimitError)
}
guard 200..<300 ~= httpResponse.statusCode else {
requestHttpError(request, httpResponse.statusCode, duration)
return .failure(KlaviyoAPIError.httpError(httpResponse.statusCode, data))
}
requestCompleted(request, data, duration)
return .success(data)
}
}
extension KlaviyoAPI.KlaviyoRequest {
func urlRequest() throws -> URLRequest {
guard let url = url else {
throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.analytics.apiURL)")
}
var request = URLRequest(url: url)
// We only support post right now
guard let body = try? encodeBody() else {
throw KlaviyoAPI.KlaviyoAPIError.dataEncodingError(self)
}
request.httpBody = body
request.httpMethod = "POST"
return request
}
var url: URL? {
switch endpoint {
case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken:
return URL(string: "\(environment.analytics.apiURL)/\(path)/?company_id=\(apiKey)")
}
}
var path: String {
switch endpoint {
case .createProfile:
return "client/profiles"
case .createEvent:
return "client/events"
case .registerPushToken:
return "client/push-tokens"
case .unregisterPushToken:
return "client/push-token-unregister"
}
}
func encodeBody() throws -> Data {
switch endpoint {
case let .createProfile(payload):
return try environment.analytics.encodeJSON(AnyEncodable(payload))
case var .createEvent(payload):
payload.appendMetadataToProperties()
return try environment.analytics.encodeJSON(AnyEncodable(payload))
case let .registerPushToken(payload):
return try environment.analytics.encodeJSON(AnyEncodable(payload))
case let .unregisterPushToken(payload):
return try environment.analytics.encodeJSON(AnyEncodable(payload))
}
}
}