@entitlehub/react-native 0.1.0 → 0.1.1
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 +10 -0
- package/dist/index.cjs +59 -11
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +59 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **`purchaseProduct` crashed** — 0.1.0 was written against expo-iap's old flat `requestPurchase`
|
|
7
|
+
API and read the purchase from the return value. `expo-iap@4.x` is **event-based** with a
|
|
8
|
+
discriminated request. Now: `requestPurchase({ request: { apple: { sku }, google: { skus } },
|
|
9
|
+
type })` with the result bridged from `purchaseUpdatedListener` / `purchaseErrorListener`.
|
|
10
|
+
- Android subscriptions now pass the required `offerToken` (fetched from the product).
|
|
11
|
+
- `getProducts` uses expo-iap's `fetchProducts` (the old `getProducts` no longer exists).
|
|
12
|
+
|
|
3
13
|
## 0.1.0 — 2026-07-18
|
|
4
14
|
|
|
5
15
|
Initial release.
|
package/dist/index.cjs
CHANGED
|
@@ -66,20 +66,68 @@ async function isEntitled(entitlementId) {
|
|
|
66
66
|
async function getOfferings() {
|
|
67
67
|
return eh().getOfferings();
|
|
68
68
|
}
|
|
69
|
-
async function getProducts(productIds) {
|
|
70
|
-
return iap().
|
|
69
|
+
async function getProducts(productIds, type = "all") {
|
|
70
|
+
return iap().fetchProducts({ skus: productIds, type });
|
|
71
71
|
}
|
|
72
72
|
async function purchaseProduct(productId, opts = {}) {
|
|
73
73
|
const I = iap();
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
const isSub = Boolean(opts.isSubscription);
|
|
75
|
+
const request = { apple: { sku: productId }, google: { skus: [productId] } };
|
|
76
|
+
if (isSub) {
|
|
77
|
+
try {
|
|
78
|
+
const products = await I.fetchProducts({ skus: [productId], type: "subs" });
|
|
79
|
+
const product = Array.isArray(products) ? products.find((p) => (p?.id ?? p?.productId) === productId) : void 0;
|
|
80
|
+
const offer = product?.subscriptionOfferDetailsAndroid?.[0] ?? product?.subscriptionOfferDetails?.[0];
|
|
81
|
+
if (offer?.offerToken) {
|
|
82
|
+
request.google = { skus: [productId], subscriptionOffers: [{ sku: productId, offerToken: offer.offerToken }] };
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
81
86
|
}
|
|
82
|
-
return
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
let settled = false;
|
|
89
|
+
const cleanup = () => {
|
|
90
|
+
try {
|
|
91
|
+
updSub?.remove();
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
errSub?.remove();
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const updSub = I.purchaseUpdatedListener(async (purchase) => {
|
|
100
|
+
if (settled) return;
|
|
101
|
+
settled = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
try {
|
|
104
|
+
const info = await reportToEntitleHub(purchase, productId, isSub);
|
|
105
|
+
try {
|
|
106
|
+
await I.finishTransaction({ purchase, isConsumable: false });
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
resolve(info);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
reject(e);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const errSub = I.purchaseErrorListener((error) => {
|
|
115
|
+
if (settled) return;
|
|
116
|
+
settled = true;
|
|
117
|
+
cleanup();
|
|
118
|
+
const code = error?.code;
|
|
119
|
+
const cancelled = code === "E_USER_CANCELLED" || code === "user-cancelled" || /cancel/i.test(String(error?.message));
|
|
120
|
+
reject(new Error(cancelled ? "purchase-cancelled" : error?.message || "purchase-failed"));
|
|
121
|
+
});
|
|
122
|
+
Promise.resolve(
|
|
123
|
+
I.requestPurchase({ request, type: isSub ? "subs" : "in-app" })
|
|
124
|
+
).catch((e) => {
|
|
125
|
+
if (settled) return;
|
|
126
|
+
settled = true;
|
|
127
|
+
cleanup();
|
|
128
|
+
reject(e);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
83
131
|
}
|
|
84
132
|
async function restorePurchases() {
|
|
85
133
|
const I = iap();
|
|
@@ -94,7 +142,7 @@ function addCustomerInfoUpdateListener(fn) {
|
|
|
94
142
|
return eh().addCustomerInfoUpdateListener(fn);
|
|
95
143
|
}
|
|
96
144
|
async function reportToEntitleHub(purchase, productId, isSubscription) {
|
|
97
|
-
const jws = purchase?.
|
|
145
|
+
const jws = purchase?.jwsRepresentation ?? purchase?.jwsRepresentationIos;
|
|
98
146
|
if (jws) return eh().reportPurchase({ signedTransaction: jws });
|
|
99
147
|
const token = purchase?.purchaseTokenAndroid ?? purchase?.purchaseToken;
|
|
100
148
|
if (token) {
|
package/dist/index.d.cts
CHANGED
|
@@ -19,12 +19,15 @@ declare function getCustomerInfo(): Promise<CustomerInfo>;
|
|
|
19
19
|
declare function isEntitled(entitlementId: string): Promise<boolean>;
|
|
20
20
|
/** Your EntitleHub catalog (entitlements + products) for building a paywall. */
|
|
21
21
|
declare function getOfferings(): Promise<Offerings>;
|
|
22
|
-
/** Live store products (prices, localized titles)
|
|
23
|
-
declare function getProducts(productIds: string[]): Promise<any[]>;
|
|
22
|
+
/** Live store products (prices, localized titles) from the native store (expo-iap `fetchProducts`). */
|
|
23
|
+
declare function getProducts(productIds: string[], type?: "in-app" | "subs" | "all"): Promise<any[]>;
|
|
24
24
|
/**
|
|
25
25
|
* Buy a product in one call: open the native purchase sheet, validate the receipt with EntitleHub,
|
|
26
26
|
* and return the updated entitlements. Purchase + entitlement sync, the RevenueCat way.
|
|
27
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* expo-iap's purchase flow is event-based — the result arrives on `purchaseUpdatedListener`, not the
|
|
29
|
+
* `requestPurchase` return value — so we bridge that back into a promise here.
|
|
30
|
+
* Rejects with "purchase-cancelled" if the user backs out.
|
|
28
31
|
*/
|
|
29
32
|
declare function purchaseProduct(productId: string, opts?: {
|
|
30
33
|
isSubscription?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -19,12 +19,15 @@ declare function getCustomerInfo(): Promise<CustomerInfo>;
|
|
|
19
19
|
declare function isEntitled(entitlementId: string): Promise<boolean>;
|
|
20
20
|
/** Your EntitleHub catalog (entitlements + products) for building a paywall. */
|
|
21
21
|
declare function getOfferings(): Promise<Offerings>;
|
|
22
|
-
/** Live store products (prices, localized titles)
|
|
23
|
-
declare function getProducts(productIds: string[]): Promise<any[]>;
|
|
22
|
+
/** Live store products (prices, localized titles) from the native store (expo-iap `fetchProducts`). */
|
|
23
|
+
declare function getProducts(productIds: string[], type?: "in-app" | "subs" | "all"): Promise<any[]>;
|
|
24
24
|
/**
|
|
25
25
|
* Buy a product in one call: open the native purchase sheet, validate the receipt with EntitleHub,
|
|
26
26
|
* and return the updated entitlements. Purchase + entitlement sync, the RevenueCat way.
|
|
27
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* expo-iap's purchase flow is event-based — the result arrives on `purchaseUpdatedListener`, not the
|
|
29
|
+
* `requestPurchase` return value — so we bridge that back into a promise here.
|
|
30
|
+
* Rejects with "purchase-cancelled" if the user backs out.
|
|
28
31
|
*/
|
|
29
32
|
declare function purchaseProduct(productId: string, opts?: {
|
|
30
33
|
isSubscription?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -41,20 +41,68 @@ async function isEntitled(entitlementId) {
|
|
|
41
41
|
async function getOfferings() {
|
|
42
42
|
return eh().getOfferings();
|
|
43
43
|
}
|
|
44
|
-
async function getProducts(productIds) {
|
|
45
|
-
return iap().
|
|
44
|
+
async function getProducts(productIds, type = "all") {
|
|
45
|
+
return iap().fetchProducts({ skus: productIds, type });
|
|
46
46
|
}
|
|
47
47
|
async function purchaseProduct(productId, opts = {}) {
|
|
48
48
|
const I = iap();
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
const isSub = Boolean(opts.isSubscription);
|
|
50
|
+
const request = { apple: { sku: productId }, google: { skus: [productId] } };
|
|
51
|
+
if (isSub) {
|
|
52
|
+
try {
|
|
53
|
+
const products = await I.fetchProducts({ skus: [productId], type: "subs" });
|
|
54
|
+
const product = Array.isArray(products) ? products.find((p) => (p?.id ?? p?.productId) === productId) : void 0;
|
|
55
|
+
const offer = product?.subscriptionOfferDetailsAndroid?.[0] ?? product?.subscriptionOfferDetails?.[0];
|
|
56
|
+
if (offer?.offerToken) {
|
|
57
|
+
request.google = { skus: [productId], subscriptionOffers: [{ sku: productId, offerToken: offer.offerToken }] };
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
|
-
return
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
let settled = false;
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
try {
|
|
66
|
+
updSub?.remove();
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
errSub?.remove();
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const updSub = I.purchaseUpdatedListener(async (purchase) => {
|
|
75
|
+
if (settled) return;
|
|
76
|
+
settled = true;
|
|
77
|
+
cleanup();
|
|
78
|
+
try {
|
|
79
|
+
const info = await reportToEntitleHub(purchase, productId, isSub);
|
|
80
|
+
try {
|
|
81
|
+
await I.finishTransaction({ purchase, isConsumable: false });
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
resolve(info);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
reject(e);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const errSub = I.purchaseErrorListener((error) => {
|
|
90
|
+
if (settled) return;
|
|
91
|
+
settled = true;
|
|
92
|
+
cleanup();
|
|
93
|
+
const code = error?.code;
|
|
94
|
+
const cancelled = code === "E_USER_CANCELLED" || code === "user-cancelled" || /cancel/i.test(String(error?.message));
|
|
95
|
+
reject(new Error(cancelled ? "purchase-cancelled" : error?.message || "purchase-failed"));
|
|
96
|
+
});
|
|
97
|
+
Promise.resolve(
|
|
98
|
+
I.requestPurchase({ request, type: isSub ? "subs" : "in-app" })
|
|
99
|
+
).catch((e) => {
|
|
100
|
+
if (settled) return;
|
|
101
|
+
settled = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
reject(e);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
58
106
|
}
|
|
59
107
|
async function restorePurchases() {
|
|
60
108
|
const I = iap();
|
|
@@ -69,7 +117,7 @@ function addCustomerInfoUpdateListener(fn) {
|
|
|
69
117
|
return eh().addCustomerInfoUpdateListener(fn);
|
|
70
118
|
}
|
|
71
119
|
async function reportToEntitleHub(purchase, productId, isSubscription) {
|
|
72
|
-
const jws = purchase?.
|
|
120
|
+
const jws = purchase?.jwsRepresentation ?? purchase?.jwsRepresentationIos;
|
|
73
121
|
if (jws) return eh().reportPurchase({ signedTransaction: jws });
|
|
74
122
|
const token = purchase?.purchaseTokenAndroid ?? purchase?.purchaseToken;
|
|
75
123
|
if (token) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entitlehub/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|