@livx.cc/native-kit 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/core/NativeKit.ts
CHANGED
|
@@ -43,6 +43,22 @@ export class NativeKitOptions {
|
|
|
43
43
|
handshakeTimeoutMs = 3000;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Method domains the web fallback must NEVER be retried against — deliberately NOT an option:
|
|
48
|
+
* this is a policy invariant, not a knob a consumer should be able to switch off.
|
|
49
|
+
*
|
|
50
|
+
* `billing.`: purchases inside a native shell belong to the device store, full stop.
|
|
51
|
+
* - {@link BillingModule.capability} promises to "never silently fall back to web inside a
|
|
52
|
+
* native shell" — the generic retry here would bypass that contract behind its back.
|
|
53
|
+
* - The WebAdapter's only billing behaviour is a hardcoded throw ("use a web checkout"), so
|
|
54
|
+
* retrying billing can't succeed — it can only REPLACE the shell's real, actionable error
|
|
55
|
+
* (e.g. UNSUPPORTED "Purchases are disabled on this device" from a Screen-Time/MDM-restricted
|
|
56
|
+
* iPhone) with a false one claiming the user is on the web. Zero upside, only downside.
|
|
57
|
+
* - Steering an iOS user to an external checkout is App Store Guideline 3.1.1 territory. Today
|
|
58
|
+
* that string only reaches a `catch`; this keeps it from ever reaching a paywall.
|
|
59
|
+
*/
|
|
60
|
+
const WEB_FALLBACK_EXCLUDED_DOMAINS = ['billing.'];
|
|
61
|
+
|
|
46
62
|
/**
|
|
47
63
|
* Flat, vendor-neutral analytics bag — spread straight into your analytics provider's
|
|
48
64
|
* super-properties (e.g. `mixpanel.register(await kit.context())`). Native-only fields
|
|
@@ -265,7 +281,12 @@ export class NativeKit {
|
|
|
265
281
|
try {
|
|
266
282
|
return await this.adapter!.invoke<T>(method, params, opts);
|
|
267
283
|
} catch (e) {
|
|
268
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
this.webFallback &&
|
|
286
|
+
e instanceof KitError &&
|
|
287
|
+
e.code === 'UNSUPPORTED' &&
|
|
288
|
+
!WEB_FALLBACK_EXCLUDED_DOMAINS.some((d) => method.startsWith(d))
|
|
289
|
+
) {
|
|
269
290
|
return this.webFallback.invoke<T>(method, params, opts);
|
|
270
291
|
}
|
|
271
292
|
throw e;
|
|
@@ -116,7 +116,7 @@ export class BillingModule {
|
|
|
116
116
|
*/
|
|
117
117
|
async receiptEntitlements(): Promise<ReceiptEntitlements> {
|
|
118
118
|
await this.kit.ready();
|
|
119
|
-
if (this.capability !== 'native') return { entitlements: [], outcome: 'not-native' }; // web, or a shell with no native store handler (Android
|
|
119
|
+
if (this.capability !== 'native') return { entitlements: [], outcome: 'not-native' }; // web, or a shell with no native store handler (Android IS native now, but has no on-disk receipt → yields 'no-receipt' below)
|
|
120
120
|
if (this.validator instanceof ClientTrustedValidator) return { entitlements: [], outcome: 'no-validator' }; // bare receipt has no product id → would grant a bogus active entitlement; needs a server validator
|
|
121
121
|
const receipt = await this.kit.invoke<PurchaseReceipt>('billing.appReceipt');
|
|
122
122
|
if (!receipt?.appReceipt) return { entitlements: [], outcome: 'no-receipt' };
|
|
@@ -142,19 +142,22 @@ export class BillingModule {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* iOS 15+
|
|
146
|
-
* (`AppStore.showManageSubscriptions(in:)`)
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
145
|
+
* The in-app / native subscription-management surface. On iOS 15+ this presents StoreKit 2's
|
|
146
|
+
* **in-app** sheet (`AppStore.showManageSubscriptions(in:)`) — unlike {@link manageSubscriptions}
|
|
147
|
+
* (App Store account page, StoreKit 1), it stays inside the app AND shows sandbox / TestFlight
|
|
148
|
+
* subscriptions, so it's the path to verify/cancel a sub during testing. On Android (native billing
|
|
149
|
+
* enabled) there is no in-app sheet, so this dispatches to the same Play subscriptions deep link as
|
|
150
|
+
* {@link manageSubscriptions} (the native handler maps both). Opt-in on iOS: the default
|
|
151
|
+
* `manageSubscriptions()` deep-link is unchanged; call this explicitly to use the sheet.
|
|
150
152
|
*
|
|
151
|
-
* Throws `UNSUPPORTED` on the web
|
|
152
|
-
* presentable scene / a StoreKit error. Resolves once the user dismisses the sheet
|
|
153
|
+
* Throws `UNSUPPORTED` only on the web (no native store), and `NATIVE_ERROR` on iOS <15 / no
|
|
154
|
+
* presentable scene / a StoreKit error. Resolves once the user dismisses the sheet (iOS) or the
|
|
155
|
+
* deep link is opened (Android).
|
|
153
156
|
*/
|
|
154
157
|
async showManageSubscriptionsSheet(): Promise<void> {
|
|
155
158
|
await this.kit.ready();
|
|
156
159
|
if (this.capability !== 'native') {
|
|
157
|
-
throw new KitError('UNSUPPORTED', 'The
|
|
160
|
+
throw new KitError('UNSUPPORTED', 'The subscriptions sheet needs a native store. On the web, use manageSubscriptions() (your web billing provider portal).');
|
|
158
161
|
}
|
|
159
162
|
// Dismiss-bound: resolves only when the USER closes the sheet (no meaningful deadline).
|
|
160
163
|
// A watchdog could only false-timeout mid-sheet → spurious fallback, so disable it.
|
|
@@ -8,6 +8,18 @@ export type ProductType =
|
|
|
8
8
|
| 'nonRenewable'
|
|
9
9
|
| 'unknown';
|
|
10
10
|
|
|
11
|
+
/** An introductory / free-trial phase that precedes the standard subscription price. */
|
|
12
|
+
export interface IntroOffer {
|
|
13
|
+
/** Price during the intro phase in `currency`; 0 for a free trial. */
|
|
14
|
+
price: number;
|
|
15
|
+
/** Localized formatted intro price, e.g. "Free" / "$0.99" — show this to users. */
|
|
16
|
+
displayPrice: string;
|
|
17
|
+
/** ISO-8601 duration of the intro phase, e.g. "P1W", "P7D". */
|
|
18
|
+
period?: string;
|
|
19
|
+
/** True when `price === 0` (a free trial). */
|
|
20
|
+
isFreeTrial: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
/** A purchasable product as the store describes it (localized). */
|
|
12
24
|
export interface Product {
|
|
13
25
|
id: string;
|
|
@@ -21,6 +33,13 @@ export interface Product {
|
|
|
21
33
|
type: ProductType;
|
|
22
34
|
/** ISO-8601 duration for auto-renewables, e.g. "P1M", "P1Y". */
|
|
23
35
|
subscriptionPeriod?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Introductory / free-trial offer the CURRENT user is eligible for, when the store reports one.
|
|
38
|
+
* Populated on Android (Play Billing SubscriptionOfferDetails' zero-price phase). Not yet populated
|
|
39
|
+
* on iOS — StoreKit 1's `introductoryPrice` is unread by the current iOS mapper (parity follow-up);
|
|
40
|
+
* the trial still APPLIES at checkout because StoreKit/Play show trial terms in their own sheet.
|
|
41
|
+
*/
|
|
42
|
+
introOffer?: IntroOffer;
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
/** A normalized entitlement — "this user owns/subscribes to X". */
|
|
@@ -65,7 +84,9 @@ export interface PurchaseResult {
|
|
|
65
84
|
* - `validated` the server-of-record read the receipt; `entitlements` is its verdict
|
|
66
85
|
* (still `[]` when the user genuinely owns nothing — the only outcome
|
|
67
86
|
* where an empty list is an ANSWER rather than a non-answer).
|
|
68
|
-
* - `not-native` web, or a shell with no native store handler (Android
|
|
87
|
+
* - `not-native` web, or a shell with no native store handler. (Android now HAS a native billing
|
|
88
|
+
* handler, so it reports `native`/`no-receipt` here — not `not-native`; it just has
|
|
89
|
+
* no StoreKit-1-style on-disk app receipt, so `receiptEntitlements` yields `no-receipt`.)
|
|
69
90
|
* - `no-validator` only a {@link ClientTrustedValidator} is wired; a bare app receipt carries no
|
|
70
91
|
* product id, so trusting it would grant a bogus entitlement. Wire a server validator.
|
|
71
92
|
* - `no-receipt` this build has no App Store receipt. StoreKit only issues one after a purchase or
|