Showing
6 changed files
with
454 additions
and
4 deletions
| ... | @@ -731,7 +731,73 @@ let networkStatus = WarplySDK.shared.getNetworkStatus() | ... | @@ -731,7 +731,73 @@ let networkStatus = WarplySDK.shared.getNetworkStatus() |
| 731 | 731 | ||
| 732 | ## 🔐 Authentication | 732 | ## 🔐 Authentication |
| 733 | 733 | ||
| 734 | -### User Login | 734 | +### DEI Login (Email-based Authentication) 🆕 |
| 735 | + | ||
| 736 | +The DEI login method provides email-based authentication for DEI platform users. This method automatically handles JWT token extraction and secure storage. | ||
| 737 | + | ||
| 738 | +```swift | ||
| 739 | +// Completion handler approach | ||
| 740 | +WarplySDK.shared.deiLogin( | ||
| 741 | + email: "user@example.com", | ||
| 742 | + completion: { response in | ||
| 743 | + if let response = response, response.getStatus == 1 { | ||
| 744 | + print("DEI login successful") | ||
| 745 | + // User is now authenticated - proceed with authenticated operations | ||
| 746 | + // Tokens are automatically stored and managed by the SDK | ||
| 747 | + } else { | ||
| 748 | + print("DEI login failed") | ||
| 749 | + } | ||
| 750 | + }, | ||
| 751 | + failureCallback: { errorCode in | ||
| 752 | + print("DEI login failed with error: \(errorCode)") | ||
| 753 | + // Handle specific error codes | ||
| 754 | + switch errorCode { | ||
| 755 | + case 401: | ||
| 756 | + print("Invalid email or authentication failed") | ||
| 757 | + case -1009: | ||
| 758 | + print("No internet connection") | ||
| 759 | + default: | ||
| 760 | + print("Unknown error occurred") | ||
| 761 | + } | ||
| 762 | + } | ||
| 763 | +) | ||
| 764 | + | ||
| 765 | +// Async/await approach (Recommended) | ||
| 766 | +Task { | ||
| 767 | + do { | ||
| 768 | + let response = try await WarplySDK.shared.deiLogin(email: "user@example.com") | ||
| 769 | + if response.getStatus == 1 { | ||
| 770 | + print("DEI login successful") | ||
| 771 | + // User is now authenticated - proceed with authenticated operations | ||
| 772 | + // Access other authenticated endpoints like getCoupons, getProfile, etc. | ||
| 773 | + } else { | ||
| 774 | + print("DEI login failed") | ||
| 775 | + } | ||
| 776 | + } catch let error as WarplyError { | ||
| 777 | + switch error { | ||
| 778 | + case .authenticationFailed: | ||
| 779 | + print("Invalid email or authentication failed") | ||
| 780 | + case .networkError: | ||
| 781 | + print("Network error occurred") | ||
| 782 | + case .noInternetConnection: | ||
| 783 | + print("No internet connection") | ||
| 784 | + default: | ||
| 785 | + print("DEI login error: \(error.localizedDescription)") | ||
| 786 | + } | ||
| 787 | + } catch { | ||
| 788 | + print("Unexpected error: \(error)") | ||
| 789 | + } | ||
| 790 | +} | ||
| 791 | +``` | ||
| 792 | + | ||
| 793 | +**Key Features:** | ||
| 794 | +- ✅ **Email-based authentication** - No complex credentials required | ||
| 795 | +- ✅ **Automatic token management** - JWT tokens extracted and stored securely | ||
| 796 | +- ✅ **Future-ready design** - Handles refresh tokens (currently null, will be valid later) | ||
| 797 | +- ✅ **Comprehensive error handling** - Detailed error codes and messages | ||
| 798 | +- ✅ **Analytics integration** - Success/failure events tracked automatically | ||
| 799 | + | ||
| 800 | +### Traditional User Login | ||
| 735 | 801 | ||
| 736 | ```swift | 802 | ```swift |
| 737 | // Completion handler approach | 803 | // Completion handler approach |
| ... | @@ -757,6 +823,32 @@ Task { | ... | @@ -757,6 +823,32 @@ Task { |
| 757 | } | 823 | } |
| 758 | ``` | 824 | ``` |
| 759 | 825 | ||
| 826 | +### Cosmote User Authentication | ||
| 827 | + | ||
| 828 | +```swift | ||
| 829 | +// For Cosmote-specific authentication | ||
| 830 | +WarplySDK.shared.getCosmoteUser(guid: "cosmote-user-guid") { response in | ||
| 831 | + if let response = response, response.getStatus == 1 { | ||
| 832 | + print("Cosmote user authenticated") | ||
| 833 | + // Tokens automatically stored | ||
| 834 | + } else { | ||
| 835 | + print("Cosmote authentication failed") | ||
| 836 | + } | ||
| 837 | +} | ||
| 838 | + | ||
| 839 | +// Async/await variant | ||
| 840 | +Task { | ||
| 841 | + do { | ||
| 842 | + let response = try await WarplySDK.shared.getCosmoteUser(guid: "cosmote-user-guid") | ||
| 843 | + if response.getStatus == 1 { | ||
| 844 | + print("Cosmote user authenticated") | ||
| 845 | + } | ||
| 846 | + } catch { | ||
| 847 | + print("Cosmote authentication error: \(error)") | ||
| 848 | + } | ||
| 849 | +} | ||
| 850 | +``` | ||
| 851 | + | ||
| 760 | ### User Logout | 852 | ### User Logout |
| 761 | 853 | ||
| 762 | ```swift | 854 | ```swift | ... | ... |
DEI_LOGIN_TEST.md
0 → 100644
| 1 | +# DEI Login Implementation Test | ||
| 2 | + | ||
| 3 | +## Implementation Summary | ||
| 4 | + | ||
| 5 | +The DEI login functionality has been successfully implemented with the following components: | ||
| 6 | + | ||
| 7 | +### 1. Endpoint Configuration (Endpoints.swift) | ||
| 8 | +- ✅ Added `deiLogin(email: String)` case to Endpoint enum | ||
| 9 | +- ✅ Configured path: `/partners/dei/app_login` | ||
| 10 | +- ✅ Configured method: POST | ||
| 11 | +- ✅ Configured parameters: `["email": email]` | ||
| 12 | +- ✅ Configured authentication: `.standard` (no authorization header) | ||
| 13 | +- ✅ Set `requiresAuthentication: false` | ||
| 14 | + | ||
| 15 | +### 2. Network Service Method (NetworkService.swift) | ||
| 16 | +- ✅ Added `deiLogin(email: String)` method in NetworkService | ||
| 17 | +- ✅ Implemented comprehensive error handling | ||
| 18 | +- ✅ Added response validation (status == 1) | ||
| 19 | +- ✅ Added token extraction and validation | ||
| 20 | +- ✅ Added automatic TokenModel creation and database storage | ||
| 21 | +- ✅ Added detailed logging for debugging | ||
| 22 | + | ||
| 23 | +### 3. Public SDK Methods (WarplySDK.swift) | ||
| 24 | +- ✅ Added completion handler variant: `deiLogin(email:completion:failureCallback:)` | ||
| 25 | +- ✅ Added async/await variant: `deiLogin(email:) async throws` | ||
| 26 | +- ✅ Added comprehensive documentation with examples | ||
| 27 | +- ✅ Added analytics events for success/failure tracking | ||
| 28 | +- ✅ Added state management (clears CCMS campaigns) | ||
| 29 | +- ✅ Added proper error handling and conversion | ||
| 30 | + | ||
| 31 | +## Key Features Implemented | ||
| 32 | + | ||
| 33 | +### Authentication Flow | ||
| 34 | +1. **Email-based authentication** - No authorization header required | ||
| 35 | +2. **JWT token extraction** - Extracts access_token and refresh_token from nested response | ||
| 36 | +3. **Automatic token storage** - Stores tokens in database using TokenModel | ||
| 37 | +4. **Future-ready design** - Handles null refresh tokens (will be valid later) | ||
| 38 | + | ||
| 39 | +### Error Handling | ||
| 40 | +1. **Status validation** - Ensures response status == 1 | ||
| 41 | +2. **Token validation** - Ensures access_token exists and is not empty | ||
| 42 | +3. **Network error handling** - Comprehensive error mapping and logging | ||
| 43 | +4. **Analytics tracking** - Success/failure events for monitoring | ||
| 44 | + | ||
| 45 | +### Response Structure Support | ||
| 46 | +Expected response format: | ||
| 47 | +```json | ||
| 48 | +{ | ||
| 49 | + "status": 1, | ||
| 50 | + "tokens": { | ||
| 51 | + "access_token": "eyJ...", | ||
| 52 | + "refresh_token": null, | ||
| 53 | + "client_id": "...", | ||
| 54 | + "client_secret": "..." | ||
| 55 | + } | ||
| 56 | +} | ||
| 57 | +``` | ||
| 58 | + | ||
| 59 | +## Usage Examples | ||
| 60 | + | ||
| 61 | +### Completion Handler Variant | ||
| 62 | +```swift | ||
| 63 | +WarplySDK.shared.deiLogin( | ||
| 64 | + email: "user@example.com", | ||
| 65 | + completion: { response in | ||
| 66 | + if let response = response, response.getStatus == 1 { | ||
| 67 | + print("DEI login successful") | ||
| 68 | + // User is now authenticated - proceed with authenticated operations | ||
| 69 | + } else { | ||
| 70 | + print("DEI login failed") | ||
| 71 | + } | ||
| 72 | + }, | ||
| 73 | + failureCallback: { errorCode in | ||
| 74 | + print("DEI login failed with error: \(errorCode)") | ||
| 75 | + } | ||
| 76 | +) | ||
| 77 | +``` | ||
| 78 | + | ||
| 79 | +### Async/Await Variant | ||
| 80 | +```swift | ||
| 81 | +Task { | ||
| 82 | + do { | ||
| 83 | + let response = try await WarplySDK.shared.deiLogin(email: "user@example.com") | ||
| 84 | + if response.getStatus == 1 { | ||
| 85 | + print("DEI login successful") | ||
| 86 | + // User is now authenticated - proceed with authenticated operations | ||
| 87 | + } else { | ||
| 88 | + print("DEI login failed") | ||
| 89 | + } | ||
| 90 | + } catch { | ||
| 91 | + print("DEI login failed with error: \(error)") | ||
| 92 | + } | ||
| 93 | +} | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +## Integration with Existing Framework | ||
| 97 | + | ||
| 98 | +### Token Management | ||
| 99 | +- ✅ Integrates with existing TokenModel system | ||
| 100 | +- ✅ Integrates with DatabaseManager for secure storage | ||
| 101 | +- ✅ Integrates with TokenRefreshManager for future token refresh | ||
| 102 | +- ✅ Integrates with NetworkService for automatic token usage | ||
| 103 | + | ||
| 104 | +### Analytics | ||
| 105 | +- ✅ Follows existing analytics patterns | ||
| 106 | +- ✅ Posts success/failure events to Dynatrace | ||
| 107 | +- ✅ Uses existing event system (SwiftEventBus + EventDispatcher) | ||
| 108 | + | ||
| 109 | +### Error Handling | ||
| 110 | +- ✅ Uses existing WarplyError system | ||
| 111 | +- ✅ Follows existing error mapping patterns | ||
| 112 | +- ✅ Provides consistent error codes and messages | ||
| 113 | + | ||
| 114 | +## Testing Checklist | ||
| 115 | + | ||
| 116 | +### Basic Functionality | ||
| 117 | +- [ ] Test successful login with valid email | ||
| 118 | +- [ ] Test failed login with invalid email | ||
| 119 | +- [ ] Test network error handling | ||
| 120 | +- [ ] Test token extraction and storage | ||
| 121 | +- [ ] Test both completion handler and async/await variants | ||
| 122 | + | ||
| 123 | +### Integration Testing | ||
| 124 | +- [ ] Test that tokens are stored in database | ||
| 125 | +- [ ] Test that tokens are used for subsequent authenticated requests | ||
| 126 | +- [ ] Test analytics events are posted correctly | ||
| 127 | +- [ ] Test error handling and conversion | ||
| 128 | + | ||
| 129 | +### Edge Cases | ||
| 130 | +- [ ] Test with malformed response | ||
| 131 | +- [ ] Test with missing tokens in response | ||
| 132 | +- [ ] Test with null refresh token (current expected behavior) | ||
| 133 | +- [ ] Test network connectivity issues | ||
| 134 | + | ||
| 135 | +## Implementation Status: ✅ COMPLETE | ||
| 136 | + | ||
| 137 | +All required components have been implemented: | ||
| 138 | +1. ✅ Endpoint configuration | ||
| 139 | +2. ✅ Network service method | ||
| 140 | +3. ✅ Public SDK methods (both variants) | ||
| 141 | +4. ✅ Comprehensive documentation | ||
| 142 | +5. ✅ Error handling and analytics | ||
| 143 | +6. ✅ Integration with existing systems | ||
| 144 | + | ||
| 145 | +The DEI login functionality is ready for testing and integration. |
This diff is collapsed. Click to expand it.
| ... | @@ -2840,6 +2840,159 @@ public final class WarplySDK { | ... | @@ -2840,6 +2840,159 @@ public final class WarplySDK { |
| 2840 | } | 2840 | } |
| 2841 | } | 2841 | } |
| 2842 | 2842 | ||
| 2843 | + // MARK: - DEI Authentication | ||
| 2844 | + | ||
| 2845 | + /** | ||
| 2846 | + * DEI Login with automatic token handling | ||
| 2847 | + * | ||
| 2848 | + * This method authenticates a user with the DEI platform using their email address. | ||
| 2849 | + * Upon successful authentication, it automatically extracts and stores JWT tokens | ||
| 2850 | + * in the database for future authenticated requests. | ||
| 2851 | + * | ||
| 2852 | + * @param email User's email address for DEI authentication | ||
| 2853 | + * @param completion Completion handler with response model (nil on failure) | ||
| 2854 | + * @param failureCallback Failure callback with error code | ||
| 2855 | + * | ||
| 2856 | + * @discussion This method performs: | ||
| 2857 | + * - Email-based authentication with DEI platform | ||
| 2858 | + * - Automatic JWT token extraction and validation | ||
| 2859 | + * - Secure token storage in database using TokenModel | ||
| 2860 | + * - Comprehensive error handling and analytics | ||
| 2861 | + * | ||
| 2862 | + * @note The DEI login endpoint does not require authorization headers. | ||
| 2863 | + * Tokens are automatically stored and will be used by NetworkService for future requests. | ||
| 2864 | + * | ||
| 2865 | + * Error Scenarios: | ||
| 2866 | + * - Invalid email format: Server validation error | ||
| 2867 | + * - Network issues: Network error callback | ||
| 2868 | + * - Invalid response structure: Parsing error | ||
| 2869 | + * - Missing tokens: Authentication error | ||
| 2870 | + * | ||
| 2871 | + * @example | ||
| 2872 | + * ```swift | ||
| 2873 | + * WarplySDK.shared.deiLogin( | ||
| 2874 | + * email: "user@example.com", | ||
| 2875 | + * completion: { response in | ||
| 2876 | + * if let response = response, response.getStatus == 1 { | ||
| 2877 | + * print("DEI login successful") | ||
| 2878 | + * // User is now authenticated - proceed with authenticated operations | ||
| 2879 | + * } else { | ||
| 2880 | + * print("DEI login failed") | ||
| 2881 | + * } | ||
| 2882 | + * }, | ||
| 2883 | + * failureCallback: { errorCode in | ||
| 2884 | + * print("DEI login failed with error: \(errorCode)") | ||
| 2885 | + * } | ||
| 2886 | + * ) | ||
| 2887 | + * ``` | ||
| 2888 | + */ | ||
| 2889 | + public func deiLogin(email: String, completion: @escaping (VerifyTicketResponseModel?) -> Void, failureCallback: @escaping (Int) -> Void) { | ||
| 2890 | + // Clear previous state | ||
| 2891 | + setCCMSLoyaltyCampaigns(campaigns: []) | ||
| 2892 | + | ||
| 2893 | + Task { | ||
| 2894 | + do { | ||
| 2895 | + let response = try await networkService.deiLogin(email: email) | ||
| 2896 | + let tempResponse = VerifyTicketResponseModel(dictionary: response) | ||
| 2897 | + | ||
| 2898 | + await MainActor.run { | ||
| 2899 | + if tempResponse.getStatus == 1 { | ||
| 2900 | + print("✅ [WarplySDK] DEI login successful") | ||
| 2901 | + | ||
| 2902 | + // Analytics for successful login | ||
| 2903 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2904 | + dynatraceEvent._eventName = "custom_success_dei_login_loyalty" | ||
| 2905 | + dynatraceEvent._parameters = nil | ||
| 2906 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2907 | + | ||
| 2908 | + // Tokens are already extracted and stored by NetworkService.deiLogin | ||
| 2909 | + print("✅ [WarplySDK] DEI tokens automatically stored by NetworkService") | ||
| 2910 | + | ||
| 2911 | + } else { | ||
| 2912 | + print("❌ [WarplySDK] DEI login failed - invalid status") | ||
| 2913 | + | ||
| 2914 | + // Analytics for failed login | ||
| 2915 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2916 | + dynatraceEvent._eventName = "custom_error_dei_login_loyalty" | ||
| 2917 | + dynatraceEvent._parameters = nil | ||
| 2918 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2919 | + } | ||
| 2920 | + | ||
| 2921 | + completion(tempResponse) | ||
| 2922 | + } | ||
| 2923 | + } catch { | ||
| 2924 | + await MainActor.run { | ||
| 2925 | + print("❌ [WarplySDK] DEI login network error: \(error)") | ||
| 2926 | + | ||
| 2927 | + // Analytics for network error | ||
| 2928 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2929 | + dynatraceEvent._eventName = "custom_error_dei_login_loyalty" | ||
| 2930 | + dynatraceEvent._parameters = nil | ||
| 2931 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2932 | + | ||
| 2933 | + self.handleError(error, context: "deiLogin", endpoint: "deiLogin", failureCallback: failureCallback) | ||
| 2934 | + } | ||
| 2935 | + } | ||
| 2936 | + } | ||
| 2937 | + } | ||
| 2938 | + | ||
| 2939 | + /** | ||
| 2940 | + * DEI Login with automatic token handling (async/await variant) | ||
| 2941 | + * | ||
| 2942 | + * This method authenticates a user with the DEI platform using their email address. | ||
| 2943 | + * Upon successful authentication, it automatically extracts and stores JWT tokens | ||
| 2944 | + * in the database for future authenticated requests. | ||
| 2945 | + * | ||
| 2946 | + * @param email User's email address for DEI authentication | ||
| 2947 | + * @returns VerifyTicketResponseModel containing authentication result | ||
| 2948 | + * @throws WarplyError if the request fails | ||
| 2949 | + * | ||
| 2950 | + * @discussion This method performs: | ||
| 2951 | + * - Email-based authentication with DEI platform | ||
| 2952 | + * - Automatic JWT token extraction and validation | ||
| 2953 | + * - Secure token storage in database using TokenModel | ||
| 2954 | + * - Comprehensive error handling and analytics | ||
| 2955 | + * | ||
| 2956 | + * @note The DEI login endpoint does not require authorization headers. | ||
| 2957 | + * Tokens are automatically stored and will be used by NetworkService for future requests. | ||
| 2958 | + * | ||
| 2959 | + * Error Scenarios: | ||
| 2960 | + * - Invalid email format: Server validation error | ||
| 2961 | + * - Network issues: WarplyError.networkError | ||
| 2962 | + * - Invalid response structure: WarplyError.dataParsingError | ||
| 2963 | + * - Missing tokens: WarplyError.authenticationFailed | ||
| 2964 | + * | ||
| 2965 | + * @example | ||
| 2966 | + * ```swift | ||
| 2967 | + * Task { | ||
| 2968 | + * do { | ||
| 2969 | + * let response = try await WarplySDK.shared.deiLogin(email: "user@example.com") | ||
| 2970 | + * if response.getStatus == 1 { | ||
| 2971 | + * print("DEI login successful") | ||
| 2972 | + * // User is now authenticated - proceed with authenticated operations | ||
| 2973 | + * } else { | ||
| 2974 | + * print("DEI login failed") | ||
| 2975 | + * } | ||
| 2976 | + * } catch { | ||
| 2977 | + * print("DEI login failed with error: \(error)") | ||
| 2978 | + * } | ||
| 2979 | + * } | ||
| 2980 | + * ``` | ||
| 2981 | + */ | ||
| 2982 | + public func deiLogin(email: String) async throws -> VerifyTicketResponseModel { | ||
| 2983 | + return try await withCheckedThrowingContinuation { continuation in | ||
| 2984 | + deiLogin(email: email, completion: { response in | ||
| 2985 | + if let response = response { | ||
| 2986 | + continuation.resume(returning: response) | ||
| 2987 | + } else { | ||
| 2988 | + continuation.resume(throwing: WarplyError.authenticationFailed) | ||
| 2989 | + } | ||
| 2990 | + }, failureCallback: { errorCode in | ||
| 2991 | + continuation.resume(throwing: WarplyError.unknownError(errorCode)) | ||
| 2992 | + }) | ||
| 2993 | + } | ||
| 2994 | + } | ||
| 2995 | + | ||
| 2843 | // MARK: - Push Notifications | 2996 | // MARK: - Push Notifications |
| 2844 | 2997 | ||
| 2845 | /// Handle notification | 2998 | /// Handle notification | ... | ... |
| ... | @@ -57,6 +57,7 @@ public enum Endpoint { | ... | @@ -57,6 +57,7 @@ public enum Endpoint { |
| 57 | case refreshToken(clientId: String, clientSecret: String, refreshToken: String) | 57 | case refreshToken(clientId: String, clientSecret: String, refreshToken: String) |
| 58 | case logout | 58 | case logout |
| 59 | case getCosmoteUser(guid: String) | 59 | case getCosmoteUser(guid: String) |
| 60 | + case deiLogin(email: String) | ||
| 60 | 61 | ||
| 61 | // Campaigns | 62 | // Campaigns |
| 62 | case getCampaigns(language: String, filters: [String: Any]) | 63 | case getCampaigns(language: String, filters: [String: Any]) |
| ... | @@ -122,6 +123,8 @@ public enum Endpoint { | ... | @@ -122,6 +123,8 @@ public enum Endpoint { |
| 122 | return "/partners/cosmote/verify" | 123 | return "/partners/cosmote/verify" |
| 123 | case .getCosmoteUser: | 124 | case .getCosmoteUser: |
| 124 | return "/partners/oauth/{appUUID}/token" | 125 | return "/partners/oauth/{appUUID}/token" |
| 126 | + case .deiLogin: | ||
| 127 | + return "/partners/dei/app_login" | ||
| 125 | 128 | ||
| 126 | // Authentication endpoints | 129 | // Authentication endpoints |
| 127 | case .refreshToken: | 130 | case .refreshToken: |
| ... | @@ -163,7 +166,7 @@ public enum Endpoint { | ... | @@ -163,7 +166,7 @@ public enum Endpoint { |
| 163 | switch self { | 166 | switch self { |
| 164 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, | 167 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, |
| 165 | .getCoupons, .getCouponSets, .getAvailableCoupons, | 168 | .getCoupons, .getCouponSets, .getAvailableCoupons, |
| 166 | - .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser: | 169 | + .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin: |
| 167 | return .POST | 170 | return .POST |
| 168 | case .getSingleCampaign, .getNetworkStatus: | 171 | case .getSingleCampaign, .getNetworkStatus: |
| 169 | return .GET | 172 | return .GET |
| ... | @@ -224,6 +227,12 @@ public enum Endpoint { | ... | @@ -224,6 +227,12 @@ public enum Endpoint { |
| 224 | "user_identifier": guid | 227 | "user_identifier": guid |
| 225 | ] | 228 | ] |
| 226 | 229 | ||
| 230 | + // DEI Login - simple email parameter structure | ||
| 231 | + case .deiLogin(let email): | ||
| 232 | + return [ | ||
| 233 | + "email": email | ||
| 234 | + ] | ||
| 235 | + | ||
| 227 | // Campaign endpoints - nested structure with campaigns wrapper | 236 | // Campaign endpoints - nested structure with campaigns wrapper |
| 228 | case .getCampaigns(let language, let filters): | 237 | case .getCampaigns(let language, let filters): |
| 229 | return [ | 238 | return [ |
| ... | @@ -447,7 +456,7 @@ public enum Endpoint { | ... | @@ -447,7 +456,7 @@ public enum Endpoint { |
| 447 | 456 | ||
| 448 | public var requiresAuthentication: Bool { | 457 | public var requiresAuthentication: Bool { |
| 449 | switch self { | 458 | switch self { |
| 450 | - case .register, .verifyTicket, .getCosmoteUser, .getNetworkStatus: | 459 | + case .register, .verifyTicket, .getCosmoteUser, .deiLogin, .getNetworkStatus: |
| 451 | return false | 460 | return false |
| 452 | default: | 461 | default: |
| 453 | return true | 462 | return true |
| ... | @@ -505,7 +514,7 @@ public enum Endpoint { | ... | @@ -505,7 +514,7 @@ public enum Endpoint { |
| 505 | // Standard Authentication (loyalty headers only) | 514 | // Standard Authentication (loyalty headers only) |
| 506 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, | 515 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, |
| 507 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, | 516 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, |
| 508 | - .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus: | 517 | + .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus, .deiLogin: |
| 509 | return .standard | 518 | return .standard |
| 510 | 519 | ||
| 511 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | 520 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | ... | ... |
| ... | @@ -962,6 +962,57 @@ extension NetworkService { | ... | @@ -962,6 +962,57 @@ extension NetworkService { |
| 962 | return response | 962 | return response |
| 963 | } | 963 | } |
| 964 | 964 | ||
| 965 | + // MARK: - DEI Authentication Methods | ||
| 966 | + | ||
| 967 | + /// DEI Login with automatic token handling | ||
| 968 | + /// - Parameter email: User's email address for DEI authentication | ||
| 969 | + /// - Returns: Response dictionary containing authentication result | ||
| 970 | + /// - Throws: NetworkError if request fails | ||
| 971 | + public func deiLogin(email: String) async throws -> [String: Any] { | ||
| 972 | + print("🔄 [NetworkService] DEI Login for email: \(email)") | ||
| 973 | + let endpoint = Endpoint.deiLogin(email: email) | ||
| 974 | + let response = try await requestRaw(endpoint) | ||
| 975 | + | ||
| 976 | + // Validate response status | ||
| 977 | + guard let status = response["status"] as? Int, status == 1 else { | ||
| 978 | + print("❌ [NetworkService] DEI Login failed - invalid status") | ||
| 979 | + throw NetworkError.serverError(400) | ||
| 980 | + } | ||
| 981 | + | ||
| 982 | + // Extract and validate tokens | ||
| 983 | + guard let tokens = response["tokens"] as? [String: Any], | ||
| 984 | + let accessToken = tokens["access_token"] as? String, | ||
| 985 | + !accessToken.isEmpty else { | ||
| 986 | + print("❌ [NetworkService] DEI Login failed - missing or empty access_token") | ||
| 987 | + throw NetworkError.invalidResponse | ||
| 988 | + } | ||
| 989 | + | ||
| 990 | + // Extract refresh token (may be null initially but prepare for future) | ||
| 991 | + let refreshToken = tokens["refresh_token"] as? String | ||
| 992 | + | ||
| 993 | + print("✅ [NetworkService] DEI Login successful - tokens received") | ||
| 994 | + print(" Access token: \(accessToken.prefix(10))...") | ||
| 995 | + print(" Refresh token: \(refreshToken?.prefix(10) ?? "null")...") | ||
| 996 | + | ||
| 997 | + // Create TokenModel and store in database | ||
| 998 | + let tokenModel = TokenModel( | ||
| 999 | + accessToken: accessToken, | ||
| 1000 | + refreshToken: refreshToken, // May be nil/null initially | ||
| 1001 | + clientId: tokens["client_id"] as? String, | ||
| 1002 | + clientSecret: tokens["client_secret"] as? String | ||
| 1003 | + ) | ||
| 1004 | + | ||
| 1005 | + do { | ||
| 1006 | + try await DatabaseManager.shared.storeTokenModel(tokenModel) | ||
| 1007 | + print("✅ [NetworkService] DEI tokens stored in database successfully") | ||
| 1008 | + } catch { | ||
| 1009 | + print("❌ [NetworkService] Failed to store DEI tokens in database: \(error)") | ||
| 1010 | + // Don't throw - the login was successful, token storage is secondary | ||
| 1011 | + } | ||
| 1012 | + | ||
| 1013 | + return response | ||
| 1014 | + } | ||
| 1015 | + | ||
| 965 | // MARK: - Merchant Categories Methods | 1016 | // MARK: - Merchant Categories Methods |
| 966 | 1017 | ||
| 967 | /// Get merchant categories | 1018 | /// Get merchant categories | ... | ... |
-
Please register or login to post a comment