@nosslabs/iap 7.0.0-next.0 → 7.0.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 +65 -0
- package/README.md +4 -4
- package/dist/index.cjs +22 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -8
- package/dist/index.d.ts +1 -8
- package/dist/index.js +22 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,71 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [7.0.0] — 2026-05-14
|
|
9
|
+
|
|
10
|
+
**GA of the Capacitor 7+ line.** `@latest` on npm moves from `5.0.0`
|
|
11
|
+
(Cap-5 maintenance) to `7.0.0` (Cap-7+). Cap-5 consumers stay on
|
|
12
|
+
`5.x` — `^5` ranges don't auto-resolve to `7.x`. The `5.x` maintenance
|
|
13
|
+
branch continues to receive patches.
|
|
14
|
+
|
|
15
|
+
### Changed (BREAKING, vs `5.0.0` — bundled with the Cap-5→Cap-7 swap)
|
|
16
|
+
|
|
17
|
+
- **EventMap pruning.** Removed two events from the public
|
|
18
|
+
`EventMap` that were declared but never emitted in any prior
|
|
19
|
+
release: `'price-stale'` and `'error'`. Subscriptions to either
|
|
20
|
+
never fired, so no runtime behavior changes — only consumers who
|
|
21
|
+
had `iap.on('price-stale', …)` or `iap.on('error', …)` in their
|
|
22
|
+
TypeScript code need to remove those calls. The
|
|
23
|
+
`'recovery-dropped-permanent'` event (introduced in 0.4 / `5.0.0`)
|
|
24
|
+
remains.
|
|
25
|
+
- `IAPErrorOptions` is now file-local (was wrongly exported from
|
|
26
|
+
`src/lib/errors.ts` but never re-exported through `src/index.ts`,
|
|
27
|
+
so no consumer had access to it). No package-root-exported symbol
|
|
28
|
+
changes.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`AppUserIdFetcherContext`** is now re-exported from the package
|
|
33
|
+
root, so a separately-defined async fetcher can be typed
|
|
34
|
+
explicitly:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import type { AppUserIdFetcherContext } from '@nosslabs/iap';
|
|
38
|
+
|
|
39
|
+
const fetchUuid = async ({ authHeaders }: AppUserIdFetcherContext) => {
|
|
40
|
+
const r = await fetch('/api/iap/uuid', { method: 'POST', headers: authHeaders });
|
|
41
|
+
return (await r.json()).uuid;
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Fixed (since `7.0.0-next.0`)
|
|
46
|
+
|
|
47
|
+
- **iOS `Cannot find product for id <id>` now maps to
|
|
48
|
+
`PRODUCT_NOT_FOUND`** (previously fell through to `STORE_ERROR`).
|
|
49
|
+
The capgo plugin uses two different messages for the same
|
|
50
|
+
semantic on iOS vs Android; the adapter now handles both.
|
|
51
|
+
- **`refresh()` is safe to detach from the IAP instance.** Internal
|
|
52
|
+
callbacks no longer reference `this.refresh()`, so
|
|
53
|
+
`const { refresh } = iap;` works without a strict-mode `this`
|
|
54
|
+
binding error. Regression test added.
|
|
55
|
+
|
|
56
|
+
### Notes
|
|
57
|
+
|
|
58
|
+
- Adapter JSDoc for `getOwnedTransactions()` now documents an iOS
|
|
59
|
+
quirk worth knowing: `@capgo/native-purchases`'s `getPurchases()`
|
|
60
|
+
bundles `Transaction.currentEntitlements` *plus* `Transaction.all`
|
|
61
|
+
(historical + revoked subscriptions). Android-side PENDING
|
|
62
|
+
purchases are filtered out (`purchaseState !== '1'`); iOS-side
|
|
63
|
+
historical transactions are not filtered and pass through to the
|
|
64
|
+
backend's `/restore` endpoint. Attesto evaluates each receipt and
|
|
65
|
+
returns per-transaction validity, so this is the documented
|
|
66
|
+
contract.
|
|
67
|
+
- Android user-cancellation reminder (carried from `7.0.0-next.0`):
|
|
68
|
+
Google Play Billing collapses user-cancel and other billing
|
|
69
|
+
errors into the same plugin rejection, so an Android cancel
|
|
70
|
+
surfaces as `status: 'failed'` rather than `'cancelled'`. iOS
|
|
71
|
+
still distinguishes reliably.
|
|
72
|
+
|
|
8
73
|
## [7.0.0-next.0] — 2026-05-14
|
|
9
74
|
|
|
10
75
|
First release of the **Capacitor 7+** line, published on the `@next`
|
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> Thin Capacitor IAP orchestrator. Server-side validation via [Attesto](https://attesto.nossdev.com).
|
|
4
4
|
|
|
5
|
-
**Status: `7.0.0
|
|
5
|
+
**Status: `7.0.0` — GA on `@latest`** (the Capacitor 7+ line, built on `@capgo/native-purchases`). The Capacitor 5 line (`cordova-plugin-purchase`) continues as `5.x` from the `5.x` branch — `^5` ranges still resolve to `5.x`. See the [CHANGELOG](./CHANGELOG.md) for the GA delta and [Migration](https://iap.nossdev.com/migration/) for upgrading from `5.x`.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @nosslabs/iap
|
|
8
|
+
npm install @nosslabs/iap @capgo/native-purchases
|
|
9
9
|
npx cap sync
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -77,8 +77,8 @@ It does **not**: talk to Attesto directly, define entitlement business logic, ma
|
|
|
77
77
|
|
|
78
78
|
| `@nosslabs/iap` | Capacitor | Native plugin | dist-tag | Status |
|
|
79
79
|
|---|---|---|---|---|
|
|
80
|
-
| 7.x | 7.x (also runs on 8.x) | `@capgo/native-purchases 7.16.x` (or `^8` on Cap 8) | `@
|
|
81
|
-
| 5.x | 5.x | `cordova-plugin-purchase ^13.x` |
|
|
80
|
+
| 7.x | 7.x (also runs on 8.x) | `@capgo/native-purchases 7.16.x` (or `^8` on Cap 8) | `@latest` | **Current** |
|
|
81
|
+
| 5.x | 5.x | `cordova-plugin-purchase ^13.x` | (pinned via `^5`) | Maintenance |
|
|
82
82
|
|
|
83
83
|
## Optional peer dependency
|
|
84
84
|
|
package/dist/index.cjs
CHANGED
|
@@ -202,7 +202,7 @@ function mapPurchaseError(error, productId) {
|
|
|
202
202
|
cause: error
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
|
-
if (lower.includes("product not found")) {
|
|
205
|
+
if (lower.includes("product not found") || lower.includes("cannot find product")) {
|
|
206
206
|
return new exports.IAPError({
|
|
207
207
|
code: exports.IAPErrorCode.PRODUCT_NOT_FOUND,
|
|
208
208
|
message: `Product "${productId}" was not found in the store catalog.`,
|
|
@@ -1737,6 +1737,24 @@ function createIAP(input) {
|
|
|
1737
1737
|
cachedAt: null,
|
|
1738
1738
|
products: Object.freeze([...config.products ?? []])
|
|
1739
1739
|
};
|
|
1740
|
+
async function refreshEntitlements() {
|
|
1741
|
+
requireInitialized(state);
|
|
1742
|
+
const previous = state.entitlements;
|
|
1743
|
+
const fetched = await state.backend.getEntitlements();
|
|
1744
|
+
const next = freezeAll(fetched);
|
|
1745
|
+
try {
|
|
1746
|
+
state.cachedAt = await state.cache.save(next);
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
state.logger.warn(
|
|
1749
|
+
"Failed to persist refreshed entitlements; in-memory state still updated.",
|
|
1750
|
+
error
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
state.entitlements = next;
|
|
1754
|
+
if (!entitlementsEqual(previous, next)) {
|
|
1755
|
+
state.emitter.emit("entitlements-changed", { entitlements: next, previous });
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1740
1758
|
return {
|
|
1741
1759
|
async initialize() {
|
|
1742
1760
|
if (state.destroyed) {
|
|
@@ -1838,7 +1856,7 @@ function createIAP(input) {
|
|
|
1838
1856
|
logger: state.logger,
|
|
1839
1857
|
onResume: async () => {
|
|
1840
1858
|
try {
|
|
1841
|
-
await
|
|
1859
|
+
await refreshEntitlements();
|
|
1842
1860
|
} catch (error) {
|
|
1843
1861
|
state.logger.warn("refreshOnResume: refresh() failed.", error);
|
|
1844
1862
|
}
|
|
@@ -1849,7 +1867,7 @@ function createIAP(input) {
|
|
|
1849
1867
|
state.logger.debug("Cache exceeds TTL; scheduling background refresh.");
|
|
1850
1868
|
queueMicrotask(() => {
|
|
1851
1869
|
if (!state.initialized || state.destroyed) return;
|
|
1852
|
-
|
|
1870
|
+
refreshEntitlements().catch((error) => {
|
|
1853
1871
|
state.logger.warn("TTL background refresh failed.", error);
|
|
1854
1872
|
});
|
|
1855
1873
|
});
|
|
@@ -1857,24 +1875,7 @@ function createIAP(input) {
|
|
|
1857
1875
|
state.initialized = true;
|
|
1858
1876
|
state.emitter.emit("ready", void 0);
|
|
1859
1877
|
},
|
|
1860
|
-
|
|
1861
|
-
requireInitialized(state);
|
|
1862
|
-
const previous = state.entitlements;
|
|
1863
|
-
const fetched = await state.backend.getEntitlements();
|
|
1864
|
-
const next = freezeAll(fetched);
|
|
1865
|
-
try {
|
|
1866
|
-
state.cachedAt = await state.cache.save(next);
|
|
1867
|
-
} catch (error) {
|
|
1868
|
-
state.logger.warn(
|
|
1869
|
-
"Failed to persist refreshed entitlements; in-memory state still updated.",
|
|
1870
|
-
error
|
|
1871
|
-
);
|
|
1872
|
-
}
|
|
1873
|
-
state.entitlements = next;
|
|
1874
|
-
if (!entitlementsEqual(previous, next)) {
|
|
1875
|
-
state.emitter.emit("entitlements-changed", { entitlements: next, previous });
|
|
1876
|
-
}
|
|
1877
|
-
},
|
|
1878
|
+
refresh: refreshEntitlements,
|
|
1878
1879
|
async destroy() {
|
|
1879
1880
|
if (state.destroyed) return;
|
|
1880
1881
|
state.destroyed = true;
|