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 | ||
| 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 | ... | ... |
-
Please register or login to post a comment