@entitlehub/react-native 0.1.8 → 0.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.10 (2026-09-21)
4
+
5
+ ### Fixed
6
+ - **0.1.9 reported itself as 0.1.8.** The bundled `RN_SDK_VERSION` was not bumped alongside
7
+ `package.json`, so the `X-EntitleHub-Client` header sent on every request attributed all 0.1.9
8
+ traffic to 0.1.8. Nothing in your app behaves differently, but it under-reported adoption of the
9
+ release that fixes the refund path, which is exactly the one worth tracking. Found by the
10
+ StatSnap integration after upgrading.
11
+ - The constant now comes from `package.json` at build time, and a test asserts the built bundle
12
+ carries the published version.
13
+
14
+ ## 0.1.9 (2026-09-20)
15
+
16
+ ### Fixed
17
+ - **`restorePurchases()` swallowed every per-purchase failure.** Each purchase was reported with
18
+ `.catch(() => {})`, so a `409 receipt_revoked` (the signal that a purchase was REFUNDED) died
19
+ inside the SDK and no amount of catching in the app could see it. A restore is the main place a
20
+ refund surfaces, so this was the path that mattered most. Failures now reach an optional
21
+ `onReportError`, and `console.warn` otherwise.
22
+
23
+ ### Read this before upgrading
24
+ - **If you were catching around `restorePurchases()` to detect a refund, that never worked and
25
+ still will not.** The loop deliberately continues past a bad receipt so one stale purchase can't
26
+ cost someone the rest of theirs, which means `restorePurchases()` **resolves** rather than
27
+ rejecting. Pass `onReportError` - it is the only way to see these:
28
+
29
+ ```ts
30
+ import { restorePurchases, isReceiptRevoked } from "@entitlehub/react-native";
31
+
32
+ const info = await restorePurchases({
33
+ onReportError: (err) => { if (isReceiptRevoked(err)) showRefundNotice(); },
34
+ });
35
+ ```
36
+
37
+ Passing the options object to **0.1.7/0.1.8 is harmless** (JS drops the extra argument), so it is
38
+ safe to add before you upgrade.
39
+ - `isReceiptRevoked` is re-exported here, so you don't need to import `@entitlehub/sdk` for it.
40
+ It is a **0.3.1** symbol, so the peer range moves to `>=0.3.1`. On an older core SDK the
41
+ re-export resolves to `undefined` rather than failing loudly, which is the whole reason the
42
+ range had to move: `>=0.2.0` would have permitted a version that cannot satisfy it.
43
+
3
44
  ## 0.1.8 (2026-09-19)
4
45
 
5
46
  ### Fixed
package/dist/index.cjs CHANGED
@@ -37,12 +37,19 @@ __export(index_exports, {
37
37
  getOfferings: () => getOfferings,
38
38
  getProducts: () => getProducts,
39
39
  isEntitled: () => isEntitled,
40
+ isReceiptRevoked: () => import_sdk2.isReceiptRevoked,
40
41
  logIn: () => logIn,
41
42
  purchaseProduct: () => purchaseProduct,
42
43
  restorePurchases: () => restorePurchases
43
44
  });
44
45
  module.exports = __toCommonJS(index_exports);
45
46
  var import_sdk = require("@entitlehub/sdk");
47
+
48
+ // src/version.ts
49
+ var RN_SDK_VERSION = "0.1.10";
50
+
51
+ // src/index.ts
52
+ var import_sdk2 = require("@entitlehub/sdk");
46
53
  var _iap;
47
54
  async function loadIap() {
48
55
  if (_iap) return _iap;
@@ -62,7 +69,6 @@ function iap() {
62
69
  if (!_iap) throw new Error("Call configureEntitleHub({ apiKey, appUserId }) before using EntitleHub purchases.");
63
70
  return _iap;
64
71
  }
65
- var RN_SDK_VERSION = "0.1.8";
66
72
  var client;
67
73
  function eh() {
68
74
  if (!client) throw new Error("Call configureEntitleHub({ apiKey, appUserId }) before using EntitleHub.");
@@ -232,12 +238,16 @@ async function purchaseProduct(productId, opts = {}) {
232
238
  armTimer(sheetTimeoutMs);
233
239
  });
234
240
  }
