react-native-smartlinks 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +33 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksActivityLifecycle.kt +34 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksBridgeHandler.kt +172 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksModule.kt +299 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksPackage.kt +16 -0
- package/ios/Linklytics/DeviceInfo.swift +114 -0
- package/ios/Linklytics/Linklytics.swift +581 -0
- package/ios/Linklytics/LinklyticsInterface.swift +10 -0
- package/ios/Linklytics/Managers/BatchEventManager.swift +218 -0
- package/ios/Linklytics/Managers/OfflineCache.swift +35 -0
- package/ios/Linklytics/Managers/SQLiteStorage.swift +225 -0
- package/ios/Linklytics/Models/AppResponse.swift +16 -0
- package/ios/Linklytics/Models/DeepLinkRoute.swift +24 -0
- package/ios/Linklytics/Models/DeferredLinkResponse.swift +81 -0
- package/ios/Linklytics/Models/DynamicLinkResponse.swift +13 -0
- package/ios/Linklytics/Models/EventResponse.swift +14 -0
- package/ios/Linklytics/Models/OfflineEvent.swift +80 -0
- package/ios/Linklytics/NetworkManager.swift +360 -0
- package/ios/Linklytics/SdkConstants.swift +29 -0
- package/ios/PrivacyInfo.xcprivacy +14 -0
- package/ios/SmartLinksModule.m +150 -0
- package/ios/SmartLinksModule.swift +281 -0
- package/package.json +41 -0
- package/react-native-smartlinks.podspec +27 -0
- package/react-native.config.js +12 -0
- package/src/index.ts +84 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public struct AnyCodable: Codable {
|
|
4
|
+
public let value: Any
|
|
5
|
+
|
|
6
|
+
public init(_ value: Any) {
|
|
7
|
+
self.value = value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public init(from decoder: Decoder) throws {
|
|
11
|
+
let container = try decoder.singleValueContainer()
|
|
12
|
+
if let x = try? container.decode(String.self) {
|
|
13
|
+
value = x
|
|
14
|
+
} else if let x = try? container.decode(Int.self) {
|
|
15
|
+
value = x
|
|
16
|
+
} else if let x = try? container.decode(Double.self) {
|
|
17
|
+
value = x
|
|
18
|
+
} else if let x = try? container.decode(Bool.self) {
|
|
19
|
+
value = x
|
|
20
|
+
} else if let x = try? container.decode([String: AnyCodable].self) {
|
|
21
|
+
value = x.mapValues { $0.value }
|
|
22
|
+
} else if let x = try? container.decode([AnyCodable].self) {
|
|
23
|
+
value = x.map { $0.value }
|
|
24
|
+
} else {
|
|
25
|
+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public func encode(to encoder: Encoder) throws {
|
|
30
|
+
var container = encoder.singleValueContainer()
|
|
31
|
+
if let x = value as? String {
|
|
32
|
+
try container.encode(x)
|
|
33
|
+
} else if let x = value as? Int {
|
|
34
|
+
try container.encode(x)
|
|
35
|
+
} else if let x = value as? Double {
|
|
36
|
+
try container.encode(x)
|
|
37
|
+
} else if let x = value as? Bool {
|
|
38
|
+
try container.encode(x)
|
|
39
|
+
} else if let x = value as? [String: Any] {
|
|
40
|
+
try container.encode(x.mapValues { AnyCodable($0) })
|
|
41
|
+
} else if let x = value as? [Any] {
|
|
42
|
+
try container.encode(x.map { AnyCodable($0) })
|
|
43
|
+
} else {
|
|
44
|
+
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded"))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public struct OfflineEvent: Codable {
|
|
50
|
+
let id: String
|
|
51
|
+
let eventName: String
|
|
52
|
+
let parameters: [String: AnyCodable]
|
|
53
|
+
let deviceInfo: [String: AnyCodable]
|
|
54
|
+
let timestamp: Int64
|
|
55
|
+
var retryCount: Int
|
|
56
|
+
let appUserId: String?
|
|
57
|
+
let isDebug: Bool
|
|
58
|
+
|
|
59
|
+
init(eventName: String, parameters: [String: Any], deviceInfo: [String: Any] = [:], timestamp: Int64 = Int64(Date().timeIntervalSince1970 * 1000), appUserId: String? = nil, isDebug: Bool = false) {
|
|
60
|
+
self.id = UUID().uuidString
|
|
61
|
+
self.eventName = eventName
|
|
62
|
+
self.parameters = parameters.mapValues { AnyCodable($0) }
|
|
63
|
+
self.deviceInfo = deviceInfo.mapValues { AnyCodable($0) }
|
|
64
|
+
self.timestamp = timestamp
|
|
65
|
+
self.retryCount = 0
|
|
66
|
+
self.appUserId = appUserId
|
|
67
|
+
self.isDebug = isDebug
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
init(id: String, eventName: String, parameters: [String: AnyCodable], deviceInfo: [String: AnyCodable], timestamp: Int64, retryCount: Int, appUserId: String?, isDebug: Bool) {
|
|
71
|
+
self.id = id
|
|
72
|
+
self.eventName = eventName
|
|
73
|
+
self.parameters = parameters
|
|
74
|
+
self.deviceInfo = deviceInfo
|
|
75
|
+
self.timestamp = timestamp
|
|
76
|
+
self.retryCount = retryCount
|
|
77
|
+
self.appUserId = appUserId
|
|
78
|
+
self.isDebug = isDebug
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class NetworkManager {
|
|
4
|
+
|
|
5
|
+
// MARK: - Properties
|
|
6
|
+
private let urlSession: URLSession
|
|
7
|
+
private let apiKey: String
|
|
8
|
+
private let isDebugMode: Bool
|
|
9
|
+
|
|
10
|
+
// MARK: - Initialization
|
|
11
|
+
init(apiKey: String, debugMode: Bool = false) {
|
|
12
|
+
self.apiKey = apiKey
|
|
13
|
+
self.isDebugMode = debugMode
|
|
14
|
+
|
|
15
|
+
let config = URLSessionConfiguration.default
|
|
16
|
+
config.timeoutIntervalForRequest = 30
|
|
17
|
+
config.timeoutIntervalForResource = 60
|
|
18
|
+
self.urlSession = URLSession(configuration: config)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// MARK: - Public Methods
|
|
22
|
+
|
|
23
|
+
// MARK: - Public Methods
|
|
24
|
+
|
|
25
|
+
func sendEvent(event: OfflineEvent, completion: @escaping (Result<Data?, Error>) -> Void) {
|
|
26
|
+
let endpoint = SdkConstants.getEventEndpoint(version: Linklytics.apiVersionString)
|
|
27
|
+
|
|
28
|
+
guard let url = URL(string: endpoint) else {
|
|
29
|
+
completion(.failure(NetworkError.invalidURL))
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Prepare body with metadata
|
|
34
|
+
let requestBody: [String: Any] = [
|
|
35
|
+
"event_name": event.eventName,
|
|
36
|
+
"params": event.parameters.mapValues { $0.value },
|
|
37
|
+
"device_info": event.deviceInfo.mapValues { $0.value },
|
|
38
|
+
"event_id": event.id,
|
|
39
|
+
"timestamp": event.timestamp,
|
|
40
|
+
"debug": event.isDebug
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
sendRequest(url: url, body: requestBody, completion: completion)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func sendEvent(eventName: String, parameters: [String: Any], completion: @escaping (Result<Data?, Error>) -> Void) {
|
|
47
|
+
// Legacy/Direct support helper -> routes to new logic if needed, or just wraps creation
|
|
48
|
+
// But for consistency we should use the new sendEvent(event:) internally if possible,
|
|
49
|
+
// OR just duplicate logic. Since BatchEventManager creates OfflineEvents,
|
|
50
|
+
// let's update this to create a temporary OfflineEvent for consistency?
|
|
51
|
+
// Actually, just keep direct implementation but add ID/Timestamp
|
|
52
|
+
|
|
53
|
+
let endpoint = SdkConstants.getEventEndpoint(version: Linklytics.apiVersionString)
|
|
54
|
+
guard let url = URL(string: endpoint) else {
|
|
55
|
+
completion(.failure(NetworkError.invalidURL))
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let requestBody: [String: Any] = [
|
|
60
|
+
"event_name": eventName,
|
|
61
|
+
"params": parameters,
|
|
62
|
+
"event_id": UUID().uuidString,
|
|
63
|
+
"timestamp": Int64(Date().timeIntervalSince1970 * 1000)
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
sendRequest(url: url, body: requestBody, completion: completion)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func sendBatchEvents(events: [OfflineEvent], appUserId: String? = nil, debug: Bool? = nil, completion: @escaping (Result<Data?, Error>) -> Void) {
|
|
70
|
+
let endpoint = SdkConstants.getBatchEventsEndpoint(version: Linklytics.apiVersionString)
|
|
71
|
+
|
|
72
|
+
guard let url = URL(string: endpoint) else {
|
|
73
|
+
completion(.failure(NetworkError.invalidURL))
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let eventsList = events.map { event -> [String: Any] in
|
|
78
|
+
return [
|
|
79
|
+
"event_name": event.eventName,
|
|
80
|
+
"params": event.parameters.mapValues { $0.value },
|
|
81
|
+
"device_info": event.deviceInfo.mapValues { $0.value },
|
|
82
|
+
"event_id": event.id,
|
|
83
|
+
"timestamp": event.timestamp,
|
|
84
|
+
"debug": event.isDebug
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var requestBody: [String: Any] = [
|
|
89
|
+
"events": eventsList
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
if let appUserId = appUserId {
|
|
93
|
+
requestBody["app_user_id"] = appUserId
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if let debug = debug {
|
|
97
|
+
requestBody["debug"] = debug
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
sendRequest(url: url, body: requestBody, completion: completion)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func sendRequest(url: URL, body: [String: Any], completion: @escaping (Result<Data?, Error>) -> Void) {
|
|
104
|
+
guard let jsonData = try? JSONSerialization.data(withJSONObject: body) else {
|
|
105
|
+
completion(.failure(NetworkError.serializationError))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var request = URLRequest(url: url)
|
|
110
|
+
request.httpMethod = "POST"
|
|
111
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
112
|
+
request.setValue(apiKey, forHTTPHeaderField: "api-key")
|
|
113
|
+
request.httpBody = jsonData
|
|
114
|
+
|
|
115
|
+
if isDebugMode {
|
|
116
|
+
print("Linklytics: Sending HTTP request to \(url.absoluteString)")
|
|
117
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
118
|
+
print("Linklytics: Request body: \(jsonString)")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
urlSession.dataTask(with: request) { [weak self] data, response, error in
|
|
123
|
+
if let error = error {
|
|
124
|
+
if self?.isDebugMode == true {
|
|
125
|
+
print("Linklytics: Request failed: \(error)")
|
|
126
|
+
}
|
|
127
|
+
completion(.failure(error))
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
132
|
+
if self?.isDebugMode == true {
|
|
133
|
+
print("Linklytics: Response code: \(httpResponse.statusCode)")
|
|
134
|
+
}
|
|
135
|
+
// Check for 200..299
|
|
136
|
+
if !(200...299).contains(httpResponse.statusCode) {
|
|
137
|
+
// Create error based on status
|
|
138
|
+
// For now just continue, but logically this might be a server error we should retry?
|
|
139
|
+
// BatchEventManager handles retry on failure result.
|
|
140
|
+
// If we return success here for 500, we won't retry.
|
|
141
|
+
// So we should return failure for non-200.
|
|
142
|
+
completion(.failure(NSError(domain: "Linklytics", code: httpResponse.statusCode, userInfo: nil)))
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
completion(.success(data))
|
|
148
|
+
}.resume()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Keep existing fetchDeferredLink and others...
|
|
152
|
+
|
|
153
|
+
func fetchDeferredLink(fingerprint: String, appUserId: String, deviceInfo: [String: String], completion: @escaping (Result<Data?, Error>) -> Void) {
|
|
154
|
+
let endpoint = SdkConstants.getDeferredLinksEndpoint(version: Linklytics.apiVersionString)
|
|
155
|
+
|
|
156
|
+
guard let url = URL(string: endpoint) else {
|
|
157
|
+
completion(.failure(NetworkError.invalidURL))
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let requestBody: [String: Any] = [
|
|
162
|
+
"fingerprint": fingerprint,
|
|
163
|
+
"app_user_id": appUserId,
|
|
164
|
+
"platform": "ios",
|
|
165
|
+
"device_info": deviceInfo
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) else {
|
|
169
|
+
completion(.failure(NetworkError.serializationError))
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var request = URLRequest(url: url)
|
|
174
|
+
request.httpMethod = "POST"
|
|
175
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
176
|
+
request.setValue(apiKey, forHTTPHeaderField: "api-key")
|
|
177
|
+
request.httpBody = jsonData
|
|
178
|
+
|
|
179
|
+
if isDebugMode {
|
|
180
|
+
print("Linklytics: Fetching deferred deep link from \(endpoint)")
|
|
181
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
182
|
+
print("Linklytics: Request body: \(jsonString)")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
urlSession.dataTask(with: request) { [weak self] data, response, error in
|
|
187
|
+
if let error = error {
|
|
188
|
+
completion(.failure(error))
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
guard let data = data else {
|
|
193
|
+
completion(.failure(NetworkError.noData))
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
198
|
+
if self?.isDebugMode == true {
|
|
199
|
+
print("Linklytics: Deferred link response code: \(httpResponse.statusCode)")
|
|
200
|
+
if let bodyString = String(data: data, encoding: .utf8) {
|
|
201
|
+
print("Linklytics: Deferred link response body: \(bodyString)")
|
|
202
|
+
} else {
|
|
203
|
+
print("Linklytics: Deferred link response body: <non-UTF8 data, size=\(data.count) bytes>")
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if httpResponse.statusCode == 404 || httpResponse.statusCode == 204 {
|
|
207
|
+
completion(.failure(NetworkError.noData))
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
completion(.success(data))
|
|
213
|
+
}.resume()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func generateDynamicLink(url: String, parameters: [String: String]?, completion: @escaping (Result<DynamicLinkResponse, Error>) -> Void) {
|
|
217
|
+
let endpoint = SdkConstants.getDynamicLinksEndpoint(version: Linklytics.apiVersionString)
|
|
218
|
+
|
|
219
|
+
guard let requestURL = URL(string: endpoint) else {
|
|
220
|
+
completion(.failure(NetworkError.invalidURL))
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let requestBody: [String: Any] = [
|
|
225
|
+
"route": url,
|
|
226
|
+
"params": parameters ?? [:],
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) else {
|
|
230
|
+
completion(.failure(NetworkError.serializationError))
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var request = URLRequest(url: requestURL)
|
|
235
|
+
request.httpMethod = "POST"
|
|
236
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
237
|
+
request.setValue(apiKey, forHTTPHeaderField: "api-key")
|
|
238
|
+
request.httpBody = jsonData
|
|
239
|
+
|
|
240
|
+
if isDebugMode {
|
|
241
|
+
print("Linklytics: Generating dynamic link at \(endpoint)")
|
|
242
|
+
print("Linklytics: Input parameters: \(parameters ?? [:])")
|
|
243
|
+
print("Linklytics: Request body object: \(requestBody)")
|
|
244
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
245
|
+
print("Linklytics: Request body JSON: \(jsonString)")
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
urlSession.dataTask(with: request) { [weak self] data, response, error in
|
|
250
|
+
if let error = error {
|
|
251
|
+
if self?.isDebugMode == true {
|
|
252
|
+
print("Linklytics: Failed to generate dynamic link, error: \(error)")
|
|
253
|
+
}
|
|
254
|
+
completion(.failure(error))
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
guard let data = data else {
|
|
259
|
+
completion(.failure(NetworkError.noData))
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
264
|
+
if self?.isDebugMode == true {
|
|
265
|
+
print("Linklytics: Dynamic link response code: \(httpResponse.statusCode)")
|
|
266
|
+
if let bodyString = String(data: data, encoding: .utf8) {
|
|
267
|
+
print("Linklytics: Dynamic link response body: \(bodyString)")
|
|
268
|
+
} else {
|
|
269
|
+
print("Linklytics: Dynamic link response body: <non-UTF8 data, size=\(data.count) bytes>")
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
do {
|
|
275
|
+
let dynamicLinkResponse = try JSONDecoder().decode(DynamicLinkResponse.self, from: data)
|
|
276
|
+
completion(.success(dynamicLinkResponse))
|
|
277
|
+
} catch {
|
|
278
|
+
if self?.isDebugMode == true {
|
|
279
|
+
print("Linklytics: Failed to decode dynamic link response: \(error)")
|
|
280
|
+
}
|
|
281
|
+
completion(.failure(error))
|
|
282
|
+
}
|
|
283
|
+
}.resume()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
func getApp(completion: @escaping (Result<AppResponse, Error>) -> Void) {
|
|
287
|
+
let endpoint = SdkConstants.getAppEndpoint(version: Linklytics.apiVersionString)
|
|
288
|
+
|
|
289
|
+
guard let url = URL(string: endpoint) else {
|
|
290
|
+
completion(.failure(NetworkError.invalidURL))
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
var request = URLRequest(url: url)
|
|
295
|
+
request.httpMethod = "GET"
|
|
296
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
297
|
+
request.setValue(apiKey, forHTTPHeaderField: "api-key")
|
|
298
|
+
|
|
299
|
+
if isDebugMode {
|
|
300
|
+
print("Linklytics: Getting app info from \(endpoint)")
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
urlSession.dataTask(with: request) { [weak self] data, response, error in
|
|
304
|
+
if let error = error {
|
|
305
|
+
if self?.isDebugMode == true {
|
|
306
|
+
print("Linklytics: Failed to get app info, error: \(error)")
|
|
307
|
+
}
|
|
308
|
+
completion(.failure(error))
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
guard let data = data else {
|
|
313
|
+
completion(.failure(NetworkError.noData))
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
318
|
+
if self?.isDebugMode == true {
|
|
319
|
+
print("Linklytics: App info response code: \(httpResponse.statusCode)")
|
|
320
|
+
if let bodyString = String(data: data, encoding: .utf8) {
|
|
321
|
+
print("Linklytics: App info response body: \(bodyString)")
|
|
322
|
+
} else {
|
|
323
|
+
print("Linklytics: App info response body: <non-UTF8 data, size=\(data.count) bytes>")
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
do {
|
|
329
|
+
let appResponse = try JSONDecoder().decode(AppResponse.self, from: data)
|
|
330
|
+
completion(.success(appResponse))
|
|
331
|
+
} catch {
|
|
332
|
+
if self?.isDebugMode == true {
|
|
333
|
+
print("Linklytics: Failed to decode app response: \(error)")
|
|
334
|
+
}
|
|
335
|
+
completion(.failure(error))
|
|
336
|
+
}
|
|
337
|
+
}.resume()
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// MARK: - Network Errors
|
|
342
|
+
enum NetworkError: Error {
|
|
343
|
+
case invalidURL
|
|
344
|
+
case serializationError
|
|
345
|
+
case noData
|
|
346
|
+
case notImplemented
|
|
347
|
+
|
|
348
|
+
var localizedDescription: String {
|
|
349
|
+
switch self {
|
|
350
|
+
case .invalidURL:
|
|
351
|
+
return "Invalid URL"
|
|
352
|
+
case .serializationError:
|
|
353
|
+
return "Failed to serialize request data"
|
|
354
|
+
case .noData:
|
|
355
|
+
return "No data received"
|
|
356
|
+
case .notImplemented:
|
|
357
|
+
return "Feature not implemented"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct SdkConstants {
|
|
4
|
+
static let baseURL = "https://smartlinks.live"
|
|
5
|
+
private static let eventsEndpointVersioned = "/api/version/events"
|
|
6
|
+
private static let deferredLinksEndpoint = "/api/version/deferred-links"
|
|
7
|
+
private static let dynamicLinksEndpoint = "/api/version/links/create"
|
|
8
|
+
private static let appEndpoint = "/api/version/app"
|
|
9
|
+
|
|
10
|
+
static func getEventEndpoint(version: String) -> String {
|
|
11
|
+
return baseURL + eventsEndpointVersioned.replacingOccurrences(of: "version", with: version)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static func getDeferredLinksEndpoint(version: String) -> String {
|
|
15
|
+
return baseURL + deferredLinksEndpoint.replacingOccurrences(of: "version", with: version)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static func getDynamicLinksEndpoint(version: String) -> String {
|
|
19
|
+
return baseURL + dynamicLinksEndpoint.replacingOccurrences(of: "version", with: version)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static func getAppEndpoint(version: String) -> String {
|
|
23
|
+
return baseURL + appEndpoint.replacingOccurrences(of: "version", with: version)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static func getBatchEventsEndpoint(version: String) -> String {
|
|
27
|
+
return baseURL + eventsEndpointVersioned.replacingOccurrences(of: "version", with: version) + "/batch"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
6
|
+
<array/>
|
|
7
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
8
|
+
<array/>
|
|
9
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
10
|
+
<array/>
|
|
11
|
+
<key>NSPrivacyTracking</key>
|
|
12
|
+
<false/>
|
|
13
|
+
</dict>
|
|
14
|
+
</plist>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_REMAP_MODULE(SmartLinks, SmartLinksModule, RCTEventEmitter)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
|
|
7
|
+
debugMode:(BOOL)debugMode
|
|
8
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
9
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
10
|
+
|
|
11
|
+
RCT_EXTERN_METHOD(getAppUserId:(RCTPromiseResolveBlock)resolve
|
|
12
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
|
|
14
|
+
RCT_EXTERN_METHOD(sendEvent:(NSString *)eventName
|
|
15
|
+
params:(NSDictionary *)params
|
|
16
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
17
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
18
|
+
|
|
19
|
+
RCT_EXTERN_METHOD(generateLink:(NSString *)route
|
|
20
|
+
params:(NSDictionary *)params
|
|
21
|
+
shortLink:(BOOL)shortLink
|
|
22
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
24
|
+
|
|
25
|
+
RCT_EXTERN_METHOD(registerLinkListener:(RCTPromiseResolveBlock)resolve
|
|
26
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
27
|
+
|
|
28
|
+
RCT_EXTERN_METHOD(fetchApps:(RCTPromiseResolveBlock)resolve
|
|
29
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
30
|
+
|
|
31
|
+
RCT_EXTERN_METHOD(submitFeedback:(NSDictionary *)args
|
|
32
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
33
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
34
|
+
|
|
35
|
+
RCT_EXTERN_METHOD(fetchAds:(NSString *)type
|
|
36
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
37
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
38
|
+
|
|
39
|
+
RCT_EXTERN_METHOD(trackAdImpression:(nonnull NSNumber *)adId
|
|
40
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
41
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
42
|
+
|
|
43
|
+
RCT_EXTERN_METHOD(trackAdClick:(nonnull NSNumber *)adId
|
|
44
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
45
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
46
|
+
|
|
47
|
+
RCT_EXTERN_METHOD(processAi:(NSString *)instructionsJson
|
|
48
|
+
files:(NSDictionary *)files
|
|
49
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
50
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
51
|
+
|
|
52
|
+
RCT_EXTERN_METHOD(validatePromoCode:(NSString *)code
|
|
53
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
54
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
55
|
+
|
|
56
|
+
RCT_EXTERN_METHOD(redeemPromoCode:(NSString *)code
|
|
57
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
58
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
59
|
+
|
|
60
|
+
RCT_EXTERN_METHOD(verifyGooglePurchase:(NSString *)productId
|
|
61
|
+
token:(NSString *)token
|
|
62
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
63
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
64
|
+
|
|
65
|
+
RCT_EXTERN_METHOD(verifyApplePurchase:(NSString *)receiptData
|
|
66
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
67
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
68
|
+
|
|
69
|
+
RCT_EXTERN_METHOD(getCommunities:(RCTPromiseResolveBlock)resolve
|
|
70
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
71
|
+
|
|
72
|
+
RCT_EXTERN_METHOD(getPosts:(nonnull NSNumber *)communityId
|
|
73
|
+
authToken:(NSString *)authToken
|
|
74
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
75
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
76
|
+
|
|
77
|
+
RCT_EXTERN_METHOD(socialAuthGoogle:(NSString *)accessToken
|
|
78
|
+
project:(NSString *)project
|
|
79
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
80
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
81
|
+
|
|
82
|
+
RCT_EXTERN_METHOD(getBlogs:(nonnull NSNumber *)page
|
|
83
|
+
search:(NSString *)search
|
|
84
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
85
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
86
|
+
|
|
87
|
+
RCT_EXTERN_METHOD(getBlogPost:(NSString *)slug
|
|
88
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
89
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
90
|
+
|
|
91
|
+
RCT_EXTERN_METHOD(getCommunity:(RCTPromiseResolveBlock)resolve
|
|
92
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
93
|
+
|
|
94
|
+
RCT_EXTERN_METHOD(togglePostLike:(nonnull NSNumber *)postId
|
|
95
|
+
authToken:(NSString *)authToken
|
|
96
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
97
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
98
|
+
|
|
99
|
+
RCT_EXTERN_METHOD(createPost:(NSString *)communityId
|
|
100
|
+
title:(NSString *)title
|
|
101
|
+
content:(NSString *)content
|
|
102
|
+
authToken:(NSString *)authToken
|
|
103
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
104
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
105
|
+
|
|
106
|
+
RCT_EXTERN_METHOD(socialAuthApple:(NSString *)accessToken
|
|
107
|
+
project:(NSString *)project
|
|
108
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
109
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
110
|
+
|
|
111
|
+
RCT_EXTERN_METHOD(getComments:(NSString *)postId
|
|
112
|
+
authToken:(NSString *)authToken
|
|
113
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
114
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
115
|
+
|
|
116
|
+
RCT_EXTERN_METHOD(createComment:(NSString *)postId
|
|
117
|
+
content:(NSString *)content
|
|
118
|
+
authToken:(NSString *)authToken
|
|
119
|
+
parentId:(NSString *)parentId
|
|
120
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
121
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
122
|
+
|
|
123
|
+
RCT_EXTERN_METHOD(toggleCommentLike:(NSString *)commentId
|
|
124
|
+
authToken:(NSString *)authToken
|
|
125
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
126
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
127
|
+
|
|
128
|
+
RCT_EXTERN_METHOD(fetchActiveNotificationAd:(NSNumber *)appId
|
|
129
|
+
language:(NSString *)language
|
|
130
|
+
isDebug:(BOOL)isDebug
|
|
131
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
132
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
133
|
+
|
|
134
|
+
RCT_EXTERN_METHOD(registerDevice:(NSString *)fcmToken
|
|
135
|
+
platform:(NSString *)platform
|
|
136
|
+
deviceType:(NSString *)deviceType
|
|
137
|
+
appVersion:(NSString *)appVersion
|
|
138
|
+
language:(NSString *)language
|
|
139
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
140
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
141
|
+
|
|
142
|
+
RCT_EXTERN_METHOD(trackNotificationAdImpression:(nonnull NSNumber *)adId
|
|
143
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
144
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
145
|
+
|
|
146
|
+
RCT_EXTERN_METHOD(trackNotificationAdClick:(nonnull NSNumber *)adId
|
|
147
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
148
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
149
|
+
|
|
150
|
+
@end
|