Skip to content

Commit

Permalink
ETagManager/HTTPClient: sending new X-RC-Last-Refresh-Time head…
Browse files Browse the repository at this point in the history
…er (#2373)

Fixes SDK-3024 and SDK-3026.

---------

Co-authored-by: Andy Boedo <andresboedo@gmail.com>
  • Loading branch information
NachoSoto and aboedo committed Apr 14, 2023
1 parent eeb0ced commit 8b4c2eb
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 124 deletions.
66 changes: 49 additions & 17 deletions Sources/Networking/HTTPClient/ETagManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Foundation
class ETagManager {

static let eTagRequestHeaderName = HTTPClient.RequestHeader.eTag.rawValue
static let eTagValidationTimeRequestHeaderName = HTTPClient.RequestHeader.eTagValidationTime.rawValue
static let eTagResponseHeaderName = HTTPClient.ResponseHeader.eTag.rawValue

private let userDefaults: SynchronizedUserDefaults
Expand All @@ -43,7 +44,7 @@ class ETagManager {
withSignatureVerification: Bool,
refreshETag: Bool = false
) -> [String: String] {
func eTag() -> String? {
func eTag() -> (tag: String, date: String)? {
if refreshETag { return nil }
guard let storedETagAndResponse = self.storedETagAndResponse(for: urlRequest) else { return nil }

Expand All @@ -53,10 +54,20 @@ class ETagManager {
storedETagAndResponse.verificationResult == .verified
)

return shouldUseETag ? storedETagAndResponse.eTag : nil
if shouldUseETag {
return (tag: storedETagAndResponse.eTag,
date: storedETagAndResponse.validationTime.millisecondsSince1970.description)
} else {
return nil
}
}

return [HTTPClient.RequestHeader.eTag.rawValue: eTag() ?? ""]
let (etag, date) = eTag() ?? ("", "")

return [
HTTPClient.RequestHeader.eTag.rawValue: etag,
HTTPClient.RequestHeader.eTagValidationTime.rawValue: date
]
}

func httpResultFromCacheOrBackend(with response: HTTPResponse<Data?>,
Expand All @@ -70,8 +81,11 @@ class ETagManager {
}

if self.shouldUseCachedVersion(responseCode: statusCode) {
if let storedResponse = self.storedHTTPResponse(for: request, withRequestDate: response.requestDate) {
return storedResponse
if let storedResponse = self.storedETagAndResponse(for: request) {
let newResponse = storedResponse.withUpdatedValidationTime()

self.storeIfPossible(newResponse, for: request)
return newResponse.asResponse(withRequestDate: response.requestDate)
}
if retried {
Logger.warn(
Expand Down Expand Up @@ -108,7 +122,7 @@ private extension ETagManager {

func storedETagAndResponse(for request: URLRequest) -> Response? {
return self.userDefaults.read {
if let cacheKey = eTagDefaultCacheKey(for: request),
if let cacheKey = self.eTagDefaultCacheKey(for: request),
let value = $0.object(forKey: cacheKey),
let data = value as? Data {
return try? JSONDecoder.default.decode(Response.self, jsonData: data)
Expand All @@ -126,18 +140,24 @@ private extension ETagManager {
response: HTTPResponse<Data?>,
eTag: String) {
if let data = response.body,
response.shouldStore(ignoreVerificationErrors: self.shouldIgnoreVerificationErrors),
let cacheKey = self.eTagDefaultCacheKey(for: request) {
let eTagAndResponse = Response(
eTag: eTag,
statusCode: response.statusCode,
data: data,
verificationResult: response.verificationResult
response.shouldStore(ignoreVerificationErrors: self.shouldIgnoreVerificationErrors) {
self.storeIfPossible(
Response(
eTag: eTag,
statusCode: response.statusCode,
data: data,
verificationResult: response.verificationResult
),
for: request
)
if let dataToStore = eTagAndResponse.asData() {
self.userDefaults.write {
$0.set(dataToStore, forKey: cacheKey)
}
}
}

func storeIfPossible(_ response: Response, for request: URLRequest) {
if let cacheKey = self.eTagDefaultCacheKey(for: request),
let dataToStore = response.asData() {
self.userDefaults.write {
$0.set(dataToStore, forKey: cacheKey)
}
}
}
Expand Down Expand Up @@ -173,18 +193,23 @@ extension ETagManager {
var eTag: String
var statusCode: HTTPStatusCode
var data: Data
/// Used by the backend for advanced load shedding techniques.
@DefaultDecodable.Now
var validationTime: Date
@DefaultValue<VerificationResult>
var verificationResult: VerificationResult

init(
eTag: String,
statusCode: HTTPStatusCode,
data: Data,
validationTime: Date = Date(),
verificationResult: VerificationResult
) {
self.eTag = eTag
self.statusCode = statusCode
self.data = data
self.validationTime = validationTime
self.verificationResult = verificationResult
}

Expand All @@ -210,6 +235,13 @@ extension ETagManager.Response {
)
}

fileprivate func withUpdatedValidationTime() -> Self {
var copy = self
copy.validationTime = Date()

return copy
}

}

// MARK: -
Expand Down
1 change: 1 addition & 0 deletions Sources/Networking/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ extension HTTPClient {
case location = "Location"
case nonce = "X-Nonce"
case eTag = "X-RevenueCat-ETag"
case eTagValidationTime = "X-RC-Last-Refresh-Time"

}

Expand Down
7 changes: 5 additions & 2 deletions Tests/UnitTests/Mocks/MockETagManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ class MockETagManager: ETagManager {
var invokedETagHeaderParametersList: [ETagHeaderRequest] = []
var stubbedETagHeaderResult: [String: String]! = [:]

func stubResponseEtag(_ tag: String) {
self.stubbedETagHeaderResult[ETagManager.eTagResponseHeaderName] = tag
func stubResponseEtag(_ tag: String, validationTime: Date = Date()) {
self.stubbedETagHeaderResult = [
ETagManager.eTagRequestHeaderName: tag,
ETagManager.eTagValidationTimeRequestHeaderName: validationTime.millisecondsSince1970.description
]
}

override func eTagHeader(
Expand Down

0 comments on commit 8b4c2eb

Please sign in to comment.