Files
purchases-ios/Sources/Networking/Operations/PostReceiptDataOperation.swift
T
NachoSoto a2fc04c09e SubscriberAttribute: converted into struct (#1648)
### Motivation:

Initially I wanted to make `SubscriberAttribute.asDictionary` work using `Codable`. However, this isn't the right refactor because we need the actual raw types to be encoded inside `UserDefaults`.
At least this refactor provides several improvements:

### Changes:

- `SubscriberAttribute` now has value semantics
- ⚠️  `SubscriberAttribute.isSynced` used to be mutable, with reference semantics, which was dangerous
- `SubscriberAttribute: Equatable` implementation is now automatic
- Cleaned up `SubscriberAttribute.asDictionary` implementation, extracting keys into an `enum`
- Moved `Dictionary` -> `SubscriberAttribute` creation to the type itself
- ⚠️ `SubscriberAttribute.init(dictionary:)` returns an `Optional` instead of using force-unwrap, avoiding potential crashes
- Added more serialization / deserialization tests
- Removed `SubscriberAttributesMarshaller` and moved method into `SubscriberAttribute` extension
- Moved `SubscriberAttributeDict` into `SubscriberAttribute.Dictionary`
2022-06-17 10:00:51 -07:00

111 lines
3.7 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// PostReceiptDataOperation.swift
//
// Created by Joshua Liebowitz on 11/18/21.
import Foundation
class PostReceiptDataOperation: CacheableNetworkOperation {
struct PostData {
let appUserID: String
let receiptData: Data
let isRestore: Bool
let productData: ProductRequestData?
let presentedOfferingIdentifier: String?
let observerMode: Bool
let subscriberAttributesByKey: SubscriberAttribute.Dictionary?
}
private let postData: PostData
private let configuration: AppUserConfiguration
private let customerInfoResponseHandler: CustomerInfoResponseHandler
private let customerInfoCallbackCache: CallbackCache<CustomerInfoCallback>
init(configuration: UserSpecificConfiguration,
postData: PostData,
customerInfoResponseHandler: CustomerInfoResponseHandler = CustomerInfoResponseHandler(),
customerInfoCallbackCache: CallbackCache<CustomerInfoCallback>) {
self.customerInfoResponseHandler = customerInfoResponseHandler
self.customerInfoCallbackCache = customerInfoCallbackCache
self.postData = postData
self.configuration = configuration
let cacheKey =
"""
\(configuration.appUserID)-\(postData.isRestore)-\(postData.receiptData.asFetchToken)
-\(postData.productData?.cacheKey ?? "")
-\(postData.presentedOfferingIdentifier ?? "")-\(postData.observerMode)
-\(postData.subscriberAttributesByKey?.debugDescription ?? "")"
"""
super.init(configuration: configuration, individualizedCacheKeyPart: cacheKey)
}
override func begin(completion: @escaping () -> Void) {
self.post(completion: completion)
}
private func post(completion: @escaping () -> Void) {
let request = HTTPRequest(method: .post(self.postData), path: .postReceiptData)
httpClient.perform(request) { (response: HTTPResponse<CustomerInfoResponseHandler.Response>.Result) in
self.customerInfoCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callbackObject in
self.customerInfoResponseHandler.handle(customerInfoResponse: response,
completion: callbackObject.completion)
}
completion()
}
}
}
extension PostReceiptDataOperation.PostData: Encodable {
private enum CodingKeys: String, CodingKey {
case fetchToken
case appUserID
case isRestore
case observerMode
case attributes
case presentedOfferingIdentifier
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.receiptData.asFetchToken, forKey: .fetchToken)
try container.encode(self.appUserID, forKey: .appUserID)
try container.encode(self.isRestore, forKey: .isRestore)
try container.encode(self.observerMode, forKey: .observerMode)
if let productData = self.productData {
try productData.encode(to: encoder)
}
try container.encodeIfPresent(self.presentedOfferingIdentifier,
forKey: .presentedOfferingIdentifier)
try container.encodeIfPresent(
self.subscriberAttributesByKey
.map(SubscriberAttribute.map)
.map(AnyEncodable.init),
forKey: .attributes
)
}
}