@invonetwork/web-sdk 0.5.0 → 0.7.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 +179 -150
- package/README.md +41 -11
- package/dist/{chunk-JOVATUDY.js → chunk-D3XBTH4C.js} +10 -2
- package/dist/index.cjs +140 -1
- package/dist/index.d.cts +28 -3
- package/dist/index.d.ts +28 -3
- package/dist/index.js +134 -3
- package/dist/server.cjs +8 -0
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/{types-CZdmipNK.d.cts → types-Bi_NMSkN.d.cts} +97 -1
- package/dist/{types-CZdmipNK.d.ts → types-Bi_NMSkN.d.ts} +97 -1
- package/package.json +76 -76
package/CHANGELOG.md
CHANGED
|
@@ -1,150 +1,179 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@invonetwork/web-sdk` are documented here. This project follows
|
|
4
|
-
[Semantic Versioning](https://semver.org/). Releases are managed with
|
|
5
|
-
[changesets](https://github.com/changesets/changesets).
|
|
6
|
-
|
|
7
|
-
## [0.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@invonetwork/web-sdk` are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/). Releases are managed with
|
|
5
|
+
[changesets](https://github.com/changesets/changesets).
|
|
6
|
+
|
|
7
|
+
## [0.7.0] — 2026-07-01
|
|
8
|
+
|
|
9
|
+
Start of the **discovery layer** for send/transfer UIs.
|
|
10
|
+
|
|
11
|
+
- **`InvoClient.getDestinations({ direction })`** — one player-token call
|
|
12
|
+
(`GET /api/sdk/destinations`) returns every game the player can send/transfer to, with
|
|
13
|
+
all display metadata **inline** (game name, icon, currency name/symbol, min/max transfer
|
|
14
|
+
limits) — no per-game lookup. Includes source game/currency info and `transferMode`
|
|
15
|
+
(`universal`/`linked` + `linkedGameIds`).
|
|
16
|
+
- _(Coming in 0.7.x: `getBalance` single-game player-token read; `getLinkedIdentities`
|
|
17
|
+
server-side.)_
|
|
18
|
+
|
|
19
|
+
## [0.6.0] — 2026-07-01
|
|
20
|
+
|
|
21
|
+
Three additive browser flows the dashboard needed, built to the live backend contracts.
|
|
22
|
+
|
|
23
|
+
- **`InvoClient.getPendingCollect()`** — the player's own pending-to-collect list
|
|
24
|
+
(player-token `GET /api/sdk/transfers/pending`), PII-free. Each row's `kind`
|
|
25
|
+
(`identity_gate` → `approve*`, `receiving_confirm` → `confirmReceipt*`) tells you the
|
|
26
|
+
action; typed fields incl. `held` / `holdReason` / `stepUpRequired`.
|
|
27
|
+
- **First-enrollment OTP grant** — `enrollmentBegin()` / `enrollmentVerify(code)` for
|
|
28
|
+
`ENROLLMENT_REQUIRES_AUTHORIZATION` (send OTP to phone+email, verify, then retry the
|
|
29
|
+
same `enrollPasskey()` — the server grant is auto-consumed). New `InvoError` helpers
|
|
30
|
+
`isEnrollmentAuthorizationRequired` / `isEnrollmentProofRequired`.
|
|
31
|
+
- **Typed approve/confirm holds** — `approve*`/`confirmReceipt*` now surface a typed
|
|
32
|
+
`holdReason` (from `error_code ?? code`) on HTTP 202 holds, plus `risk` /
|
|
33
|
+
`guardianApproval` / `pollEndpoint` on approve. `RECIPIENT_IDENTITY_PENDING` correctly
|
|
34
|
+
surfaces on confirm-receipt. Success carries no `holdReason`.
|
|
35
|
+
|
|
36
|
+
## [0.5.0] — 2026-07-01
|
|
37
|
+
|
|
38
|
+
Support partner `metadata` end-to-end on the purchase flow (additive, backward-compatible).
|
|
39
|
+
|
|
40
|
+
- **`PurchaseCompletedData.metadata?`** — the `purchase.completed` webhook now echoes your
|
|
41
|
+
metadata back (all rails); `event.data.metadata` is typed.
|
|
42
|
+
- **`purchaseCurrency`** now accepts and forwards `metadata` (the direct `/purchase-currency`
|
|
43
|
+
endpoint accepts it too, matching `createCheckout`).
|
|
44
|
+
|
|
45
|
+
## [0.4.2] — 2026-06-30
|
|
46
|
+
|
|
47
|
+
Fixes from an independent line-by-line audit against the live backend.
|
|
48
|
+
|
|
49
|
+
- **`linkDevice` now works.** `device/link/webauthn/begin` returns a wrapped
|
|
50
|
+
`{ link_id, options }` body (challenge nested under `options`) and binds the
|
|
51
|
+
challenge to a server-generated `link_id`. The client now unwraps `options` and
|
|
52
|
+
echoes the server's `link_id` to `/complete` (it previously threw on an undefined
|
|
53
|
+
challenge and sent the wrong id).
|
|
54
|
+
- **`InvoError.code` is now populated for the direct purchase rail + guardian flows.**
|
|
55
|
+
`errorFromResponse` reads `error_code` (SecureErrorHandler + `/purchase-currency`)
|
|
56
|
+
in addition to `code`, and promotes known no-code tokens (`flow_paused`,
|
|
57
|
+
`spending_limit_exceeded`, `receiver_not_enrolled_use_claim_code`) to `.code`. Fixes
|
|
58
|
+
`isDuplicateRequest` on the direct rail.
|
|
59
|
+
- **Guardian/minor routing.** The 202 guardian body also carries
|
|
60
|
+
`verification_method:"sms"`; the SDK now suppresses it (reports
|
|
61
|
+
`verificationMethod: undefined`) so a guardian-pending minor isn't routed into the
|
|
62
|
+
SMS-PIN UI. README example reordered to branch on `guardianApproval` first.
|
|
63
|
+
|
|
64
|
+
## [0.4.1] — 2026-06-30
|
|
65
|
+
|
|
66
|
+
Docs only — replaced the README's internal-leaking "Deployment prerequisites"
|
|
67
|
+
(backend flag names, DB columns, gating mechanics) with a partner-facing
|
|
68
|
+
"Before you go live"; republished so the npm page README is current.
|
|
69
|
+
|
|
70
|
+
## [0.4.0] — 2026-06-30
|
|
71
|
+
|
|
72
|
+
Additive release — more server reads, edge-ready webhooks, cancellation, and tooling.
|
|
73
|
+
|
|
74
|
+
- **`getInboundPending({ playerEmail | playerPhone })`** — live, unclaimed inbound
|
|
75
|
+
sends/transfers for a player (the source of truth behind the "you have X to collect"
|
|
76
|
+
badge; pairs with `transfer.claim_pending`).
|
|
77
|
+
- **`verifyWebhookAsync`** — Web Crypto variant of `verifyWebhook` that runs on
|
|
78
|
+
Cloudflare Workers / Deno / Vercel+Netlify Edge / Bun / browsers; and
|
|
79
|
+
**`createWebhookHandler`** — a zero-dep Fetch-API `(Request) => Promise<Response>`
|
|
80
|
+
webhook route handler (Next.js App Router, Workers, Deno, Hono, Bun).
|
|
81
|
+
- **`iterateItemPurchaseHistory`** — async iterator that pages through a player's
|
|
82
|
+
full item-purchase history.
|
|
83
|
+
- **Per-call `AbortSignal`** — every method accepts an optional `{ signal }`; an
|
|
84
|
+
aborted call throws `InvoError` code `ABORTED` and is never retried.
|
|
85
|
+
- **Tooling**: ESLint (+ lint in CI), changesets release automation, `SECURITY.md`,
|
|
86
|
+
and `CODEOWNERS`.
|
|
87
|
+
|
|
88
|
+
## [0.3.0] — 2026-06-30
|
|
89
|
+
|
|
90
|
+
Additive release — new server capabilities plus transport resilience/observability.
|
|
91
|
+
|
|
92
|
+
- **Webhook verification** (`/server`): `verifyWebhook(rawBody, signatureHeader, secret | secrets, opts?)`
|
|
93
|
+
— constant-time HMAC-SHA256 over `${t}.${rawBody}`, 5-minute replay window,
|
|
94
|
+
multi-secret rotation; returns a typed `InvoWebhookEvent` discriminated union
|
|
95
|
+
(`purchase.*`, `item.purchased`, `transfer.*`, `payout.status_changed`, `webhook.test`).
|
|
96
|
+
Throws `InvoError` (`WEBHOOK_SIGNATURE_INVALID` / `WEBHOOK_TIMESTAMP_EXPIRED` /
|
|
97
|
+
`WEBHOOK_MALFORMED` / `WEBHOOK_SECRET_MISSING`). Server-only; the browser bundle
|
|
98
|
+
stays crypto-free. Independently security-audited.
|
|
99
|
+
- **`getPlayerBalance({ playerEmail | playerId })`** (`/server`): typed `player` / `balances` / `summary`.
|
|
100
|
+
- **Automatic retries**: network errors/timeouts, `429` (honoring `retry_after`), and
|
|
101
|
+
`5xx` are retried with exponential backoff + jitter. New config `maxRetries`
|
|
102
|
+
(default 2, `0` disables) and `retryBaseDelayMs` (default 250).
|
|
103
|
+
- **Observability hooks**: optional `onRequest` / `onResponse` / `onError` on both
|
|
104
|
+
entries (best-effort/non-throwing); `InvoError.requestId` carries the backend
|
|
105
|
+
request id for support/tracing.
|
|
106
|
+
- **Typed reads**: `confirmPayment` → `ConfirmPaymentResult`; `getOrderDetails` /
|
|
107
|
+
`getItemOrderDetails` → `OrderDetailsResult`; `getItemPurchaseHistory` →
|
|
108
|
+
`ItemHistoryResult` (previously untyped `Record`). All keep `raw`.
|
|
109
|
+
- **Light validation**: `mintPlayerToken` and `createCheckout` require a non-blank
|
|
110
|
+
`playerEmail` (throws `INVALID_INPUT` before the network call).
|
|
111
|
+
- **License**: `package.json` `license` is now `SEE LICENSE IN LICENSE` (was
|
|
112
|
+
`UNLICENSED`); `LICENSE` rewritten as an explicit install-and-use grant for
|
|
113
|
+
building INVO integrations.
|
|
114
|
+
|
|
115
|
+
## [0.2.1] — 2026-06-30
|
|
116
|
+
|
|
117
|
+
Docs only — no code change (republished so the npm page README is current).
|
|
118
|
+
|
|
119
|
+
- Rewrote the README into a complete integration/deployment guide: capability
|
|
120
|
+
overview, architecture, deployment prerequisites, per-flow sections (currency
|
|
121
|
+
purchase, item purchase, sends, transfers, passkeys), webhooks, errors, and a
|
|
122
|
+
full API reference for both entries.
|
|
123
|
+
- Added INVO console onboarding: `https://console.invo.network` (production) and
|
|
124
|
+
`https://dev.console.invo.network` (testing/sandbox), mapped to their API base URLs.
|
|
125
|
+
|
|
126
|
+
## [0.2.0] — 2026-06-30
|
|
127
|
+
|
|
128
|
+
Adds **item purchase** (spend existing game currency on an in-game item, §4.8) — an
|
|
129
|
+
additive, server-only surface.
|
|
130
|
+
|
|
131
|
+
- `InvoServer` (`/server`): `purchaseItem`, `getItemPurchaseHistory`, `getItemOrderDetails`.
|
|
132
|
+
- Server-side, game-secret auth — no passkey, no real money, no payment rail (it's a
|
|
133
|
+
balance debit). Grant the item off the `item.purchased` webhook.
|
|
134
|
+
- Client-side guards: required fields (trim-checked), `itemQuantity` integer `1..1000`,
|
|
135
|
+
prices `> 0` and `<= 999999.99` (magnitude-safe 2-decimal check), and
|
|
136
|
+
`totalPrice == unitPrice × itemQuantity (±0.01)` compared in integer cents.
|
|
137
|
+
- Load-bearing response fields (`transaction_id`, `order_id`) throw `INVALID_RESPONSE`
|
|
138
|
+
if missing on a 200.
|
|
139
|
+
- `InvoError` helpers: `isInsufficientBalance` (gated to 400; not the `429` throttle),
|
|
140
|
+
`isDuplicateRequest` (409), `retryAfter` (numeric or string `retry_after`); all
|
|
141
|
+
null-safe against non-JSON error bodies.
|
|
142
|
+
- Independently audited (2 agents) against handoff doc §4.8/§6/§8; contract verified,
|
|
143
|
+
error-classification edge cases fixed.
|
|
144
|
+
|
|
145
|
+
## [0.1.0] — 2026-06-30
|
|
146
|
+
|
|
147
|
+
Initial scaffold.
|
|
148
|
+
|
|
149
|
+
- `InvoServer` (`/server`): `mintPlayerToken`, `initiateSend`, `initiateTransfer`,
|
|
150
|
+
`createCheckout`, `purchaseCurrency`, `confirmPayment`, `getOrderDetails`.
|
|
151
|
+
- Client-side purchase guards (§4.7): USD amount `0 < x ≤ 999.99`, required
|
|
152
|
+
`purchaseReference`, and `rail:"steam"` rejected before the network call
|
|
153
|
+
(`INVALID_INPUT` / `MISSING_PURCHASE_REFERENCE` / `WRONG_RAIL_ENDPOINT`).
|
|
154
|
+
- `InvoClient` (browser): `enrollPasskey`, `approveSend`/`approveTransfer`,
|
|
155
|
+
`confirmReceiptSend`/`confirmReceiptTransfer`, and `linkDevice` for the
|
|
156
|
+
interchangeable-methods flow (§4.6).
|
|
157
|
+
- Optional `refreshToken` hook: transparently re-mints and retries once on
|
|
158
|
+
`SDK_TOKEN_EXPIRED` (§11.2).
|
|
159
|
+
- Shared: typed `InvoError`, isomorphic HTTP client, WebAuthn JSON⇄binary helpers.
|
|
160
|
+
- Tests: HTTP layer, server request mapping + purchase guards, browser client
|
|
161
|
+
flows (enroll/approve/link/token-refresh), and WebAuthn serialization.
|
|
162
|
+
- Contracts extracted + auditor-verified against the INVO backend.
|
|
163
|
+
|
|
164
|
+
### Hardening (independent red-team pass)
|
|
165
|
+
|
|
166
|
+
- **Guardian/minor `202` path no longer mismapped to `verificationMethod:"sms"`** —
|
|
167
|
+
`initiateSend`/`initiateTransfer` now return `verificationMethod: undefined` and a
|
|
168
|
+
`guardianApproval` block on the guardian path, so callers don't route into the
|
|
169
|
+
PIN UI by mistake (§4.3).
|
|
170
|
+
- `usdAmount` validation tightened: rejects non-plain-decimal strings
|
|
171
|
+
(`"0x10"`, `"1e2"`, whitespace) and >2 decimal places before any network call.
|
|
172
|
+
- Load-bearing response fields (`token`, `checkout_url`) now throw `INVALID_RESPONSE`
|
|
173
|
+
instead of silently surfacing as empty strings.
|
|
174
|
+
- `getOrderDetails` requires at least one of `orderId`/`transactionId`.
|
|
175
|
+
- Token refresh now re-runs the **whole** passkey ceremony on `SDK_TOKEN_EXPIRED`
|
|
176
|
+
(never replays a single-use assertion) and single-flights concurrent refreshes.
|
|
177
|
+
- `baseUrl` must be `https://` (localhost exempt) so the token/secret can't travel
|
|
178
|
+
in cleartext.
|
|
179
|
+
- Published tarball excludes sourcemaps (no proprietary source shipped).
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
First-party TypeScript SDK for integrating **INVO** into partner **web** platforms (storefronts, web games, dashboards). It wraps INVO's web money flows behind a typed, versioned API — the web analog of the Unity/Unreal plugins.
|
|
4
4
|
|
|
5
|
-
> **Status:** `v0.
|
|
5
|
+
> **Status:** `v0.7.0`, published on npm. The backend it wraps is **live** on sandbox + production, so you can build and test against sandbox today.
|
|
6
6
|
> Canonical partner reference: **https://docs.invo.network/docs/currency-purchase** and **https://docs.invo.network/docs/game-developer-integration**.
|
|
7
7
|
|
|
8
8
|
## What it does
|
|
@@ -251,6 +251,13 @@ Move already-owned game currency from one player to another, authorized by the *
|
|
|
251
251
|
| Approve (browser) | `approveSend` | `approveTransfer` — also returns the sender's **claim code** |
|
|
252
252
|
| Recipient claim (browser) | `confirmReceiptSend` | `confirmReceiptTransfer` |
|
|
253
253
|
|
|
254
|
+
**Where can I send? (browser, v0.7.0+)** — before a player can move value, populate the "pick a destination" UI with `client.getDestinations({ direction: "transfer" | "send" })`. One player-token call returns every reachable game with display metadata inline — no per-game lookup:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
const { availableGames, sourceCurrencyName } = await client.getDestinations({ direction: "transfer" });
|
|
258
|
+
// each: { gameId, gameName, gameIcon, currencyName, currencySymbol, minimumTransfer, maximumTransfer, … }
|
|
259
|
+
```
|
|
260
|
+
|
|
254
261
|
```ts
|
|
255
262
|
// 1. SERVER — initiate, then branch on how the sender must verify
|
|
256
263
|
const t = await server.initiateTransfer({
|
|
@@ -286,14 +293,12 @@ try {
|
|
|
286
293
|
- **Claim codes** are returned only by `approveTransfer`; they're the out-of-band fallback when the recipient isn't enrolled.
|
|
287
294
|
- **`err.isReceiverNotEnrolled`** on `confirmReceipt*` is the explicit signal to switch to claim-code entry.
|
|
288
295
|
- Transfer self-claim may be disabled for your tenant; if it is, surface the claim-code path instead.
|
|
296
|
+
- **Holds:** an approve/claim can return an HTTP 202 hold instead of success — inspect `result.holdReason` (`"RISK_HOLD"`, `"GUARDIAN_APPROVAL_PENDING"`, and on confirm-receipt `"RECIPIENT_IDENTITY_PENDING"`); success has no `holdReason` (and `next: "pending_claim"`). Terminal guardian outcomes throw (`GUARDIAN_APPROVAL_REJECTED`/`_EXPIRED`).
|
|
289
297
|
|
|
290
|
-
**"You have X to collect"**
|
|
298
|
+
**"You have X to collect"**
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// each row: { transactionId, flow, amount, netAmount, sourceGame, toPhone, toIdentityId, claimCodeExpiresAt }
|
|
295
|
-
// match toPhone to the logged-in player (toIdentityId is null when the phone maps to >1 of your players)
|
|
296
|
-
```
|
|
300
|
+
- **Browser (player-token):** `client.getPendingCollect()` returns the player's own pending items. Each row's `kind` tells you the action — `"identity_gate"` → `approve*` (they initiated), `"receiving_confirm"` → `confirmReceipt*` (a peer sent to them). PII-free (no claim code / phone).
|
|
301
|
+
- **Server (game-secret):** `server.getInboundPending({ playerEmail | playerPhone })` — richer, includes `toPhone`/`toIdentityId` for routing a notification to the right account. Match `toPhone` to the player (`toIdentityId` is null when the phone maps to >1 of your players).
|
|
297
302
|
|
|
298
303
|
---
|
|
299
304
|
|
|
@@ -312,6 +317,26 @@ await client.linkDevice(linkId); // → { status: "authorized" }
|
|
|
312
317
|
await client.enrollPasskey();
|
|
313
318
|
```
|
|
314
319
|
|
|
320
|
+
**First-enrollment OTP grant.** For tenants that require it, the first `enrollPasskey()`
|
|
321
|
+
throws `ENROLLMENT_REQUIRES_AUTHORIZATION`. Send a one-time code (to the player's phone +
|
|
322
|
+
email on file), verify it, then **retry the same `enrollPasskey()`** — the server-side
|
|
323
|
+
grant is consumed automatically:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
try {
|
|
327
|
+
await client.enrollPasskey();
|
|
328
|
+
} catch (e) {
|
|
329
|
+
if (e instanceof InvoError && e.isEnrollmentAuthorizationRequired) {
|
|
330
|
+
await client.enrollmentBegin(); // → { status: "sent", channels: ["sms","email"] }
|
|
331
|
+
await client.enrollmentVerify(codeFromUser); // 6-digit OTP
|
|
332
|
+
await client.enrollPasskey(); // retry — grant auto-consumed
|
|
333
|
+
} else if (e instanceof InvoError && e.isEnrollmentProofRequired) {
|
|
334
|
+
await client.linkDevice(linkId); // a different method already exists
|
|
335
|
+
await client.enrollPasskey();
|
|
336
|
+
} else throw e;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
315
340
|
- **User verification is required** on every approve/claim (a missing-UV assertion fails closed).
|
|
316
341
|
- Challenges are single-use and bound to `{flow}:{transactionId}`.
|
|
317
342
|
- The SDK passes the backend's WebAuthn options through unchanged (it does not hard-code `pubKeyCredParams`/`timeout`/`attestation`).
|
|
@@ -419,6 +444,8 @@ Helpers:
|
|
|
419
444
|
| `.isInsufficientBalance` | item purchase failed (400); `required_amount` + `current_balance` on `.body` |
|
|
420
445
|
| `.isDuplicateRequest` | idempotency-keyed request was a duplicate (409) |
|
|
421
446
|
| `.retryAfter` | seconds to back off on a 429 throttle |
|
|
447
|
+
| `.isEnrollmentAuthorizationRequired` | first-enrollment needs the OTP grant → `enrollmentBegin`/`enrollmentVerify` |
|
|
448
|
+
| `.isEnrollmentProofRequired` | another method exists → prove it via `linkDevice` |
|
|
422
449
|
|
|
423
450
|
Client-side guards (bad amount, missing idempotency key, `rail:"steam"` on `purchaseCurrency`, item validation) throw `InvoError` with `.status === 0` **before** any network call. Notable backend codes: `SDK_TOKEN_EXPIRED`, `TENANT_NOT_MIGRATED`, `WEBAUTHN_NOT_ENABLED_FOR_TENANT`, `WEBAUTHN_UV_REQUIRED`, `ENROLLMENT_REQUIRES_PROOF`, `WRONG_RAIL_ENDPOINT`, `flow_paused`.
|
|
424
451
|
|
|
@@ -465,11 +492,14 @@ Every method also accepts an optional final `{ signal }` (`AbortSignal`) for can
|
|
|
465
492
|
| Method | Returns |
|
|
466
493
|
|---|---|
|
|
467
494
|
| `enrollPasskey()` | `{ status, device, raw }` |
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
495
|
+
| `enrollmentBegin()` / `enrollmentVerify(code)` | OTP grant for `ENROLLMENT_REQUIRES_AUTHORIZATION` (then retry `enrollPasskey`) |
|
|
496
|
+
| `approveSend(txnId)` / `approveTransfer(txnId)` | `{ status, next, transactionId, claimCode?, claimCodeExpiresAt?, holdReason?, risk?, guardianApproval?, pollEndpoint?, raw }` |
|
|
497
|
+
| `confirmReceiptSend(txnId)` / `confirmReceiptTransfer(txnId)` | `{ status, holdReason?, raw }` |
|
|
470
498
|
| `linkDevice(linkId)` | `{ status, raw }` |
|
|
499
|
+
| `getPendingCollect()` | `{ pending, raw }` — the player's own pending-to-collect list (player-token) |
|
|
500
|
+
| `getDestinations({ direction? })` | `{ availableGames, sourceGameName, transferMode, … }` — where the player can send/transfer, metadata inline |
|
|
471
501
|
|
|
472
|
-
Every method throws `InvoError` on failure
|
|
502
|
+
Every method throws `InvoError` on failure and takes an optional final `{ signal }`. Full inline types ship with the package.
|
|
473
503
|
|
|
474
504
|
---
|
|
475
505
|
|
|
@@ -481,7 +511,7 @@ npm run typecheck # tsc --noEmit
|
|
|
481
511
|
npm test # vitest
|
|
482
512
|
```
|
|
483
513
|
|
|
484
|
-
The package follows **semver**: patch = fixes, minor = additive surface, major = breaking changes (rare, with a migration note). The server contract is backward-compatible within a major, so an old pinned SDK keeps working. Pin a version and subscribe to release notes for security updates.
|
|
514
|
+
The package follows **semver**: patch = fixes, minor = additive surface, major = breaking changes (rare, with a migration note). The server contract is backward-compatible within a major, so an old pinned SDK keeps working. Pin a version and subscribe to release notes for security updates. Full history: [CHANGELOG](https://github.com/Invo-Technologies/invo-web-sdk/blob/main/CHANGELOG.md) · [release notes](https://github.com/Invo-Technologies/invo-web-sdk/blob/main/docs/RELEASES.md) (absolute links to `main`).
|
|
485
515
|
|
|
486
516
|
## License
|
|
487
517
|
|
|
@@ -17,6 +17,14 @@ var InvoError = class _InvoError extends Error {
|
|
|
17
17
|
get isTokenExpired() {
|
|
18
18
|
return this.code === "SDK_TOKEN_EXPIRED";
|
|
19
19
|
}
|
|
20
|
+
/** True if `enrollPasskey()` needs the OTP-grant flow (`enrollmentBegin`/`enrollmentVerify`). */
|
|
21
|
+
get isEnrollmentAuthorizationRequired() {
|
|
22
|
+
return this.code === "ENROLLMENT_REQUIRES_AUTHORIZATION";
|
|
23
|
+
}
|
|
24
|
+
/** True if enrolling is blocked because another method exists — use `linkDevice`. */
|
|
25
|
+
get isEnrollmentProofRequired() {
|
|
26
|
+
return this.code === "ENROLLMENT_REQUIRES_PROOF";
|
|
27
|
+
}
|
|
20
28
|
/**
|
|
21
29
|
* True if an item purchase failed because the player's balance was too low (§4.8 → 400).
|
|
22
30
|
* The backend carries `required_amount` + `current_balance` on the body for the UI.
|
|
@@ -249,5 +257,5 @@ function retryAfterMs(parsed, headers) {
|
|
|
249
257
|
}
|
|
250
258
|
|
|
251
259
|
export { Http, InvoError, assertSecureBaseUrl };
|
|
252
|
-
//# sourceMappingURL=chunk-
|
|
253
|
-
//# sourceMappingURL=chunk-
|
|
260
|
+
//# sourceMappingURL=chunk-D3XBTH4C.js.map
|
|
261
|
+
//# sourceMappingURL=chunk-D3XBTH4C.js.map
|