Dynamic BaseURL Management, Language Configuration Support, complete HTTP header management
Showing
3 changed files
with
219 additions
and
39 deletions
... | @@ -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 | ||
213 | + | ||
214 | + // Add comprehensive headers based on original Objective-C implementation | ||
215 | + addWarplyHeaders(to: &request, endpoint: endpoint) | ||
171 | 216 | ||
172 | - // Add headers | 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 | ... | ... |
-
Please register or login to post a comment