Manos Chorianopoulos

Dynamic BaseURL Management, Language Configuration Support, complete HTTP header management

...@@ -129,7 +129,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -129,7 +129,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
129 WarplySDK.shared.configure( 129 WarplySDK.shared.configure(
130 appUuid: "YOUR_APP_UUID", // Replace with your App Uuid 130 appUuid: "YOUR_APP_UUID", // Replace with your App Uuid
131 merchantId: "YOUR_MERCHANT_ID", // Replace with your Merchant ID 131 merchantId: "YOUR_MERCHANT_ID", // Replace with your Merchant ID
132 - environment: .production // Use .development for testing 132 + environment: .production, // Use .development for testing
133 + language: "el" // Language: "en" or "el" (default: "el")
133 ) 134 )
134 135
135 return true 136 return true
...@@ -213,7 +214,7 @@ class ViewController: UIViewController { ...@@ -213,7 +214,7 @@ class ViewController: UIViewController {
213 214
214 func testWarplySDK() { 215 func testWarplySDK() {
215 // Simple completion handler approach 216 // Simple completion handler approach
216 - WarplySDK.shared.getCampaigns(language: "en") { campaigns in 217 + WarplySDK.shared.getCampaigns(language: "el") { campaigns in
217 DispatchQueue.main.async { 218 DispatchQueue.main.async {
218 if let campaigns = campaigns { 219 if let campaigns = campaigns {
219 print("🎉 Success! Retrieved \(campaigns.count) campaigns") 220 print("🎉 Success! Retrieved \(campaigns.count) campaigns")
...@@ -234,7 +235,7 @@ For modern Swift development: ...@@ -234,7 +235,7 @@ For modern Swift development:
234 func testWarplySDKAsync() { 235 func testWarplySDKAsync() {
235 Task { 236 Task {
236 do { 237 do {
237 - let campaigns = try await WarplySDK.shared.getCampaigns(language: "en") 238 + let campaigns = try await WarplySDK.shared.getCampaigns(language: "el")
238 print("🎉 Success! Retrieved \(campaigns.count) campaigns") 239 print("🎉 Success! Retrieved \(campaigns.count) campaigns")
239 240
240 // Update UI on main thread 241 // Update UI on main thread
...@@ -688,11 +689,32 @@ WarplySDK.shared.configure( ...@@ -688,11 +689,32 @@ WarplySDK.shared.configure(
688 ) 689 )
689 ``` 690 ```
690 691
692 +### Language Configuration
693 +
694 +```swift
695 +// Configure language during SDK setup (recommended)
696 +WarplySDK.shared.configure(
697 + appUuid: "your-app-uuid",
698 + merchantId: "your-merchant-id",
699 + environment: .production,
700 + language: "el" // "en" for English, "el" for Greek (default)
701 +)
702 +
703 +// Change language at runtime
704 +WarplySDK.shared.applicationLocale = "el" // or "el"
705 +
706 +// Language affects:
707 +// - API responses and content localization
708 +// - Campaign and coupon descriptions
709 +// - Error messages and UI text
710 +// - Date and number formatting
711 +```
712 +
691 ### SDK Properties 713 ### SDK Properties
692 714
693 ```swift 715 ```swift
694 // Language setting (default: "el") 716 // Language setting (default: "el")
695 -WarplySDK.shared.applicationLocale = "en" // or "el" 717 +WarplySDK.shared.applicationLocale = "el" // or "en"
696 718
697 // Dark mode support 719 // Dark mode support
698 WarplySDK.shared.isDarkModeEnabled = true 720 WarplySDK.shared.isDarkModeEnabled = true
...@@ -775,7 +797,7 @@ WarplySDK.shared.updateRefreshToken( ...@@ -775,7 +797,7 @@ WarplySDK.shared.updateRefreshToken(
775 797
776 ```swift 798 ```swift
777 // Basic campaign retrieval 799 // Basic campaign retrieval
778 -WarplySDK.shared.getCampaigns(language: "en") { campaigns in 800 +WarplySDK.shared.getCampaigns(language: "el") { campaigns in
779 guard let campaigns = campaigns else { return } 801 guard let campaigns = campaigns else { return }
780 802
781 for campaign in campaigns { 803 for campaign in campaigns {
...@@ -794,7 +816,7 @@ let filters: [String: Any] = [ ...@@ -794,7 +816,7 @@ let filters: [String: Any] = [
794 ] 816 ]
795 ] 817 ]
796 818
797 -WarplySDK.shared.getCampaigns(language: "en", filters: filters) { campaigns in 819 +WarplySDK.shared.getCampaigns(language: "el", filters: filters) { campaigns in
798 // Handle filtered campaigns 820 // Handle filtered campaigns
799 } 821 }
800 ``` 822 ```
...@@ -820,7 +842,7 @@ Task { ...@@ -820,7 +842,7 @@ Task {
820 ### Get Supermarket Campaign 842 ### Get Supermarket Campaign
821 843
822 ```swift 844 ```swift
823 -WarplySDK.shared.getSupermarketCampaign(language: "en") { campaign in 845 +WarplySDK.shared.getSupermarketCampaign(language: "el") { campaign in
824 if let campaign = campaign { 846 if let campaign = campaign {
825 print("Supermarket campaign available: \(campaign.title ?? "")") 847 print("Supermarket campaign available: \(campaign.title ?? "")")
826 // Show supermarket offers 848 // Show supermarket offers
...@@ -869,7 +891,7 @@ let carouselCampaigns = WarplySDK.shared.getCarouselList() ...@@ -869,7 +891,7 @@ let carouselCampaigns = WarplySDK.shared.getCarouselList()
869 891
870 ```swift 892 ```swift
871 // Completion handler approach 893 // Completion handler approach
872 -WarplySDK.shared.getCoupons(language: "en", completion: { coupons in 894 +WarplySDK.shared.getCoupons(language: "el", completion: { coupons in
873 guard let coupons = coupons else { return } 895 guard let coupons = coupons else { return }
874 896
875 for coupon in coupons { 897 for coupon in coupons {
...@@ -885,7 +907,7 @@ WarplySDK.shared.getCoupons(language: "en", completion: { coupons in ...@@ -885,7 +907,7 @@ WarplySDK.shared.getCoupons(language: "en", completion: { coupons in
885 // Async/await approach 907 // Async/await approach
886 Task { 908 Task {
887 do { 909 do {
888 - let coupons = try await WarplySDK.shared.getCoupons(language: "en") 910 + let coupons = try await WarplySDK.shared.getCoupons(language: "el")
889 911
890 // Filter active coupons 912 // Filter active coupons
891 let activeCoupons = coupons.filter { $0.status == 1 } 913 let activeCoupons = coupons.filter { $0.status == 1 }
...@@ -984,7 +1006,7 @@ Task { ...@@ -984,7 +1006,7 @@ Task {
984 ```swift 1006 ```swift
985 Task { 1007 Task {
986 do { 1008 do {
987 - let history = try await WarplySDK.shared.getRedeemedSMHistory(language: "en") 1009 + let history = try await WarplySDK.shared.getRedeemedSMHistory(language: "el")
988 1010
989 print("Total redeemed value: \(history._totalRedeemedValue)") 1011 print("Total redeemed value: \(history._totalRedeemedValue)")
990 print("Redeemed coupons: \(history._redeemedCouponList.count)") 1012 print("Redeemed coupons: \(history._redeemedCouponList.count)")
...@@ -1153,7 +1175,7 @@ public enum WarplyError: Error { ...@@ -1153,7 +1175,7 @@ public enum WarplyError: Error {
1153 // Async/await error handling 1175 // Async/await error handling
1154 Task { 1176 Task {
1155 do { 1177 do {
1156 - let campaigns = try await WarplySDK.shared.getCampaigns(language: "en") 1178 + let campaigns = try await WarplySDK.shared.getCampaigns(language: "el")
1157 // Success 1179 // Success
1158 } catch let error as WarplyError { 1180 } catch let error as WarplyError {
1159 switch error { 1181 switch error {
...@@ -1174,7 +1196,7 @@ Task { ...@@ -1174,7 +1196,7 @@ Task {
1174 } 1196 }
1175 1197
1176 // Completion handler error handling 1198 // Completion handler error handling
1177 -WarplySDK.shared.getCoupons(language: "en", completion: { coupons in 1199 +WarplySDK.shared.getCoupons(language: "el", completion: { coupons in
1178 if let coupons = coupons { 1200 if let coupons = coupons {
1179 // Success 1201 // Success
1180 } else { 1202 } else {
...@@ -1240,7 +1262,7 @@ let subscription = WarplySDK.shared.subscribe(CampaignsRetrievedEvent.self) { ev ...@@ -1240,7 +1262,7 @@ let subscription = WarplySDK.shared.subscribe(CampaignsRetrievedEvent.self) { ev
1240 1262
1241 **Before:** 1263 **Before:**
1242 ```swift 1264 ```swift
1243 -WarplySDK.shared.getCampaigns(language: "en") { campaigns in 1265 +WarplySDK.shared.getCampaigns(language: "el") { campaigns in
1244 if let campaigns = campaigns { 1266 if let campaigns = campaigns {
1245 // Handle success 1267 // Handle success
1246 } else { 1268 } else {
...@@ -1253,7 +1275,7 @@ WarplySDK.shared.getCampaigns(language: "en") { campaigns in ...@@ -1253,7 +1275,7 @@ WarplySDK.shared.getCampaigns(language: "en") { campaigns in
1253 ```swift 1275 ```swift
1254 Task { 1276 Task {
1255 do { 1277 do {
1256 - let campaigns = try await WarplySDK.shared.getCampaigns(language: "en") 1278 + let campaigns = try await WarplySDK.shared.getCampaigns(language: "el")
1257 // Handle success 1279 // Handle success
1258 } catch { 1280 } catch {
1259 // Handle error with proper error types 1281 // Handle error with proper error types
...@@ -1296,7 +1318,7 @@ override func viewDidLoad() { ...@@ -1296,7 +1318,7 @@ override func viewDidLoad() {
1296 // ✅ Good: Comprehensive error handling 1318 // ✅ Good: Comprehensive error handling
1297 Task { 1319 Task {
1298 do { 1320 do {
1299 - let campaigns = try await WarplySDK.shared.getCampaigns(language: "en") 1321 + let campaigns = try await WarplySDK.shared.getCampaigns(language: "el")
1300 updateUI(with: campaigns) 1322 updateUI(with: campaigns)
1301 } catch let error as WarplyError { 1323 } catch let error as WarplyError {
1302 handleWarplyError(error) 1324 handleWarplyError(error)
...@@ -1307,7 +1329,7 @@ Task { ...@@ -1307,7 +1329,7 @@ Task {
1307 1329
1308 // ❌ Bad: Ignoring errors 1330 // ❌ Bad: Ignoring errors
1309 Task { 1331 Task {
1310 - let campaigns = try? await WarplySDK.shared.getCampaigns(language: "en") 1332 + let campaigns = try? await WarplySDK.shared.getCampaigns(language: "el")
1311 // Silently fails 1333 // Silently fails
1312 } 1334 }
1313 ``` 1335 ```
...@@ -1347,7 +1369,7 @@ class BadViewController: UIViewController { ...@@ -1347,7 +1369,7 @@ class BadViewController: UIViewController {
1347 ```swift 1369 ```swift
1348 // ✅ Good: Main thread UI updates 1370 // ✅ Good: Main thread UI updates
1349 Task { 1371 Task {
1350 - let campaigns = try await WarplySDK.shared.getCampaigns(language: "en") 1372 + let campaigns = try await WarplySDK.shared.getCampaigns(language: "el")
1351 1373
1352 await MainActor.run { 1374 await MainActor.run {
1353 self.updateCampaignsUI(campaigns) 1375 self.updateCampaignsUI(campaigns)
...@@ -1355,7 +1377,7 @@ Task { ...@@ -1355,7 +1377,7 @@ Task {
1355 } 1377 }
1356 1378
1357 // ✅ Also good: Using completion handlers 1379 // ✅ Also good: Using completion handlers
1358 -WarplySDK.shared.getCampaigns(language: "en") { campaigns in 1380 +WarplySDK.shared.getCampaigns(language: "el") { campaigns in
1359 DispatchQueue.main.async { 1381 DispatchQueue.main.async {
1360 self.updateCampaignsUI(campaigns) 1382 self.updateCampaignsUI(campaigns)
1361 } 1383 }
...@@ -1376,7 +1398,7 @@ func loadCampaigns() { ...@@ -1376,7 +1398,7 @@ func loadCampaigns() {
1376 // Then, fetch fresh data 1398 // Then, fetch fresh data
1377 Task { 1399 Task {
1378 do { 1400 do {
1379 - let freshCampaigns = try await WarplySDK.shared.getCampaigns(language: "en") 1401 + let freshCampaigns = try await WarplySDK.shared.getCampaigns(language: "el")
1380 updateUI(with: freshCampaigns) 1402 updateUI(with: freshCampaigns)
1381 } catch { 1403 } catch {
1382 // Handle error, keep cached data 1404 // Handle error, keep cached data
...@@ -1427,7 +1449,7 @@ if networkStatus != 1 { ...@@ -1427,7 +1449,7 @@ if networkStatus != 1 {
1427 } 1449 }
1428 1450
1429 // Check language parameter 1451 // Check language parameter
1430 -WarplySDK.shared.getCampaigns(language: "en") // Try different language 1452 +WarplySDK.shared.getCampaigns(language: "el") // Try different language
1431 ``` 1453 ```
1432 1454
1433 #### 3. Events Not Firing 1455 #### 3. Events Not Firing
...@@ -1457,7 +1479,7 @@ SwiftEventBus.onMainThread(self, name: "campaigns_retrieved") { result in ...@@ -1457,7 +1479,7 @@ SwiftEventBus.onMainThread(self, name: "campaigns_retrieved") { result in
1457 **Solutions:** 1479 **Solutions:**
1458 ```swift 1480 ```swift
1459 // Use weak references in closures 1481 // Use weak references in closures
1460 -WarplySDK.shared.getCampaigns(language: "en") { [weak self] campaigns in 1482 +WarplySDK.shared.getCampaigns(language: "el") { [weak self] campaigns in
1461 self?.updateUI(campaigns) 1483 self?.updateUI(campaigns)
1462 } 1484 }
1463 1485
...@@ -1486,8 +1508,8 @@ print("Network Status: \(WarplySDK.shared.getNetworkStatus())") ...@@ -1486,8 +1508,8 @@ print("Network Status: \(WarplySDK.shared.getNetworkStatus())")
1486 ```swift 1508 ```swift
1487 // Batch API calls when possible 1509 // Batch API calls when possible
1488 Task { 1510 Task {
1489 - async let campaigns = WarplySDK.shared.getCampaigns(language: "en") 1511 + async let campaigns = WarplySDK.shared.getCampaigns(language: "el")
1490 - async let coupons = WarplySDK.shared.getCoupons(language: "en") 1512 + async let coupons = WarplySDK.shared.getCoupons(language: "el")
1491 async let marketPass = WarplySDK.shared.getMarketPassDetails() 1513 async let marketPass = WarplySDK.shared.getMarketPassDetails()
1492 1514
1493 do { 1515 do {
...@@ -1548,4 +1570,32 @@ Start with the Quick Start guide and gradually adopt the advanced features as ne ...@@ -1548,4 +1570,32 @@ Start with the Quick Start guide and gradually adopt the advanced features as ne
1548 1570
1549 --- 1571 ---
1550 1572
1573 +## 📋 **Changelog**
1574 +
1575 +### **Version 2.2.10** - *June 23, 2025*
1576 +
1577 +#### **🆕 New Features**
1578 +- **Language Configuration Support**: Added configurable language parameter to SDK initialization
1579 + - New `language` parameter in `configure()` method (defaults to "el")
1580 + - Runtime language switching via `applicationLocale` property
1581 + - Automatic configuration sync when language changes
1582 +
1583 +#### **🔧 Network Improvements**
1584 +- **Comprehensive Header System**: Implemented complete HTTP header management based on original Objective-C implementation
1585 + - Core loyalty headers: `loyalty-web-id`, `loyalty-date`, `loyalty-signature`
1586 + - Device identification headers: `unique-device-id`, `vendor`, `platform`, `os_version`
1587 + - App identification headers: `loyalty-bundle-id`, `manufacturer`, `ios_device_model`
1588 + - Authentication headers with proper Bearer token handling
1589 + - Special endpoint headers for registration, and logout flows
1590 +- **Dynamic BaseURL Management**: Enhanced baseURL handling for improved configuration flexibility
1591 + - Dynamic baseURL reading from Configuration on every request
1592 + - Environment-aware URL switching (development/production)
1593 + - Real-time configuration updates without restart
1594 + - Fallback safety mechanisms with default stage URL
1595 +- **SHA256 Signature Generation**: Added secure signature generation for API authentication
1596 +- **Device Info Utilities**: Enhanced device information collection for headers
1597 +- **Platform-Specific Headers**: iOS-specific headers for better backend compatibility
1598 +
1599 +---
1600 +
1551 **Happy Coding! 🚀** 1601 **Happy Coding! 🚀**
......
...@@ -135,14 +135,16 @@ public final class WarplySDK { ...@@ -135,14 +135,16 @@ public final class WarplySDK {
135 // MARK: - Configuration 135 // MARK: - Configuration
136 136
137 /// Configure the SDK with app uuid and merchant ID 137 /// Configure the SDK with app uuid and merchant ID
138 - public func configure(appUuid: String, merchantId: String, environment: Configuration.Environment = .production) { 138 + public func configure(appUuid: String, merchantId: String, environment: Configuration.Environment = .production, language: String = "el") {
139 Configuration.baseURL = environment.baseURL 139 Configuration.baseURL = environment.baseURL
140 Configuration.host = environment.host 140 Configuration.host = environment.host
141 Configuration.errorDomain = environment.host 141 Configuration.errorDomain = environment.host
142 Configuration.merchantId = merchantId 142 Configuration.merchantId = merchantId
143 + Configuration.language = language
143 144
144 storage.appUuid = appUuid 145 storage.appUuid = appUuid
145 storage.merchantId = merchantId 146 storage.merchantId = merchantId
147 + storage.applicationLocale = language
146 } 148 }
147 149
148 /// Set environment (development/production) 150 /// Set environment (development/production)
...@@ -196,7 +198,6 @@ public final class WarplySDK { ...@@ -196,7 +198,6 @@ public final class WarplySDK {
196 set { 198 set {
197 let tempLang = (newValue == "EN" || newValue == "en") ? "en" : "el" 199 let tempLang = (newValue == "EN" || newValue == "en") ? "en" : "el"
198 storage.applicationLocale = tempLang 200 storage.applicationLocale = tempLang
199 - // Language setting now handled by pure Swift configuration
200 Configuration.language = tempLang 201 Configuration.language = tempLang
201 } 202 }
202 } 203 }
......
...@@ -8,6 +8,46 @@ ...@@ -8,6 +8,46 @@
8 8
9 import Foundation 9 import Foundation
10 import Network 10 import Network
11 +import UIKit
12 +import CommonCrypto
13 +
14 +// MARK: - String Extensions for SHA256
15 +
16 +extension String {
17 + func sha256() -> String {
18 + let data = Data(self.utf8)
19 + var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
20 +
21 + data.withUnsafeBytes { bytes in
22 + _ = CC_SHA256(bytes.bindMemory(to: UInt8.self).baseAddress, CC_LONG(data.count), &hash)
23 + }
24 +
25 + return hash.map { String(format: "%02x", $0) }.joined()
26 + }
27 +}
28 +
29 +// MARK: - Device Info Utilities
30 +
31 +extension UIDevice {
32 + var modelName: String {
33 + var systemInfo = utsname()
34 + uname(&systemInfo)
35 + let machineMirror = Mirror(reflecting: systemInfo.machine)
36 + let identifier = machineMirror.children.reduce("") { identifier, element in
37 + guard let value = element.value as? Int8, value != 0 else { return identifier }
38 + return identifier + String(UnicodeScalar(UInt8(value))!)
39 + }
40 + return identifier
41 + }
42 +
43 + var bundleIdentifier: String {
44 + return Bundle.main.bundleIdentifier ?? ""
45 + }
46 +
47 + var appVersion: String {
48 + return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
49 + }
50 +}
11 51
12 // MARK: - Network Service Protocol 52 // MARK: - Network Service Protocol
13 53
...@@ -25,7 +65,10 @@ public final class NetworkService: NetworkServiceProtocol { ...@@ -25,7 +65,10 @@ public final class NetworkService: NetworkServiceProtocol {
25 // MARK: - Properties 65 // MARK: - Properties
26 66
27 private let session: URLSession 67 private let session: URLSession
28 - private let baseURL: String 68 + private var baseURL: String {
69 + // Dynamic baseURL that always reads from Configuration
70 + return Configuration.baseURL.isEmpty ? "https://engage-stage.warp.ly" : Configuration.baseURL
71 + }
29 private var accessToken: String? 72 private var accessToken: String?
30 private var refreshToken: String? 73 private var refreshToken: String?
31 private let networkMonitor: NWPathMonitor 74 private let networkMonitor: NWPathMonitor
...@@ -34,9 +77,7 @@ public final class NetworkService: NetworkServiceProtocol { ...@@ -34,9 +77,7 @@ public final class NetworkService: NetworkServiceProtocol {
34 77
35 // MARK: - Initialization 78 // MARK: - Initialization
36 79
37 - public init(baseURL: String = Configuration.baseURL) { 80 + public init() {
38 - self.baseURL = baseURL
39 -
40 // Configure URLSession 81 // Configure URLSession
41 let config = URLSessionConfiguration.default 82 let config = URLSessionConfiguration.default
42 config.timeoutIntervalForRequest = 30.0 83 config.timeoutIntervalForRequest = 30.0
...@@ -168,21 +209,16 @@ public final class NetworkService: NetworkServiceProtocol { ...@@ -168,21 +209,16 @@ public final class NetworkService: NetworkServiceProtocol {
168 209
169 var request = URLRequest(url: url) 210 var request = URLRequest(url: url)
170 request.httpMethod = endpoint.method.rawValue 211 request.httpMethod = endpoint.method.rawValue
212 + request.timeoutInterval = 30.0
171 213
172 - // Add headers 214 + // Add comprehensive headers based on original Objective-C implementation
215 + addWarplyHeaders(to: &request, endpoint: endpoint)
216 +
217 + // Add endpoint-specific headers
173 for (key, value) in endpoint.headers { 218 for (key, value) in endpoint.headers {
174 request.setValue(value, forHTTPHeaderField: key) 219 request.setValue(value, forHTTPHeaderField: key)
175 } 220 }
176 221
177 - // Add authentication if required
178 - if endpoint.requiresAuthentication, let accessToken = accessToken {
179 - request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
180 - }
181 -
182 - // Add app-specific headers
183 - request.setValue(Configuration.merchantId, forHTTPHeaderField: "X-Merchant-ID")
184 - request.setValue(Configuration.language, forHTTPHeaderField: "X-Language")
185 -
186 // Add parameters 222 // Add parameters
187 if let parameters = endpoint.parameters { 223 if let parameters = endpoint.parameters {
188 switch endpoint.method { 224 switch endpoint.method {
...@@ -210,6 +246,99 @@ public final class NetworkService: NetworkServiceProtocol { ...@@ -210,6 +246,99 @@ public final class NetworkService: NetworkServiceProtocol {
210 return request 246 return request
211 } 247 }
212 248
249 + /// Add comprehensive Warply headers based on original Objective-C implementation
250 + private func addWarplyHeaders(to request: inout URLRequest, endpoint: Endpoint) {
251 + // Core headers (always sent)
252 + let timestamp = Int(Date().timeIntervalSince1970)
253 +
254 + // Loyalty headers - core authentication
255 + request.setValue(Configuration.merchantId, forHTTPHeaderField: "loyalty-web-id")
256 + request.setValue("\(timestamp)", forHTTPHeaderField: "loyalty-date")
257 +
258 + // Generate loyalty signature (apiKey + timestamp SHA256)
259 + // TODO: Get apiKey from secure storage or configuration
260 + let apiKey = getApiKey()
261 + if !apiKey.isEmpty {
262 + let signatureString = "\(apiKey)\(timestamp)"
263 + let signature = signatureString.sha256()
264 + request.setValue(signature, forHTTPHeaderField: "loyalty-signature")
265 + }
266 +
267 + // Standard HTTP headers
268 + request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
269 + request.setValue("application/json", forHTTPHeaderField: "Accept")
270 + request.setValue("gzip", forHTTPHeaderField: "User-Agent")
271 +
272 + // App identification headers
273 + let bundleId = UIDevice.current.bundleIdentifier
274 + if !bundleId.isEmpty {
275 + request.setValue("ios:\(bundleId)", forHTTPHeaderField: "loyalty-bundle-id")
276 + }
277 +
278 + // Device identification
279 + if let deviceId = UIDevice.current.identifierForVendor?.uuidString {
280 + request.setValue(deviceId, forHTTPHeaderField: "unique-device-id")
281 + }
282 +
283 + // Platform headers
284 + request.setValue("apple", forHTTPHeaderField: "vendor")
285 + request.setValue("ios", forHTTPHeaderField: "platform")
286 + request.setValue(UIDevice.current.systemVersion, forHTTPHeaderField: "os_version")
287 + request.setValue("mobile", forHTTPHeaderField: "channel")
288 +
289 + // Device info headers (if trackers enabled)
290 + if UserDefaults.standard.bool(forKey: "trackersEnabled") {
291 + request.setValue("Apple", forHTTPHeaderField: "manufacturer")
292 + request.setValue(UIDevice.current.modelName, forHTTPHeaderField: "ios_device_model")
293 +
294 + let appVersion = UIDevice.current.appVersion
295 + if !appVersion.isEmpty {
296 + request.setValue(appVersion, forHTTPHeaderField: "app_version")
297 + }
298 + }
299 +
300 + // Authentication headers
301 + if endpoint.requiresAuthentication {
302 + if let accessToken = accessToken {
303 + request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
304 + }
305 + }
306 +
307 + // Special headers for specific endpoints
308 + addSpecialHeaders(to: &request, endpoint: endpoint)
309 + }
310 +
311 + /// Add special headers for specific endpoint types
312 + private func addSpecialHeaders(to request: inout URLRequest, endpoint: Endpoint) {
313 + // Handle Cosmote-specific endpoints
314 + if endpoint.path.contains("/partners/cosmote/") || endpoint.path.contains("/partners/oauth/") {
315 + // Basic auth for Cosmote endpoints (from original implementation)
316 + let basicAuth = "MVBQNFhCQzhFYTJBaUdCNkJWZGFGUERlTTNLQ3kzMjU6YzViMzAyZDY5N2FiNGY3NzhiNThhMTg0YzBkZWRmNGU="
317 + request.setValue("Basic \(basicAuth)", forHTTPHeaderField: "Authorization")
318 + }
319 +
320 + // Handle logout endpoints
321 + if endpoint.path.contains("/logout") {
322 + // Logout endpoints may need special token handling
323 + // The tokens are included in the request body, not headers
324 + }
325 +
326 + // Handle registration endpoints
327 + if endpoint.path.contains("/register") {
328 + // Registration endpoints don't need authentication headers
329 + request.setValue(nil, forHTTPHeaderField: "Authorization")
330 + }
331 + }
332 +
333 + /// Get API key from secure storage or configuration
334 + private func getApiKey() -> String {
335 + // TODO: Implement secure API key retrieval
336 + // This should come from keychain or secure configuration
337 + // For now, return empty string - this needs to be implemented
338 + // based on how the original Objective-C code stored the API key
339 + return ""
340 + }
341 +
213 private func validateResponse(_ response: URLResponse) throws { 342 private func validateResponse(_ response: URLResponse) throws {
214 guard let httpResponse = response as? HTTPURLResponse else { 343 guard let httpResponse = response as? HTTPURLResponse else {
215 throw NetworkError.invalidResponse 344 throw NetworkError.invalidResponse
......