@takeal/cusfront-sdk 0.1.0 → 0.2.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/README.md +29 -12
  3. package/dist/{http-BkZZZI8K.d.cts → http-BkCfOCR0.d.cts} +4 -1
  4. package/dist/{http-BkZZZI8K.d.ts → http-BkCfOCR0.d.ts} +4 -1
  5. package/dist/index.cjs +150 -10
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +16 -3
  8. package/dist/index.d.ts +16 -3
  9. package/dist/index.js +150 -10
  10. package/dist/index.js.map +1 -1
  11. package/dist/react/index.d.cts +4 -1
  12. package/dist/react/index.d.ts +4 -1
  13. package/dist/resources/auth.cjs +37 -4
  14. package/dist/resources/auth.cjs.map +1 -1
  15. package/dist/resources/auth.d.cts +45 -6
  16. package/dist/resources/auth.d.ts +45 -6
  17. package/dist/resources/auth.js +37 -4
  18. package/dist/resources/auth.js.map +1 -1
  19. package/dist/resources/balance.cjs +4 -3
  20. package/dist/resources/balance.cjs.map +1 -1
  21. package/dist/resources/balance.d.cts +8 -9
  22. package/dist/resources/balance.d.ts +8 -9
  23. package/dist/resources/balance.js +4 -3
  24. package/dist/resources/balance.js.map +1 -1
  25. package/dist/resources/blog.d.cts +1 -1
  26. package/dist/resources/blog.d.ts +1 -1
  27. package/dist/resources/branding.cjs.map +1 -1
  28. package/dist/resources/branding.d.cts +8 -2
  29. package/dist/resources/branding.d.ts +8 -2
  30. package/dist/resources/branding.js.map +1 -1
  31. package/dist/resources/cards.d.cts +1 -1
  32. package/dist/resources/cards.d.ts +1 -1
  33. package/dist/resources/deposits.d.cts +1 -1
  34. package/dist/resources/deposits.d.ts +1 -1
  35. package/dist/resources/push.cjs +61 -0
  36. package/dist/resources/push.cjs.map +1 -0
  37. package/dist/resources/push.d.cts +79 -0
  38. package/dist/resources/push.d.ts +79 -0
  39. package/dist/resources/push.js +58 -0
  40. package/dist/resources/push.js.map +1 -0
  41. package/dist/resources/sessions.cjs +26 -0
  42. package/dist/resources/sessions.cjs.map +1 -0
  43. package/dist/resources/sessions.d.cts +43 -0
  44. package/dist/resources/sessions.d.ts +43 -0
  45. package/dist/resources/sessions.js +24 -0
  46. package/dist/resources/sessions.js.map +1 -0
  47. package/dist/resources/subscriptions.d.cts +1 -1
  48. package/dist/resources/subscriptions.d.ts +1 -1
  49. package/dist/resources/wallet.cjs +24 -0
  50. package/dist/resources/wallet.cjs.map +1 -0
  51. package/dist/resources/wallet.d.cts +39 -0
  52. package/dist/resources/wallet.d.ts +39 -0
  53. package/dist/resources/wallet.js +22 -0
  54. package/dist/resources/wallet.js.map +1 -0
  55. package/dist/telegram.cjs +150 -10
  56. package/dist/telegram.cjs.map +1 -1
  57. package/dist/telegram.d.cts +4 -1
  58. package/dist/telegram.d.ts +4 -1
  59. package/dist/telegram.js +150 -10
  60. package/dist/telegram.js.map +1 -1
  61. package/package.json +22 -3
package/CHANGELOG.md CHANGED
@@ -4,10 +4,36 @@ All notable changes to `@takeal/cusfront-sdk` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased]
7
+ ## [0.2.8] — 2026-09-13
8
+
9
+ The version number now matches the Takeal platform release the SDK was
10
+ checked against, so this release jumps from 0.1.0 to 0.2.8 (platform tag
11
+ `v0.2.8`).
12
+
13
+ ### Changed
14
+
15
+ - **API paths are versioned.** Every request now goes to `/v1/...`
16
+ (`/v1/auth/login`, `/v1/me/deposits`, …); the SDK adds the prefix itself,
17
+ so `baseUrl` stays the bare API origin and resource calls are unchanged.
18
+ Only `/ready` and `/branding` remain unversioned. The API answers the old
19
+ unversioned paths with `410 Gone`, so 0.1.0 no longer works against it.
20
+ - The npm page links to the repository with a plain `https://` URL.
8
21
 
9
22
  ### Added
10
23
 
