@sudobility/consumables_client 0.0.6 → 0.0.8
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/CLAUDE.md +79 -37
- package/dist/adapters/revenuecat-rn.d.ts.map +1 -1
- package/dist/adapters/revenuecat-rn.js +5 -6
- package/dist/adapters/revenuecat-rn.js.map +1 -1
- package/dist/adapters/revenuecat-web.d.ts.map +1 -1
- package/dist/adapters/revenuecat-web.js +4 -2
- package/dist/adapters/revenuecat-web.js.map +1 -1
- package/dist/core/service.d.ts +1 -1
- package/dist/core/service.d.ts.map +1 -1
- package/dist/core/service.js +14 -11
- package/dist/core/service.js.map +1 -1
- package/dist/core/singleton.d.ts.map +1 -1
- package/dist/core/singleton.js.map +1 -1
- package/dist/hooks/useBalance.d.ts.map +1 -1
- package/dist/hooks/useBalance.js.map +1 -1
- package/dist/hooks/useConsumableProducts.d.ts.map +1 -1
- package/dist/hooks/useConsumableProducts.js.map +1 -1
- package/dist/hooks/usePurchaseCredits.d.ts.map +1 -1
- package/dist/hooks/usePurchaseCredits.js.map +1 -1
- package/dist/hooks/usePurchaseHistory.d.ts.map +1 -1
- package/dist/hooks/usePurchaseHistory.js.map +1 -1
- package/dist/hooks/useUsageHistory.d.ts.map +1 -1
- package/dist/hooks/useUsageHistory.js.map +1 -1
- package/dist/network/ConsumablesApiClient.d.ts.map +1 -1
- package/dist/network/ConsumablesApiClient.js.map +1 -1
- package/dist/types/adapter.d.ts.map +1 -1
- package/dist/types/adapter.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +4 -3
package/CLAUDE.md
CHANGED
|
@@ -3,75 +3,113 @@
|
|
|
3
3
|
Cross-platform consumable credits client with RevenueCat adapter pattern.
|
|
4
4
|
|
|
5
5
|
**npm**: `@sudobility/consumables_client` (public)
|
|
6
|
+
**Version**: 0.0.6
|
|
7
|
+
**License**: BUSL-1.1
|
|
6
8
|
|
|
7
9
|
## Tech Stack
|
|
8
10
|
|
|
9
|
-
- **Language**: TypeScript (strict mode)
|
|
11
|
+
- **Language**: TypeScript 5.9.3 (strict mode)
|
|
10
12
|
- **Runtime**: Bun
|
|
11
|
-
- **Package Manager**: Bun
|
|
12
|
-
- **Build**: TypeScript compiler (ESM)
|
|
13
|
-
- **Test**:
|
|
13
|
+
- **Package Manager**: Bun (never npm/yarn/pnpm)
|
|
14
|
+
- **Build**: TypeScript compiler (ESM only)
|
|
15
|
+
- **Test**: Vitest 4.x
|
|
16
|
+
- **JSX**: react-jsx (React 18/19)
|
|
14
17
|
|
|
15
18
|
## Project Structure
|
|
16
19
|
|
|
17
20
|
```
|
|
18
21
|
src/
|
|
19
|
-
├── index.ts # Main exports
|
|
22
|
+
├── index.ts # Main barrel exports
|
|
20
23
|
├── types/
|
|
21
|
-
│ ├── index.ts # CreditPackage,
|
|
22
|
-
│ └── adapter.ts # ConsumablesAdapter interface
|
|
24
|
+
│ ├── index.ts # CreditPackage, CreditOffering, CreditBalance
|
|
25
|
+
│ └── adapter.ts # ConsumablesAdapter interface, ConsumablePurchaseResult, ConsumablePurchaseParams
|
|
23
26
|
├── core/
|
|
24
|
-
│ ├── index.ts
|
|
25
|
-
│ ├── service.ts # ConsumablesService class
|
|
26
|
-
│ └── singleton.ts #
|
|
27
|
+
│ ├── index.ts # Re-exports from service.ts and singleton.ts
|
|
28
|
+
│ ├── service.ts # ConsumablesService class (caching, purchase flow, usage)
|
|
29
|
+
│ └── singleton.ts # Module-level singleton + event listeners (init, userId, balance change)
|
|
27
30
|
├── network/
|
|
28
|
-
│ └── ConsumablesApiClient.ts # HTTP
|
|
31
|
+
│ └── ConsumablesApiClient.ts # HTTP client wrapping NetworkClient for /api/v1/consumables/* endpoints
|
|
29
32
|
├── adapters/
|
|
30
|
-
│ ├── index.ts
|
|
31
|
-
│ ├── revenuecat-web.ts # Web adapter
|
|
32
|
-
│ └── revenuecat-rn.ts # React Native adapter
|
|
33
|
+
│ ├── index.ts # Re-exports web and RN adapters
|
|
34
|
+
│ ├── revenuecat-web.ts # Web adapter (wraps @revenuecat/purchases-js, lazy-loaded)
|
|
35
|
+
│ └── revenuecat-rn.ts # React Native adapter (wraps react-native-purchases, lazy-loaded)
|
|
33
36
|
└── hooks/
|
|
34
|
-
├── index.ts
|
|
35
|
-
├── useBalance.ts
|
|
36
|
-
├── useConsumableProducts.ts
|
|
37
|
-
├── usePurchaseCredits.ts
|
|
38
|
-
├── usePurchaseHistory.ts
|
|
39
|
-
└── useUsageHistory.ts
|
|
37
|
+
├── index.ts # Re-exports all hooks
|
|
38
|
+
├── useBalance.ts # Current credit balance (subscribes to balance + userId changes)
|
|
39
|
+
├── useConsumableProducts.ts # Available credit packages from a specific offering
|
|
40
|
+
├── usePurchaseCredits.ts # Purchase flow (adapter.purchase -> api.recordPurchase -> notify)
|
|
41
|
+
├── usePurchaseHistory.ts # Paginated purchase audit trail with loadMore
|
|
42
|
+
└── useUsageHistory.ts # Paginated usage audit trail with loadMore
|
|
43
|
+
tests/
|
|
44
|
+
├── singleton.test.ts
|
|
45
|
+
├── ConsumablesService.test.ts
|
|
46
|
+
└── ConsumablesApiClient.test.ts
|
|
40
47
|
```
|
|
41
48
|
|
|
42
49
|
## Commands
|
|
43
50
|
|
|
44
51
|
```bash
|
|
45
|
-
bun run build # Build ESM
|
|
52
|
+
bun run build # Build ESM via tsc
|
|
46
53
|
bun run clean # Remove dist/
|
|
47
|
-
bun run dev # Watch mode
|
|
48
|
-
bun test # Run tests
|
|
49
|
-
bun run
|
|
54
|
+
bun run dev # Watch mode (tsc --watch)
|
|
55
|
+
bun test # Run tests (vitest run)
|
|
56
|
+
bun run test:watch # Watch tests
|
|
57
|
+
bun run typecheck # TypeScript check (bunx tsc --noEmit)
|
|
58
|
+
bun run lint # ESLint
|
|
59
|
+
bun run lint:fix # ESLint with auto-fix
|
|
60
|
+
bun run format # Prettier format
|
|
50
61
|
```
|
|
51
62
|
|
|
63
|
+
## Dependencies
|
|
64
|
+
|
|
65
|
+
### Peer Dependencies
|
|
66
|
+
- `@sudobility/types` ^1.9.53 -- shared type definitions (ConsumablePurchaseRecord, ConsumableUsageRecord, NetworkClient)
|
|
67
|
+
- `react` ^18.0.0 || ^19.0.0 -- hooks use React useState/useEffect/useCallback
|
|
68
|
+
- `@revenuecat/purchases-js` ^1.0.0 (optional) -- only needed for web adapter
|
|
69
|
+
- `react-native-purchases` >=7.0.0 (optional) -- only needed for RN adapter
|
|
70
|
+
|
|
71
|
+
### Dev Dependencies
|
|
72
|
+
- TypeScript ~5.9.3, Vitest ^4.0.4, ESLint ^9.x, Prettier ^3.x
|
|
73
|
+
|
|
52
74
|
## Key Concepts
|
|
53
75
|
|
|
54
76
|
### Adapter Pattern
|
|
55
|
-
- Web adapter wraps
|
|
56
|
-
- RN adapter wraps react-native-purchases
|
|
57
|
-
- Both implement ConsumablesAdapter interface
|
|
77
|
+
- Web adapter wraps `@revenuecat/purchases-js`
|
|
78
|
+
- RN adapter wraps `react-native-purchases`
|
|
79
|
+
- Both implement the `ConsumablesAdapter` interface (getOfferings, purchase, setUserId)
|
|
80
|
+
- Adapters lazy-load their SDK via dynamic `import()` (web) or `require()` (RN)
|
|
81
|
+
|
|
82
|
+
### ConsumablesService
|
|
83
|
+
- Core orchestration class constructed with `{ adapter, apiClient }`
|
|
84
|
+
- Manages two caches: `balanceCache` (per-user) and `offeringsCache` (global)
|
|
85
|
+
- Purchase flow: adapter.purchase() -> apiClient.recordPurchase() -> update cache
|
|
86
|
+
- Usage flow: apiClient.recordUsage() -> update cache
|
|
58
87
|
|
|
59
88
|
### Singleton + Event Listeners
|
|
60
|
-
- No React Context needed
|
|
61
|
-
- initializeConsumables() at app startup
|
|
62
|
-
- setConsumablesUserId() on auth
|
|
63
|
-
- onConsumablesBalanceChange()
|
|
89
|
+
- No React Context needed -- module-level singleton pattern
|
|
90
|
+
- `initializeConsumables(config)` at app startup
|
|
91
|
+
- `setConsumablesUserId(userId, email?)` on auth state changes
|
|
92
|
+
- `onConsumablesBalanceChange(listener)` returns unsubscribe function
|
|
93
|
+
- `onConsumablesUserIdChange(listener)` returns unsubscribe function
|
|
94
|
+
- `notifyBalanceChange()` manually triggers balance listeners
|
|
64
95
|
|
|
65
96
|
### Hooks
|
|
66
|
-
- useBalance()
|
|
67
|
-
- useConsumableProducts(offeringId)
|
|
68
|
-
- usePurchaseCredits()
|
|
69
|
-
- usePurchaseHistory()
|
|
97
|
+
- `useBalance()` -- current credit balance, subscribes to balance and userId changes
|
|
98
|
+
- `useConsumableProducts(offeringId)` -- available packages from a specific RevenueCat offering
|
|
99
|
+
- `usePurchaseCredits()` -- returns `purchase(packageId, offeringId)` callback + isPurchasing state
|
|
100
|
+
- `usePurchaseHistory(limit?)` -- paginated purchase records with loadMore
|
|
101
|
+
- `useUsageHistory(limit?)` -- paginated usage records with loadMore
|
|
102
|
+
|
|
103
|
+
### ConsumablesApiClient
|
|
104
|
+
- HTTP wrapper using `NetworkClient` from `@sudobility/types`
|
|
105
|
+
- Base URL pattern: `{baseUrl}/api/v1/consumables/{endpoint}`
|
|
106
|
+
- Endpoints: `/balance` (GET), `/purchase` (POST), `/use` (POST), `/purchases` (GET), `/usages` (GET)
|
|
107
|
+
- Maps snake_case API responses to camelCase client types
|
|
70
108
|
|
|
71
109
|
## Related Projects
|
|
72
110
|
|
|
73
|
-
- **consumables_pages** (`@sudobility/consumables_pages`)
|
|
74
|
-
- **consumables_service** (`@sudobility/consumables_service`)
|
|
111
|
+
- **consumables_pages** (`@sudobility/consumables_pages`) -- UI components for the credits store and history pages. Depends on this package for types (not runtime hooks).
|
|
112
|
+
- **consumables_service** (`@sudobility/consumables_service`) -- Backend counterpart that manages balances, purchases, and usage in the database. This client calls its API endpoints via HTTP.
|
|
75
113
|
|
|
76
114
|
Dependency direction: `consumables_pages` --> `consumables_client` --> `consumables_service` (via HTTP)
|
|
77
115
|
|
|
@@ -81,6 +119,7 @@ Dependency direction: `consumables_pages` --> `consumables_client` --> `consumab
|
|
|
81
119
|
- **Adapter pattern for RevenueCat**: Platform-specific SDK logic lives in adapters that implement `ConsumablesAdapter`. To support a new platform, add a new adapter file -- do not modify existing adapters.
|
|
82
120
|
- **Event-driven balance updates**: Balance changes are broadcast via `onConsumablesBalanceChange()` listeners. Hooks subscribe to these events. When modifying balance-related logic, always emit the balance change event so subscribers stay in sync.
|
|
83
121
|
- **Lazy-loaded adapters**: Adapters dynamically import their underlying SDK (`@revenuecat/purchases-js` or `react-native-purchases`) to keep initial bundle size small.
|
|
122
|
+
- **snake_case API <-> camelCase client**: The API client maps between backend snake_case (e.g., `initial_credits`) and client camelCase (e.g., `initialCredits`).
|
|
84
123
|
|
|
85
124
|
## Gotchas
|
|
86
125
|
|
|
@@ -88,6 +127,9 @@ Dependency direction: `consumables_pages` --> `consumables_client` --> `consumab
|
|
|
88
127
|
- **Singleton must be initialized before hooks work**: Calling `useBalance()` or any hook before `initializeConsumables()` will throw or return undefined. Ensure initialization happens at app startup (e.g., in a root layout or entry file).
|
|
89
128
|
- **Offerings cache survives user change, but balance cache does not**: When `setConsumablesUserId()` is called, the balance is refetched for the new user, but cached offerings (credit packages) are kept because they are not user-specific. Do not clear offerings on user change.
|
|
90
129
|
- **Two adapters, one interface**: Web and React Native adapters have different underlying SDKs with different APIs. Always code against `ConsumablesAdapter`, never against a specific SDK directly.
|
|
130
|
+
- **Web adapter uses `Purchases.close()` on user switch**: The web SDK requires closing and reconfiguring when switching users. The RN adapter uses `logOut()` + `logIn()` instead.
|
|
131
|
+
- **RN adapter defaults source to "apple"**: The React Native adapter currently always returns `"apple"` as the purchase source. Platform detection for Android ("google") is not implemented.
|
|
132
|
+
- **`removeComments: true` in tsconfig**: JSDoc comments are stripped from the build output. This is intentional for bundle size but means consumers cannot see inline docs from `dist/`.
|
|
91
133
|
|
|
92
134
|
## Testing
|
|
93
135
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenuecat-rn.d.ts","sourceRoot":"","sources":["../../src/adapters/revenuecat-rn.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAQ1B,wBAAgB,6BAA6B,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAE5E;AAsBD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAoBf;AAGD,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ5D;AAGD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAGD,wBAAgB,0BAA0B,IAAI,kBAAkB,
|
|
1
|
+
{"version":3,"file":"revenuecat-rn.d.ts","sourceRoot":"","sources":["../../src/adapters/revenuecat-rn.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAQ1B,wBAAgB,6BAA6B,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAE5E;AAsBD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAoBf;AAGD,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ5D;AAGD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAGD,wBAAgB,0BAA0B,IAAI,kBAAkB,CAqG/D"}
|
|
@@ -71,7 +71,7 @@ export function createConsumablesRNAdapter() {
|
|
|
71
71
|
identifier: offering.identifier,
|
|
72
72
|
metadata: offering.metadata || null,
|
|
73
73
|
packages: offering.availablePackages.map((pkg) => {
|
|
74
|
-
const metadata = pkg.product?.metadata
|
|
74
|
+
const metadata = (pkg.product?.metadata ?? {});
|
|
75
75
|
const credits = typeof metadata.credits === "number" ? metadata.credits : 0;
|
|
76
76
|
return {
|
|
77
77
|
packageId: pkg.identifier,
|
|
@@ -106,15 +106,14 @@ export function createConsumablesRNAdapter() {
|
|
|
106
106
|
throw new Error(`Package not found: ${params.packageId}`);
|
|
107
107
|
}
|
|
108
108
|
const result = await Purchases.purchasePackage(packageToPurchase);
|
|
109
|
-
const metadata = packageToPurchase.product?.metadata ||
|
|
109
|
+
const metadata = (packageToPurchase.product?.metadata ||
|
|
110
|
+
{});
|
|
110
111
|
const credits = typeof metadata.credits === "number" ? metadata.credits : 0;
|
|
111
112
|
const nonSubTransactions = result.customerInfo?.nonSubscriptionTransactions || [];
|
|
112
113
|
const latestTransaction = nonSubTransactions[nonSubTransactions.length - 1];
|
|
113
114
|
const transactionId = latestTransaction?.transactionIdentifier || `rn_${Date.now()}`;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
? "apple"
|
|
117
|
-
: "apple";
|
|
115
|
+
const { Platform } = require("react-native");
|
|
116
|
+
const platform = Platform.OS === "ios" ? "apple" : "google";
|
|
118
117
|
return {
|
|
119
118
|
transactionId,
|
|
120
119
|
productId: packageToPurchase.product?.identifier || "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenuecat-rn.js","sourceRoot":"","sources":["../../src/adapters/revenuecat-rn.ts"],"names":[],"mappings":"AAcA,IAAI,MAAM,GAAkB,IAAI,CAAC;AACjC,IAAI,aAAa,GAAkB,IAAI,CAAC;AACxC,IAAI,YAAY,GAAG,KAAK,CAAC;AAGzB,MAAM,UAAU,6BAA6B,CAAC,gBAAwB;IACpE,MAAM,GAAG,gBAAgB,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB;IAC7B,MAAM,SAAS,GAAG,MAAM,cAAc,EAAE,CAAC;IAEzC,IAAI,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC;QAC5B,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAChC,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAc,EACd,KAAc;IAEd,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,IAAI,aAAa,KAAK,MAAM;QAAE,OAAO;IAErC,MAAM,SAAS,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAE3C,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,GAAG,MAAM,CAAC;IAEvB,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;QAET,CAAC;IACH,CAAC;AACH,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,cAAc,EAAE,CAAC;QACzC,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;IAET,CAAC;IACD,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC;AAGD,MAAM,UAAU,oBAAoB;IAClC,OAAO,aAAa,KAAK,IAAI,CAAC;AAChC,CAAC;AAGD,MAAM,UAAU,0BAA0B;IACxC,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,MAA0B,EAAE,KAAc;YACxD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC5C,CAAC;iBAAM,CAAC;gBACN,MAAM,sBAAsB,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;QAED,KAAK,CAAC,YAAY;YAChB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,EAAE,CAAC;gBAC3C,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;gBAEjD,MAAM,GAAG,GAOL,EAAE,CAAC;gBAEP,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAU,EAAE,CAAC;oBAC7D,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG;wBACzB,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,IAAI;wBACnC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE;4BACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"revenuecat-rn.js","sourceRoot":"","sources":["../../src/adapters/revenuecat-rn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RevenueCat React Native Adapter for Consumables\n *\n * Wraps react-native-purchases for consumable credit purchases.\n * Lazy loads the SDK with try/catch for environments where it's not installed.\n */\n\nimport type {\n ConsumablePurchaseParams,\n ConsumablePurchaseResult,\n ConsumablesAdapter,\n} from \"../types/adapter\";\nimport type { CreditPackage, ProductMetadata } from \"../types\";\n\nlet apiKey: string | null = null;\nlet currentUserId: string | null = null;\nlet isConfigured = false;\n\n/** Configure the RN adapter with RevenueCat API key. */\nexport function configureConsumablesRNAdapter(revenueCatApiKey: string): void {\n apiKey = revenueCatApiKey;\n}\n\nasync function getRNPurchases() {\n try {\n return require(\"react-native-purchases\");\n } catch {\n throw new Error(\"react-native-purchases is not installed\");\n }\n}\n\nasync function ensureConfigured(): Promise<any> {\n const Purchases = await getRNPurchases();\n\n if (!isConfigured && apiKey) {\n Purchases.configure({ apiKey });\n isConfigured = true;\n }\n\n return Purchases;\n}\n\n/** Set the current user for RevenueCat RN. */\nexport async function setConsumablesRNUser(\n userId: string,\n email?: string,\n): Promise<void> {\n if (!apiKey) return;\n if (currentUserId === userId) return;\n\n const Purchases = await ensureConfigured();\n\n if (currentUserId) {\n await Purchases.logOut();\n }\n\n await Purchases.logIn(userId);\n currentUserId = userId;\n\n if (email) {\n try {\n await Purchases.setAttributes({ email });\n } catch {\n // Ignore attribute errors\n }\n }\n}\n\n/** Clear the current user (on logout). */\nexport async function clearConsumablesRNUser(): Promise<void> {\n try {\n const Purchases = await getRNPurchases();\n await Purchases.logOut();\n } catch {\n // SDK not available\n }\n currentUserId = null;\n}\n\n/** Check if a user is configured. */\nexport function hasConsumablesRNUser(): boolean {\n return currentUserId !== null;\n}\n\n/** Create the consumables adapter for React Native. */\nexport function createConsumablesRNAdapter(): ConsumablesAdapter {\n return {\n async setUserId(userId: string | undefined, email?: string): Promise<void> {\n if (userId) {\n await setConsumablesRNUser(userId, email);\n } else {\n await clearConsumablesRNUser();\n }\n },\n\n async getOfferings() {\n try {\n const Purchases = await ensureConfigured();\n const offerings = await Purchases.getOfferings();\n\n const all: Record<\n string,\n {\n identifier: string;\n metadata: Record<string, unknown> | null;\n packages: CreditPackage[];\n }\n > = {};\n\n for (const offering of Object.values(offerings.all) as any[]) {\n all[offering.identifier] = {\n identifier: offering.identifier,\n metadata: offering.metadata || null,\n packages: offering.availablePackages.map((pkg: any) => {\n const metadata = (pkg.product?.metadata ?? {}) as ProductMetadata;\n const credits =\n typeof metadata.credits === \"number\" ? metadata.credits : 0;\n return {\n packageId: pkg.identifier,\n productId: pkg.product?.identifier || \"\",\n title: pkg.product?.title || \"\",\n description: pkg.product?.description || null,\n credits,\n price: pkg.product?.price || 0,\n priceString: pkg.product?.priceString || \"$0\",\n currencyCode: pkg.product?.currencyCode || \"USD\",\n } satisfies CreditPackage;\n }),\n };\n }\n\n return { all };\n } catch (error) {\n console.error(\"[consumables-rn] Failed to get offerings:\", error);\n return { all: {} };\n }\n },\n\n async purchase(\n params: ConsumablePurchaseParams,\n ): Promise<ConsumablePurchaseResult> {\n const Purchases = await ensureConfigured();\n const offerings = await Purchases.getOfferings();\n\n let packageToPurchase: any;\n for (const offering of Object.values(offerings.all) as any[]) {\n packageToPurchase = offering.availablePackages.find(\n (pkg: any) => pkg.identifier === params.packageId,\n );\n if (packageToPurchase) break;\n }\n\n if (!packageToPurchase) {\n throw new Error(`Package not found: ${params.packageId}`);\n }\n\n const result = await Purchases.purchasePackage(packageToPurchase);\n const metadata = (packageToPurchase.product?.metadata ||\n {}) as ProductMetadata;\n const credits =\n typeof metadata.credits === \"number\" ? metadata.credits : 0;\n\n const nonSubTransactions =\n result.customerInfo?.nonSubscriptionTransactions || [];\n const latestTransaction =\n nonSubTransactions[nonSubTransactions.length - 1];\n const transactionId =\n latestTransaction?.transactionIdentifier || `rn_${Date.now()}`;\n\n // Determine source based on platform\n const { Platform } = require(\"react-native\") as {\n Platform: { OS: string };\n };\n const platform: \"apple\" | \"google\" =\n Platform.OS === \"ios\" ? \"apple\" : \"google\";\n\n return {\n transactionId,\n productId: packageToPurchase.product?.identifier || \"\",\n credits,\n priceCents: Math.round((packageToPurchase.product?.price || 0) * 100),\n currency: packageToPurchase.product?.currencyCode || \"USD\",\n source: platform,\n };\n },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenuecat-web.d.ts","sourceRoot":"","sources":["../../src/adapters/revenuecat-web.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAc1B,wBAAgB,8BAA8B,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAE7E;AAmBD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAwBf;AAGD,wBAAgB,uBAAuB,IAAI,IAAI,CAM9C;AAGD,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;
|
|
1
|
+
{"version":3,"file":"revenuecat-web.d.ts","sourceRoot":"","sources":["../../src/adapters/revenuecat-web.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAc1B,wBAAgB,8BAA8B,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAE7E;AAmBD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAwBf;AAGD,wBAAgB,uBAAuB,IAAI,IAAI,CAM9C;AAGD,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAuBD,wBAAgB,2BAA2B,IAAI,kBAAkB,CAuFhE"}
|
|
@@ -62,7 +62,8 @@ export function hasConsumablesWebUser() {
|
|
|
62
62
|
}
|
|
63
63
|
function convertPackage(pkg) {
|
|
64
64
|
const product = pkg.rcBillingProduct;
|
|
65
|
-
const metadata = product.metadata ||
|
|
65
|
+
const metadata = (product.metadata ||
|
|
66
|
+
{});
|
|
66
67
|
const credits = typeof metadata.credits === "number" ? metadata.credits : 0;
|
|
67
68
|
return {
|
|
68
69
|
packageId: pkg.identifier,
|
|
@@ -122,7 +123,8 @@ export function createConsumablesWebAdapter() {
|
|
|
122
123
|
rcPackage: packageToPurchase,
|
|
123
124
|
});
|
|
124
125
|
const product = packageToPurchase.rcBillingProduct;
|
|
125
|
-
const metadata = product
|
|
126
|
+
const metadata = (product
|
|
127
|
+
.metadata || {});
|
|
126
128
|
const credits = typeof metadata.credits === "number" ? metadata.credits : 0;
|
|
127
129
|
const nonSubTransactions = result.customerInfo.nonSubscriptionTransactions || [];
|
|
128
130
|
const latestTransaction = nonSubTransactions[nonSubTransactions.length - 1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenuecat-web.js","sourceRoot":"","sources":["../../src/adapters/revenuecat-web.ts"],"names":[],"mappings":"AAiBA,IAAI,iBAAiB,GAAqB,IAAI,CAAC;AAC/C,IAAI,aAAa,GAAkB,IAAI,CAAC;AACxC,IAAI,MAAM,GAAkB,IAAI,CAAC;AACjC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C,MAAM,iBAAiB,GAAG,+BAA+B,CAAC;AAG1D,MAAM,UAAU,8BAA8B,CAAC,gBAAwB;IACrE,MAAM,GAAG,gBAAgB,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,WAAW,GAAG,KAAK;IAClD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC1D,IAAI,WAAW,IAAI,CAAC,aAAa,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QACrD,iBAAiB,GAAG,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;YAC1C,MAAM;YACN,SAAS,EAAE,aAAa,IAAI,iBAAiB;SAC9C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,KAAc;IAEd,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,IAAI,aAAa,KAAK,MAAM,IAAI,iBAAiB;QAAE,OAAO;IAC1D,IAAI,gBAAgB,KAAK,MAAM;QAAE,OAAO;IAExC,gBAAgB,GAAG,MAAM,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QACrD,IAAI,iBAAiB;YAAE,iBAAiB,CAAC,KAAK,EAAE,CAAC;QACjD,iBAAiB,GAAG,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;YAC1C,MAAM;YACN,SAAS,EAAE,MAAM;SAClB,CAAC,CAAC;QACH,aAAa,GAAG,MAAM,CAAC;QACvB,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC;gBACH,MAAM,iBAAiB,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;YAET,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,gBAAgB,GAAG,IAAI,CAAC;IAC1B,CAAC;AACH,CAAC;AAGD,MAAM,UAAU,uBAAuB;IACrC,IAAI,iBAAiB,EAAE,CAAC;QACtB,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC1B,iBAAiB,GAAG,IAAI,CAAC;IAC3B,CAAC;IACD,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC;AAGD,MAAM,UAAU,qBAAqB;IACnC,OAAO,aAAa,KAAK,IAAI,IAAI,iBAAiB,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,cAAc,CAAC,GAAY;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACrC,MAAM,QAAQ,GAAI,OAAe,CAAC,QAAQ,IAAI,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5E,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,OAAO,CAAC,UAAU;QAC7B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;QACxC,OAAO;QACP,KAAK,EAAE,OAAO,CAAC,YAAY,EAAE,YAAY;YACvC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,GAAG,OAAS;YAC/C,CAAC,CAAC,CAAC;QACL,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,cAAc,IAAI,IAAI;QACzD,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,QAAQ,IAAI,KAAK;KACtD,CAAC;AACJ,CAAC;AAGD,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,MAA0B,EAAE,KAAc;YACxD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,qBAAqB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACN,uBAAuB,EAAE,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,KAAK,CAAC,YAAY;YAChB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBACjD,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;gBAEjD,MAAM,GAAG,GAOL,EAAE,CAAC;gBAEP,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5D,GAAG,CAAC,GAAG,CAAC,GAAG;wBACT,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,QAAQ,EAAG,QAAQ,CAAC,QAAoC,IAAI,IAAI;wBAChE,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC;qBACzD,CAAC;gBACJ,CAAC;gBAED,OAAO,EAAE,GAAG,EAAE,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;gBACnE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QAED,KAAK,CAAC,QAAQ,CACZ,MAAgC;YAEhC,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;YAEjD,IAAI,iBAAsC,CAAC;YAC3C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpD,iBAAiB,GAAG,QAAQ,CAAC,iBAAiB,CAAC,IAAI,CACjD,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,MAAM,CAAC,SAAS,CAC7C,CAAC;gBACF,IAAI,iBAAiB;oBAAE,MAAM;YAC/B,CAAC;YAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;YAC5D,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC;gBACtC,SAAS,EAAE,iBAAiB;aAC7B,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,iBAAiB,CAAC,gBAAgB,CAAC;YACnD,MAAM,QAAQ,GAAI,OAAe,CAAC,QAAQ,IAAI,EAAE,CAAC;YACjD,MAAM,OAAO,GACX,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAG9D,MAAM,kBAAkB,GACtB,MAAM,CAAC,YAAY,CAAC,2BAA2B,IAAI,EAAE,CAAC;YACxD,MAAM,iBAAiB,GACrB,kBAAkB,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACpD,MAAM,aAAa,GACjB,iBAAiB,EAAE,qBAAqB,IAAI,OAAO,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAElE,OAAO;gBACL,aAAa;gBACb,SAAS,EAAE,OAAO,CAAC,UAAU;gBAC7B,OAAO;gBACP,UAAU,EAAE,OAAO,CAAC,YAAY,EAAE,YAAY;oBAC5C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,GAAG,KAAM,CAAC;oBACxD,CAAC,CAAC,CAAC;gBACL,QAAQ,EAAE,OAAO,CAAC,YAAY,EAAE,QAAQ,IAAI,KAAK;gBACjD,MAAM,EAAE,KAAK;aACd,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * RevenueCat Web Adapter for Consumables\n *\n * Wraps @revenuecat/purchases-js for consumable credit purchases.\n * Lazy loads the SDK to minimize bundle size.\n */\n\nimport type {\n ConsumablePurchaseParams,\n ConsumablePurchaseResult,\n ConsumablesAdapter,\n} from \"../types/adapter\";\nimport type { CreditPackage } from \"../types\";\n\ntype Purchases = import(\"@revenuecat/purchases-js\").Purchases;\ntype Package = import(\"@revenuecat/purchases-js\").Package;\n\nlet purchasesInstance: Purchases | null = null;\nlet currentUserId: string | null = null;\nlet apiKey: string | null = null;\nlet pendingUserSetup: string | null = null;\n\nconst ANONYMOUS_USER_ID = \"$RCAnonymousID:credits_viewer\";\n\n/** Configure the web adapter with RevenueCat API key. */\nexport function configureConsumablesWebAdapter(revenueCatApiKey: string): void {\n apiKey = revenueCatApiKey;\n}\n\nasync function ensureInitialized(requireUser = false): Promise<Purchases> {\n if (!apiKey) throw new Error(\"RevenueCat not configured\");\n if (requireUser && !currentUserId) {\n throw new Error(\"RevenueCat user not set\");\n }\n\n if (!purchasesInstance) {\n const SDK = await import(\"@revenuecat/purchases-js\");\n purchasesInstance = SDK.Purchases.configure({\n apiKey,\n appUserId: currentUserId || ANONYMOUS_USER_ID,\n });\n }\n return purchasesInstance;\n}\n\n/** Set the current user for RevenueCat. */\nexport async function setConsumablesWebUser(\n userId: string,\n email?: string,\n): Promise<void> {\n if (!apiKey) return;\n if (currentUserId === userId && purchasesInstance) return;\n if (pendingUserSetup === userId) return;\n\n pendingUserSetup = userId;\n try {\n const SDK = await import(\"@revenuecat/purchases-js\");\n if (purchasesInstance) purchasesInstance.close();\n purchasesInstance = SDK.Purchases.configure({\n apiKey,\n appUserId: userId,\n });\n currentUserId = userId;\n if (email) {\n try {\n await purchasesInstance.setAttributes({ email });\n } catch {\n // Ignore attribute errors\n }\n }\n } finally {\n pendingUserSetup = null;\n }\n}\n\n/** Clear the current user (on logout). */\nexport function clearConsumablesWebUser(): void {\n if (purchasesInstance) {\n purchasesInstance.close();\n purchasesInstance = null;\n }\n currentUserId = null;\n}\n\n/** Check if a user is configured. */\nexport function hasConsumablesWebUser(): boolean {\n return currentUserId !== null && purchasesInstance !== null;\n}\n\nfunction convertPackage(pkg: Package): CreditPackage {\n const product = pkg.rcBillingProduct;\n const metadata = (product as any).metadata || {};\n const credits = typeof metadata.credits === \"number\" ? metadata.credits : 0;\n\n return {\n packageId: pkg.identifier,\n productId: product.identifier,\n title: product.title,\n description: product.description || null,\n credits,\n price: product.currentPrice?.amountMicros\n ? product.currentPrice.amountMicros / 1_000_000\n : 0,\n priceString: product.currentPrice?.formattedPrice || \"$0\",\n currencyCode: product.currentPrice?.currency || \"USD\",\n };\n}\n\n/** Create the consumables adapter for web. */\nexport function createConsumablesWebAdapter(): ConsumablesAdapter {\n return {\n async setUserId(userId: string | undefined, email?: string): Promise<void> {\n if (userId) {\n await setConsumablesWebUser(userId, email);\n } else {\n clearConsumablesWebUser();\n }\n },\n\n async getOfferings() {\n try {\n const purchases = await ensureInitialized(false);\n const offerings = await purchases.getOfferings();\n\n const all: Record<\n string,\n {\n identifier: string;\n metadata: Record<string, unknown> | null;\n packages: CreditPackage[];\n }\n > = {};\n\n for (const [key, offering] of Object.entries(offerings.all)) {\n all[key] = {\n identifier: offering.identifier,\n metadata: (offering.metadata as Record<string, unknown>) || null,\n packages: offering.availablePackages.map(convertPackage),\n };\n }\n\n return { all };\n } catch (error) {\n console.error(\"[consumables-web] Failed to get offerings:\", error);\n return { all: {} };\n }\n },\n\n async purchase(\n params: ConsumablePurchaseParams,\n ): Promise<ConsumablePurchaseResult> {\n const purchases = await ensureInitialized(true);\n const offerings = await purchases.getOfferings();\n\n let packageToPurchase: Package | undefined;\n for (const offering of Object.values(offerings.all)) {\n packageToPurchase = offering.availablePackages.find(\n (pkg) => pkg.identifier === params.packageId,\n );\n if (packageToPurchase) break;\n }\n\n if (!packageToPurchase) {\n throw new Error(`Package not found: ${params.packageId}`);\n }\n\n const result = await purchases.purchase({\n rcPackage: packageToPurchase,\n });\n\n const product = packageToPurchase.rcBillingProduct;\n const metadata = (product as any).metadata || {};\n const credits =\n typeof metadata.credits === \"number\" ? metadata.credits : 0;\n\n // Extract transaction ID from the result\n const nonSubTransactions =\n result.customerInfo.nonSubscriptionTransactions || [];\n const latestTransaction =\n nonSubTransactions[nonSubTransactions.length - 1];\n const transactionId =\n latestTransaction?.transactionIdentifier || `web_${Date.now()}`;\n\n return {\n transactionId,\n productId: product.identifier,\n credits,\n priceCents: product.currentPrice?.amountMicros\n ? Math.round(product.currentPrice.amountMicros / 10_000)\n : 0,\n currency: product.currentPrice?.currency || \"USD\",\n source: \"web\",\n };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"revenuecat-web.js","sourceRoot":"","sources":["../../src/adapters/revenuecat-web.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RevenueCat Web Adapter for Consumables\n *\n * Wraps @revenuecat/purchases-js for consumable credit purchases.\n * Lazy loads the SDK to minimize bundle size.\n */\n\nimport type {\n ConsumablePurchaseParams,\n ConsumablePurchaseResult,\n ConsumablesAdapter,\n} from \"../types/adapter\";\nimport type { CreditPackage, ProductMetadata } from \"../types\";\n\ntype Purchases = import(\"@revenuecat/purchases-js\").Purchases;\ntype Package = import(\"@revenuecat/purchases-js\").Package;\n\nlet purchasesInstance: Purchases | null = null;\nlet currentUserId: string | null = null;\nlet apiKey: string | null = null;\nlet pendingUserSetup: string | null = null;\n\nconst ANONYMOUS_USER_ID = \"$RCAnonymousID:credits_viewer\";\n\n/** Configure the web adapter with RevenueCat API key. */\nexport function configureConsumablesWebAdapter(revenueCatApiKey: string): void {\n apiKey = revenueCatApiKey;\n}\n\nasync function ensureInitialized(requireUser = false): Promise<Purchases> {\n if (!apiKey) throw new Error(\"RevenueCat not configured\");\n if (requireUser && !currentUserId) {\n throw new Error(\"RevenueCat user not set\");\n }\n\n if (!purchasesInstance) {\n const SDK = await import(\"@revenuecat/purchases-js\");\n purchasesInstance = SDK.Purchases.configure({\n apiKey,\n appUserId: currentUserId || ANONYMOUS_USER_ID,\n });\n }\n return purchasesInstance;\n}\n\n/** Set the current user for RevenueCat. */\nexport async function setConsumablesWebUser(\n userId: string,\n email?: string,\n): Promise<void> {\n if (!apiKey) return;\n if (currentUserId === userId && purchasesInstance) return;\n if (pendingUserSetup === userId) return;\n\n pendingUserSetup = userId;\n try {\n const SDK = await import(\"@revenuecat/purchases-js\");\n if (purchasesInstance) purchasesInstance.close();\n purchasesInstance = SDK.Purchases.configure({\n apiKey,\n appUserId: userId,\n });\n currentUserId = userId;\n if (email) {\n try {\n await purchasesInstance.setAttributes({ email });\n } catch {\n // Ignore attribute errors\n }\n }\n } finally {\n pendingUserSetup = null;\n }\n}\n\n/** Clear the current user (on logout). */\nexport function clearConsumablesWebUser(): void {\n if (purchasesInstance) {\n purchasesInstance.close();\n purchasesInstance = null;\n }\n currentUserId = null;\n}\n\n/** Check if a user is configured. */\nexport function hasConsumablesWebUser(): boolean {\n return currentUserId !== null && purchasesInstance !== null;\n}\n\nfunction convertPackage(pkg: Package): CreditPackage {\n const product = pkg.rcBillingProduct;\n const metadata = ((product as unknown as Record<string, unknown>).metadata ||\n {}) as ProductMetadata;\n const credits = typeof metadata.credits === \"number\" ? metadata.credits : 0;\n\n return {\n packageId: pkg.identifier,\n productId: product.identifier,\n title: product.title,\n description: product.description || null,\n credits,\n price: product.currentPrice?.amountMicros\n ? product.currentPrice.amountMicros / 1_000_000\n : 0,\n priceString: product.currentPrice?.formattedPrice || \"$0\",\n currencyCode: product.currentPrice?.currency || \"USD\",\n };\n}\n\n/** Create the consumables adapter for web. */\nexport function createConsumablesWebAdapter(): ConsumablesAdapter {\n return {\n async setUserId(userId: string | undefined, email?: string): Promise<void> {\n if (userId) {\n await setConsumablesWebUser(userId, email);\n } else {\n clearConsumablesWebUser();\n }\n },\n\n async getOfferings() {\n try {\n const purchases = await ensureInitialized(false);\n const offerings = await purchases.getOfferings();\n\n const all: Record<\n string,\n {\n identifier: string;\n metadata: Record<string, unknown> | null;\n packages: CreditPackage[];\n }\n > = {};\n\n for (const [key, offering] of Object.entries(offerings.all)) {\n all[key] = {\n identifier: offering.identifier,\n metadata: (offering.metadata as Record<string, unknown>) || null,\n packages: offering.availablePackages.map(convertPackage),\n };\n }\n\n return { all };\n } catch (error) {\n console.error(\"[consumables-web] Failed to get offerings:\", error);\n return { all: {} };\n }\n },\n\n async purchase(\n params: ConsumablePurchaseParams,\n ): Promise<ConsumablePurchaseResult> {\n const purchases = await ensureInitialized(true);\n const offerings = await purchases.getOfferings();\n\n let packageToPurchase: Package | undefined;\n for (const offering of Object.values(offerings.all)) {\n packageToPurchase = offering.availablePackages.find(\n (pkg) => pkg.identifier === params.packageId,\n );\n if (packageToPurchase) break;\n }\n\n if (!packageToPurchase) {\n throw new Error(`Package not found: ${params.packageId}`);\n }\n\n const result = await purchases.purchase({\n rcPackage: packageToPurchase,\n });\n\n const product = packageToPurchase.rcBillingProduct;\n const metadata = ((product as unknown as Record<string, unknown>)\n .metadata || {}) as ProductMetadata;\n const credits =\n typeof metadata.credits === \"number\" ? metadata.credits : 0;\n\n // Extract transaction ID from the result\n const nonSubTransactions =\n result.customerInfo.nonSubscriptionTransactions || [];\n const latestTransaction =\n nonSubTransactions[nonSubTransactions.length - 1];\n const transactionId =\n latestTransaction?.transactionIdentifier || `web_${Date.now()}`;\n\n return {\n transactionId,\n productId: product.identifier,\n credits,\n priceCents: product.currentPrice?.amountMicros\n ? Math.round(product.currentPrice.amountMicros / 10_000)\n : 0,\n currency: product.currentPrice?.currency || \"USD\",\n source: \"web\",\n };\n },\n };\n}\n"]}
|
package/dist/core/service.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare class ConsumablesService {
|
|
|
11
11
|
private balanceCache;
|
|
12
12
|
private offeringsCache;
|
|
13
13
|
private loadOfferingsPromise;
|
|
14
|
-
private
|
|
14
|
+
private loadBalancePromise;
|
|
15
15
|
constructor(config: ConsumablesServiceConfig);
|
|
16
16
|
loadOfferings(): Promise<void>;
|
|
17
17
|
getOffering(offeringId: string): CreditOffering | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAA0C;IAChE,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,OAAO,CAAC,kBAAkB,CAAuC;gBAErD,MAAM,EAAE,wBAAwB;IAUtC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAQtD,cAAc,IAAI,MAAM,EAAE;IASpB,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC;IAoB3C,gBAAgB,IAAI,aAAa,GAAG,IAAI;IAUlC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,aAAa,CAAC;IA0BlE,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAoB3C,kBAAkB,CACtB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAUhC,eAAe,CACnB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAQnC,UAAU,IAAI,IAAI;IAMlB,kBAAkB,IAAI,OAAO;IAK7B,gBAAgB,IAAI,OAAO;CAG5B"}
|
package/dist/core/service.js
CHANGED
|
@@ -3,7 +3,7 @@ export class ConsumablesService {
|
|
|
3
3
|
this.balanceCache = null;
|
|
4
4
|
this.offeringsCache = new Map();
|
|
5
5
|
this.loadOfferingsPromise = null;
|
|
6
|
-
this.
|
|
6
|
+
this.loadBalancePromise = null;
|
|
7
7
|
this.adapter = config.adapter;
|
|
8
8
|
this.apiClient = config.apiClient;
|
|
9
9
|
}
|
|
@@ -35,17 +35,20 @@ export class ConsumablesService {
|
|
|
35
35
|
return Array.from(this.offeringsCache.keys());
|
|
36
36
|
}
|
|
37
37
|
async loadBalance() {
|
|
38
|
-
if (this.
|
|
38
|
+
if (this.balanceCache)
|
|
39
39
|
return this.balanceCache;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
if (this.loadBalancePromise)
|
|
41
|
+
return this.loadBalancePromise;
|
|
42
|
+
this.loadBalancePromise = (async () => {
|
|
43
|
+
try {
|
|
44
|
+
this.balanceCache = await this.apiClient.getBalance();
|
|
45
|
+
return this.balanceCache;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.loadBalancePromise = null;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
return this.loadBalancePromise;
|
|
49
52
|
}
|
|
50
53
|
getCachedBalance() {
|
|
51
54
|
return this.balanceCache;
|
package/dist/core/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Core ConsumablesService class that orchestrates adapter and API client\n * interactions. Manages caching of offerings (global) and balance (per-user).\n */\n\nimport type {\n ConsumablePurchaseRecord,\n ConsumableUsageRecord,\n} from \"@sudobility/types\";\nimport type {\n ConsumablePurchaseParams,\n ConsumablesAdapter,\n CreditBalance,\n CreditOffering,\n} from \"../types\";\nimport type { ConsumablesApiClient } from \"../network/ConsumablesApiClient\";\n\n/** Configuration for constructing a ConsumablesService instance. */\nexport interface ConsumablesServiceConfig {\n adapter: ConsumablesAdapter;\n apiClient: ConsumablesApiClient;\n}\n\nexport class ConsumablesService {\n private adapter: ConsumablesAdapter;\n private apiClient: ConsumablesApiClient;\n private balanceCache: CreditBalance | null = null;\n private offeringsCache: Map<string, CreditOffering> = new Map();\n private loadOfferingsPromise: Promise<void> | null = null;\n private loadBalancePromise: Promise<CreditBalance> | null = null;\n\n constructor(config: ConsumablesServiceConfig) {\n this.adapter = config.adapter;\n this.apiClient = config.apiClient;\n }\n\n /**\n * Loads offerings from RevenueCat via the adapter.\n * Caches results globally; subsequent calls are no-ops if cache is populated.\n * Deduplicates concurrent calls via a shared promise.\n */\n async loadOfferings(): Promise<void> {\n if (this.offeringsCache.size > 0) return;\n if (this.loadOfferingsPromise) return this.loadOfferingsPromise;\n\n this.loadOfferingsPromise = (async () => {\n try {\n const result = await this.adapter.getOfferings();\n for (const [key, offering] of Object.entries(result.all)) {\n this.offeringsCache.set(key, {\n offeringId: offering.identifier,\n packages: offering.packages,\n });\n }\n } finally {\n this.loadOfferingsPromise = null;\n }\n })();\n\n return this.loadOfferingsPromise;\n }\n\n /**\n * Retrieves a cached offering by its identifier.\n * @param offeringId - The RevenueCat offering identifier.\n * @returns The offering with its packages, or null if not found in cache.\n */\n getOffering(offeringId: string): CreditOffering | null {\n return this.offeringsCache.get(offeringId) ?? null;\n }\n\n /**\n * Returns all cached offering identifiers.\n * @returns Array of offering ID strings.\n */\n getOfferingIds(): string[] {\n return Array.from(this.offeringsCache.keys());\n }\n\n /**\n * Loads the current user's balance from the backend API.\n * Caches the result; returns cached value if a load is already in progress.\n * @returns The user's credit balance.\n */\n async loadBalance(): Promise<CreditBalance> {\n if (this.balanceCache) return this.balanceCache;\n if (this.loadBalancePromise) return this.loadBalancePromise;\n\n this.loadBalancePromise = (async () => {\n try {\n this.balanceCache = await this.apiClient.getBalance();\n return this.balanceCache;\n } finally {\n this.loadBalancePromise = null;\n }\n })();\n\n return this.loadBalancePromise;\n }\n\n /**\n * Returns the cached balance without making an API call.\n * @returns The cached credit balance, or null if not yet loaded.\n */\n getCachedBalance(): CreditBalance | null {\n return this.balanceCache;\n }\n\n /**\n * Executes a full purchase flow: opens RevenueCat payment UI via adapter,\n * records the purchase on the backend, and updates the local balance cache.\n * @param params - The package and offering IDs to purchase.\n * @returns The updated credit balance after the purchase.\n */\n async purchase(params: ConsumablePurchaseParams): Promise<CreditBalance> {\n // 1. Call adapter.purchase() — opens RevenueCat payment UI\n const purchaseResult = await this.adapter.purchase(params);\n\n // 2. Record on backend\n const balance = await this.apiClient.recordPurchase({\n credits: purchaseResult.credits,\n source: purchaseResult.source,\n transaction_ref_id: purchaseResult.transactionId,\n product_id: purchaseResult.productId,\n price_cents: purchaseResult.priceCents,\n currency: purchaseResult.currency,\n });\n\n // 3. Update cache\n this.balanceCache = balance;\n\n return balance;\n }\n\n /**\n * Records a credit usage (e.g., file download) on the backend.\n * Updates the local balance cache with the new balance.\n * @param filename - Optional filename associated with this usage.\n * @returns The updated balance and whether the usage was successful.\n */\n async recordUsage(\n filename?: string,\n ): Promise<{ balance: number; success: boolean }> {\n const result = await this.apiClient.recordUsage(filename);\n\n // Update cache\n if (this.balanceCache) {\n this.balanceCache = {\n ...this.balanceCache,\n balance: result.balance,\n };\n }\n\n return result;\n }\n\n /**\n * Fetches paginated purchase history from the API.\n * @param limit - Maximum number of records to return.\n * @param offset - Number of records to skip.\n * @returns Array of purchase records.\n */\n async getPurchaseHistory(\n limit?: number,\n offset?: number,\n ): Promise<ConsumablePurchaseRecord[]> {\n return this.apiClient.getPurchaseHistory(limit, offset);\n }\n\n /**\n * Fetches paginated usage history from the API.\n * @param limit - Maximum number of records to return.\n * @param offset - Number of records to skip.\n * @returns Array of usage records.\n */\n async getUsageHistory(\n limit?: number,\n offset?: number,\n ): Promise<ConsumableUsageRecord[]> {\n return this.apiClient.getUsageHistory(limit, offset);\n }\n\n /**\n * Clears the balance cache. Called on user change.\n * Preserves the offerings cache since products are not user-specific.\n */\n clearCache(): void {\n this.balanceCache = null;\n // Preserve offerings cache — products don't change per user\n }\n\n /** @returns Whether offerings have been loaded and cached. */\n hasLoadedOfferings(): boolean {\n return this.offeringsCache.size > 0;\n }\n\n /** @returns Whether the balance has been loaded and cached. */\n hasLoadedBalance(): boolean {\n return this.balanceCache !== null;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAG/C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,oBAAoB,CAAC;CACjC;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAMrE;AAGD,wBAAgB,sBAAsB,IAAI,kBAAkB,CAO3D;AAGD,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAGD,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAGD,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/D;AAMD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAgBf;AAGD,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAEzD;AAGD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAM3E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAM1E;AAGD,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAQ/C,IAAI,QAAQ,GAA8B,IAAI,CAAC;AAC/C,IAAI,cAAc,GAA8B,IAAI,CAAC;AACrD,IAAI,aAAa,GAAuB,SAAS,CAAC;AAClD,MAAM,sBAAsB,GAAsB,EAAE,CAAC;AACrD,MAAM,qBAAqB,GAAsB,EAAE,CAAC;AAGpD,MAAM,UAAU,qBAAqB,CAAC,MAAyB;IAC7D,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,QAAQ,GAAG,IAAI,kBAAkB,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;AACL,CAAC;AAGD,MAAM,UAAU,sBAAsB;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAGD,MAAM,UAAU,wBAAwB;IACtC,OAAO,QAAQ,KAAK,IAAI,CAAC;AAC3B,CAAC;AAGD,MAAM,UAAU,gBAAgB;IAC9B,QAAQ,GAAG,IAAI,CAAC;IAChB,cAAc,GAAG,IAAI,CAAC;IACtB,aAAa,GAAG,SAAS,CAAC;AAC5B,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC7B,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,KAAc;IAEd,IAAI,aAAa,KAAK,MAAM;QAAE,OAAO;IAErC,aAAa,GAAG,MAAM,CAAC;IAEvB,IAAI,cAAc,EAAE,SAAS,EAAE,CAAC;QAC9B,MAAM,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,UAAU,EAAE,CAAC;IACxB,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,qBAAqB,EAAE,CAAC;QAC7C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAGD,MAAM,UAAU,oBAAoB;IAClC,OAAO,aAAa,CAAC;AACvB,CAAC;AAGD,MAAM,UAAU,0BAA0B,CAAC,QAAoB;IAC7D,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO,GAAG,EAAE;QACV,MAAM,KAAK,GAAG,sBAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,KAAK,IAAI,CAAC;YAAE,sBAAsB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC;AAGD,MAAM,UAAU,yBAAyB,CAAC,QAAoB;IAC5D,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,GAAG,EAAE;QACV,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,KAAK,IAAI,CAAC;YAAE,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC;AAGD,MAAM,UAAU,mBAAmB;IACjC,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC","sourcesContent":["/**\n * @fileoverview Module-level singleton for the consumables system.\n * Provides initialization, user management, and event subscription functions\n * without requiring a React Context provider.\n */\n\nimport type { ConsumablesAdapter } from \"../types/adapter\";\nimport type { ConsumablesApiClient } from \"../network/ConsumablesApiClient\";\nimport { ConsumablesService } from \"./service\";\n\n/** Configuration for initializing the consumables singleton. */\nexport interface ConsumablesConfig {\n adapter: ConsumablesAdapter;\n apiClient: ConsumablesApiClient;\n}\n\nlet instance: ConsumablesService | null = null;\nlet currentAdapter: ConsumablesAdapter | null = null;\nlet currentUserId: string | undefined = undefined;\nconst balanceChangeListeners: Array<() => void> = [];\nconst userIdChangeListeners: Array<() => void> = [];\n\n/** Initialize the consumables singleton. Call once at app startup. */\nexport function initializeConsumables(config: ConsumablesConfig): void {\n currentAdapter = config.adapter;\n instance = new ConsumablesService({\n adapter: config.adapter,\n apiClient: config.apiClient,\n });\n}\n\n/** Get the consumables service singleton. Throws if not initialized. */\nexport function getConsumablesInstance(): ConsumablesService {\n if (!instance) {\n throw new Error(\n \"Consumables not initialized. Call initializeConsumables() first.\",\n );\n }\n return instance;\n}\n\n/** Check if consumables singleton is initialized. */\nexport function isConsumablesInitialized(): boolean {\n return instance !== null;\n}\n\n/** Reset the singleton (mainly for testing). */\nexport function resetConsumables(): void {\n instance = null;\n currentAdapter = null;\n currentUserId = undefined;\n}\n\n/** Refresh balance from server and notify listeners. */\nexport async function refreshConsumablesBalance(): Promise<void> {\n if (!instance) return;\n await instance.loadBalance();\n for (const listener of balanceChangeListeners) {\n listener();\n }\n}\n\n/**\n * Set the user ID for the consumables service.\n * Clears cached balance and re-initializes RevenueCat adapter.\n */\nexport async function setConsumablesUserId(\n userId: string | undefined,\n email?: string,\n): Promise<void> {\n if (currentUserId === userId) return;\n\n currentUserId = userId;\n\n if (currentAdapter?.setUserId) {\n await currentAdapter.setUserId(userId, email);\n }\n\n if (instance) {\n instance.clearCache();\n }\n\n for (const listener of userIdChangeListeners) {\n listener();\n }\n}\n\n/** Get the current user ID. */\nexport function getConsumablesUserId(): string | undefined {\n return currentUserId;\n}\n\n/** Subscribe to balance changes. Returns unsubscribe function. */\nexport function onConsumablesBalanceChange(listener: () => void): () => void {\n balanceChangeListeners.push(listener);\n return () => {\n const index = balanceChangeListeners.indexOf(listener);\n if (index >= 0) balanceChangeListeners.splice(index, 1);\n };\n}\n\n/** Subscribe to user ID changes. Returns unsubscribe function. */\nexport function onConsumablesUserIdChange(listener: () => void): () => void {\n userIdChangeListeners.push(listener);\n return () => {\n const index = userIdChangeListeners.indexOf(listener);\n if (index >= 0) userIdChangeListeners.splice(index, 1);\n };\n}\n\n/** Notify balance change listeners (call after purchase/usage). */\nexport function notifyBalanceChange(): void {\n for (const listener of balanceChangeListeners) {\n listener();\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBalance.d.ts","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBalance.d.ts","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,UAAU,IAAI,gBAAgB,CAoD7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBalance.js","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBalance.js","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC1E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,iBAAiB,CAAC,IAAI,CAAC,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC5C,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3B,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,EAAE,CAAC;IAChB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAG,0BAA0B,CAAC,GAAG,EAAE;YACnD,IAAI,wBAAwB,EAAE,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,sBAAsB,EAAE,CAAC,gBAAgB,EAAE,CAAC;gBAC3D,IAAI,MAAM,EAAE,CAAC;oBACX,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAC3B,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,yBAAyB,CAAC,GAAG,EAAE;YAC/C,WAAW,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,YAAY,EAAE,CAAC;YACf,SAAS,EAAE,CAAC;QACd,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAC7E,CAAC","sourcesContent":["/**\n * @fileoverview React hook for tracking the current user's credit balance.\n * Subscribes to both balance change and user ID change events from the singleton.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesBalanceChange,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\n\n/** Result object returned by the useBalance hook. */\nexport interface UseBalanceResult {\n balance: number | null;\n initialCredits: number | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that tracks the current user's credit balance.\n * Automatically loads the balance on mount and re-fetches when the user ID changes.\n * Listens to balance change events emitted after purchases or usage.\n * @returns Current balance, loading/error state, and a refetch function.\n */\nexport function useBalance(): UseBalanceResult {\n const [balance, setBalance] = useState<number | null>(null);\n const [initialCredits, setInitialCredits] = useState<number | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const loadBalance = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setBalance(null);\n setInitialCredits(null);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.loadBalance();\n setBalance(result.balance);\n setInitialCredits(result.initialCredits);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n useEffect(() => {\n loadBalance();\n }, [loadBalance]);\n\n useEffect(() => {\n const unsubBalance = onConsumablesBalanceChange(() => {\n if (isConsumablesInitialized()) {\n const cached = getConsumablesInstance().getCachedBalance();\n if (cached) {\n setBalance(cached.balance);\n setInitialCredits(cached.initialCredits);\n }\n }\n });\n const unsubUser = onConsumablesUserIdChange(() => {\n loadBalance();\n });\n return () => {\n unsubBalance();\n unsubUser();\n };\n }, [loadBalance]);\n\n return { balance, initialCredits, isLoading, error, refetch: loadBalance };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConsumableProducts.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useConsumableProducts.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GACjB,2BAA2B,CA8B7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConsumableProducts.js","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useConsumableProducts.js","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,UAAU,qBAAqB,CACnC,UAAkB;IAElB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAkB,EAAE,CAAC,CAAC;IAC9D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC1C,IAAI,CAAC,wBAAwB,EAAE,EAAE,CAAC;YAChC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YAClD,WAAW,CAAC,QAAQ,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,CAAC;IACjB,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading available credit packages from a RevenueCat offering.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n isConsumablesInitialized,\n} from \"../core/singleton\";\nimport type { CreditPackage } from \"../types\";\n\n/** Result object returned by the useConsumableProducts hook. */\nexport interface UseConsumableProductsResult {\n packages: CreditPackage[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads available credit packages from a specific RevenueCat offering.\n * Caches offerings globally (they do not change per user).\n * @param offeringId - The RevenueCat offering identifier to load packages from.\n * @returns Available packages, loading/error state, and a refetch function.\n */\nexport function useConsumableProducts(\n offeringId: string,\n): UseConsumableProductsResult {\n const [packages, setPackages] = useState<CreditPackage[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const loadProducts = useCallback(async () => {\n if (!isConsumablesInitialized()) {\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n await instance.loadOfferings();\n const offering = instance.getOffering(offeringId);\n setPackages(offering?.packages ?? []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [offeringId]);\n\n useEffect(() => {\n loadProducts();\n }, [loadProducts]);\n\n return { packages, isLoading, error, refetch: loadProducts };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseCredits.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseCredits.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAQD,wBAAgB,kBAAkB,IAAI,wBAAwB,CA6B7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseCredits.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseCredits.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAe3B,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,WAAW,CAC1B,KAAK,EAAE,SAAiB,EAAE,UAAkB,EAAoB,EAAE;QAChE,IAAI,CAAC,wBAAwB,EAAE,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACnD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;YACnD,mBAAmB,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9D,OAAO,KAAK,CAAC;QACf,CAAC;gBAAS,CAAC;YACT,eAAe,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * @fileoverview React hook for executing credit purchases via the RevenueCat adapter.\n * Handles the full purchase flow: adapter payment UI -> backend recording -> balance notification.\n */\n\nimport { useCallback, useState } from \"react\";\nimport {\n getConsumablesInstance,\n isConsumablesInitialized,\n notifyBalanceChange,\n} from \"../core/singleton\";\n\n/** Result object returned by the usePurchaseCredits hook. */\nexport interface UsePurchaseCreditsResult {\n purchase: (packageId: string, offeringId: string) => Promise<boolean>;\n isPurchasing: boolean;\n error: Error | null;\n}\n\n/**\n * Hook that provides a purchase callback for buying credit packages.\n * Opens the platform payment UI, records the purchase on the backend,\n * and notifies balance change listeners on success.\n * @returns A purchase function, purchasing state, and error state.\n */\nexport function usePurchaseCredits(): UsePurchaseCreditsResult {\n const [isPurchasing, setIsPurchasing] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const purchase = useCallback(\n async (packageId: string, offeringId: string): Promise<boolean> => {\n if (!isConsumablesInitialized()) {\n setError(new Error(\"Consumables not initialized\"));\n return false;\n }\n\n setIsPurchasing(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n await instance.purchase({ packageId, offeringId });\n notifyBalanceChange();\n return true;\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n return false;\n } finally {\n setIsPurchasing(false);\n }\n },\n [],\n );\n\n return { purchase, isPurchasing, error };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,wBAAwB,EAAE,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,kBAAkB,CAAC,KAAK,SAAK,GAAG,wBAAwB,CA8CvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseHistory.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseHistory.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAkB3B,MAAM,UAAU,kBAAkB,CAAC,KAAK,GAAG,EAAE;IAC3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAA6B,EAAE,CAAC,CAAC;IAC3E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,YAAY,CAAC,EAAE,CAAC,CAAC;YACjB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC3D,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACtC,IAAI,CAAC,wBAAwB,EAAE;YAAE,OAAO;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;YAC1E,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,EAAE,CAAC;IACT,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,yBAAyB,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading paginated purchase history.\n * Supports initial load, load-more pagination, and automatic refresh on user change.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\nimport type { ConsumablePurchaseRecord } from \"@sudobility/types\";\n\n/** Result object returned by the usePurchaseHistory hook. */\nexport interface UsePurchaseHistoryResult {\n purchases: ConsumablePurchaseRecord[];\n isLoading: boolean;\n error: Error | null;\n loadMore: () => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads paginated purchase history for the current user.\n * Automatically reloads when the user ID changes.\n * @param limit - Maximum number of records per page. Defaults to 50.\n * @returns Purchase records, loading/error state, loadMore and refetch functions.\n */\nexport function usePurchaseHistory(limit = 50): UsePurchaseHistoryResult {\n const [purchases, setPurchases] = useState<ConsumablePurchaseRecord[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const load = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setPurchases([]);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getPurchaseHistory(limit, 0);\n setPurchases(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [limit]);\n\n const loadMore = useCallback(async () => {\n if (!isConsumablesInitialized()) return;\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getPurchaseHistory(limit, purchases.length);\n setPurchases((prev) => [...prev, ...result]);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [limit, purchases.length]);\n\n useEffect(() => {\n load();\n }, [load]);\n\n useEffect(() => {\n const unsub = onConsumablesUserIdChange(() => load());\n return unsub;\n }, [load]);\n\n return { purchases, isLoading, error, loadMore, refetch: load };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUsageHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useUsageHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,eAAe,CAAC,KAAK,SAAK,GAAG,qBAAqB,CA8CjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUsageHistory.js","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useUsageHistory.js","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAkB3B,MAAM,UAAU,eAAe,CAAC,KAAK,GAAG,EAAE;IACxC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA0B,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,SAAS,CAAC,EAAE,CAAC,CAAC;YACd,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxD,SAAS,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACtC,IAAI,CAAC,wBAAwB,EAAE;YAAE,OAAO;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACpE,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,EAAE,CAAC;IACT,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,yBAAyB,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading paginated usage history.\n * Supports initial load, load-more pagination, and automatic refresh on user change.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\nimport type { ConsumableUsageRecord } from \"@sudobility/types\";\n\n/** Result object returned by the useUsageHistory hook. */\nexport interface UseUsageHistoryResult {\n usages: ConsumableUsageRecord[];\n isLoading: boolean;\n error: Error | null;\n loadMore: () => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads paginated usage history for the current user.\n * Automatically reloads when the user ID changes.\n * @param limit - Maximum number of records per page. Defaults to 50.\n * @returns Usage records, loading/error state, loadMore and refetch functions.\n */\nexport function useUsageHistory(limit = 50): UseUsageHistoryResult {\n const [usages, setUsages] = useState<ConsumableUsageRecord[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const load = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setUsages([]);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getUsageHistory(limit, 0);\n setUsages(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [limit]);\n\n const loadMore = useCallback(async () => {\n if (!isConsumablesInitialized()) return;\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getUsageHistory(limit, usages.length);\n setUsages((prev) => [...prev, ...result]);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [limit, usages.length]);\n\n useEffect(() => {\n load();\n }, [load]);\n\n useEffect(() => {\n const unsub = onConsumablesUserIdChange(() => load());\n return unsub;\n }, [load]);\n\n return { usages, isLoading, error, loadMore, refetch: load };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsumablesApiClient.d.ts","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConsumablesApiClient.d.ts","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACrB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAOD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAElC,MAAM,EAAE,0BAA0B;IAK9C,OAAO,CAAC,QAAQ;YAIF,GAAG;YAYH,IAAI;IAiBZ,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;IAgBpC,cAAc,CAAC,MAAM,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,aAAa,CAAC;IAgBpB,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAY3C,kBAAkB,CACtB,KAAK,SAAK,EACV,MAAM,SAAI,GACT,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAYhC,eAAe,CACnB,KAAK,SAAK,EACV,MAAM,SAAI,GACT,OAAO,CAAC,qBAAqB,EAAE,CAAC;CAKpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsumablesApiClient.js","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConsumablesApiClient.js","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"AAwBA,MAAM,OAAO,oBAAoB;IAI/B,YAAY,MAAkC;QAC5C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC5C,CAAC;IAEO,QAAQ,CAAC,IAAY;QAC3B,OAAO,GAAG,IAAI,CAAC,OAAO,sBAAsB,IAAI,EAAE,CAAC;IACrD,CAAC;IAEO,KAAK,CAAC,GAAG,CAAI,IAAY;QAC/B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAC3C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CACpB,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAc;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EACnB,IAAI,CACL,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAMD,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAGxB,UAAU,CAAC,CAAC;QACf,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,cAAc,EAAE,IAAI,CAAC,eAAe;SACrC,CAAC;IACJ,CAAC;IAOD,KAAK,CAAC,cAAc,CAAC,MAOpB;QACC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAGzB,WAAW,EAAE,MAAM,CAAC,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,cAAc,EAAE,IAAI,CAAC,eAAe;SACrC,CAAC;IACJ,CAAC;IAOD,KAAK,CAAC,WAAW,CACf,QAAiB;QAEjB,OAAO,IAAI,CAAC,IAAI,CAAwC,MAAM,EAAE;YAC9D,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAQD,KAAK,CAAC,kBAAkB,CACtB,KAAK,GAAG,EAAE,EACV,MAAM,GAAG,CAAC;QAEV,OAAO,IAAI,CAAC,GAAG,CACb,oBAAoB,KAAK,WAAW,MAAM,EAAE,CAC7C,CAAC;IACJ,CAAC;IAQD,KAAK,CAAC,eAAe,CACnB,KAAK,GAAG,EAAE,EACV,MAAM,GAAG,CAAC;QAEV,OAAO,IAAI,CAAC,GAAG,CACb,iBAAiB,KAAK,WAAW,MAAM,EAAE,CAC1C,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * @fileoverview HTTP client for the consumables API endpoints.\n * Wraps a NetworkClient to communicate with /api/v1/consumables/* endpoints,\n * mapping between snake_case API responses and camelCase client types.\n */\n\nimport type {\n ConsumablePurchaseRecord,\n ConsumableUsageRecord,\n NetworkClient,\n} from \"@sudobility/types\";\nimport type { CreditBalance } from \"../types\";\n\n/** Configuration for constructing a ConsumablesApiClient instance. */\nexport interface ConsumablesApiClientConfig {\n baseUrl: string;\n networkClient: NetworkClient;\n}\n\ninterface ApiResponse<T> {\n data: T;\n error?: string;\n}\n\nexport class ConsumablesApiClient {\n private readonly baseUrl: string;\n private readonly networkClient: NetworkClient;\n\n constructor(config: ConsumablesApiClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.networkClient = config.networkClient;\n }\n\n private buildUrl(path: string): string {\n return `${this.baseUrl}/api/v1/consumables${path}`;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.networkClient.get<ApiResponse<T>>(\n this.buildUrl(path),\n );\n if (!response.ok || !response.data) {\n throw new Error(\n response.data?.error || `Request failed: ${response.status}`,\n );\n }\n return response.data.data;\n }\n\n private async post<T>(path: string, body?: unknown): Promise<T> {\n const response = await this.networkClient.post<ApiResponse<T>>(\n this.buildUrl(path),\n body,\n );\n if (!response.ok || !response.data) {\n throw new Error(\n response.data?.error || `Request failed: ${response.status}`,\n );\n }\n return response.data.data;\n }\n\n /**\n * Fetches the current user's credit balance from the API.\n * @returns The credit balance with current balance and initial credits.\n */\n async getBalance(): Promise<CreditBalance> {\n const data = await this.get<{\n balance: number;\n initial_credits: number;\n }>(\"/balance\");\n return {\n balance: data.balance,\n initialCredits: data.initial_credits,\n };\n }\n\n /**\n * Records a purchase on the backend and returns the updated balance.\n * @param params - Purchase details including credits, source, and optional transaction metadata.\n * @returns The updated credit balance after the purchase.\n */\n async recordPurchase(params: {\n credits: number;\n source: string;\n transaction_ref_id?: string;\n product_id?: string;\n price_cents?: number;\n currency?: string;\n }): Promise<CreditBalance> {\n const data = await this.post<{\n balance: number;\n initial_credits: number;\n }>(\"/purchase\", params);\n return {\n balance: data.balance,\n initialCredits: data.initial_credits,\n };\n }\n\n /**\n * Records a credit usage (download) on the backend.\n * @param filename - Optional filename associated with this usage.\n * @returns The updated balance and whether the usage was successful.\n */\n async recordUsage(\n filename?: string,\n ): Promise<{ balance: number; success: boolean }> {\n return this.post<{ balance: number; success: boolean }>(\"/use\", {\n filename,\n });\n }\n\n /**\n * Fetches paginated purchase history for the current user.\n * @param limit - Maximum number of records to return. Defaults to 50.\n * @param offset - Number of records to skip. Defaults to 0.\n * @returns Array of purchase records ordered by most recent first.\n */\n async getPurchaseHistory(\n limit = 50,\n offset = 0,\n ): Promise<ConsumablePurchaseRecord[]> {\n return this.get<ConsumablePurchaseRecord[]>(\n `/purchases?limit=${limit}&offset=${offset}`,\n );\n }\n\n /**\n * Fetches paginated usage history for the current user.\n * @param limit - Maximum number of records to return. Defaults to 50.\n * @param offset - Number of records to skip. Defaults to 0.\n * @returns Array of usage records ordered by most recent first.\n */\n async getUsageHistory(\n limit = 50,\n offset = 0,\n ): Promise<ConsumableUsageRecord[]> {\n return this.get<ConsumableUsageRecord[]>(\n `/usages?limit=${limit}&offset=${offset}`,\n );\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;CACpC;AAGD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,WAAW,kBAAkB;IAEjC,YAAY,IAAI,OAAO,CAAC;QACtB,GAAG,EAAE,MAAM,CACT,MAAM,EACN;YACE,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YACzC,QAAQ,EAAE,aAAa,EAAE,CAAC;SAC3B,CACF,CAAC;KACH,CAAC,CAAC;IAGH,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAG9E,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @fileoverview Adapter interface and related types for platform-specific purchase flows.\n * Defines the ConsumablesAdapter contract implemented by web and React Native adapters.\n */\n\nimport type { CreditPackage } from \"./index\";\n\n/** Platform-specific purchase result returned after a successful purchase. */\nexport interface ConsumablePurchaseResult {\n transactionId: string;\n productId: string;\n credits: number;\n priceCents: number;\n currency: string;\n source: \"web\" | \"apple\" | \"google\";\n}\n\n/** Purchase parameters */\nexport interface ConsumablePurchaseParams {\n packageId: string;\n offeringId: string;\n}\n\n/**\n * Adapter interface for platform-specific purchase flows.\n */\nexport interface ConsumablesAdapter {\n /** Get offerings (credit packages) from RevenueCat */\n getOfferings(): Promise<{\n all: Record<\n string,\n {\n identifier: string;\n metadata: Record<string, unknown> | null;\n packages: CreditPackage[];\n }\n >;\n }>;\n\n /** Execute a purchase via RevenueCat (opens payment UI) */\n purchase(params: ConsumablePurchaseParams): Promise<ConsumablePurchaseResult>;\n\n /** Set the current user (called on auth state change) */\n setUserId?(userId: string | undefined, email?: string): Promise<void>;\n}\n"]}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAMA,cAAc,WAAW,CAAC;AAK1B,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAMA,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @fileoverview Client-only type definitions for the consumables credit system.\n * Defines CreditPackage, CreditOffering, and CreditBalance interfaces used\n * by hooks and the ConsumablesService.\n */\n\nexport * from \"./adapter\";\n\n// === Client-only types ===\n\n/** Typed metadata from RevenueCat product. */\nexport interface ProductMetadata {\n credits?: number;\n [key: string]: unknown;\n}\n\n/** A purchasable credit package from RevenueCat. */\nexport interface CreditPackage {\n packageId: string;\n productId: string;\n title: string;\n description: string | null;\n credits: number;\n price: number;\n priceString: string;\n currencyCode: string;\n}\n\n/** An offering containing credit packages */\nexport interface CreditOffering {\n offeringId: string;\n packages: CreditPackage[];\n}\n\n/** Balance info from API (client-side camelCase mapping) */\nexport interface CreditBalance {\n balance: number;\n initialCredits: number;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/consumables_client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Cross-platform consumable credits client with RevenueCat adapter pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,10 +26,11 @@
|
|
|
26
26
|
"format": "bunx prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"test:watch": "vitest",
|
|
29
|
+
"verify": "bun run typecheck && bun run lint && bun run test && bun run build",
|
|
29
30
|
"prepublishOnly": "bun run build"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"@sudobility/types": "^1.9.
|
|
33
|
+
"@sudobility/types": "^1.9.54",
|
|
33
34
|
"react": "^18.0.0 || ^19.0.0",
|
|
34
35
|
"@revenuecat/purchases-js": "^1.0.0",
|
|
35
36
|
"react-native-purchases": ">=7.0.0"
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@eslint/js": "^10.0.1",
|
|
47
48
|
"@revenuecat/purchases-js": "^1.1.3",
|
|
48
|
-
"@sudobility/types": "^1.9.
|
|
49
|
+
"@sudobility/types": "^1.9.54",
|
|
49
50
|
"@types/bun": "^1.2.8",
|
|
50
51
|
"@types/node": "^22.0.0",
|
|
51
52
|
"@types/react": "^19.2.5",
|