Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored TransactionPoster: removed 2 dependencies and abstracted parameters #2542

Merged
merged 1 commit into from
May 30, 2023

Conversation

NachoSoto
Copy link
Contributor

@NachoSoto NachoSoto commented May 25, 2023

Follow up to #2540.

This was necessary to ensure that we can implement #2533 without reference cycles.
This was the hierarchy before this change:
IMG_B25ECE86C2F9-1

The red arrows are what this PR removes.

Changes:

  • TransactionPoster no longer has references to IdentityManager and Attribution
  • markSyncedIfNeeded is no longer in TransactionPoster, moved back to PurchasesOrchestrator
  • Changed TransactionPoster's callback to return Result<CustomerInfo, BackendError>, providing greater flexibility for users to be able to read the specific errors.
  • Extracted PurchasedTransactionData to be reduce the number of parameters passed around. This removed the need for 2 swiftlint:disable:next function_parameter_count. This is the majority of this diff. It was helpful to combine in this PR because I needed to add another parameter to the flow: unsyncedAttributes, since TransactionPoster could no longer read these directly.

@NachoSoto NachoSoto requested a review from a team May 25, 2023 01:20
expect(self.mockSubscriberAttributesManager.invokedMarkAttributesParameters!.syncedAttributes) == mockAttributes
expect(self.mockSubscriberAttributesManager.invokedMarkAttributesParameters!.appUserID) ==
mockIdentityManager.currentAppUserID
expect(self.mockSubscriberAttributesManager.invokedMarkAttributes).toEventually(beTrue())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only semantic change. Because of this refactor, attributes are marked as synced after the transactions are finished. The test below (like 732) already had toEventually. This is purely an ordering change and shouldn't have any impact.

@NachoSoto NachoSoto marked this pull request as ready for review May 25, 2023 01:29
@codecov
Copy link

codecov bot commented May 25, 2023

Codecov Report

Merging #2542 (34de8f5) into main (456b96b) will increase coverage by 0.07%.
The diff coverage is 99.64%.

❗ Current head 34de8f5 differs from pull request most recent head 397edda. Consider uploading reports for the commit 397edda to get more accurate results

@@            Coverage Diff             @@
##             main    #2542      +/-   ##
==========================================
+ Coverage   87.88%   87.95%   +0.07%     
==========================================
  Files         201      201              
  Lines       13871    13868       -3     
==========================================
+ Hits        12190    12198       +8     
+ Misses       1681     1670      -11     
Impacted Files Coverage Δ
Sources/Misc/Concurrency/OperationDispatcher.swift 65.71% <ø> (ø)
Sources/Purchasing/Purchases/Purchases.swift 77.48% <90.90%> (-0.06%) ⬇️
Sources/Networking/Backend.swift 88.63% <100.00%> (-0.50%) ⬇️
Sources/Networking/CustomerAPI.swift 100.00% <100.00%> (ø)
...tworking/Operations/PostReceiptDataOperation.swift 91.85% <100.00%> (+0.79%) ⬆️
...s/Purchasing/Purchases/PurchasesOrchestrator.swift 85.31% <100.00%> (+0.26%) ⬆️
...urces/Purchasing/Purchases/TransactionPoster.swift 100.00% <100.00%> (+1.80%) ⬆️

... and 8 files with indirect coverage changes

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great refactor! Looks great!

