@livx.cc/native-kit 0.38.0 → 0.39.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/README.md CHANGED
@@ -26,7 +26,7 @@ mixpanel.register(await kit.context());
26
26
 
27
27
  The kit detects whether it's running inside the appwrap shell and routes each call to the native bridge or a web fallback, so a single web build works everywhere. Capabilities advertise their real backing (`native` / `web` / `none`) so your UI can be honest about what's available.
28
28
 
29
- Covers haptics, share, storage, notifications, OAuth, billing (IAP), health, geo, camera, photos, contacts, calendar, motion, reviews, tracking (iOS App Tracking Transparency), and more.
29
+ Covers haptics, share, storage, notifications, OAuth, billing (IAP), health, geo, heading (compass), camera, photos, contacts, calendar, motion, reviews, tracking (iOS App Tracking Transparency), and more.
30
30
 
31
31
  **Full documentation, capability model, and the bridge protocol:** see the [appwrap README](https://github.com/Livshitz/appwrap#readme).
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/native-kit",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "Isomorphic native-capabilities kit for PWAs — same API in browser and in an appwrap native shell. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
package/src/index.ts CHANGED
@@ -65,4 +65,6 @@ export type {
65
65
  ProductType,
66
66
  PurchaseReceipt,
67
67
  PurchaseResult,
68
+ ReceiptEntitlements,
69
+ ReceiptOutcome,
68
70
  } from './modules/billing/types';
@@ -1,7 +1,16 @@
1
1
  import type { NativeKit } from '../../core/NativeKit';
2
2
  import { KitError, type Capability, type Unsubscribe } from '../../core/types';
3
3
  import { ClientTrustedValidator } from './validators';
4
- import type { BillingProvider, BillingValidator, Entitlement, Product, PurchaseReceipt, PurchaseResult } from './types';
4
+ import type {
5
+ BillingProvider,
6
+ BillingValidator,
7
+ Entitlement,
8
+ Product,
9
+ PurchaseReceipt,
10
+ PurchaseResult,
11
+ ReceiptEntitlements,
12
+ ReceiptOutcome,
13
+ } from './types';
5
14
 
6
15
  /**
7
16
  * In-app purchases & subscriptions — ONE API across web and native.
@@ -89,14 +98,29 @@ export class BillingModule {
89
98
  * Native iOS only, and only meaningful with a **server validator** (the bare receipt has no
90
99
  * product id — a client-trusted validator can't interpret it). Resolves `[]` on the web,
91
100
  * on Android (no StoreKit-1 receipt), or when no receipt is present (e.g. a dev build).
101
+ *
102
+ * The `[]` is AMBIGUOUS by design-history: it means both "no entitlements" and "never even
103
+ * asked". Use {@link receiptEntitlements} to tell those apart.
92
104
  */
93
105
  async entitlementsFromReceipt(): Promise<Entitlement[]> {
106
+ return (await this.receiptEntitlements()).entitlements;
107
+ }
108
+
109
+ /**
110
+ * {@link entitlementsFromReceipt} plus **why** — the same check, but the three
111
+ * didn't-even-ask paths are reported instead of being flattened into `[]`. See
112
+ * {@link ReceiptOutcome}: only `'validated'` makes an empty list a real answer.
113
+ *
114
+ * A validator that errors (network, Apple status 21004, …) REJECTS rather than
115
+ * reporting an outcome — a failed check is not a verdict about the user.
116
+ */
117
+ async receiptEntitlements(): Promise<ReceiptEntitlements> {
94
118
  await this.kit.ready();
95
- if (this.capability !== 'native') return []; // web, or a shell with no native store handler (Android today)
96
- if (this.validator instanceof ClientTrustedValidator) return []; // bare receipt has no product id → would grant a bogus active entitlement; needs a server validator
119
+ if (this.capability !== 'native') return { entitlements: [], outcome: 'not-native' }; // web, or a shell with no native store handler (Android today)
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
97
121
  const receipt = await this.kit.invoke<PurchaseReceipt>('billing.appReceipt');
98
- if (!receipt?.appReceipt) return [];
99
- return this.validator.validate(receipt); // server-of-record turns the receipt into entitlements
122
+ if (!receipt?.appReceipt) return { entitlements: [], outcome: 'no-receipt' };
123
+ return { entitlements: await this.validator.validate(receipt), outcome: 'validated' }; // server-of-record turns the receipt into entitlements
100
124
  }
101
125
 
102
126
  /** Current entitlements from the configured server-of-record (web provider or validator). */
@@ -58,6 +58,27 @@ export interface PurchaseResult {
58
58
  entitlements: Entitlement[];
59
59
  }
60
60
 
61
+ /**
62
+ * Why a receipt check produced what it did — the four outcomes that `Entitlement[]`
63
+ * alone collapses into an indistinguishable `[]`.
64
+ *
65
+ * - `validated` the server-of-record read the receipt; `entitlements` is its verdict
66
+ * (still `[]` when the user genuinely owns nothing — the only outcome
67
+ * 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: no StoreKit-1 receipt).
69
+ * - `no-validator` only a {@link ClientTrustedValidator} is wired; a bare app receipt carries no
70
+ * product id, so trusting it would grant a bogus entitlement. Wire a server validator.
71
+ * - `no-receipt` this build has no App Store receipt. StoreKit only issues one after a purchase or
72
+ * restore, so locally-installed dev builds legitimately have none.
73
+ */
74
+ export type ReceiptOutcome = 'validated' | 'not-native' | 'no-validator' | 'no-receipt';
75
+
76
+ /** The outcome-carrying result of {@link BillingModule.receiptEntitlements}. */
77
+ export interface ReceiptEntitlements {
78
+ entitlements: Entitlement[];
79
+ outcome: ReceiptOutcome;
80
+ }
81
+
61
82
  /**
62
83
  * The swappable seam. The kit owns the on-device purchase flow; *who decides the
63
84
  * user is actually entitled* is up to the app. Implement this (or configure the