24
+ - `client.auth.register()` creates an account and signs it in. Takes an
25
+ optional `wallet_currency`.
26
+ - `client.auth.completeTotpSetup()` finishes a `totp_setup_required` login
27
+ and returns the ten one-time backup codes.
28
+ - `client.auth.changePassword()`. The account's other sessions are signed out.
29
+ - `client.sessions`: `list()`, `revoke(jti)` and `revokeOthers()`.
30
+ - `client.push`: `status()`, `save()`, `remove()`, plus `enable(registration)`
31
+ and `disable(registration)`, which subscribe or unsubscribe the browser and
32
+ update the server in one call.
33
+ - Sub-exports `@takeal/cusfront-sdk/wallet`, `/sessions` and `/push`. Auth
34
+ types (`User`, `LoginResponse`, `Session` and the rest) are exported from
35
+ the package root.
36
+
11
37
  - `client.blog.list()` / `client.blog.get(slug)` — public posts returned as
12
38
  structured blocks (`body_blocks`) plus raw Markdown, so a site renders them
13
39
  in its own components instead of fighting server-side HTML.
@@ -31,7 +57,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
31
57
  billing fields, issuing metrics, branding). `refresh-types` now scrubs
32
58
  internal references from doc comments before generating.
33
59
 
34
- ## [0.1.0] — first public release
60
+ ## [0.1.0] — 2026-08-25 — first public release
35
61
 
36
62
  Initial release of the typed client for the Takeal end-user API.
37
63
 
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @takeal/cusfront-sdk
2
2
 
3
- Typed TypeScript client for the **Takeal end-user API** auth, deposits, cards, balance, and webhook signature verification.
3
+ Typed TypeScript client for the **Takeal end-user API**: sign-up and login, wallet, deposits, cards, sessions, push notifications and webhook signature verification.
4
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.
5
+ The SDK powers any end-user client app 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
6
 
7
- > **Status:** 0.x pre-release scaffold. Auth resource lands first; deposits + cards + balance + webhook verifier ship in subsequent releases.
7
+ > **Versioning:** the SDK version matches the Takeal platform release it was checked against (SDK 0.2.8 goes with API release v0.2.8). Until 1.0, a minor version can break things; read the [changelog](./CHANGELOG.md) before upgrading.
8
8
 
9
9
  ## Install
10
10
 
@@ -42,9 +42,23 @@ if (result.stage === "jwt") {
42
42
  } else if (result.stage === "totp_required") {
43
43
  // Step-up required. Prompt the user for their TOTP code,
44
44
  // then call client.auth.verifyTotp({ challenge_token, code }).
45
+ } else if (result.stage === "totp_setup_required") {
46
+ // The account has to enrol an authenticator first. Show result.otpauth_url
47
+ // as a QR code, then call client.auth.completeTotpSetup({ challenge_token, code })
48
+ // and show the backup_codes it returns. They are never sent again.
45
49
  }
46
50
  ```
47
51
 
52
+ New users sign up with `register`, which also signs them in:
53
+
54
+ ```ts
55
+ await client.auth.register({
56
+ email: "user@example.com",
57
+ password: "a-long-Passw0rd",
58
+ wallet_currency: "EUR", // optional; must be offered by the deployment
59
+ });
60
+ ```
61
+
48
62
  ## Telegram Mini App
49
63
 
50
64
  Run inside a Telegram Mini App? Exchange the Telegram-signed `initData` for a
@@ -96,7 +110,7 @@ you built yourself.
96
110
 
97
111
  ## Brand config
98
112
 
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`:
113
+ Whitelabel-friendly by design — the SDK ships no embedded brand. Pass `brand` at runtime and the client app reads it back via `client.brand`:
100
114
 
101
115
  ```ts
102
116
  type BrandConfig = {
@@ -112,8 +126,9 @@ Switching brands does **not** require forking or re-publishing the SDK.
112
126
 
113
127
  The deployment also publishes its live brand config at `GET /branding` (public,
114
128
  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
129
+ `merchant_portal_name`, `logo_url`, `favicon_url`, `wallet_label` and the
130
+ `primary_color` / `secondary_color` / `accent_color` hex values, so a
131
+ client app can re-theme itself at runtime and call the balance whatever the
117
132
  operator configured. Server values win over the build-time `brand` when both
118
133
  are present.
119
134
 
@@ -134,6 +149,9 @@ Available now:
134
149
  - `@takeal/cusfront-sdk/balance` — `BalanceResource` (per-currency wallet balance).
135
150
  - `@takeal/cusfront-sdk/blog` — `BlogResource` (public posts as structured blocks).
136
151
  - `@takeal/cusfront-sdk/subscriptions` — `SubscriptionsResource` (announcement channels).
152
+ - `@takeal/cusfront-sdk/wallet` — `WalletResource` (the wallet's currency).
153
+ - `@takeal/cusfront-sdk/sessions` — `SessionsResource` (where the user is signed in).
154
+ - `@takeal/cusfront-sdk/push` — `PushResource` (Web Push subscription).
137
155
  - `@takeal/cusfront-sdk/webhooks` — HMAC-SHA256 signature verifier (no network, isomorphic).
138
156
  - `@takeal/cusfront-sdk/react` — optional React hooks layer (`ClientProvider` + `useMe` / `useDeposits` / `useCards` / `useBalance`). React is a `peerDependency`, never bundled.
139
157
  - `@takeal/cusfront-sdk/telegram` — Telegram Mini App `initData` → authenticated client bridge.
@@ -158,11 +176,10 @@ if (!ok) return res.status(401).end();
158
176
  Algorithm: HMAC-SHA256, header `X-Takeal-Signature: sha256=<hex>`, signed over
159
177
  the raw body bytes (no timestamp). Comparison is constant-time.
160
178
 
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.
179
+ > **Card details:** `client.cards.reveal()` returns the full card number and
180
+ > CVV. It asks for the user's password again (and a TOTP code if they have one
181
+ > enrolled), and the API rate-limits and logs every call. The rest of
182
+ > `client.cards` is create, get, list, balance, freeze, unfreeze and terminate.
166
183
 
167
184
  ## React hooks (`@takeal/cusfront-sdk/react`)
168
185
 
@@ -274,7 +291,7 @@ pnpm typecheck # tsc --noEmit
274
291
  The OpenAPI snapshot lives at `openapi-snapshot.json` and is committed; refresh it from a running Takeal deployment with:
275
292
 
276
293
  ```sh
277
- curl https://api.your-deployment.example.com/api/docs/openapi.json > openapi-snapshot.json
294
+ curl https://api.your-deployment.example.com/docs/openapi.json > openapi-snapshot.json
278
295
  pnpm refresh-types
279
296
  ```
280
297
 
@@ -28,7 +28,10 @@ declare function inMemoryStore(): TokenStore;
28
28
  * Internal HTTP client. One per SDK instance; injected into each
29
29
  * resource. Responsibilities:
30
30
  *
31
- * - Construct absolute URLs from `baseUrl` + path.
31
+ * - Construct absolute URLs from `baseUrl` + API version + path.
32
+ * Resources pass contract paths (`/me/deposits`); the `/v1` prefix is
33
+ * added here, in one place. Only the deployment-level endpoints
34
+ * (`/ready`, `/branding`, …) stay unversioned.
32
35
  * - Set `Authorization: Bearer <jwt>` from the `TokenStore`.
33
36
  * - Set `Content-Type: application/json` on bodies.
34
37
  * - Parse JSON responses + map non-2xx to `ApiError`.
@@ -28,7 +28,10 @@ declare function inMemoryStore(): TokenStore;
28
28
  * Internal HTTP client. One per SDK instance; injected into each
29
29
  * resource. Responsibilities:
30
30
  *
31
- * - Construct absolute URLs from `baseUrl` + path.
31
+ * - Construct absolute URLs from `baseUrl` + API version + path.
32
+ * Resources pass contract paths (`/me/deposits`); the `/v1` prefix is
33
+ * added here, in one place. Only the deployment-level endpoints
34
+ * (`/ready`, `/branding`, …) stay unversioned.
32
35
  * - Set `Authorization: Bearer <jwt>` from the `TokenStore`.
33
36
  * - Set `Content-Type: application/json` on bodies.
34
37
  * - Parse JSON responses + map non-2xx to `ApiError`.
package/dist/index.cjs CHANGED
@@ -27,6 +27,15 @@ function isNetworkError(e) {
27
27
  }
28
28
 
29
29
  // src/http.ts
30
+ var API_VERSION = "/v1";
31
+ var UNVERSIONED = /* @__PURE__ */ new Set(["/health", "/ready", "/branding", "/favicon.ico"]);
32
+ function versionedPath(path) {
33
+ const p = path.startsWith("/") ? path : `/${path}`;
34
+ const bare = p.split("?")[0];
35
+ if (UNVERSIONED.has(bare)) return p;
36
+ if (bare === API_VERSION || bare.startsWith(`${API_VERSION}/`)) return p;
37
+ return `${API_VERSION}${p}`;
38
+ }
30
39
  var HttpClient = class {
31
40
  constructor(baseUrl, tokens, fetchImpl) {
32
41
  this.baseUrl = baseUrl;
@@ -83,8 +92,7 @@ var HttpClient = class {
83
92
  absolute(path) {
84
93
  if (path.startsWith("http://") || path.startsWith("https://")) return path;
85
94
  const base = this.baseUrl.replace(/\/+$/, "");
86
- const tail = path.startsWith("/") ? path : `/${path}`;
87
- return `${base}${tail}`;
95
+ return `${base}${versionedPath(path)}`;
88
96
  }
89
97
  };
90
98
  async function parseResponse(resp) {
@@ -149,6 +157,40 @@ var AuthResource = class {
149
157
  await Promise.resolve(this.tokens.set(resp.access_token));
150
158
  return resp;
151
159
  }
160
+ /** Create an end-user account and sign it in. The JWT is stored.
161
+ * `wallet_currency` must be one the deployment offers; omit it to take
162
+ * the deployment default (it can be switched later via `client.wallet`
163
+ * while the wallet is empty).
164
+ * Errors: 400 `validation` / `currency_not_allowed`, 409 email taken. */
165
+ async register(input) {
166
+ const resp = await this.http.post("/auth/register", {
167
+ body: input,
168
+ skipAuth: true
169
+ });
170
+ await Promise.resolve(this.tokens.set(resp.access_token));
171
+ return resp;
172
+ }
173
+ /** Finish a `totp_setup_required` login: send the first code from the
174
+ * authenticator app. The JWT is stored. The response carries ten
175
+ * one-time `backup_codes` that are never returned again, so show them to
176
+ * the user right away and don't keep them on the device. */
177
+ async completeTotpSetup(input) {
178
+ const resp = await this.http.post("/auth/login/totp-setup", {
179
+ body: input,
180
+ skipAuth: true
181
+ });
182
+ await Promise.resolve(this.tokens.set(resp.access_token));
183
+ return resp;
184
+ }
185
+ /** Change the signed-in user's password. Every other session of the
186
+ * account is signed out; this one stays valid.
187
+ * Errors: 400 when the new password fails the policy, 401 when the
188
+ * current one is wrong. */
189
+ async changePassword(input) {
190
+ return this.http.post("/auth/password", {
191
+ body: input
192
+ });
193
+ }
152
194
  /** Swap the 2FA method mid-flow. Resolves to `otp_sent` for
153
195
  * telegram/email; for TOTP it short-circuits back to the challenge. */
154
196
  async switchMethod(input) {
@@ -185,10 +227,9 @@ var AuthResource = class {
185
227
  async me() {
186
228
  return this.http.get("/auth/me");
187
229
  }
188
- /** Local sign-out. The SDK doesn't currently call a server-side
189
- * endpoint (the Takeal API issues stateless JWTs); clearing the
190
- * store is enough. Server-side session invalidation may land
191
- * with a future session table. */
230
+ /** Local sign-out: forgets the stored JWT. To end the session on the
231
+ * server as well, revoke it first with `client.sessions.revoke(jti)`
232
+ * (the current one is marked `current: true` in `client.sessions.list()`). */
192
233
  async signOut() {
193
234
  await Promise.resolve(this.tokens.clear());
194
235
  }
@@ -333,11 +374,12 @@ var BalanceResource = class {
333
374
  this.http = http;
334
375
  }
335
376
  /**
336
- * Fetch the user's wallet balance for `currency` (ISO 4217, e.g. `"USD"`).
337
- * Required the ledger holds a separate balance per currency, so there is
338
- * no "total" without one.
377
+ * Fetch the user's wallet balance. Omit `currency` for the wallet's own
378
+ * currency; pass an ISO 4217 code (e.g. `"USD"`) to read another one
379
+ * the ledger holds a separate balance per currency.
339
380
  */
340
381
  async get(currency) {
382
+ if (!currency) return this.http.get("/me/balance");
341
383
  const q = new URLSearchParams({ currency }).toString();
342
384
  return this.http.get(`/me/balance?${q}`);
343
385
  }
@@ -354,6 +396,25 @@ var BrandingResource = class {
354
396
  }
355
397
  };
356
398
 
399
+ // src/resources/wallet.ts
400
+ var WalletResource = class {
401
+ constructor(http) {
402
+ this.http = http;
403
+ }
404
+ /** Current wallet currency + the currencies the user may switch to. */
405
+ async get(opts = {}) {
406
+ return this.http.get("/me/wallet", { signal: opts.signal });
407
+ }
408
+ /**
409
+ * Switch the wallet currency. Fails with `409 wallet_not_empty` while any
410
+ * balance is non-zero, and `400 currency_not_allowed` for a currency the
411
+ * deployment does not offer.
412
+ */
413
+ async set(currency, opts = {}) {
414
+ return this.http.put("/me/wallet", { body: { currency }, signal: opts.signal });
415
+ }
416
+ };
417
+
357
418
  // src/resources/blog.ts
358
419
  var BlogResource = class {
359
420
  constructor(http) {
@@ -407,6 +468,82 @@ var SubscriptionsResource = class {
407
468
  }
408
469
  };
409
470
 
471
+ // src/resources/sessions.ts
472
+ var SessionsResource = class {
473
+ constructor(http) {
474
+ this.http = http;
475
+ }
476
+ /** All live sessions of the account, this one included. */
477
+ async list() {
478
+ return this.http.get("/me/sessions");
479
+ }
480
+ /** Sign out one session. Works on the current one too; the stored JWT
481
+ * stops working, so follow with `client.auth.signOut()`.
482
+ * 404 when the session is unknown or already signed out. */
483
+ async revoke(jti) {
484
+ await this.http.delete(`/me/sessions/${encodeURIComponent(jti)}`);
485
+ }
486
+ /** Sign out every session except this one. */
487
+ async revokeOthers() {
488
+ return this.http.post("/me/sessions/terminate-others");
489
+ }
490
+ };
491
+
492
+ // src/resources/push.ts
493
+ var PushResource = class {
494
+ constructor(http) {
495
+ this.http = http;
496
+ }
497
+ /** Whether the user is subscribed, plus the VAPID key to subscribe with. */
498
+ async status() {
499
+ return this.http.get("/me/push-subscription");
500
+ }
501
+ /** Store a subscription you created yourself.
502
+ * 409 `push_disabled` when the deployment has no VAPID keys. */
503
+ async save(input) {
504
+ return this.http.put("/me/push-subscription", { body: input });
505
+ }
506
+ /** Delete the stored subscription. Safe when nothing is stored. */
507
+ async remove() {
508
+ await this.http.delete("/me/push-subscription");
509
+ }
510
+ /** Subscribe this browser and store the subscription. Ask for
511
+ * notification permission before calling. Throws when push isn't set up
512
+ * on the deployment. */
513
+ async enable(registration) {
514
+ const { vapid_public_key } = await this.status();
515
+ if (!vapid_public_key) {
516
+ throw new Error("@takeal/cusfront-sdk: push notifications are not enabled on this deployment");
517
+ }
518
+ const sub = await registration.pushManager.subscribe({
519
+ userVisibleOnly: true,
520
+ applicationServerKey: base64UrlToBytes(vapid_public_key)
521
+ });
522
+ const json = sub.toJSON();
523
+ if (!json.endpoint || !json.keys?.p256dh || !json.keys.auth) {
524
+ throw new Error("@takeal/cusfront-sdk: the browser returned an incomplete push subscription");
525
+ }
526
+ return this.save({
527
+ endpoint: json.endpoint,
528
+ keys: { p256dh: json.keys.p256dh, auth: json.keys.auth }
529
+ });
530
+ }
531
+ /** Unsubscribe this browser and delete the stored subscription. */
532
+ async disable(registration) {
533
+ const sub = await registration.pushManager.getSubscription();
534
+ if (sub) await sub.unsubscribe();
535
+ await this.remove();
536
+ }
537
+ };
538
+ function base64UrlToBytes(input) {
539
+ const b64 = input.replace(/-/g, "+").replace(/_/g, "/");
540
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
541
+ const bin = atob(padded);
542
+ const out = new Uint8Array(bin.length);
543
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
544
+ return out;
545
+ }
546
+
410
547
  // src/token-store.ts
411
548
  function defaultBrowserStore(key = "takeal_jwt") {
412
549
  const fallback = inMemoryStore();
@@ -455,9 +592,12 @@ function createClient(opts) {
455
592
  deposits: new DepositsResource(http),
456
593
  cards: new CardsResource(http),
457
594
  balance: new BalanceResource(http),
595
+ wallet: new WalletResource(http),
458
596
  branding: new BrandingResource(http),
459
597
  blog: new BlogResource(http),
460
- subscriptions: new SubscriptionsResource(http)
598
+ subscriptions: new SubscriptionsResource(http),
599
+ sessions: new SessionsResource(http),
600
+ push: new PushResource(http)
461
601
  };
462
602
  }
463
603
  function pickDefaultStore() {