@takeal/cusfront-sdk 0.1.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 +66 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/customer-CoxPwe5o.d.cts +32 -0
- package/dist/customer-CoxPwe5o.d.ts +32 -0
- package/dist/http-BkZZZI8K.d.cts +72 -0
- package/dist/http-BkZZZI8K.d.ts +72 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +82 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +93 -0
- package/dist/react/index.d.ts +93 -0
- package/dist/react/index.js +75 -0
- package/dist/react/index.js.map +1 -0
- package/dist/resources/auth.cjs +88 -0
- package/dist/resources/auth.cjs.map +1 -0
- package/dist/resources/auth.d.cts +132 -0
- package/dist/resources/auth.d.ts +132 -0
- package/dist/resources/auth.js +86 -0
- package/dist/resources/auth.js.map +1 -0
- package/dist/resources/balance.cjs +21 -0
- package/dist/resources/balance.cjs.map +1 -0
- package/dist/resources/balance.d.cts +27 -0
- package/dist/resources/balance.d.ts +27 -0
- package/dist/resources/balance.js +19 -0
- package/dist/resources/balance.js.map +1 -0
- package/dist/resources/blog.cjs +39 -0
- package/dist/resources/blog.cjs.map +1 -0
- package/dist/resources/blog.d.cts +112 -0
- package/dist/resources/blog.d.ts +112 -0
- package/dist/resources/blog.js +37 -0
- package/dist/resources/blog.js.map +1 -0
- package/dist/resources/branding.cjs +16 -0
- package/dist/resources/branding.cjs.map +1 -0
- package/dist/resources/branding.d.cts +35 -0
- package/dist/resources/branding.d.ts +35 -0
- package/dist/resources/branding.js +14 -0
- package/dist/resources/branding.js.map +1 -0
- package/dist/resources/cards.cjs +91 -0
- package/dist/resources/cards.cjs.map +1 -0
- package/dist/resources/cards.d.cts +166 -0
- package/dist/resources/cards.d.ts +166 -0
- package/dist/resources/cards.js +89 -0
- package/dist/resources/cards.js.map +1 -0
- package/dist/resources/deposits.cjs +52 -0
- package/dist/resources/deposits.cjs.map +1 -0
- package/dist/resources/deposits.d.cts +168 -0
- package/dist/resources/deposits.d.ts +168 -0
- package/dist/resources/deposits.js +50 -0
- package/dist/resources/deposits.js.map +1 -0
- package/dist/resources/subscriptions.cjs +24 -0
- package/dist/resources/subscriptions.cjs.map +1 -0
- package/dist/resources/subscriptions.d.cts +36 -0
- package/dist/resources/subscriptions.d.ts +36 -0
- package/dist/resources/subscriptions.js +22 -0
- package/dist/resources/subscriptions.js.map +1 -0
- package/dist/telegram.cjs +557 -0
- package/dist/telegram.cjs.map +1 -0
- package/dist/telegram.d.cts +105 -0
- package/dist/telegram.d.ts +105 -0
- package/dist/telegram.js +550 -0
- package/dist/telegram.js.map +1 -0
- package/dist/webhooks.cjs +78 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +70 -0
- package/dist/webhooks.d.ts +70 -0
- package/dist/webhooks.js +72 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +123 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@takeal/cusfront-sdk` are documented here. The format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `client.blog.list()` / `client.blog.get(slug)` — public posts returned as
|
|
12
|
+
structured blocks (`body_blocks`) plus raw Markdown, so a site renders them
|
|
13
|
+
in its own components instead of fighting server-side HTML.
|
|
14
|
+
- `client.subscriptions.get()` / `.set(channels)` — the user's announcement
|
|
15
|
+
channels, with `available` reflecting what the deployment can deliver.
|
|
16
|
+
- `client.deposits.quote()` + `quote_id` on `initiate()` — cross-currency
|
|
17
|
+
top-ups now execute against a rate-locked quote (the wallet holds a single
|
|
18
|
+
local currency; the API converts on funding). `Deposit` gains
|
|
19
|
+
`quote_id` / `fx_rate` / `fx_spread_bps` / `credited_currency` /
|
|
20
|
+
`credited_amount`.
|
|
21
|
+
- `client.branding.get()` — fetches the deployment's public runtime brand
|
|
22
|
+
config (`GET /branding`): platform / portal names, logo, favicon and the new
|
|
23
|
+
`wallet_label` (how the end-user's balance is called, e.g. "Acme Wallet").
|
|
24
|
+
- `BrandConfig.walletLabel` — build-time fallback for the same label.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Docs: "master balance" wording replaced by "wallet balance" everywhere. No
|
|
29
|
+
API field names changed.
|
|
30
|
+
- Generated types refreshed from the current API spec (card-transaction
|
|
31
|
+
billing fields, issuing metrics, branding). `refresh-types` now scrubs
|
|
32
|
+
internal references from doc comments before generating.
|
|
33
|
+
|
|
34
|
+
## [0.1.0] — first public release
|
|
35
|
+
|
|
36
|
+
Initial release of the typed client for the Takeal end-user API.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `createClient({ baseUrl, brand?, tokenStore?, fetch?, validate? })` — the
|
|
41
|
+
entry point; resources hang off the returned client.
|
|
42
|
+
- **Auth** (`client.auth`) — staged login (`login` → optional `verifyTotp` /
|
|
43
|
+
`verifyOtp` / `switchMethod`), `me`, `signOut`. JWT auto-stored on success.
|
|
44
|
+
- **Deposits** (`client.deposits`) — `initiate` / `get` / `list` / `refund`
|
|
45
|
+
(money in via a funder connector), with `Idempotency-Key` support.
|
|
46
|
+
- **Cards** (`client.cards`) — `create` / `get` / `list` / `balance` plus the
|
|
47
|
+
ownership-gated `freeze` / `unfreeze` / `terminate`, and the security-gated
|
|
48
|
+
`reveal` (re-auth with password + optional TOTP).
|
|
49
|
+
- **Balance** (`client.balance`) — per-currency wallet balance lookup.
|
|
50
|
+
- **Telegram Mini App** (`@takeal/cusfront-sdk/telegram`) — `fromTelegramWebApp`
|
|
51
|
+
/ `fromInitData` exchange a Telegram-signed `initData` for an authenticated
|
|
52
|
+
client; `parseInitData` / `assertWellFormed` helpers; `client.auth.exchangeTelegram`
|
|
53
|
+
+ `client.auth.linkEmail` for the auto-provision-then-attach-email flow.
|
|
54
|
+
- **Webhooks** (`@takeal/cusfront-sdk/webhooks`) — isomorphic, no-network
|
|
55
|
+
HMAC-SHA256 signature verifier (`verifyWebhook`).
|
|
56
|
+
- **React** (`@takeal/cusfront-sdk/react`) — optional `ClientProvider` +
|
|
57
|
+
`useMe` / `useDeposits` / `useCards` / `useBalance` hooks (React is a peer dep,
|
|
58
|
+
never bundled).
|
|
59
|
+
- Pluggable token storage (`defaultBrowserStore` / `inMemoryStore` / custom
|
|
60
|
+
`TokenStore`), typed `ApiError` / `NetworkError`, and runtime `BrandConfig`
|
|
61
|
+
for whitelabel theming.
|
|
62
|
+
- Dual ESM + CJS builds with `.d.ts` types; per-resource sub-entrypoints for
|
|
63
|
+
tree-shaking.
|
|
64
|
+
|
|
65
|
+
[Unreleased]: https://git.wiseless.xyz/takeal/cusfront-sdk/compare/v0.1.0...HEAD
|
|
66
|
+
[0.1.0]: https://git.wiseless.xyz/takeal/cusfront-sdk/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takeal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @takeal/cusfront-sdk
|
|
2
|
+
|
|
3
|
+
Typed TypeScript client for the **Takeal end-user API** — auth, deposits, cards, balance, and webhook signature verification.
|
|
4
|
+
|
|
5
|
+
The SDK powers any "Cusfront" (consumer front-end) on top of a Takeal deployment: a PWA, a Capacitor-wrapped mobile app, a Telegram Mini App, or anything else that talks to the `/auth/*` + `/me/*` surface. One client, runtime brand swap, zero runtime deps in the core.
|
|
6
|
+
|
|
7
|
+
> **Status:** 0.x — pre-release scaffold. Auth resource lands first; deposits + cards + balance + webhook verifier ship in subsequent releases.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @takeal/cusfront-sdk
|
|
13
|
+
# optional peer dep for runtime validation
|
|
14
|
+
pnpm add zod
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Native `fetch` is required. Node ≥ 18, modern browsers, Bun, Deno, and React Native ≥ 0.74 (Hermes) all ship it out of the box. For older runtimes inject a polyfill via `createClient({ fetch })`.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createClient } from "@takeal/cusfront-sdk";
|
|
23
|
+
|
|
24
|
+
const client = createClient({
|
|
25
|
+
baseUrl: "https://api.your-deployment.example.com",
|
|
26
|
+
brand: {
|
|
27
|
+
name: "Your Brand",
|
|
28
|
+
logoUrl: "/logo.svg",
|
|
29
|
+
primaryColor: "#0047AB",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await client.auth.login({
|
|
34
|
+
email: "user@example.com",
|
|
35
|
+
password: "secret",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (result.stage === "jwt") {
|
|
39
|
+
// Authenticated — JWT auto-stored.
|
|
40
|
+
const me = await client.auth.me();
|
|
41
|
+
console.log("hello", me.email);
|
|
42
|
+
} else if (result.stage === "totp_required") {
|
|
43
|
+
// Step-up required. Prompt the user for their TOTP code,
|
|
44
|
+
// then call client.auth.verifyTotp({ challenge_token, code }).
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Telegram Mini App
|
|
49
|
+
|
|
50
|
+
Run inside a Telegram Mini App? Exchange the Telegram-signed `initData` for a
|
|
51
|
+
session in one call — no password:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { fromTelegramWebApp } from "@takeal/cusfront-sdk/telegram";
|
|
55
|
+
|
|
56
|
+
// Reads window.Telegram.WebApp.initData, exchanges it, returns a ready client.
|
|
57
|
+
const client = await fromTelegramWebApp({
|
|
58
|
+
baseUrl: "https://api.your-deployment.example.com",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const me = await client.auth.me();
|
|
62
|
+
if (me.email_pending) {
|
|
63
|
+
// First-time Telegram users are auto-provisioned without an email.
|
|
64
|
+
// Collect a real address and attach it:
|
|
65
|
+
await client.auth.linkEmail({ email: "user@example.com" });
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Already hold the raw string (e.g. from a custom launch)? Use `fromInitData`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { fromInitData, parseInitData } from "@takeal/cusfront-sdk/telegram";
|
|
73
|
+
|
|
74
|
+
const client = await fromInitData(initData, {
|
|
75
|
+
baseUrl: "https://api.your-deployment.example.com",
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
How it works end-to-end:
|
|
80
|
+
|
|
81
|
+
1. **Telegram** signs `initData` with the bot token when it launches your Mini App.
|
|
82
|
+
2. The SDK does a fast **structural** check (`hash` + `auth_date` present, not
|
|
83
|
+
stale) and POSTs the raw string to the API's exchange endpoint. The SDK
|
|
84
|
+
**cannot** verify the cryptographic signature — only the server holds the bot
|
|
85
|
+
token, so the API performs the authoritative HMAC check. A forged or stale
|
|
86
|
+
payload is rejected there with a `401 ApiError`.
|
|
87
|
+
3. On success the JWT is stored in the configured token store; the returned
|
|
88
|
+
client is authenticated for all `client.*` calls.
|
|
89
|
+
4. First-time Telegram users are **auto-provisioned**. They have no email yet, so
|
|
90
|
+
`me.email_pending === true` — prompt for an address and call
|
|
91
|
+
`client.auth.linkEmail` to clear it.
|
|
92
|
+
|
|
93
|
+
For the lower-level call returning the raw session envelope (including
|
|
94
|
+
`email_pending`), use `client.auth.exchangeTelegram({ initData })` on a client
|
|
95
|
+
you built yourself.
|
|
96
|
+
|
|
97
|
+
## Brand config
|
|
98
|
+
|
|
99
|
+
Whitelabel-friendly by design — the SDK ships no embedded brand. Pass `brand` at runtime and the consumer Cusfront reads it back via `client.brand`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
type BrandConfig = {
|
|
103
|
+
name: string;
|
|
104
|
+
logoUrl?: string;
|
|
105
|
+
primaryColor?: string;
|
|
106
|
+
supportUrl?: string;
|
|
107
|
+
walletLabel?: string; // how the user's balance is called, e.g. "Acme Wallet"
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Switching brands does **not** require forking or re-publishing the SDK.
|
|
112
|
+
|
|
113
|
+
The deployment also publishes its live brand config at `GET /branding` (public,
|
|
114
|
+
no login needed) — `client.branding.get()` returns `platform_name`,
|
|
115
|
+
`merchant_portal_name`, `logo_url`, `favicon_url` and `wallet_label`, so a
|
|
116
|
+
Cusfront can re-theme itself at runtime and call the balance whatever the
|
|
117
|
+
operator configured. Server values win over the build-time `brand` when both
|
|
118
|
+
are present.
|
|
119
|
+
|
|
120
|
+
## Sub-exports
|
|
121
|
+
|
|
122
|
+
Tree-shaking-friendly: import only the resource you need.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { AuthResource } from "@takeal/cusfront-sdk/auth";
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Available now:
|
|
129
|
+
|
|
130
|
+
- `@takeal/cusfront-sdk` — `createClient` + types.
|
|
131
|
+
- `@takeal/cusfront-sdk/auth` — auth-only entry.
|
|
132
|
+
- `@takeal/cusfront-sdk/deposits` — `DepositsResource` (money IN via a funder connector).
|
|
133
|
+
- `@takeal/cusfront-sdk/cards` — `CardsResource` (cards backed by the wallet balance).
|
|
134
|
+
- `@takeal/cusfront-sdk/balance` — `BalanceResource` (per-currency wallet balance).
|
|
135
|
+
- `@takeal/cusfront-sdk/blog` — `BlogResource` (public posts as structured blocks).
|
|
136
|
+
- `@takeal/cusfront-sdk/subscriptions` — `SubscriptionsResource` (announcement channels).
|
|
137
|
+
- `@takeal/cusfront-sdk/webhooks` — HMAC-SHA256 signature verifier (no network, isomorphic).
|
|
138
|
+
- `@takeal/cusfront-sdk/react` — optional React hooks layer (`ClientProvider` + `useMe` / `useDeposits` / `useCards` / `useBalance`). React is a `peerDependency`, never bundled.
|
|
139
|
+
- `@takeal/cusfront-sdk/telegram` — Telegram Mini App `initData` → authenticated client bridge.
|
|
140
|
+
|
|
141
|
+
### Verifying webhooks
|
|
142
|
+
|
|
143
|
+
`verifyWebhook` is pure (no network) and isomorphic (Web Crypto — Node 18+,
|
|
144
|
+
browsers, Bun, Deno, Workers). Verify against the **raw** request body bytes —
|
|
145
|
+
not re-serialised JSON — using the secret your endpoint was provisioned with:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { verifyWebhook, SIGNATURE_HEADER } from "@takeal/cusfront-sdk/webhooks";
|
|
149
|
+
|
|
150
|
+
const ok = await verifyWebhook({
|
|
151
|
+
payload: rawBody, // string or Uint8Array, verbatim
|
|
152
|
+
signatureHeader: req.headers[SIGNATURE_HEADER.toLowerCase()],
|
|
153
|
+
secret: process.env.TAKEAL_WEBHOOK_SECRET!,
|
|
154
|
+
});
|
|
155
|
+
if (!ok) return res.status(401).end();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Algorithm: HMAC-SHA256, header `X-Takeal-Signature: sha256=<hex>`, signed over
|
|
159
|
+
the raw body bytes (no timestamp). Comparison is constant-time.
|
|
160
|
+
|
|
161
|
+
> **Card lifecycle note:** card-data reveal (PAN / CVV) is a separate,
|
|
162
|
+
> security-gated flow with its own re-auth + rate-limit + audit contract, so it
|
|
163
|
+
> is intentionally not part of `client.cards`. The cards resource covers
|
|
164
|
+
> create / get / list / balance plus the ownership-gated freeze / unfreeze /
|
|
165
|
+
> terminate lifecycle actions.
|
|
166
|
+
|
|
167
|
+
## React hooks (`@takeal/cusfront-sdk/react`)
|
|
168
|
+
|
|
169
|
+
An optional React layer ships from a separate entry point. React is a
|
|
170
|
+
`peerDependency` (`>=18`) and is never bundled, so non-React consumers pay
|
|
171
|
+
nothing for it. Install React in your app, then:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
pnpm add react # if not already present
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Wrap your tree once in a `ClientProvider`, then read data with the hooks:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { createClient } from "@takeal/cusfront-sdk";
|
|
181
|
+
import { ClientProvider, useBalance } from "@takeal/cusfront-sdk/react";
|
|
182
|
+
|
|
183
|
+
// Build the client once — module scope or a useMemo, not per-render.
|
|
184
|
+
const client = createClient({
|
|
185
|
+
baseUrl: "https://api.your-deployment.example.com",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
function Root() {
|
|
189
|
+
return (
|
|
190
|
+
<ClientProvider client={client}>
|
|
191
|
+
<Wallet />
|
|
192
|
+
</ClientProvider>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function Wallet() {
|
|
197
|
+
const { data, error, loading, refetch } = useBalance("USD");
|
|
198
|
+
|
|
199
|
+
if (loading) return <Spinner />;
|
|
200
|
+
if (error) return <ErrorBanner onRetry={refetch} />;
|
|
201
|
+
return (
|
|
202
|
+
<div>
|
|
203
|
+
{data!.amount} {data!.currency}
|
|
204
|
+
<button onClick={() => void refetch()}>Refresh</button>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Every hook returns the same shape — `{ data, error, loading, refetch }`:
|
|
211
|
+
|
|
212
|
+
- `useMe()` — current authenticated user (`client.auth.me()`).
|
|
213
|
+
- `useDeposits()` — the user's deposits (`client.deposits.list()`).
|
|
214
|
+
- `useCards()` — the user's cards (`client.cards.list()`).
|
|
215
|
+
- `useBalance(currency)` — wallet balance for one currency
|
|
216
|
+
(`client.balance.get(currency)`); re-fetches when `currency` changes.
|
|
217
|
+
|
|
218
|
+
`useClient()` exposes the raw client from context for one-off writes
|
|
219
|
+
(e.g. `client.deposits.initiate(...)`) — it throws a clear error if called
|
|
220
|
+
outside a `ClientProvider`.
|
|
221
|
+
|
|
222
|
+
The hooks are SSR-safe (fetches run only inside `useEffect`, never during
|
|
223
|
+
server render) and have no third-party data-fetching dependency. In-flight
|
|
224
|
+
requests are guarded against unmounted-component writes.
|
|
225
|
+
|
|
226
|
+
## Token storage
|
|
227
|
+
|
|
228
|
+
`createClient` accepts a pluggable `TokenStore`. The default is:
|
|
229
|
+
|
|
230
|
+
- Browser: `localStorage` (key `takeal_jwt`).
|
|
231
|
+
- Node / SSR / Worker: in-memory.
|
|
232
|
+
- Capacitor / React Native: pass your own (Keychain / EncryptedSharedPreferences wrapper).
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { createClient, inMemoryStore } from "@takeal/cusfront-sdk";
|
|
236
|
+
|
|
237
|
+
const client = createClient({
|
|
238
|
+
baseUrl: "...",
|
|
239
|
+
tokenStore: inMemoryStore(), // never persist
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Errors
|
|
244
|
+
|
|
245
|
+
Two narrow error types — branch on the type guard, not `instanceof`:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { isApiError, isNetworkError } from "@takeal/cusfront-sdk";
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await client.auth.login({ email, password });
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (isApiError(e)) {
|
|
254
|
+
// e.status, e.code, e.message, e.body
|
|
255
|
+
if (e.code === "invalid_credentials") showInlineError();
|
|
256
|
+
} else if (isNetworkError(e)) {
|
|
257
|
+
showOfflineBanner();
|
|
258
|
+
} else {
|
|
259
|
+
throw e;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Development
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
pnpm install
|
|
268
|
+
pnpm refresh-types # regenerate src/types.gen.ts from openapi-snapshot.json
|
|
269
|
+
pnpm build # tsup → dist/
|
|
270
|
+
pnpm test # vitest
|
|
271
|
+
pnpm typecheck # tsc --noEmit
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The OpenAPI snapshot lives at `openapi-snapshot.json` and is committed; refresh it from a running Takeal deployment with:
|
|
275
|
+
|
|
276
|
+
```sh
|
|
277
|
+
curl https://api.your-deployment.example.com/api/docs/openapi.json > openapi-snapshot.json
|
|
278
|
+
pnpm refresh-types
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured customer billing details — shared by the deposits and cards
|
|
3
|
+
* resources. Mirrors the Takeal API's `CustomerInfo` / `Address`.
|
|
4
|
+
*
|
|
5
|
+
* Every field is optional at this layer; each provider connector enforces its
|
|
6
|
+
* own required subset and fails the operation if a mandatory field is missing.
|
|
7
|
+
* Real PSPs / card issuers demand a billing address + name + email for
|
|
8
|
+
* compliance, 3DS, or APM redirect flows; crypto / gift rails usually ignore it.
|
|
9
|
+
*/
|
|
10
|
+
/** Postal address. ISO 3166 codes recommended where the provider supports them. */
|
|
11
|
+
interface Address {
|
|
12
|
+
line1?: string;
|
|
13
|
+
line2?: string;
|
|
14
|
+
city?: string;
|
|
15
|
+
/** ISO 3166-2 subdivision code, e.g. `"US-CA"`. Free-form fallback accepted. */
|
|
16
|
+
state?: string;
|
|
17
|
+
postal_code?: string;
|
|
18
|
+
/** ISO 3166-1 alpha-2, e.g. `"US"`. */
|
|
19
|
+
country?: string;
|
|
20
|
+
}
|
|
21
|
+
interface CustomerInfo {
|
|
22
|
+
email?: string;
|
|
23
|
+
first_name?: string;
|
|
24
|
+
last_name?: string;
|
|
25
|
+
/** E.164 format, e.g. `"+48..."`. Free-form fallback accepted. */
|
|
26
|
+
phone?: string;
|
|
27
|
+
/** ISO 8601 `YYYY-MM-DD`. KYC-tier issuers may require it. */
|
|
28
|
+
birth_date?: string;
|
|
29
|
+
billing_address?: Address;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { Address as A, CustomerInfo as C };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured customer billing details — shared by the deposits and cards
|
|
3
|
+
* resources. Mirrors the Takeal API's `CustomerInfo` / `Address`.
|
|
4
|
+
*
|
|
5
|
+
* Every field is optional at this layer; each provider connector enforces its
|
|
6
|
+
* own required subset and fails the operation if a mandatory field is missing.
|
|
7
|
+
* Real PSPs / card issuers demand a billing address + name + email for
|
|
8
|
+
* compliance, 3DS, or APM redirect flows; crypto / gift rails usually ignore it.
|
|
9
|
+
*/
|
|
10
|
+
/** Postal address. ISO 3166 codes recommended where the provider supports them. */
|
|
11
|
+
interface Address {
|
|
12
|
+
line1?: string;
|
|
13
|
+
line2?: string;
|
|
14
|
+
city?: string;
|
|
15
|
+
/** ISO 3166-2 subdivision code, e.g. `"US-CA"`. Free-form fallback accepted. */
|
|
16
|
+
state?: string;
|
|
17
|
+
postal_code?: string;
|
|
18
|
+
/** ISO 3166-1 alpha-2, e.g. `"US"`. */
|
|
19
|
+
country?: string;
|
|
20
|
+
}
|
|
21
|
+
interface CustomerInfo {
|
|
22
|
+
email?: string;
|
|
23
|
+
first_name?: string;
|
|
24
|
+
last_name?: string;
|
|
25
|
+
/** E.164 format, e.g. `"+48..."`. Free-form fallback accepted. */
|
|
26
|
+
phone?: string;
|
|
27
|
+
/** ISO 8601 `YYYY-MM-DD`. KYC-tier issuers may require it. */
|
|
28
|
+
birth_date?: string;
|
|
29
|
+
billing_address?: Address;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { Address as A, CustomerInfo as C };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable storage for the JWT.
|
|
3
|
+
*
|
|
4
|
+
* Default behaviour by environment:
|
|
5
|
+
* - browser → `localStorage` (synchronous, persists across reloads)
|
|
6
|
+
* - Node / SSR / worker → in-memory (no global state to leak)
|
|
7
|
+
* - mobile (Capacitor / RN) → consumer must inject Keychain /
|
|
8
|
+
* EncryptedSharedPreferences via `createClient({ tokenStore })`
|
|
9
|
+
*
|
|
10
|
+
* The store is intentionally tiny — three methods, no eviction
|
|
11
|
+
* policy, no encryption. Encryption is the consumer's call (mobile
|
|
12
|
+
* adapters wrap platform-native secure storage). Persistence shape
|
|
13
|
+
* is opaque to the SDK; tokens are passed through verbatim.
|
|
14
|
+
*/
|
|
15
|
+
interface TokenStore {
|
|
16
|
+
get(): string | null | Promise<string | null>;
|
|
17
|
+
set(token: string): void | Promise<void>;
|
|
18
|
+
clear(): void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Sync `localStorage`-backed store. Falls through to in-memory when
|
|
21
|
+
* `window.localStorage` is unavailable (private mode quota, SSR). */
|
|
22
|
+
declare function defaultBrowserStore(key?: string): TokenStore;
|
|
23
|
+
/** In-memory store. Use for SSR / tests / when persistence isn't
|
|
24
|
+
* desired (e.g. ephemeral kiosk sessions). */
|
|
25
|
+
declare function inMemoryStore(): TokenStore;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Internal HTTP client. One per SDK instance; injected into each
|
|
29
|
+
* resource. Responsibilities:
|
|
30
|
+
*
|
|
31
|
+
* - Construct absolute URLs from `baseUrl` + path.
|
|
32
|
+
* - Set `Authorization: Bearer <jwt>` from the `TokenStore`.
|
|
33
|
+
* - Set `Content-Type: application/json` on bodies.
|
|
34
|
+
* - Parse JSON responses + map non-2xx to `ApiError`.
|
|
35
|
+
* - Forward `Idempotency-Key` when the caller passes one.
|
|
36
|
+
* - Surface network failures (DNS, abort, broken pipe) as
|
|
37
|
+
* `NetworkError`.
|
|
38
|
+
*
|
|
39
|
+
* Auto-refresh + retry policies are deliberately NOT in here — they
|
|
40
|
+
* belong to the resource layer (auth.refresh) which has the right
|
|
41
|
+
* context for "is this refreshable" decisions.
|
|
42
|
+
*/
|
|
43
|
+
type FetchLike = typeof fetch;
|
|
44
|
+
interface HttpOptions {
|
|
45
|
+
/** Request body. Will be JSON-serialised if not already a
|
|
46
|
+
* string / FormData / Blob. */
|
|
47
|
+
body?: unknown;
|
|
48
|
+
/** Idempotency key forwarded as the `Idempotency-Key` header.
|
|
49
|
+
* Required by the Takeal API on most write endpoints. */
|
|
50
|
+
idempotencyKey?: string;
|
|
51
|
+
/** Extra headers merged after the SDK's defaults — caller wins. */
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
/** Bypass the token store for one call (login, refresh). */
|
|
54
|
+
skipAuth?: boolean;
|
|
55
|
+
/** AbortSignal forwarded to fetch. */
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}
|
|
58
|
+
declare class HttpClient {
|
|
59
|
+
private readonly baseUrl;
|
|
60
|
+
private readonly tokens;
|
|
61
|
+
private readonly fetchImpl;
|
|
62
|
+
constructor(baseUrl: string, tokens: TokenStore, fetchImpl: FetchLike);
|
|
63
|
+
get<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
64
|
+
post<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
65
|
+
put<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
66
|
+
patch<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
67
|
+
delete<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
68
|
+
private request;
|
|
69
|
+
private absolute;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { type FetchLike as F, HttpClient as H, type TokenStore as T, defaultBrowserStore as d, inMemoryStore as i };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable storage for the JWT.
|
|
3
|
+
*
|
|
4
|
+
* Default behaviour by environment:
|
|
5
|
+
* - browser → `localStorage` (synchronous, persists across reloads)
|
|
6
|
+
* - Node / SSR / worker → in-memory (no global state to leak)
|
|
7
|
+
* - mobile (Capacitor / RN) → consumer must inject Keychain /
|
|
8
|
+
* EncryptedSharedPreferences via `createClient({ tokenStore })`
|
|
9
|
+
*
|
|
10
|
+
* The store is intentionally tiny — three methods, no eviction
|
|
11
|
+
* policy, no encryption. Encryption is the consumer's call (mobile
|
|
12
|
+
* adapters wrap platform-native secure storage). Persistence shape
|
|
13
|
+
* is opaque to the SDK; tokens are passed through verbatim.
|
|
14
|
+
*/
|
|
15
|
+
interface TokenStore {
|
|
16
|
+
get(): string | null | Promise<string | null>;
|
|
17
|
+
set(token: string): void | Promise<void>;
|
|
18
|
+
clear(): void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Sync `localStorage`-backed store. Falls through to in-memory when
|
|
21
|
+
* `window.localStorage` is unavailable (private mode quota, SSR). */
|
|
22
|
+
declare function defaultBrowserStore(key?: string): TokenStore;
|
|
23
|
+
/** In-memory store. Use for SSR / tests / when persistence isn't
|
|
24
|
+
* desired (e.g. ephemeral kiosk sessions). */
|
|
25
|
+
declare function inMemoryStore(): TokenStore;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Internal HTTP client. One per SDK instance; injected into each
|
|
29
|
+
* resource. Responsibilities:
|
|
30
|
+
*
|
|
31
|
+
* - Construct absolute URLs from `baseUrl` + path.
|
|
32
|
+
* - Set `Authorization: Bearer <jwt>` from the `TokenStore`.
|
|
33
|
+
* - Set `Content-Type: application/json` on bodies.
|
|
34
|
+
* - Parse JSON responses + map non-2xx to `ApiError`.
|
|
35
|
+
* - Forward `Idempotency-Key` when the caller passes one.
|
|
36
|
+
* - Surface network failures (DNS, abort, broken pipe) as
|
|
37
|
+
* `NetworkError`.
|
|
38
|
+
*
|
|
39
|
+
* Auto-refresh + retry policies are deliberately NOT in here — they
|
|
40
|
+
* belong to the resource layer (auth.refresh) which has the right
|
|
41
|
+
* context for "is this refreshable" decisions.
|
|
42
|
+
*/
|
|
43
|
+
type FetchLike = typeof fetch;
|
|
44
|
+
interface HttpOptions {
|
|
45
|
+
/** Request body. Will be JSON-serialised if not already a
|
|
46
|
+
* string / FormData / Blob. */
|
|
47
|
+
body?: unknown;
|
|
48
|
+
/** Idempotency key forwarded as the `Idempotency-Key` header.
|
|
49
|
+
* Required by the Takeal API on most write endpoints. */
|
|
50
|
+
idempotencyKey?: string;
|
|
51
|
+
/** Extra headers merged after the SDK's defaults — caller wins. */
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
/** Bypass the token store for one call (login, refresh). */
|
|
54
|
+
skipAuth?: boolean;
|
|
55
|
+
/** AbortSignal forwarded to fetch. */
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}
|
|
58
|
+
declare class HttpClient {
|
|
59
|
+
private readonly baseUrl;
|
|
60
|
+
private readonly tokens;
|
|
61
|
+
private readonly fetchImpl;
|
|
62
|
+
constructor(baseUrl: string, tokens: TokenStore, fetchImpl: FetchLike);
|
|
63
|
+
get<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
64
|
+
post<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
65
|
+
put<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
66
|
+
patch<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
67
|
+
delete<T>(path: string, opts?: HttpOptions): Promise<T>;
|
|
68
|
+
private request;
|
|
69
|
+
private absolute;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { type FetchLike as F, HttpClient as H, type TokenStore as T, defaultBrowserStore as d, inMemoryStore as i };
|