You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
OnboardingMetadata carries variantKey and deploymentId (#234).
Both are optional, nullable and typed only: getSteps already passes the
server's metadata through untouched, so a payload that carries them now
reaches useOnboardingStep().onboardingMetadata and onComplete's metadata typed, and a payload that does not reads them as undefined. variantKey names the variant served; the parent onboarding reports the
reserved "main", exported as MAIN_VARIANT_KEY. null and absent mean
the same thing (no variant information) and should be treated identically. deploymentId is null on a draft (sandbox) payload, which has no
deployment. No runtime change.
Resolved Paywall: variantKey, deploymentId (#235). Optional and
nullable (string | null, number | null), resolved per moment like audienceId, so two moments in one catalog can carry different values. The
parent paywall reports the reserved variantKey: "main". Passed through from get-paywalls untouched; null and absent are equivalent (no variant or
deployment information). CustomPaywallScreenProps.paywall carries the same
two fields, so a custom paywall screen sees them too.
PaywallObserver.start() reports the served arm (#237): PaywallPresentationInfo.variantKey and deploymentId (a decimal string,
the contract's type) are now filled from the served paywall, on both present() and the inline Paywall step. A purchase or dismiss ends the
presentation start() opened, so it is attributed to the same arm. Both are
left unset (never empty or "none") when the server sent no variantKey,
so an app on an older server sees no new property.
Every getSteps and getPaywalls request carries a stable assignmentId (#236), so the server can keep a person in the same variant
across launches. Resolved per request: the new assignmentId client option
(a string, or a sync/async getter called on every request, e.g. () => Purchases.getAppUserID()), else an installation id generated once and
persisted in AsyncStorage under rocapine-assignment-id, else omitted. The
installation id survives clearCache() and OnboardingStudio.reset(). It is
omitted, rather than invented, when storage cannot be read or written. A host
value of undefined, null or "" falls back to the installation id. assignmentId: false, or a getter returning false, opts out: nothing is
sent and the installation id is never read, created or written (e.g. until
consent). A getter that throws, rejects or takes over 2s fails closed the
same way. With RevenueCat, Purchases.configure() must run before the first
request. assignmentId is now a reserved user property key. It is not part of any
cache key. Servers that do not read it ignore it. Changing the id (anonymous
to identified) can move a person to another variant once; see the API
reference.
timeout is now end-to-end for getSteps and getPaywalls: it covers
resolving the request identity (including the assignmentId getter) as well
as the fetch, so fallbackOnboarding arrives on time. A request whose budget
ran out while the id resolved is not sent afterwards.
OnboardingStudio.clearAssignmentId() / client.clearAssignmentId()
(#236) delete the stored rocapine-assignment-id and forget the in-memory
id, so a later request mints a new one (e.g. after consent is withdrawn,
together with a getter returning false). A request already resolving the
id sends none, even while its assignmentId getter is still pending, and a
first-launch id still being written is not brought back. Rejects if the
removal fails or is still waiting after 2s. The caller's wait on each queued
operation is bounded the same way, but operations still run strictly in
order, so a removal never overtakes a write in flight. A request stuck behind
a hung write or removal omits the id at its own 2s bound, which counts
against timeout. A hung read does not block later requests. A timed-out
request never mints, unless its write was already in flight (that write
still lands, ahead of any later removal). reset() does not call it.
Fixed
assignmentId is sent as the X-Assignment-Id request header, never as a
query param (#345). Edge invocation logs record the full request URL, and
the id may be a host's user id (e.g. a RevenueCat App User ID), so on the
querystring it was logged on every getSteps and getPaywalls request. A
custom audience param named assignmentId is still dropped from the URL. The
resolution rules are unchanged: false opts out, a getter that throws,
rejects or takes over 2s sends no header, the installation id fallback and clearAssignmentId() behave as before. Requires a server that reads the
header; one that reads only the old query param assigns as if no id were
sent.
expoIapProductProvider: an Ask to Buy purchase resolves "pending", not "error" (#337). openiap reports StoreKit's .pending purchase result as
the error code deferred-payment, and the adapter only recognised the
cancellation codes, so the paywall ran onError for a purchase still
awaiting approval. deferred-payment, pending, and the pre-4.x E_DEFERRED_PAYMENT / E_PENDING now resolve { status: "pending" }, from
the error listener and from a requestPurchase rejection alike, so the
purchase action's onPending branch runs instead.
expoIapProductProvider: a purchase that completes after its purchase() call returned now reaches onUnclaimedPurchase (#337). The
approved Ask to Buy transaction, a verdict landing after purchaseTimeoutMs, and a Play slow-payment purchase that cleared after "pending" all arrive with no caller waiting. They were finished silently,
so the user was charged and nothing granted the purchase. They are now
handed to onUnclaimedPurchase and finished only when it returns true,
the same contract as a launch replay. A delivery whose caller is still
waiting is unchanged: it is answered "purchased" and never shown to the
host, and a second copy of it (openiap both emits a transaction and returns
it from requestPurchase) is treated as a replay, not offered to the host.
The answer is now decided when the transaction arrives rather than after it
is acknowledged, so a slow finishTransaction can no longer turn a finished
purchase into a "pending" answer.
expoIapProductProvider acknowledges each transaction once (#337). A
second copy of a transaction (openiap both emits it and returns it from requestPurchase) could land while the first finishTransaction was still
in flight and start a second acknowledgement, which Play rejects. It now
joins the acknowledgement already in flight. A transaction the host granted
whose acknowledgement failed is retried on its next copy, never offered to onUnclaimedPurchase a second time.
Changed
expoIapProductProvider answers the caller before acknowledging, for
every host. On the listener path purchase() can resolve "purchased"
before finishTransaction settles, so a host that reads unfinished
transactions straight after "purchased" may still see it. When iOS returns
the transaction from requestPurchase directly, purchase() still waits for
the acknowledgement, as before.
Hosts passing onUnclaimedPurchase to expoIapProductProvider now see
more transactions. Any completed transaction with no live caller is
offered, including later transactions for a product this process already
bought (for example, a renewal arriving on the update stream). Those were
previously finished without asking. Return true only once entitlement is
granted; a throw is treated as false. A declined transaction is left
unfinished: iOS re-delivers it next launch, but on Android this provider
does not re-query it, and Play refunds it after three days unacknowledged.
Without onUnclaimedPurchase, a late transaction is still finished but
no longer silently: a dispatched product's transaction that arrives after its purchase() returned is still finished, and the provider now warns once
that nothing granted it. Pass onUnclaimedPurchase; it is the only place
an approved Ask to Buy purchase can be granted.