initiationSource: initiationSource,
subscriberAttributesByKey: subscriberAttributesToPost)
let postData = PostReceiptDataOperation.PostData(
transactionData: transactionData.withAttributesToPost(subscriberAttributesToPost),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are recreating the transaction data here since the consentStatus could be added to the attributes correct? If so, I wonder if we could extract that piece of code adding that attribute to the moment where we create the PurchasedTransactionData to avoid this recreation. Not a blocker though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are recreating the transaction data here since the consentStatus could be added to the attributes correct

Yeah, which matches the existing behavior

If so, I wonder if we could extract that piece of code adding that attribute to the moment where we create the PurchasedTransactionData to avoid this recreation. Not a blocker though.

That might make more sense, but it would change the behavior and I'm trying to keep this exactly working just like before.

data: .init(
appUserID: self.appUserID,
presentedOfferingID: offeringID,
unsyncedAttributes: unsyncedAttributes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we were not syncing unsyncedAttributes in some of these posts. I think it shouldn't cause any issues, but it seems to be a behavior change so I just wanted to bring it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was being done inside by TransactionPoster because it had access to the unsynced attributes there.

source: PurchaseSource,
completion: @escaping PurchaseCompletedBlock
data: PurchasedTransactionData,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm it's a bit weird to have a reference to CustomerAPI in the TransactionPoster... I think it's ok though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomerAPI also has the post(receiptData:) method.

@NachoSoto NachoSoto merged commit 4b21383 into main May 30, 2023
@NachoSoto NachoSoto deleted the transaction-poster-3 branch May 30, 2023 19:16
NachoSoto added a commit that referenced this pull request May 30, 2023
…2533)

This fixes the last edge-case for offline entitlements. Solves the
following scenario:
```swift
func testPurchaseWhileServerIsDownPostsReceiptWhenForegroundingApp() async throws {
    let logger = TestLogHandler()

    // 1. Purchase while server is down
    self.serverDown()
    try await self.purchaseMonthlyProduct()

    logger.verifyMessageWasNotLogged("Finishing transaction")

    // 2. Server is back
    self.serverUp()

    // 3. Request current CustomerInfo
    let info1 = try await Purchases.shared.customerInfo()
    try await self.verifyEntitlementWentThrough(info1)

    // 4. Ensure transaction is finished
    logger.verifyMessageWasLogged("Finishing transaction", level: .info)

    // 5. Restart app
    Purchases.shared.invalidateCustomerInfoCache()
    await self.resetSingleton()

    // 6. To ensure (with a clean cache) that the receipt was posted
    let info2 = try await Purchases.shared.customerInfo()
    try await self.verifyEntitlementWentThrough(info2)
}
```

This race condition happened when fetching `CustomerInfo` after we had
computed an offline `CustomerInfo` with a purchase: if the server is
still not aware of the purchase, then we lose that until some arbitrary
amount of time when `StoreKit` might decide to send the pending
transactions in the queue.
For that reason, instead of waiting, we proactively check now whenever
`CustomerInfoManager` fetches new info.

The refactors #2540 and #2542 where required to break a retain cycle.
Also thanks to #2534 we know for sure that there aren't any cycles.

Note that this is an iOS 15.0+ only feature. It would be useful to
better ensure consistency in older versions, but it's not strictly
required, as those versions don't support offline entitlements either.
NachoSoto added a commit that referenced this pull request May 31, 2023
…#2566)

Cherry-picked #2549 post refactors (#2540 and #2542). This becomes a lot
simpler since #2542, thanks to the fact that we can put this new
parameter in `PurchasedTransactionData`.

### Changes:
- Added `aadAttributionToken` to `PurchasedTransactionData`
- Exposed `AttributionPoster.adServicesTokenToPostIfNeeded`
- Added snapshot test to verify it's sent
- Added `PurchasesOrchestrator` tests (SK1/SK2) for sending the
attribution token
- Added missing `PurchasesOrchestrator` tests for sending attributes
- Added log when marking `AdServices` token as synced
- Exposed `Purchases.attribution` for custom entitlement computation
framework (and added to API tester)
- Exposed `Purchases.enableAdServicesAttributionTokenCollection` for
custom entitlement computation framework (and added to API tester)
This was referenced May 31, 2023
@NachoSoto NachoSoto mentioned this pull request May 31, 2023
NachoSoto added a commit that referenced this pull request Jun 1, 2023
**This is an automatic release.**

### New Features
* `Offline Entitlements`: use offline-computed `CustomerInfo` when
server is down (#2368) via NachoSoto (@NachoSoto)

### Bugfixes
* `AppleReceipt.debugDescription`: don't pretty-print JSON (#2564) via
NachoSoto (@NachoSoto)
* `SK2StoreProduct`: fix crash on iOS 12 (#2565) via NachoSoto
(@NachoSoto)
* `GetCustomerInfo` posts receipts if there are pending transactions
(#2533) via NachoSoto (@NachoSoto)
### Performance Improvements
* `PurchasedProductsFetcher`: cache current entitlements (#2507) via
NachoSoto (@NachoSoto)
* Performance: new check to ensure serialization / deserialization is
done from background thread (#2496) via NachoSoto (@NachoSoto)
### Dependency Updates
* Bump fastlane from 2.212.2 to 2.213.0 (#2544) via dependabot[bot]
(@dependabot[bot])
### Other Changes
* `CustomerInfoManager`: post all unfinished transactions (#2563) via
NachoSoto (@NachoSoto)
* `PostReceiptOperation`: added ability to also post `AdServices` token
(#2566) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: improved computation log (#2562) via NachoSoto
(@NachoSoto)
* Added `TransactionPoster` tests (#2557) via NachoSoto (@NachoSoto)
* Refactored `TransactionPoster`: removed 2 dependencies and abstracted
parameters (#2542) via NachoSoto (@NachoSoto)
* `CustomerInfoManagerTests`: wait for `getAndCacheCustomerInfo` to
finish (#2555) via NachoSoto (@NachoSoto)
* `StoreTransaction`: implemented `description` (#2556) via NachoSoto
(@NachoSoto)
* `Backend.ResponseHandler` is now `@Sendable` (#2541) via NachoSoto
(@NachoSoto)
* Extracted `TransactionPoster` from `PurchasesOrchestrator` (#2540) via
NachoSoto (@NachoSoto)
* `enableAdServicesAttributionTokenCollection`: added integration test
(#2551) via NachoSoto (@NachoSoto)
* `AttributionPoster`: replaced hardcoded strings with constants (#2548)
via NachoSoto (@NachoSoto)
* `DefaultDecodable`: moved to `Misc/Codable/DefaultDecodable.swift`
(#2528) via NachoSoto (@NachoSoto)
* `CircleCI`: specify device to run `backend_integration_tests` (#2547)
via NachoSoto (@NachoSoto)
* Created `StoreKit2TransactionFetcher` (#2539) via NachoSoto
(@NachoSoto)
* Fix load shedder integration tests (#2546) via Josh Holtz
(@joshdholtz)
* Fix doc on `Offering.getMetadataValue` (#2545) via Josh Holtz
(@joshdholtz)
* Extracted and tested `AsyncSequence.extractValues` (#2538) via
NachoSoto (@NachoSoto)
* `Offline Entitlements`: don't compute offline `CustomerInfo` when
purchasing a consumable products (#2522) via NachoSoto (@NachoSoto)
* `OfflineEntitlementsManager`: disable offline `CustomerInfo` in
observer mode (#2520) via NachoSoto (@NachoSoto)
* `BasePurchasesTests`: fixed leak detection (#2534) via NachoSoto
(@NachoSoto)
* `PurchaseTesterSwiftUI`: added `ProxyView` to `iOS` (#2531) via
NachoSoto (@NachoSoto)
* `PurchasedProductsFetcher`: removed `AppStore.sync` call (#2521) via
NachoSoto (@NachoSoto)
* `PurchaseTesterSwiftUI`: added new window on Mac to manage proxy
(#2518) via NachoSoto (@NachoSoto)
* `PurchasedProductsFetcher`: added log if fetching purchased products
is slow (#2515) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: disable for custom entitlements mode (#2509)
via NachoSoto (@NachoSoto)
* `Offline Entitlements`: fixed iOS 12 tests (#2514) via NachoSoto
(@NachoSoto)
* `PurchasedProductsFetcher`: don't throw errors if purchased products
were found (#2506) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: allow creating offline `CustomerInfo` with
empty `ProductEntitlementMapping` (#2504) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: integration tests (#2501) via NachoSoto
(@NachoSoto)
* `CustomerInfoManager`: don't cache offline `CustomerInfo` (#2378) via
NachoSoto (@NachoSoto)
* `DangerousSettings`: debug-only `forceServerErrors` (#2486) via
NachoSoto (@NachoSoto)
* `CocoapodsInstallation`: fixed `Xcode 14.3.0` issue (#2489) via
NachoSoto (@NachoSoto)
* `CarthageInstallation`: removed workaround (#2488) via NachoSoto
(@NachoSoto)

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants