@entitlehub/sdk 0.2.0 → 0.3.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/CHANGELOG.md +21 -9
- package/README.md +43 -6
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +257 -15
- package/dist/index.d.ts +257 -15
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,30 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.3.0 (2026-09-19)
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
|
-
-
|
|
8
|
+
- Catalog API on `EntitleHubServer`: `eh.entitlements`, `eh.products`, `eh.offerings`. Create,
|
|
9
|
+
update, archive and delete entitlements, products and offerings; attach/detach mappings from
|
|
10
|
+
either side; manage paywall packages. Refs take ids or the key / SKU / identifier you already
|
|
11
|
+
have. See [the Catalog API docs](https://entitlehub.com/docs/catalog-api).
|
|
12
|
+
- `ensure(...)` on entitlements and products: create, or return the existing row, so a catalog
|
|
13
|
+
setup script can run on every deploy.
|
|
14
|
+
- `EntitleHubError.apiCode` and `.body`: the API's machine-readable error code (e.g.
|
|
15
|
+
`product_exists`, `ambiguous_product`, `rate_limited`) and the full error payload.
|
|
16
|
+
|
|
17
|
+
## 0.2.0 (2026-07-18)
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Client `EntitleHub.reportPurchase(...)`: report a **validated** purchase (Apple JWS / Google
|
|
9
21
|
token / Stripe subscription) directly from the app with your publishable key; updates the cache
|
|
10
22
|
and notifies listeners. Validation makes a forged receipt impossible, so no secret key is needed
|
|
11
23
|
on the client. (The trusted server-report mode still requires a secret key.)
|
|
12
24
|
- Powers the new [`@entitlehub/react-native`](https://www.npmjs.com/package/@entitlehub/react-native)
|
|
13
25
|
purchase SDK.
|
|
14
26
|
|
|
15
|
-
## 0.1.3
|
|
27
|
+
## 0.1.3 (2026-07-18)
|
|
16
28
|
|
|
17
29
|
### Added
|
|
18
|
-
- `reportPurchase` accepts `stripeSubscriptionId
|
|
30
|
+
- `reportPurchase` accepts `stripeSubscriptionId`, EntitleHub validates the subscription against
|
|
19
31
|
your Stripe secret key (active/trialing → granted, current period end as the expiry).
|
|
20
32
|
|
|
21
|
-
## 0.1.2
|
|
33
|
+
## 0.1.2 (2026-07-18)
|
|
22
34
|
|
|
23
35
|
### Changed
|
|
24
36
|
- The SDK now lives in its own public repository:
|
|
25
37
|
[github.com/DanCue44/entitlehub-sdk](https://github.com/DanCue44/entitlehub-sdk) (MIT). Open-source
|
|
26
38
|
SDK, closed backend. `repository` / `bugs` metadata point there.
|
|
27
39
|
|
|
28
|
-
## 0.1.1
|
|
40
|
+
## 0.1.1 (2026-07-18)
|
|
29
41
|
|
|
30
42
|
### Added
|
|
31
43
|
- README badges (npm version, types, zero-deps, license) and docs links.
|
|
@@ -36,14 +48,14 @@ All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https
|
|
|
36
48
|
+ `isSubscription`), Apple StoreKit 2 (validated via `signedTransaction`), and trusted
|
|
37
49
|
server-report.
|
|
38
50
|
|
|
39
|
-
## 0.1.0
|
|
51
|
+
## 0.1.0 (2026-07-18)
|
|
40
52
|
|
|
41
53
|
Initial release.
|
|
42
54
|
|
|
43
|
-
- **`EntitleHub`** (client, publishable key)
|
|
55
|
+
- **`EntitleHub`** (client, publishable key): `getCustomerInfo`, `isEntitled`,
|
|
44
56
|
`checkEntitlement`, `getOfferings`, `logIn` / `logOut`, `addCustomerInfoUpdateListener`.
|
|
45
57
|
Cached CustomerInfo; safe for browsers, React Native, and Expo.
|
|
46
|
-
- **`EntitleHubServer`** (server, secret key)
|
|
58
|
+
- **`EntitleHubServer`** (server, secret key): `reportPurchase` (Apple JWS / Google Play
|
|
47
59
|
token / trusted), `grantEntitlement`, `getCustomerInfo`, `check`.
|
|
48
60
|
- **`CustomerInfo`** ergonomic wrapper; **`EntitleHubError`** with `.status` / `.code`.
|
|
49
61
|
- Zero dependencies; ESM + CJS + bundled type declarations.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@entitlehub/sdk)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
One entitlement API across **App Store, Google Play, Stripe, and web
|
|
8
|
+
One entitlement API across **App Store, Google Play, Stripe, and web**, for browsers, React Native, Expo, and Node. Stop checking raw product IDs; ask *"what is this user entitled to right now?"*
|
|
9
9
|
|
|
10
10
|
> Docs: **[entitlehub.com/docs](https://entitlehub.com/docs)** · [REST API](https://entitlehub.com/docs/api) · [Migrating from RevenueCat](https://entitlehub.com/docs/migrating-from-revenuecat)
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ npm install @entitlehub/sdk
|
|
|
15
15
|
|
|
16
16
|
Zero dependencies. Uses the global `fetch` (Node 18+, all modern browsers, React Native, Expo).
|
|
17
17
|
|
|
18
|
-
## Client (in your app)
|
|
18
|
+
## Client (in your app) (publishable key)
|
|
19
19
|
|
|
20
20
|
Reads only. Safe to ship in your app. **Never put a secret (`sk_`) key here.**
|
|
21
21
|
|
|
@@ -66,7 +66,7 @@ eh.logOut(); // forget cached info
|
|
|
66
66
|
| `getOfferings()` | `Offerings` | Entitlements + products, for building a paywall. |
|
|
67
67
|
| `addCustomerInfoUpdateListener(fn)` | `() => void` | Returns an unsubscribe. |
|
|
68
68
|
|
|
69
|
-
## Server (in your backend)
|
|
69
|
+
## Server (in your backend) (secret key)
|
|
70
70
|
|
|
71
71
|
Report purchases and grant entitlements. **Server only.**
|
|
72
72
|
|
|
@@ -76,17 +76,17 @@ import { EntitleHubServer } from "@entitlehub/sdk";
|
|
|
76
76
|
const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
77
77
|
|
|
78
78
|
// After the store purchase is confirmed on-device, report it from your server.
|
|
79
|
-
// Google Play
|
|
79
|
+
// Google Play: validated (EntitleHub verifies the token with your Play service account):
|
|
80
80
|
const info = await eh.reportPurchase(userId, {
|
|
81
81
|
store: "play", storeProductId: "pro_monthly",
|
|
82
82
|
purchaseToken: playToken, isSubscription: true,
|
|
83
83
|
});
|
|
84
84
|
info.isActive("pro"); // → true
|
|
85
85
|
|
|
86
|
-
// Apple StoreKit 2
|
|
86
|
+
// Apple StoreKit 2: validated (product / environment read from the signed JWS):
|
|
87
87
|
await eh.reportPurchase(userId, { store: "app_store", storeProductId: "", signedTransaction: jws });
|
|
88
88
|
|
|
89
|
-
// Trusted server-report (no store validation
|
|
89
|
+
// Trusted server-report (no store validation, only if you validated the receipt elsewhere):
|
|
90
90
|
await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
91
91
|
|
|
92
92
|
// Grant directly (promo / comp / support), no purchase:
|
|
@@ -99,6 +99,41 @@ await eh.grantEntitlement(userId, "pro", { durationDays: 30 });
|
|
|
99
99
|
| `grantEntitlement(userId, id, { durationDays?, isSandbox? })` | `CustomerInfo` |
|
|
100
100
|
| `getCustomerInfo(userId)` / `check(userId, id)` | `CustomerInfo` / `CheckResult` |
|
|
101
101
|
|
|
102
|
+
### Catalog: entitlements, products, offerings
|
|
103
|
+
|
|
104
|
+
Set up and change your catalog from code. Refs take the id **or** the key / SKU / identifier you
|
|
105
|
+
already have. Every create has an `ensure` twin that returns the existing row instead of failing,
|
|
106
|
+
so a setup script can run on every deploy.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
110
|
+
|
|
111
|
+
await eh.products.ensure({
|
|
112
|
+
store: "stripe", storeProductId: "price_1Q...", type: "subscription", duration: "P1M",
|
|
113
|
+
priceMicros: 9_990_000, entitlements: ["pro"],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Remap (future purchases only; access already granted is untouched):
|
|
117
|
+
await eh.products.update({ store: "play", storeProductId: "pro_monthly" }, { entitlements: ["pro", "team"] });
|
|
118
|
+
await eh.entitlements.attachProducts("team", ["price_1Q..."]);
|
|
119
|
+
|
|
120
|
+
// Retire a SKU you no longer sell. Existing subscribers keep access and renewals:
|
|
121
|
+
await eh.products.archive("price_old");
|
|
122
|
+
|
|
123
|
+
await eh.offerings.create({
|
|
124
|
+
identifier: "default", isCurrent: true, metadata: { headline: "Go Pro" },
|
|
125
|
+
packages: [{ identifier: "monthly", product: "price_1Q..." }],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Namespace | Methods |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `eh.entitlements` | `list` `get` `create` `ensure` `update` `delete` `products` `attachProducts` `detachProducts` `archive` `unarchive` |
|
|
132
|
+
| `eh.products` | `list` `get` `create` `ensure` `update` `delete` `attachEntitlements` `detachEntitlements` `archive` `unarchive` |
|
|
133
|
+
| `eh.offerings` | `list` `get` `create` `update` `delete` `archive` `unarchive` `packages` `addPackage` `updatePackage` `deletePackage` |
|
|
134
|
+
|
|
135
|
+
Full reference: [entitlehub.com/docs/catalog-api](https://entitlehub.com/docs/catalog-api).
|
|
136
|
+
|
|
102
137
|
## Already have an IAP setup?
|
|
103
138
|
|
|
104
139
|
Keep making the actual store purchase with your existing IAP flow (StoreKit / Play Billing /
|
|
@@ -108,6 +143,8 @@ with `getCustomerInfo()`. See the full guide at [entitlehub.com/docs](https://en
|
|
|
108
143
|
## Errors
|
|
109
144
|
|
|
110
145
|
Every failure throws an `EntitleHubError` with `.status` and `.code` (`auth` | `http` | `network` | `config`).
|
|
146
|
+
When the API sends its own machine-readable code it is on `.apiCode` (e.g. `product_exists`,
|
|
147
|
+
`ambiguous_product`, `rate_limited`), with the full payload on `.body`.
|
|
111
148
|
|
|
112
149
|
```ts
|
|
113
150
|
import { EntitleHubError } from "@entitlehub/sdk";
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var I=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var A=(r,t)=>{for(var e in t)I(r,e,{get:t[e],enumerable:!0})},D=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of H(t))!U.call(r,s)&&s!==e&&I(r,s,{get:()=>t[s],enumerable:!(n=R(t,s))||n.enumerable});return r};var q=r=>D(I({},"__esModule",{value:!0}),r);var j={};A(j,{CustomerInfo:()=>c,EntitleHub:()=>y,EntitleHubError:()=>a,EntitleHubServer:()=>b,EntitlementsApi:()=>l,OfferingsApi:()=>m,ProductsApi:()=>g});module.exports=q(j);var c=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let n of this.entitlements)e[n.entitlement]=n;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var S="0.3.0";var a=class extends Error{constructor(t,e,n="error",s){super(t),this.name="EntitleHubError",this.status=e,this.code=n,this.body=s,this.apiCode=typeof s?.code=="string"?s.code:void 0}},M=r=>new Promise(t=>setTimeout(t,r));async function i(r,t,e,n){let s=r.fetchImpl??globalThis.fetch;if(!s)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let C=`${r.baseUrl.replace(/\/$/,"")}${e}`,k=t==="GET"?r.retries??2:0,_;for(let f=0;f<=k;f++){let T=new AbortController,w=r.timeoutMs?setTimeout(()=>T.abort(),r.timeoutMs):void 0;try{let u=await s(C,{method:t,headers:{Authorization:`Bearer ${r.apiKey}`,"X-EntitleHub-Client":r.client??`${"js"}/${S}`,...n?{"Content-Type":"application/json"}:{}},body:n?JSON.stringify(n):void 0,signal:T.signal}),O=await u.text(),h=O?K(O):{};if(!u.ok){let v=`HTTP ${u.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(v=String(P.error)),new a(v,u.status,u.status===401||u.status===403?"auth":"http",P)}return h}catch(u){if(_=u,u instanceof a)throw u;f<k&&await M(250*(f+1))}finally{w&&clearTimeout(w)}}throw new a(`Network request failed: ${String(_?.message??_)}`,0,"network")}function K(r){try{return JSON.parse(r)}catch{return{}}}var y=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let n=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),s=new c(n);this.cached={info:s,at:Date.now()};for(let C of this.listeners)try{C(s)}catch{}return s}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),n=new c(e);this.cached={info:n,at:Date.now()};for(let s of this.listeners)try{s(n)}catch{}return n}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var o=encodeURIComponent;function d(r){return typeof r=="string"?r:"id"in r?{id:r.id}:{store:r.store,store_product_id:r.storeProductId}}function p(r){return typeof r=="string"?{seg:o(r),qs:""}:"id"in r?{seg:o(r.id),qs:""}:{seg:o(r.storeProductId),qs:`?store=${o(r.store)}`}}var E=r=>r?.includeArchived?"include_archived=true":"";async function $(r,t){try{return await r()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=E(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${o(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(d)})}ensure(t){return $(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${o(t)}`,{name:e.name,description:e.description,products:e.products?.map(d)})}delete(t){return i(this.http,"DELETE",`/entitlements/${o(t)}`)}async products(t,e){let n=E(e);return(await i(this.http,"GET",`/entitlements/${o(t)}/products${n?`?${n}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/attach_products`,{products:e.map(d)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/detach_products`,{products:e.map(d)})}archive(t){return i(this.http,"POST",`/entitlements/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${o(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let n=e.toString();return(await i(this.http,"GET",`/products${n?`?${n}`:""}`)).products}get(t){let{seg:e,qs:n}=p(t);return i(this.http,"GET",`/products/${e}${n}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return $(()=>this.create(t),"product_exists")}update(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"PATCH",`/products/${n}${s}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:n}=p(t);return i(this.http,"DELETE",`/products/${e}${n}`)}attachEntitlements(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"POST",`/products/${n}/attach_entitlements${s}`,{entitlements:e})}detachEntitlements(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"POST",`/products/${n}/detach_entitlements${s}`,{entitlements:e})}archive(t){let{seg:e,qs:n}=p(t);return i(this.http,"POST",`/products/${e}/archive${n}`)}unarchive(t){let{seg:e,qs:n}=p(t);return i(this.http,"POST",`/products/${e}/unarchive${n}`)}},m=class{constructor(t){this.http=t}async list(t){let e=E(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${o(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(x)})}update(t,e){return i(this.http,"PATCH",`/offerings/${o(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${o(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${o(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${o(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${o(t)}/packages`,x(e))}updatePackage(t,e,n){return i(this.http,"PATCH",`/offerings/${o(t)}/packages/${o(e)}`,{display_name:n.displayName,position:n.position,product:n.product===void 0?void 0:d(n.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${o(t)}/packages/${o(e)}`)}};function x(r){return{identifier:r.identifier,product:d(r.product),position:r.position,display_name:r.displayName}}var b=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let n=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new c(n)}async grantEntitlement(t,e,n={}){let s=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:n.durationDays??0,is_sandbox:!!n.isSandbox});return new c(s)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new c(e)}async check(t,e,n={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!n.sandbox})}};0&&(module.exports={CustomerInfo,EntitleHub,EntitleHubError,EntitleHubServer,EntitlementsApi,OfferingsApi,ProductsApi});
|
package/dist/index.d.cts
CHANGED
|
@@ -80,7 +80,7 @@ interface PurchaseInput {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Ergonomic wrapper around a subscriber's active entitlements
|
|
83
|
+
* Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
|
|
84
84
|
*
|
|
85
85
|
* const info = await eh.getCustomerInfo();
|
|
86
86
|
* if (info.isActive("pro")) showProFeatures();
|
|
@@ -112,11 +112,16 @@ interface EntitleHubOptions {
|
|
|
112
112
|
fetchImpl?: typeof fetch;
|
|
113
113
|
/** How long a cached CustomerInfo is considered fresh, ms (default 5 min). */
|
|
114
114
|
cacheTtlMs?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Identifies the calling SDK as `name/version`, e.g. "react-native/0.1.5". Wrapper SDKs set this
|
|
117
|
+
* so the dashboard can flag an out-of-date SDK. Defaults to this package's own name/version.
|
|
118
|
+
*/
|
|
119
|
+
client?: string;
|
|
115
120
|
}
|
|
116
121
|
type CustomerInfoListener = (info: CustomerInfo) => void;
|
|
117
122
|
type FetchPolicy = "cache-first" | "network-only";
|
|
118
123
|
/**
|
|
119
|
-
* The client-side EntitleHub SDK
|
|
124
|
+
* The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
|
|
120
125
|
* Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
|
|
121
126
|
*
|
|
122
127
|
* const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
|
|
@@ -146,7 +151,7 @@ declare class EntitleHub {
|
|
|
146
151
|
checkEntitlement(entitlementId: string, opts?: {
|
|
147
152
|
sandbox?: boolean;
|
|
148
153
|
}): Promise<CheckResult>;
|
|
149
|
-
/** The project's entitlements + products
|
|
154
|
+
/** The project's entitlements + products: for building a paywall. */
|
|
150
155
|
getOfferings(): Promise<Offerings>;
|
|
151
156
|
/**
|
|
152
157
|
* Report a VALIDATED store purchase for the current user and return the updated entitlements.
|
|
@@ -169,30 +174,273 @@ declare class EntitleHub {
|
|
|
169
174
|
addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
|
|
170
175
|
}
|
|
171
176
|
|
|
177
|
+
declare class EntitleHubError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
/** SDK-level category: config | auth | http | network | no_fetch. */
|
|
180
|
+
readonly code: string;
|
|
181
|
+
/**
|
|
182
|
+
* The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
|
|
183
|
+
* "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
|
|
184
|
+
*/
|
|
185
|
+
readonly apiCode?: string;
|
|
186
|
+
/** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
|
|
187
|
+
readonly body?: Record<string, unknown>;
|
|
188
|
+
constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
|
|
189
|
+
}
|
|
190
|
+
interface HttpOptions {
|
|
191
|
+
baseUrl: string;
|
|
192
|
+
apiKey: string;
|
|
193
|
+
/** Network-error retries for idempotent reads (default 2). */
|
|
194
|
+
retries?: number;
|
|
195
|
+
timeoutMs?: number;
|
|
196
|
+
fetchImpl?: typeof fetch;
|
|
197
|
+
/** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
|
|
198
|
+
client?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type ProductType = "subscription" | "non_consumable" | "consumable";
|
|
202
|
+
type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
|
|
203
|
+
/** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
|
|
204
|
+
type ProductRef = string | {
|
|
205
|
+
id: string;
|
|
206
|
+
} | {
|
|
207
|
+
store: Store;
|
|
208
|
+
storeProductId: string;
|
|
209
|
+
};
|
|
210
|
+
interface CatalogEntitlement {
|
|
211
|
+
object: "entitlement";
|
|
212
|
+
id: string;
|
|
213
|
+
key: string;
|
|
214
|
+
name: string;
|
|
215
|
+
description: string;
|
|
216
|
+
products: {
|
|
217
|
+
id: string;
|
|
218
|
+
store: Store;
|
|
219
|
+
store_product_id: string;
|
|
220
|
+
}[];
|
|
221
|
+
active_grants: number;
|
|
222
|
+
created_at: string | null;
|
|
223
|
+
archived_at: string | null;
|
|
224
|
+
}
|
|
225
|
+
interface CatalogProduct {
|
|
226
|
+
object: "product";
|
|
227
|
+
id: string;
|
|
228
|
+
store: Store;
|
|
229
|
+
store_product_id: string;
|
|
230
|
+
type: ProductType;
|
|
231
|
+
duration: Duration | null;
|
|
232
|
+
display_name: string;
|
|
233
|
+
price_micros: number;
|
|
234
|
+
currency: string;
|
|
235
|
+
entitlements: string[];
|
|
236
|
+
active_grants: number;
|
|
237
|
+
archived_at: string | null;
|
|
238
|
+
}
|
|
239
|
+
interface CatalogPackage {
|
|
240
|
+
object: "package";
|
|
241
|
+
id: string;
|
|
242
|
+
identifier: string;
|
|
243
|
+
display_name: string;
|
|
244
|
+
position: number;
|
|
245
|
+
store: Store;
|
|
246
|
+
store_product_id: string;
|
|
247
|
+
product: CatalogProduct | null;
|
|
248
|
+
}
|
|
249
|
+
interface CatalogOffering {
|
|
250
|
+
object: "offering";
|
|
251
|
+
id: string;
|
|
252
|
+
identifier: string;
|
|
253
|
+
display_name: string;
|
|
254
|
+
is_current: boolean;
|
|
255
|
+
metadata: Record<string, unknown> | null;
|
|
256
|
+
packages: CatalogPackage[];
|
|
257
|
+
created_at: string;
|
|
258
|
+
archived_at: string | null;
|
|
259
|
+
}
|
|
260
|
+
/** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
|
|
261
|
+
interface ListedOffering {
|
|
262
|
+
id: string;
|
|
263
|
+
identifier: string;
|
|
264
|
+
display_name: string;
|
|
265
|
+
is_current: boolean;
|
|
266
|
+
metadata: Record<string, unknown> | null;
|
|
267
|
+
archived_at: string | null;
|
|
268
|
+
packages: {
|
|
269
|
+
id: string;
|
|
270
|
+
identifier: string;
|
|
271
|
+
display_name: string;
|
|
272
|
+
position: number;
|
|
273
|
+
store: Store;
|
|
274
|
+
store_product_id: string;
|
|
275
|
+
product: {
|
|
276
|
+
display_name: string;
|
|
277
|
+
type: string;
|
|
278
|
+
price_micros: number;
|
|
279
|
+
currency: string;
|
|
280
|
+
entitlements: string[];
|
|
281
|
+
} | null;
|
|
282
|
+
}[];
|
|
283
|
+
}
|
|
284
|
+
interface Deleted {
|
|
285
|
+
object: string;
|
|
286
|
+
id: string;
|
|
287
|
+
deleted: true;
|
|
288
|
+
}
|
|
289
|
+
interface CreateEntitlementInput {
|
|
290
|
+
/** The exact string your app checks with isEntitled(). Can't change later. */
|
|
291
|
+
key: string;
|
|
292
|
+
name?: string;
|
|
293
|
+
description?: string;
|
|
294
|
+
/** Products that grant it, attached in the same call. */
|
|
295
|
+
products?: ProductRef[];
|
|
296
|
+
}
|
|
297
|
+
interface CreateProductInput {
|
|
298
|
+
store: Store;
|
|
299
|
+
storeProductId: string;
|
|
300
|
+
type: ProductType;
|
|
301
|
+
/** Required for subscriptions. */
|
|
302
|
+
duration?: Duration;
|
|
303
|
+
displayName?: string;
|
|
304
|
+
/** Integer micros (4.99 → 4_990_000). */
|
|
305
|
+
priceMicros?: number;
|
|
306
|
+
currency?: string;
|
|
307
|
+
/** Entitlement keys this product grants. */
|
|
308
|
+
entitlements?: string[];
|
|
309
|
+
}
|
|
310
|
+
interface PackageInput {
|
|
311
|
+
identifier: string;
|
|
312
|
+
product: ProductRef;
|
|
313
|
+
position?: number;
|
|
314
|
+
displayName?: string;
|
|
315
|
+
}
|
|
316
|
+
declare class EntitlementsApi {
|
|
317
|
+
private http;
|
|
318
|
+
constructor(http: HttpOptions);
|
|
319
|
+
list(opts?: {
|
|
320
|
+
includeArchived?: boolean;
|
|
321
|
+
}): Promise<CatalogEntitlement[]>;
|
|
322
|
+
/** By id or key. */
|
|
323
|
+
get(ref: string): Promise<CatalogEntitlement>;
|
|
324
|
+
create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
325
|
+
/** Create, or return the entitlement that already has this key. */
|
|
326
|
+
ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
327
|
+
/** `products`, when given, REPLACES the set of products that grant it. */
|
|
328
|
+
update(ref: string, patch: {
|
|
329
|
+
name?: string;
|
|
330
|
+
description?: string;
|
|
331
|
+
products?: ProductRef[];
|
|
332
|
+
}): Promise<CatalogEntitlement>;
|
|
333
|
+
/** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
|
|
334
|
+
delete(ref: string): Promise<Deleted>;
|
|
335
|
+
products(ref: string, opts?: {
|
|
336
|
+
includeArchived?: boolean;
|
|
337
|
+
}): Promise<CatalogProduct[]>;
|
|
338
|
+
attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
339
|
+
detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
340
|
+
/** Hide it from listings. Never takes access away from anyone who holds it. */
|
|
341
|
+
archive(ref: string): Promise<CatalogEntitlement>;
|
|
342
|
+
unarchive(ref: string): Promise<CatalogEntitlement>;
|
|
343
|
+
}
|
|
344
|
+
declare class ProductsApi {
|
|
345
|
+
private http;
|
|
346
|
+
constructor(http: HttpOptions);
|
|
347
|
+
list(opts?: {
|
|
348
|
+
store?: Store;
|
|
349
|
+
entitlement?: string;
|
|
350
|
+
includeArchived?: boolean;
|
|
351
|
+
}): Promise<CatalogProduct[]>;
|
|
352
|
+
get(ref: ProductRef): Promise<CatalogProduct>;
|
|
353
|
+
create(input: CreateProductInput): Promise<CatalogProduct>;
|
|
354
|
+
/** Create, or return the product that already has this store + SKU. */
|
|
355
|
+
ensure(input: CreateProductInput): Promise<CatalogProduct>;
|
|
356
|
+
/** `entitlements`, when given, REPLACES what it grants (future purchases only). */
|
|
357
|
+
update(ref: ProductRef, patch: {
|
|
358
|
+
displayName?: string;
|
|
359
|
+
priceMicros?: number;
|
|
360
|
+
currency?: string;
|
|
361
|
+
entitlements?: string[];
|
|
362
|
+
}): Promise<CatalogProduct>;
|
|
363
|
+
/** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
|
|
364
|
+
delete(ref: ProductRef): Promise<Deleted>;
|
|
365
|
+
attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
366
|
+
detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
367
|
+
/** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
|
|
368
|
+
archive(ref: ProductRef): Promise<CatalogProduct>;
|
|
369
|
+
unarchive(ref: ProductRef): Promise<CatalogProduct>;
|
|
370
|
+
}
|
|
371
|
+
declare class OfferingsApi {
|
|
372
|
+
private http;
|
|
373
|
+
constructor(http: HttpOptions);
|
|
374
|
+
/** Every offering. Archived ones only with includeArchived. */
|
|
375
|
+
list(opts?: {
|
|
376
|
+
includeArchived?: boolean;
|
|
377
|
+
}): Promise<ListedOffering[]>;
|
|
378
|
+
/** By id or identifier. */
|
|
379
|
+
get(ref: string): Promise<CatalogOffering>;
|
|
380
|
+
create(input: {
|
|
381
|
+
identifier: string;
|
|
382
|
+
displayName?: string;
|
|
383
|
+
isCurrent?: boolean;
|
|
384
|
+
metadata?: Record<string, unknown>;
|
|
385
|
+
packages?: PackageInput[];
|
|
386
|
+
}): Promise<CatalogOffering>;
|
|
387
|
+
/** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
|
|
388
|
+
update(ref: string, patch: {
|
|
389
|
+
displayName?: string;
|
|
390
|
+
isCurrent?: true;
|
|
391
|
+
metadata?: Record<string, unknown> | null;
|
|
392
|
+
}): Promise<CatalogOffering>;
|
|
393
|
+
delete(ref: string): Promise<Deleted>;
|
|
394
|
+
archive(ref: string): Promise<CatalogOffering>;
|
|
395
|
+
unarchive(ref: string): Promise<CatalogOffering>;
|
|
396
|
+
packages(ref: string): Promise<CatalogPackage[]>;
|
|
397
|
+
addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
|
|
398
|
+
/** `pkg` is the package id, or its identifier when that is used on one store only. */
|
|
399
|
+
updatePackage(ref: string, pkg: string, patch: {
|
|
400
|
+
displayName?: string;
|
|
401
|
+
position?: number;
|
|
402
|
+
product?: ProductRef;
|
|
403
|
+
}): Promise<CatalogPackage>;
|
|
404
|
+
deletePackage(ref: string, pkg: string): Promise<Deleted>;
|
|
405
|
+
}
|
|
406
|
+
|
|
172
407
|
interface EntitleHubServerOptions {
|
|
173
|
-
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY
|
|
408
|
+
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
|
|
174
409
|
apiKey: string;
|
|
175
410
|
baseUrl?: string;
|
|
176
411
|
fetchImpl?: typeof fetch;
|
|
177
412
|
}
|
|
178
413
|
/**
|
|
179
|
-
* The server-side EntitleHub SDK
|
|
414
|
+
* The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
|
|
180
415
|
* Keep this on your server; it uses a secret key.
|
|
181
416
|
*
|
|
182
417
|
* const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
183
418
|
* await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
419
|
+
*
|
|
420
|
+
* Manage the catalog from code too (every create has an `ensure` twin that returns the existing
|
|
421
|
+
* row instead of failing, so a setup script can run on every deploy):
|
|
422
|
+
*
|
|
423
|
+
* await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
424
|
+
* await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
|
|
425
|
+
* duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
|
|
184
426
|
*/
|
|
185
427
|
declare class EntitleHubServer {
|
|
186
428
|
private http;
|
|
429
|
+
/** Catalog API: entitlements. */
|
|
430
|
+
readonly entitlements: EntitlementsApi;
|
|
431
|
+
/** Catalog API: products and their entitlement mappings. */
|
|
432
|
+
readonly products: ProductsApi;
|
|
433
|
+
/** Catalog API: paywall offerings and packages. */
|
|
434
|
+
readonly offerings: OfferingsApi;
|
|
187
435
|
constructor(opts: EntitleHubServerOptions);
|
|
188
436
|
/**
|
|
189
437
|
* Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
|
|
190
438
|
*
|
|
191
439
|
* Three modes (pick one):
|
|
192
440
|
* • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
|
|
193
|
-
* • Apple (validated): { signedTransaction }
|
|
194
|
-
* • Stripe (validated): { stripeSubscriptionId }
|
|
195
|
-
* • Trusted server-report: { store, storeProductId }
|
|
441
|
+
* • Apple (validated): { signedTransaction } , store/product are read from the JWS
|
|
442
|
+
* • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
|
|
443
|
+
* • Trusted server-report: { store, storeProductId }, no store validation; only when you've
|
|
196
444
|
* already validated the receipt elsewhere.
|
|
197
445
|
*/
|
|
198
446
|
reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
|
|
@@ -209,10 +457,4 @@ declare class EntitleHubServer {
|
|
|
209
457
|
}): Promise<CheckResult>;
|
|
210
458
|
}
|
|
211
459
|
|
|
212
|
-
|
|
213
|
-
readonly status: number;
|
|
214
|
-
readonly code: string;
|
|
215
|
-
constructor(message: string, status: number, code?: string);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
|
|
460
|
+
export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store };
|
package/dist/index.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ interface PurchaseInput {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Ergonomic wrapper around a subscriber's active entitlements
|
|
83
|
+
* Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
|
|
84
84
|
*
|
|
85
85
|
* const info = await eh.getCustomerInfo();
|
|
86
86
|
* if (info.isActive("pro")) showProFeatures();
|
|
@@ -112,11 +112,16 @@ interface EntitleHubOptions {
|
|
|
112
112
|
fetchImpl?: typeof fetch;
|
|
113
113
|
/** How long a cached CustomerInfo is considered fresh, ms (default 5 min). */
|
|
114
114
|
cacheTtlMs?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Identifies the calling SDK as `name/version`, e.g. "react-native/0.1.5". Wrapper SDKs set this
|
|
117
|
+
* so the dashboard can flag an out-of-date SDK. Defaults to this package's own name/version.
|
|
118
|
+
*/
|
|
119
|
+
client?: string;
|
|
115
120
|
}
|
|
116
121
|
type CustomerInfoListener = (info: CustomerInfo) => void;
|
|
117
122
|
type FetchPolicy = "cache-first" | "network-only";
|
|
118
123
|
/**
|
|
119
|
-
* The client-side EntitleHub SDK
|
|
124
|
+
* The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
|
|
120
125
|
* Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
|
|
121
126
|
*
|
|
122
127
|
* const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
|
|
@@ -146,7 +151,7 @@ declare class EntitleHub {
|
|
|
146
151
|
checkEntitlement(entitlementId: string, opts?: {
|
|
147
152
|
sandbox?: boolean;
|
|
148
153
|
}): Promise<CheckResult>;
|
|
149
|
-
/** The project's entitlements + products
|
|
154
|
+
/** The project's entitlements + products: for building a paywall. */
|
|
150
155
|
getOfferings(): Promise<Offerings>;
|
|
151
156
|
/**
|
|
152
157
|
* Report a VALIDATED store purchase for the current user and return the updated entitlements.
|
|
@@ -169,30 +174,273 @@ declare class EntitleHub {
|
|
|
169
174
|
addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
|
|
170
175
|
}
|
|
171
176
|
|
|
177
|
+
declare class EntitleHubError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
/** SDK-level category: config | auth | http | network | no_fetch. */
|
|
180
|
+
readonly code: string;
|
|
181
|
+
/**
|
|
182
|
+
* The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
|
|
183
|
+
* "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
|
|
184
|
+
*/
|
|
185
|
+
readonly apiCode?: string;
|
|
186
|
+
/** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
|
|
187
|
+
readonly body?: Record<string, unknown>;
|
|
188
|
+
constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
|
|
189
|
+
}
|
|
190
|
+
interface HttpOptions {
|
|
191
|
+
baseUrl: string;
|
|
192
|
+
apiKey: string;
|
|
193
|
+
/** Network-error retries for idempotent reads (default 2). */
|
|
194
|
+
retries?: number;
|
|
195
|
+
timeoutMs?: number;
|
|
196
|
+
fetchImpl?: typeof fetch;
|
|
197
|
+
/** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
|
|
198
|
+
client?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type ProductType = "subscription" | "non_consumable" | "consumable";
|
|
202
|
+
type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
|
|
203
|
+
/** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
|
|
204
|
+
type ProductRef = string | {
|
|
205
|
+
id: string;
|
|
206
|
+
} | {
|
|
207
|
+
store: Store;
|
|
208
|
+
storeProductId: string;
|
|
209
|
+
};
|
|
210
|
+
interface CatalogEntitlement {
|
|
211
|
+
object: "entitlement";
|
|
212
|
+
id: string;
|
|
213
|
+
key: string;
|
|
214
|
+
name: string;
|
|
215
|
+
description: string;
|
|
216
|
+
products: {
|
|
217
|
+
id: string;
|
|
218
|
+
store: Store;
|
|
219
|
+
store_product_id: string;
|
|
220
|
+
}[];
|
|
221
|
+
active_grants: number;
|
|
222
|
+
created_at: string | null;
|
|
223
|
+
archived_at: string | null;
|
|
224
|
+
}
|
|
225
|
+
interface CatalogProduct {
|
|
226
|
+
object: "product";
|
|
227
|
+
id: string;
|
|
228
|
+
store: Store;
|
|
229
|
+
store_product_id: string;
|
|
230
|
+
type: ProductType;
|
|
231
|
+
duration: Duration | null;
|
|
232
|
+
display_name: string;
|
|
233
|
+
price_micros: number;
|
|
234
|
+
currency: string;
|
|
235
|
+
entitlements: string[];
|
|
236
|
+
active_grants: number;
|
|
237
|
+
archived_at: string | null;
|
|
238
|
+
}
|
|
239
|
+
interface CatalogPackage {
|
|
240
|
+
object: "package";
|
|
241
|
+
id: string;
|
|
242
|
+
identifier: string;
|
|
243
|
+
display_name: string;
|
|
244
|
+
position: number;
|
|
245
|
+
store: Store;
|
|
246
|
+
store_product_id: string;
|
|
247
|
+
product: CatalogProduct | null;
|
|
248
|
+
}
|
|
249
|
+
interface CatalogOffering {
|
|
250
|
+
object: "offering";
|
|
251
|
+
id: string;
|
|
252
|
+
identifier: string;
|
|
253
|
+
display_name: string;
|
|
254
|
+
is_current: boolean;
|
|
255
|
+
metadata: Record<string, unknown> | null;
|
|
256
|
+
packages: CatalogPackage[];
|
|
257
|
+
created_at: string;
|
|
258
|
+
archived_at: string | null;
|
|
259
|
+
}
|
|
260
|
+
/** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
|
|
261
|
+
interface ListedOffering {
|
|
262
|
+
id: string;
|
|
263
|
+
identifier: string;
|
|
264
|
+
display_name: string;
|
|
265
|
+
is_current: boolean;
|
|
266
|
+
metadata: Record<string, unknown> | null;
|
|
267
|
+
archived_at: string | null;
|
|
268
|
+
packages: {
|
|
269
|
+
id: string;
|
|
270
|
+
identifier: string;
|
|
271
|
+
display_name: string;
|
|
272
|
+
position: number;
|
|
273
|
+
store: Store;
|
|
274
|
+
store_product_id: string;
|
|
275
|
+
product: {
|
|
276
|
+
display_name: string;
|
|
277
|
+
type: string;
|
|
278
|
+
price_micros: number;
|
|
279
|
+
currency: string;
|
|
280
|
+
entitlements: string[];
|
|
281
|
+
} | null;
|
|
282
|
+
}[];
|
|
283
|
+
}
|
|
284
|
+
interface Deleted {
|
|
285
|
+
object: string;
|
|
286
|
+
id: string;
|
|
287
|
+
deleted: true;
|
|
288
|
+
}
|
|
289
|
+
interface CreateEntitlementInput {
|
|
290
|
+
/** The exact string your app checks with isEntitled(). Can't change later. */
|
|
291
|
+
key: string;
|
|
292
|
+
name?: string;
|
|
293
|
+
description?: string;
|
|
294
|
+
/** Products that grant it, attached in the same call. */
|
|
295
|
+
products?: ProductRef[];
|
|
296
|
+
}
|
|
297
|
+
interface CreateProductInput {
|
|
298
|
+
store: Store;
|
|
299
|
+
storeProductId: string;
|
|
300
|
+
type: ProductType;
|
|
301
|
+
/** Required for subscriptions. */
|
|
302
|
+
duration?: Duration;
|
|
303
|
+
displayName?: string;
|
|
304
|
+
/** Integer micros (4.99 → 4_990_000). */
|
|
305
|
+
priceMicros?: number;
|
|
306
|
+
currency?: string;
|
|
307
|
+
/** Entitlement keys this product grants. */
|
|
308
|
+
entitlements?: string[];
|
|
309
|
+
}
|
|
310
|
+
interface PackageInput {
|
|
311
|
+
identifier: string;
|
|
312
|
+
product: ProductRef;
|
|
313
|
+
position?: number;
|
|
314
|
+
displayName?: string;
|
|
315
|
+
}
|
|
316
|
+
declare class EntitlementsApi {
|
|
317
|
+
private http;
|
|
318
|
+
constructor(http: HttpOptions);
|
|
319
|
+
list(opts?: {
|
|
320
|
+
includeArchived?: boolean;
|
|
321
|
+
}): Promise<CatalogEntitlement[]>;
|
|
322
|
+
/** By id or key. */
|
|
323
|
+
get(ref: string): Promise<CatalogEntitlement>;
|
|
324
|
+
create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
325
|
+
/** Create, or return the entitlement that already has this key. */
|
|
326
|
+
ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
327
|
+
/** `products`, when given, REPLACES the set of products that grant it. */
|
|
328
|
+
update(ref: string, patch: {
|
|
329
|
+
name?: string;
|
|
330
|
+
description?: string;
|
|
331
|
+
products?: ProductRef[];
|
|
332
|
+
}): Promise<CatalogEntitlement>;
|
|
333
|
+
/** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
|
|
334
|
+
delete(ref: string): Promise<Deleted>;
|
|
335
|
+
products(ref: string, opts?: {
|
|
336
|
+
includeArchived?: boolean;
|
|
337
|
+
}): Promise<CatalogProduct[]>;
|
|
338
|
+
attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
339
|
+
detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
340
|
+
/** Hide it from listings. Never takes access away from anyone who holds it. */
|
|
341
|
+
archive(ref: string): Promise<CatalogEntitlement>;
|
|
342
|
+
unarchive(ref: string): Promise<CatalogEntitlement>;
|
|
343
|
+
}
|
|
344
|
+
declare class ProductsApi {
|
|
345
|
+
private http;
|
|
346
|
+
constructor(http: HttpOptions);
|
|
347
|
+
list(opts?: {
|
|
348
|
+
store?: Store;
|
|
349
|
+
entitlement?: string;
|
|
350
|
+
includeArchived?: boolean;
|
|
351
|
+
}): Promise<CatalogProduct[]>;
|
|
352
|
+
get(ref: ProductRef): Promise<CatalogProduct>;
|
|
353
|
+
create(input: CreateProductInput): Promise<CatalogProduct>;
|
|
354
|
+
/** Create, or return the product that already has this store + SKU. */
|
|
355
|
+
ensure(input: CreateProductInput): Promise<CatalogProduct>;
|
|
356
|
+
/** `entitlements`, when given, REPLACES what it grants (future purchases only). */
|
|
357
|
+
update(ref: ProductRef, patch: {
|
|
358
|
+
displayName?: string;
|
|
359
|
+
priceMicros?: number;
|
|
360
|
+
currency?: string;
|
|
361
|
+
entitlements?: string[];
|
|
362
|
+
}): Promise<CatalogProduct>;
|
|
363
|
+
/** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
|
|
364
|
+
delete(ref: ProductRef): Promise<Deleted>;
|
|
365
|
+
attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
366
|
+
detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
367
|
+
/** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
|
|
368
|
+
archive(ref: ProductRef): Promise<CatalogProduct>;
|
|
369
|
+
unarchive(ref: ProductRef): Promise<CatalogProduct>;
|
|
370
|
+
}
|
|
371
|
+
declare class OfferingsApi {
|
|
372
|
+
private http;
|
|
373
|
+
constructor(http: HttpOptions);
|
|
374
|
+
/** Every offering. Archived ones only with includeArchived. */
|
|
375
|
+
list(opts?: {
|
|
376
|
+
includeArchived?: boolean;
|
|
377
|
+
}): Promise<ListedOffering[]>;
|
|
378
|
+
/** By id or identifier. */
|
|
379
|
+
get(ref: string): Promise<CatalogOffering>;
|
|
380
|
+
create(input: {
|
|
381
|
+
identifier: string;
|
|
382
|
+
displayName?: string;
|
|
383
|
+
isCurrent?: boolean;
|
|
384
|
+
metadata?: Record<string, unknown>;
|
|
385
|
+
packages?: PackageInput[];
|
|
386
|
+
}): Promise<CatalogOffering>;
|
|
387
|
+
/** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
|
|
388
|
+
update(ref: string, patch: {
|
|
389
|
+
displayName?: string;
|
|
390
|
+
isCurrent?: true;
|
|
391
|
+
metadata?: Record<string, unknown> | null;
|
|
392
|
+
}): Promise<CatalogOffering>;
|
|
393
|
+
delete(ref: string): Promise<Deleted>;
|
|
394
|
+
archive(ref: string): Promise<CatalogOffering>;
|
|
395
|
+
unarchive(ref: string): Promise<CatalogOffering>;
|
|
396
|
+
packages(ref: string): Promise<CatalogPackage[]>;
|
|
397
|
+
addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
|
|
398
|
+
/** `pkg` is the package id, or its identifier when that is used on one store only. */
|
|
399
|
+
updatePackage(ref: string, pkg: string, patch: {
|
|
400
|
+
displayName?: string;
|
|
401
|
+
position?: number;
|
|
402
|
+
product?: ProductRef;
|
|
403
|
+
}): Promise<CatalogPackage>;
|
|
404
|
+
deletePackage(ref: string, pkg: string): Promise<Deleted>;
|
|
405
|
+
}
|
|
406
|
+
|
|
172
407
|
interface EntitleHubServerOptions {
|
|
173
|
-
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY
|
|
408
|
+
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
|
|
174
409
|
apiKey: string;
|
|
175
410
|
baseUrl?: string;
|
|
176
411
|
fetchImpl?: typeof fetch;
|
|
177
412
|
}
|
|
178
413
|
/**
|
|
179
|
-
* The server-side EntitleHub SDK
|
|
414
|
+
* The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
|
|
180
415
|
* Keep this on your server; it uses a secret key.
|
|
181
416
|
*
|
|
182
417
|
* const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
183
418
|
* await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
419
|
+
*
|
|
420
|
+
* Manage the catalog from code too (every create has an `ensure` twin that returns the existing
|
|
421
|
+
* row instead of failing, so a setup script can run on every deploy):
|
|
422
|
+
*
|
|
423
|
+
* await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
424
|
+
* await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
|
|
425
|
+
* duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
|
|
184
426
|
*/
|
|
185
427
|
declare class EntitleHubServer {
|
|
186
428
|
private http;
|
|
429
|
+
/** Catalog API: entitlements. */
|
|
430
|
+
readonly entitlements: EntitlementsApi;
|
|
431
|
+
/** Catalog API: products and their entitlement mappings. */
|
|
432
|
+
readonly products: ProductsApi;
|
|
433
|
+
/** Catalog API: paywall offerings and packages. */
|
|
434
|
+
readonly offerings: OfferingsApi;
|
|
187
435
|
constructor(opts: EntitleHubServerOptions);
|
|
188
436
|
/**
|
|
189
437
|
* Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
|
|
190
438
|
*
|
|
191
439
|
* Three modes (pick one):
|
|
192
440
|
* • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
|
|
193
|
-
* • Apple (validated): { signedTransaction }
|
|
194
|
-
* • Stripe (validated): { stripeSubscriptionId }
|
|
195
|
-
* • Trusted server-report: { store, storeProductId }
|
|
441
|
+
* • Apple (validated): { signedTransaction } , store/product are read from the JWS
|
|
442
|
+
* • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
|
|
443
|
+
* • Trusted server-report: { store, storeProductId }, no store validation; only when you've
|
|
196
444
|
* already validated the receipt elsewhere.
|
|
197
445
|
*/
|
|
198
446
|
reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
|
|
@@ -209,10 +457,4 @@ declare class EntitleHubServer {
|
|
|
209
457
|
}): Promise<CheckResult>;
|
|
210
458
|
}
|
|
211
459
|
|
|
212
|
-
|
|
213
|
-
readonly status: number;
|
|
214
|
-
readonly code: string;
|
|
215
|
-
constructor(message: string, status: number, code?: string);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
|
|
460
|
+
export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let r of this.entitlements)e[r.entitlement]=r;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var v="0.3.0";var a=class extends Error{constructor(t,e,r="error",o){super(t),this.name="EntitleHubError",this.status=e,this.code=r,this.body=o,this.apiCode=typeof o?.code=="string"?o.code:void 0}},R=n=>new Promise(t=>setTimeout(t,n));async function i(n,t,e,r){let o=n.fetchImpl??globalThis.fetch;if(!o)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let y=`${n.baseUrl.replace(/\/$/,"")}${e}`,E=t==="GET"?n.retries??2:0,b;for(let f=0;f<=E;f++){let k=new AbortController,T=n.timeoutMs?setTimeout(()=>k.abort(),n.timeoutMs):void 0;try{let c=await o(y,{method:t,headers:{Authorization:`Bearer ${n.apiKey}`,"X-EntitleHub-Client":n.client??`${"js"}/${v}`,...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(r):void 0,signal:k.signal}),w=await c.text(),h=w?H(w):{};if(!c.ok){let O=`HTTP ${c.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(O=String(P.error)),new a(O,c.status,c.status===401||c.status===403?"auth":"http",P)}return h}catch(c){if(b=c,c instanceof a)throw c;f<E&&await R(250*(f+1))}finally{T&&clearTimeout(T)}}throw new a(`Network request failed: ${String(b?.message??b)}`,0,"network")}function H(n){try{return JSON.parse(n)}catch{return{}}}var C=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let r=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),o=new u(r);this.cached={info:o,at:Date.now()};for(let y of this.listeners)try{y(o)}catch{}return o}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),r=new u(e);this.cached={info:r,at:Date.now()};for(let o of this.listeners)try{o(r)}catch{}return r}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var s=encodeURIComponent;function d(n){return typeof n=="string"?n:"id"in n?{id:n.id}:{store:n.store,store_product_id:n.storeProductId}}function p(n){return typeof n=="string"?{seg:s(n),qs:""}:"id"in n?{seg:s(n.id),qs:""}:{seg:s(n.storeProductId),qs:`?store=${s(n.store)}`}}var _=n=>n?.includeArchived?"include_archived=true":"";async function x(n,t){try{return await n()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${s(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(d)})}ensure(t){return x(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${s(t)}`,{name:e.name,description:e.description,products:e.products?.map(d)})}delete(t){return i(this.http,"DELETE",`/entitlements/${s(t)}`)}async products(t,e){let r=_(e);return(await i(this.http,"GET",`/entitlements/${s(t)}/products${r?`?${r}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/attach_products`,{products:e.map(d)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/detach_products`,{products:e.map(d)})}archive(t){return i(this.http,"POST",`/entitlements/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${s(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let r=e.toString();return(await i(this.http,"GET",`/products${r?`?${r}`:""}`)).products}get(t){let{seg:e,qs:r}=p(t);return i(this.http,"GET",`/products/${e}${r}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return x(()=>this.create(t),"product_exists")}update(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"PATCH",`/products/${r}${o}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:r}=p(t);return i(this.http,"DELETE",`/products/${e}${r}`)}attachEntitlements(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"POST",`/products/${r}/attach_entitlements${o}`,{entitlements:e})}detachEntitlements(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"POST",`/products/${r}/detach_entitlements${o}`,{entitlements:e})}archive(t){let{seg:e,qs:r}=p(t);return i(this.http,"POST",`/products/${e}/archive${r}`)}unarchive(t){let{seg:e,qs:r}=p(t);return i(this.http,"POST",`/products/${e}/unarchive${r}`)}},m=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${s(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(S)})}update(t,e){return i(this.http,"PATCH",`/offerings/${s(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${s(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${s(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${s(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${s(t)}/packages`,S(e))}updatePackage(t,e,r){return i(this.http,"PATCH",`/offerings/${s(t)}/packages/${s(e)}`,{display_name:r.displayName,position:r.position,product:r.product===void 0?void 0:d(r.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${s(t)}/packages/${s(e)}`)}};function S(n){return{identifier:n.identifier,product:d(n.product),position:n.position,display_name:n.displayName}}var I=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let r=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new u(r)}async grantEntitlement(t,e,r={}){let o=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:r.durationDays??0,is_sandbox:!!r.isSandbox});return new u(o)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new u(e)}async check(t,e,r={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!r.sandbox})}};export{u as CustomerInfo,C as EntitleHub,a as EntitleHubError,I as EntitleHubServer,l as EntitlementsApi,m as OfferingsApi,g as ProductsApi};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entitlehub/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "EntitleHub SDK
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "EntitleHub SDK: one entitlement API across App Store, Google Play, Stripe, and web. Works in browsers, React Native, Expo, and Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entitlehub",
|
|
7
7
|
"entitlements",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --minify",
|
|
39
39
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "node --test",
|
|
40
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
41
41
|
"prepublishOnly": "npm run build"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|