235
- async function restorePurchases() {
241
+ async function restorePurchases(options = {}) {
236
242
  const I = iap();
237
243
  const purchases = await I.getAvailablePurchases() || [];
238
244
  for (const p of purchases) {
239
- await reportToEntitleHub(p, p.productId ?? p.id ?? p.sku, true).catch(() => {
240
- });
245
+ try {
246
+ await reportToEntitleHub(p, p.productId ?? p.id ?? p.sku, true);
247
+ } catch (err) {
248
+ if (options.onReportError) options.onReportError(err, p);
249
+ else console.warn("[entitlehub] restore: could not report", p?.productId ?? p?.id ?? p?.sku, err);
250
+ }
241
251
  }
242
252
  return eh().getCustomerInfo({ fetchPolicy: "network-only" });
243
253
  }
@@ -267,6 +277,7 @@ async function reportToEntitleHub(purchase, productId, isSubscription) {
267
277
  getOfferings,
268
278
  getProducts,
269
279
  isEntitled,
280
+ isReceiptRevoked,
270
281
  logIn,
271
282
  purchaseProduct,
272
283
  restorePurchases
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CustomerInfo, Offerings } from '@entitlehub/sdk';
2
- export { CustomerInfo, Offerings } from '@entitlehub/sdk';
2
+ export { CustomerInfo, Offerings, isReceiptRevoked } from '@entitlehub/sdk';
3
3
 
4
4
  interface ConfigureOptions {
5
5
  /** Your EntitleHub publishable key (pk_live_… / pk_test_…). Client-safe. */
@@ -76,9 +76,22 @@ declare function purchaseProduct(productId: string, opts?: {
76
76
  /** How long to poll for confirmation before reporting pending (default 30s). */
77
77
  reconcileMs?: number;
78
78
  }): Promise<CustomerInfo>;
79
- /** Restore the user's purchases: re-validate each with EntitleHub, then return entitlements. */
80
- declare function restorePurchases(): Promise<CustomerInfo>;
79
+
80
+ interface RestoreOptions {
81
+ /**
82
+ * Called for each purchase that could not be reported. The restore continues either way, so one
83
+ * stale receipt can't cost the user the rest of their purchases.
84
+ *
85
+ * Worth handling: a restore is where a REFUND surfaces. Apple stamps a refunded transaction with
86
+ * revocationDate permanently and StoreKit keeps handing it back, so reporting it answers
87
+ * 409 `receipt_revoked` (EntitleHub revokes the grant server-side at the same time). That 409 is
88
+ * the only signal telling you the entitlement went away because the money was returned, rather
89
+ * than for some unexplained reason.
90
+ */
91
+ onReportError?: (error: unknown, purchase: unknown) => void;
92
+ }
93
+ declare function restorePurchases(options?: RestoreOptions): Promise<CustomerInfo>;
81
94
  /** Subscribe to entitlement changes (e.g. after a purchase). Returns an unsubscribe function. */
82
95
  declare function addCustomerInfoUpdateListener(fn: (info: CustomerInfo) => void): () => void;
83
96
 
84
- export { type ConfigureOptions, PurchaseError, type PurchaseErrorCode, addCustomerInfoUpdateListener, configureEntitleHub, getCustomerInfo, getOfferings, getProducts, isEntitled, logIn, purchaseProduct, restorePurchases };
97
+ export { type ConfigureOptions, PurchaseError, type PurchaseErrorCode, type RestoreOptions, addCustomerInfoUpdateListener, configureEntitleHub, getCustomerInfo, getOfferings, getProducts, isEntitled, logIn, purchaseProduct, restorePurchases };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CustomerInfo, Offerings } from '@entitlehub/sdk';
2
- export { CustomerInfo, Offerings } from '@entitlehub/sdk';
2
+ export { CustomerInfo, Offerings, isReceiptRevoked } from '@entitlehub/sdk';
3
3
 
4
4
  interface ConfigureOptions {
5
5
  /** Your EntitleHub publishable key (pk_live_… / pk_test_…). Client-safe. */
@@ -76,9 +76,22 @@ declare function purchaseProduct(productId: string, opts?: {
76
76
  /** How long to poll for confirmation before reporting pending (default 30s). */
77
77
  reconcileMs?: number;
78
78
  }): Promise<CustomerInfo>;
79
- /** Restore the user's purchases: re-validate each with EntitleHub, then return entitlements. */
80
- declare function restorePurchases(): Promise<CustomerInfo>;
79
+
80
+ interface RestoreOptions {
81
+ /**
82
+ * Called for each purchase that could not be reported. The restore continues either way, so one
83
+ * stale receipt can't cost the user the rest of their purchases.
84
+ *
85
+ * Worth handling: a restore is where a REFUND surfaces. Apple stamps a refunded transaction with
86
+ * revocationDate permanently and StoreKit keeps handing it back, so reporting it answers
87
+ * 409 `receipt_revoked` (EntitleHub revokes the grant server-side at the same time). That 409 is
88
+ * the only signal telling you the entitlement went away because the money was returned, rather
89
+ * than for some unexplained reason.
90
+ */
91
+ onReportError?: (error: unknown, purchase: unknown) => void;
92
+ }
93
+ declare function restorePurchases(options?: RestoreOptions): Promise<CustomerInfo>;
81
94
  /** Subscribe to entitlement changes (e.g. after a purchase). Returns an unsubscribe function. */
82
95
  declare function addCustomerInfoUpdateListener(fn: (info: CustomerInfo) => void): () => void;
83
96
 
84
- export { type ConfigureOptions, PurchaseError, type PurchaseErrorCode, addCustomerInfoUpdateListener, configureEntitleHub, getCustomerInfo, getOfferings, getProducts, isEntitled, logIn, purchaseProduct, restorePurchases };
97
+ export { type ConfigureOptions, PurchaseError, type PurchaseErrorCode, type RestoreOptions, addCustomerInfoUpdateListener, configureEntitleHub, getCustomerInfo, getOfferings, getProducts, isEntitled, logIn, purchaseProduct, restorePurchases };
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/index.ts
2
2
  import { EntitleHub } from "@entitlehub/sdk";
3
+
4
+ // src/version.ts
5
+ var RN_SDK_VERSION = "0.1.10";
6
+
7
+ // src/index.ts
8
+ import { isReceiptRevoked } from "@entitlehub/sdk";
3
9
  var _iap;
4
10
  async function loadIap() {
5
11
  if (_iap) return _iap;
@@ -19,7 +25,6 @@ function iap() {
19
25
  if (!_iap) throw new Error("Call configureEntitleHub({ apiKey, appUserId }) before using EntitleHub purchases.");
20
26
  return _iap;
21
27
  }
22
- var RN_SDK_VERSION = "0.1.8";
23
28
  var client;
24
29
  function eh() {
25
30
  if (!client) throw new Error("Call configureEntitleHub({ apiKey, appUserId }) before using EntitleHub.");
@@ -189,12 +194,16 @@ async function purchaseProduct(productId, opts = {}) {
189
194
  armTimer(sheetTimeoutMs);
190
195
  });
191
196
  }
192
- async function restorePurchases() {
197
+ async function restorePurchases(options = {}) {
193
198
  const I = iap();
194
199
  const purchases = await I.getAvailablePurchases() || [];
195
200
  for (const p of purchases) {
196
- await reportToEntitleHub(p, p.productId ?? p.id ?? p.sku, true).catch(() => {
197
- });
201
+ try {
202
+ await reportToEntitleHub(p, p.productId ?? p.id ?? p.sku, true);
203
+ } catch (err) {
204
+ if (options.onReportError) options.onReportError(err, p);
205
+ else console.warn("[entitlehub] restore: could not report", p?.productId ?? p?.id ?? p?.sku, err);
206
+ }
198
207
  }
199
208
  return eh().getCustomerInfo({ fetchPolicy: "network-only" });
200
209
  }
@@ -223,6 +232,7 @@ export {
223
232
  getOfferings,
224
233
  getProducts,
225
234
  isEntitled,
235
+ isReceiptRevoked,
226
236
  logIn,
227
237
  purchaseProduct,
228
238
  restorePurchases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entitlehub/react-native",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "EntitleHub for React Native & Expo, one call to purchase and unlock entitlements. Wraps expo-iap for the native store sheet and validates receipts via EntitleHub.",
5
5
  "keywords": [
6
6
  "entitlehub",
@@ -45,15 +45,16 @@
45
45
  ],
46
46
  "sideEffects": false,
47
47
  "scripts": {
48
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
48
+ "build": "node scripts/gen-version.mjs && tsup src/index.ts --format esm,cjs --dts --clean",
49
49
  "typecheck": "tsc --noEmit",
50
+ "test": "npm run build && node --test \"test/*.test.mjs\"",
50
51
  "prepublishOnly": "npm run build"
51
52
  },
52
53
  "engines": {
53
54
  "node": ">=18"
54
55
  },
55
56
  "peerDependencies": {
56
- "@entitlehub/sdk": ">=0.2.0",
57
+ "@entitlehub/sdk": ">=0.3.1",
57
58
  "expo-iap": "*",
58
59
  "react": ">=17"
59
60
  },