@invonetwork/web-sdk 0.7.0 → 0.8.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 CHANGED
@@ -4,6 +4,21 @@ All notable changes to `@invonetwork/web-sdk` are documented here. This project
4
4
  [Semantic Versioning](https://semver.org/). Releases are managed with
5
5
  [changesets](https://github.com/changesets/changesets).
6
6
 
7
+ ## [0.8.0] — 2026-07-01
8
+
9
+ Completes the **discovery layer** for send/transfer UIs.
10
+
11
+ - **`InvoClient.getBalance()`** (browser, player-token, `GET /api/sdk/balance`) — the
12
+ player's balances for the token's game: `balances[]` (`currencyId`, `currencyName`,
13
+ `currencySymbol`/`currencySymbolUrl` (nullable), `availableBalance`/`reservedBalance`/
14
+ `totalBalance` as decimal strings) plus `totalValue` / `currencyCount` / `hasFunds`. A
15
+ player who hasn't transacted here yet returns an empty `balances` array (a `200`, not an
16
+ error); `404 GAME_NOT_FOUND` throws.
17
+ - **`InvoServer.getLinkedIdentities({ playerEmail | playerPhone })`** (server-only,
18
+ game-secret) — a player's linked wallet identities (game-scoped). Provide one identifier
19
+ (phone wins if both); no in-game match returns `notFound: true` with empty `emails`
20
+ instead of throwing. **Returns first-party PII — never expose to the browser.**
21
+
7
22
  ## [0.7.0] — 2026-07-01
8
23
 
9
24
  Start of the **discovery layer** for send/transfer UIs.
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.7.0`, published on npm. The backend it wraps is **live** on sandbox + production, so you can build and test against sandbox today.
5
+ > **Status:** `v0.8.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
@@ -258,6 +258,13 @@ const { availableGames, sourceCurrencyName } = await client.getDestinations({ di
258
258
  // each: { gameId, gameName, gameIcon, currencyName, currencySymbol, minimumTransfer, maximumTransfer, … }
259
259
  ```
260
260
 
261
+ **What do I have? (browser, v0.8.0+)** — `client.getBalance()` returns the player's balances for this game (amounts are decimal strings; rows carry `currencySymbol`/`currencySymbolUrl`). A player who hasn't transacted here yet returns an empty `balances` array (show 0.00) — not an error:
262
+
263
+ ```ts
264
+ const { balances, hasFunds, totalValue } = await client.getBalance();
265
+ // each: { currencyId, currencyName, currencySymbol, availableBalance, reservedBalance, totalBalance }
266
+ ```
267
+
261
268
  ```ts
262
269
  // 1. SERVER — initiate, then branch on how the sender must verify
263
270
  const t = await server.initiateTransfer({
@@ -480,6 +487,7 @@ try {
480
487
  | `getItemOrderDetails({ orderId? \| transactionId? \| clientRequestId? })` | `{ order, financialSummary, statusTimeline, raw }` |
481
488
  | `getPlayerBalance({ playerEmail? \| playerId? })` | `{ player, balances, summary, raw }` |
482
489
  | `getInboundPending({ playerEmail? \| playerPhone? })` | `{ inboundPending, raw }` — live unclaimed inbound sends/transfers |
490
+ | `getLinkedIdentities({ playerEmail? \| playerPhone? })` | `{ walletUserId, primaryEmail, primaryPhone, isMinor, emails, notFound, raw }` — **server-only** (returns PII) |
483
491
  | `iterateItemPurchaseHistory({ playerEmail, pageSize? })` | async iterator over all history rows |
484
492
  | `verifyWebhook(rawBody, signatureHeader, secret \| secrets, opts?)` | typed `InvoWebhookEvent` (throws on bad signature) |
485
493
  | `verifyWebhookAsync(...)` | same as `verifyWebhook`, Web Crypto (edge/Workers/Deno/Bun) |
@@ -498,6 +506,7 @@ Every method also accepts an optional final `{ signal }` (`AbortSignal`) for can
498
506
  | `linkDevice(linkId)` | `{ status, raw }` |
499
507
  | `getPendingCollect()` | `{ pending, raw }` — the player's own pending-to-collect list (player-token) |
500
508
  | `getDestinations({ direction? })` | `{ availableGames, sourceGameName, transferMode, … }` — where the player can send/transfer, metadata inline |
509
+ | `getBalance()` | `{ gameId, gameName, balances, totalValue, currencyCount, hasFunds, raw }` — the player's balances for this game |
501
510
 
502
511
  Every method throws `InvoError` on failure and takes an optional final `{ signal }`. Full inline types ship with the package.
503
512
 
package/dist/index.cjs CHANGED
@@ -355,6 +355,18 @@ function toPendingCollectItem(row) {
355
355
  raw: row
356
356
  };
357
357
  }
358
+ function toBalanceRow(row) {
359
+ return {
360
+ currencyId: row["currency_id"] ?? "",
361
+ currencyName: String(row["currency_name"] ?? ""),
362
+ currencySymbol: row["currency_symbol"] ?? null,
363
+ currencySymbolUrl: row["currency_symbol_url"] ?? null,
364
+ availableBalance: String(row["available_balance"] ?? ""),
365
+ reservedBalance: String(row["reserved_balance"] ?? ""),
366
+ totalBalance: String(row["total_balance"] ?? ""),
367
+ raw: row
368
+ };
369
+ }
358
370
  function toDestinationGame(row) {
359
371
  const s = (k) => row[k] === void 0 || row[k] === null ? void 0 : String(row[k]);
360
372
  return {
@@ -516,6 +528,28 @@ var InvoClient = class {
516
528
  };
517
529
  });
518
530
  }
531
+ /**
532
+ * The player's balances for the token's game (browser, player-token). Amounts are
533
+ * decimal strings. A player who hasn't transacted here yet returns `200` with an empty
534
+ * `balances` array (show 0.00) — not an error; a `404 GAME_NOT_FOUND` (bad game/token)
535
+ * throws. Rows include the currency symbol + icon URL (each may be null).
536
+ */
537
+ async getBalance(opts) {
538
+ return this.withTokenRetry(async () => {
539
+ const raw = await this.get("/api/sdk/balance", opts?.signal);
540
+ const rows = Array.isArray(raw["balances"]) ? raw["balances"] : [];
541
+ const summary = raw["summary"] ?? {};
542
+ return {
543
+ gameId: raw["game_id"] ?? "",
544
+ gameName: String(raw["game_name"] ?? ""),
545
+ balances: rows.map(toBalanceRow),
546
+ totalValue: String(summary["total_value"] ?? "0"),
547
+ currencyCount: Number(summary["currency_count"] ?? rows.length),
548
+ hasFunds: summary["has_funds"] === true,
549
+ raw
550
+ };
551
+ });
552
+ }
519
553
  /**
520
554
  * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
521
555
  * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult, P as PendingCollectResult, D as DestinationsQuery, c as DestinationsResult, E as EnrollmentBeginResult, d as EnrollmentVerifyResult } from './types-Bi_NMSkN.cjs';
2
- export { e as DestinationGame, I as InvoError, f as InvoErrorInfo, g as InvoHooks, h as InvoRequestInfo, i as InvoResponseInfo, j as PendingCollectItem, R as Rail, V as VerificationMethod } from './types-Bi_NMSkN.cjs';
1
+ import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult, P as PendingCollectResult, D as DestinationsQuery, c as DestinationsResult, B as BalanceResult, E as EnrollmentBeginResult, d as EnrollmentVerifyResult } from './types-DLSCxpoT.cjs';
2
+ export { e as BalanceRow, f as DestinationGame, I as InvoError, g as InvoErrorInfo, h as InvoHooks, i as InvoRequestInfo, j as InvoResponseInfo, k as PendingCollectItem, R as Rail, V as VerificationMethod } from './types-DLSCxpoT.cjs';
3
3
 
4
4
  declare class InvoClient {
5
5
  private readonly http;
@@ -45,6 +45,13 @@ declare class InvoClient {
45
45
  * Source game is the token's own game. Player-token (browser).
46
46
  */
47
47
  getDestinations(query?: DestinationsQuery, opts?: CallOptions): Promise<DestinationsResult>;
48
+ /**
49
+ * The player's balances for the token's game (browser, player-token). Amounts are
50
+ * decimal strings. A player who hasn't transacted here yet returns `200` with an empty
51
+ * `balances` array (show 0.00) — not an error; a `404 GAME_NOT_FOUND` (bad game/token)
52
+ * throws. Rows include the currency symbol + icon URL (each may be null).
53
+ */
54
+ getBalance(opts?: CallOptions): Promise<BalanceResult>;
48
55
  /**
49
56
  * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
50
57
  * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
@@ -74,4 +81,4 @@ declare class InvoClient {
74
81
  private confirmReceipt;
75
82
  }
76
83
 
77
- export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, DestinationsQuery, DestinationsResult, EnrollmentBeginResult, EnrollmentVerifyResult, InvoClient, LinkDeviceResult, PendingCollectResult };
84
+ export { ApproveResult, BalanceResult, CallOptions, ClientConfig, ConfirmReceiptResult, DestinationsQuery, DestinationsResult, EnrollmentBeginResult, EnrollmentVerifyResult, InvoClient, LinkDeviceResult, PendingCollectResult };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult, P as PendingCollectResult, D as DestinationsQuery, c as DestinationsResult, E as EnrollmentBeginResult, d as EnrollmentVerifyResult } from './types-Bi_NMSkN.js';
2
- export { e as DestinationGame, I as InvoError, f as InvoErrorInfo, g as InvoHooks, h as InvoRequestInfo, i as InvoResponseInfo, j as PendingCollectItem, R as Rail, V as VerificationMethod } from './types-Bi_NMSkN.js';
1
+ import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult, P as PendingCollectResult, D as DestinationsQuery, c as DestinationsResult, B as BalanceResult, E as EnrollmentBeginResult, d as EnrollmentVerifyResult } from './types-DLSCxpoT.js';
2
+ export { e as BalanceRow, f as DestinationGame, I as InvoError, g as InvoErrorInfo, h as InvoHooks, i as InvoRequestInfo, j as InvoResponseInfo, k as PendingCollectItem, R as Rail, V as VerificationMethod } from './types-DLSCxpoT.js';
3
3
 
4
4
  declare class InvoClient {
5
5
  private readonly http;
@@ -45,6 +45,13 @@ declare class InvoClient {
45
45
  * Source game is the token's own game. Player-token (browser).
46
46
  */
47
47
  getDestinations(query?: DestinationsQuery, opts?: CallOptions): Promise<DestinationsResult>;
48
+ /**
49
+ * The player's balances for the token's game (browser, player-token). Amounts are
50
+ * decimal strings. A player who hasn't transacted here yet returns `200` with an empty
51
+ * `balances` array (show 0.00) — not an error; a `404 GAME_NOT_FOUND` (bad game/token)
52
+ * throws. Rows include the currency symbol + icon URL (each may be null).
53
+ */
54
+ getBalance(opts?: CallOptions): Promise<BalanceResult>;
48
55
  /**
49
56
  * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
50
57
  * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
@@ -74,4 +81,4 @@ declare class InvoClient {
74
81
  private confirmReceipt;
75
82
  }
76
83
 
77
- export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, DestinationsQuery, DestinationsResult, EnrollmentBeginResult, EnrollmentVerifyResult, InvoClient, LinkDeviceResult, PendingCollectResult };
84
+ export { ApproveResult, BalanceResult, CallOptions, ClientConfig, ConfirmReceiptResult, DestinationsQuery, DestinationsResult, EnrollmentBeginResult, EnrollmentVerifyResult, InvoClient, LinkDeviceResult, PendingCollectResult };
package/dist/index.js CHANGED
@@ -98,6 +98,18 @@ function toPendingCollectItem(row) {
98
98
  raw: row
99
99
  };
100
100
  }
101
+ function toBalanceRow(row) {
102
+ return {
103
+ currencyId: row["currency_id"] ?? "",
104
+ currencyName: String(row["currency_name"] ?? ""),
105
+ currencySymbol: row["currency_symbol"] ?? null,
106
+ currencySymbolUrl: row["currency_symbol_url"] ?? null,
107
+ availableBalance: String(row["available_balance"] ?? ""),
108
+ reservedBalance: String(row["reserved_balance"] ?? ""),
109
+ totalBalance: String(row["total_balance"] ?? ""),
110
+ raw: row
111
+ };
112
+ }
101
113
  function toDestinationGame(row) {
102
114
  const s = (k) => row[k] === void 0 || row[k] === null ? void 0 : String(row[k]);
103
115
  return {
@@ -259,6 +271,28 @@ var InvoClient = class {
259
271
  };
260
272
  });
261
273
  }
274
+ /**
275
+ * The player's balances for the token's game (browser, player-token). Amounts are
276
+ * decimal strings. A player who hasn't transacted here yet returns `200` with an empty
277
+ * `balances` array (show 0.00) — not an error; a `404 GAME_NOT_FOUND` (bad game/token)
278
+ * throws. Rows include the currency symbol + icon URL (each may be null).
279
+ */
280
+ async getBalance(opts) {
281
+ return this.withTokenRetry(async () => {
282
+ const raw = await this.get("/api/sdk/balance", opts?.signal);
283
+ const rows = Array.isArray(raw["balances"]) ? raw["balances"] : [];
284
+ const summary = raw["summary"] ?? {};
285
+ return {
286
+ gameId: raw["game_id"] ?? "",
287
+ gameName: String(raw["game_name"] ?? ""),
288
+ balances: rows.map(toBalanceRow),
289
+ totalValue: String(summary["total_value"] ?? "0"),
290
+ currencyCount: Number(summary["currency_count"] ?? rows.length),
291
+ hasFunds: summary["has_funds"] === true,
292
+ raw
293
+ };
294
+ });
295
+ }
262
296
  /**
263
297
  * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
264
298
  * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
package/dist/server.cjs CHANGED
@@ -418,7 +418,7 @@ async function hmacHexSubtle(secret, message) {
418
418
  }
419
419
 
420
420
  // src/server.ts
421
- var DEFAULT_UA = "invonetwork-web-sdk/0.4.0 (+https://invo.network)";
421
+ var DEFAULT_UA = "invonetwork-web-sdk/0.8.0 (+https://invo.network)";
422
422
  var MAX_USD_AMOUNT = 999.99;
423
423
  var MAX_ITEM_PRICE = 999999.99;
424
424
  function invalidInput(label, value, why) {
@@ -458,6 +458,13 @@ function toOrderDetails(raw) {
458
458
  raw
459
459
  };
460
460
  }
461
+ function toLinkedEmail(row) {
462
+ return {
463
+ email: String(row["email"] ?? ""),
464
+ primary: row["primary"] === true,
465
+ verifiedAt: row["verified_at"] ?? null
466
+ };
467
+ }
461
468
  function toInboundPendingItem(row) {
462
469
  return {
463
470
  transactionId: String(row["transaction_id"] ?? ""),
@@ -877,6 +884,48 @@ var InvoServer = class {
877
884
  const rows = Array.isArray(raw["inbound_pending"]) ? raw["inbound_pending"] : [];
878
885
  return { inboundPending: rows.map(toInboundPendingItem), raw };
879
886
  }
887
+ /**
888
+ * Look up a player's linked wallet identities (SERVER-only, game-secret). Game-scoped:
889
+ * returns wallet members that also have a Player row on this game. Provide a `playerEmail`
890
+ * or `playerPhone` (phone wins if both). No in-game match (backend 404) returns
891
+ * `notFound: true` with empty `emails` — not an error.
892
+ *
893
+ * ⚠️ Returns first-party PII (emails/phones) — never expose this to the browser.
894
+ */
895
+ async getLinkedIdentities(query, opts) {
896
+ const phone = typeof query.playerPhone === "string" && query.playerPhone.trim() ? query.playerPhone.trim() : "";
897
+ const email = typeof query.playerEmail === "string" && query.playerEmail.trim() ? query.playerEmail.trim() : "";
898
+ if (!phone && !email) {
899
+ throw invalidInput("query", query, "requires a playerEmail or playerPhone");
900
+ }
901
+ const q = new URLSearchParams();
902
+ if (phone) q.set("player_phone", phone);
903
+ else q.set("player_email", email);
904
+ let raw;
905
+ try {
906
+ raw = await this.http.get(
907
+ `/api/wallet/identities?${q.toString()}`,
908
+ this.auth,
909
+ { signal: opts?.signal }
910
+ );
911
+ } catch (e) {
912
+ if (e instanceof InvoError && e.status === 404) {
913
+ const raw404 = e.body && typeof e.body === "object" ? e.body : {};
914
+ return { walletUserId: null, primaryEmail: "", primaryPhone: "", isMinor: false, emails: [], notFound: true, raw: raw404 };
915
+ }
916
+ throw e;
917
+ }
918
+ const emails = Array.isArray(raw["emails"]) ? raw["emails"].map(toLinkedEmail) : [];
919
+ return {
920
+ walletUserId: raw["wallet_user_id"] ?? null,
921
+ primaryEmail: String(raw["primary_email"] ?? ""),
922
+ primaryPhone: String(raw["primary_phone"] ?? ""),
923
+ isMinor: raw["is_minor"] === true,
924
+ emails,
925
+ notFound: false,
926
+ raw
927
+ };
928
+ }
880
929
  toInitiateResult(raw) {
881
930
  const vm = raw["verification_method"];
882
931
  const guardian = raw["guardian_approval"];
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InvoError, S as ServerConfig, a as CallOptions, k as PlayerToken, l as InitiateSendInput, m as InitiateResult, n as InitiateTransferInput, o as CreateCheckoutInput, p as CreateCheckoutResult, q as PurchaseInput, r as PurchaseResult, s as ConfirmPaymentResult, O as OrderDetailsResult, t as PurchaseItemInput, u as PurchaseItemResult, v as ItemHistoryQuery, w as ItemHistoryResult, x as ItemOrderQuery, y as PlayerBalanceQuery, z as PlayerBalanceResult, B as InboundPendingQuery, F as InboundPendingResult } from './types-Bi_NMSkN.cjs';
2
- export { G as CurrencyBalance, H as InboundPendingItem, f as InvoErrorInfo, g as InvoHooks, h as InvoRequestInfo, i as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-Bi_NMSkN.cjs';
1
+ import { I as InvoError, S as ServerConfig, a as CallOptions, l as PlayerToken, m as InitiateSendInput, n as InitiateResult, o as InitiateTransferInput, p as CreateCheckoutInput, q as CreateCheckoutResult, r as PurchaseInput, s as PurchaseResult, t as ConfirmPaymentResult, O as OrderDetailsResult, u as PurchaseItemInput, v as PurchaseItemResult, w as ItemHistoryQuery, x as ItemHistoryResult, y as ItemOrderQuery, z as PlayerBalanceQuery, F as PlayerBalanceResult, G as InboundPendingQuery, H as InboundPendingResult, J as LinkedIdentitiesQuery, K as LinkedIdentitiesResult } from './types-DLSCxpoT.cjs';
2
+ export { M as CurrencyBalance, N as InboundPendingItem, g as InvoErrorInfo, h as InvoHooks, i as InvoRequestInfo, j as InvoResponseInfo, Q as LinkedIdentityEmail, R as Rail, V as VerificationMethod } from './types-DLSCxpoT.cjs';
3
3
 
4
4
  interface VerifyWebhookOptions {
5
5
  /** Max age of the signed timestamp, in seconds. Default 300 (5 min). */
@@ -216,7 +216,16 @@ declare class InvoServer {
216
216
  * `transfer.claim_pending` webhook (the webhook is the wake-up; this is the list).
217
217
  */
218
218
  getInboundPending(query: InboundPendingQuery, opts?: CallOptions): Promise<InboundPendingResult>;
219
+ /**
220
+ * Look up a player's linked wallet identities (SERVER-only, game-secret). Game-scoped:
221
+ * returns wallet members that also have a Player row on this game. Provide a `playerEmail`
222
+ * or `playerPhone` (phone wins if both). No in-game match (backend 404) returns
223
+ * `notFound: true` with empty `emails` — not an error.
224
+ *
225
+ * ⚠️ Returns first-party PII (emails/phones) — never expose this to the browser.
226
+ */
227
+ getLinkedIdentities(query: LinkedIdentitiesQuery, opts?: CallOptions): Promise<LinkedIdentitiesResult>;
219
228
  private toInitiateResult;
220
229
  }
221
230
 
222
- export { CallOptions, ConfirmPaymentResult, CreateCheckoutInput, CreateCheckoutResult, InboundPendingQuery, InboundPendingResult, InitiateResult, InitiateSendInput, InitiateTransferInput, InvoError, InvoServer, type InvoWebhookEvent, ItemHistoryQuery, ItemHistoryResult, ItemOrderQuery, type ItemPurchasedData, OrderDetailsResult, PlayerBalanceQuery, PlayerBalanceResult, PlayerToken, type PurchaseCompletedData, type PurchaseEventData, PurchaseInput, PurchaseItemInput, PurchaseItemResult, PurchaseResult, ServerConfig, type TransferEventData, type VerifyWebhookOptions, type WebhookHandlerOptions, createWebhookHandler, verifyWebhook, verifyWebhookAsync };
231
+ export { CallOptions, ConfirmPaymentResult, CreateCheckoutInput, CreateCheckoutResult, InboundPendingQuery, InboundPendingResult, InitiateResult, InitiateSendInput, InitiateTransferInput, InvoError, InvoServer, type InvoWebhookEvent, ItemHistoryQuery, ItemHistoryResult, ItemOrderQuery, type ItemPurchasedData, LinkedIdentitiesQuery, LinkedIdentitiesResult, OrderDetailsResult, PlayerBalanceQuery, PlayerBalanceResult, PlayerToken, type PurchaseCompletedData, type PurchaseEventData, PurchaseInput, PurchaseItemInput, PurchaseItemResult, PurchaseResult, ServerConfig, type TransferEventData, type VerifyWebhookOptions, type WebhookHandlerOptions, createWebhookHandler, verifyWebhook, verifyWebhookAsync };
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InvoError, S as ServerConfig, a as CallOptions, k as PlayerToken, l as InitiateSendInput, m as InitiateResult, n as InitiateTransferInput, o as CreateCheckoutInput, p as CreateCheckoutResult, q as PurchaseInput, r as PurchaseResult, s as ConfirmPaymentResult, O as OrderDetailsResult, t as PurchaseItemInput, u as PurchaseItemResult, v as ItemHistoryQuery, w as ItemHistoryResult, x as ItemOrderQuery, y as PlayerBalanceQuery, z as PlayerBalanceResult, B as InboundPendingQuery, F as InboundPendingResult } from './types-Bi_NMSkN.js';
2
- export { G as CurrencyBalance, H as InboundPendingItem, f as InvoErrorInfo, g as InvoHooks, h as InvoRequestInfo, i as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-Bi_NMSkN.js';
1
+ import { I as InvoError, S as ServerConfig, a as CallOptions, l as PlayerToken, m as InitiateSendInput, n as InitiateResult, o as InitiateTransferInput, p as CreateCheckoutInput, q as CreateCheckoutResult, r as PurchaseInput, s as PurchaseResult, t as ConfirmPaymentResult, O as OrderDetailsResult, u as PurchaseItemInput, v as PurchaseItemResult, w as ItemHistoryQuery, x as ItemHistoryResult, y as ItemOrderQuery, z as PlayerBalanceQuery, F as PlayerBalanceResult, G as InboundPendingQuery, H as InboundPendingResult, J as LinkedIdentitiesQuery, K as LinkedIdentitiesResult } from './types-DLSCxpoT.js';
2
+ export { M as CurrencyBalance, N as InboundPendingItem, g as InvoErrorInfo, h as InvoHooks, i as InvoRequestInfo, j as InvoResponseInfo, Q as LinkedIdentityEmail, R as Rail, V as VerificationMethod } from './types-DLSCxpoT.js';
3
3
 
4
4
  interface VerifyWebhookOptions {
5
5
  /** Max age of the signed timestamp, in seconds. Default 300 (5 min). */
@@ -216,7 +216,16 @@ declare class InvoServer {
216
216
  * `transfer.claim_pending` webhook (the webhook is the wake-up; this is the list).
217
217
  */
218
218
  getInboundPending(query: InboundPendingQuery, opts?: CallOptions): Promise<InboundPendingResult>;
219
+ /**
220
+ * Look up a player's linked wallet identities (SERVER-only, game-secret). Game-scoped:
221
+ * returns wallet members that also have a Player row on this game. Provide a `playerEmail`
222
+ * or `playerPhone` (phone wins if both). No in-game match (backend 404) returns
223
+ * `notFound: true` with empty `emails` — not an error.
224
+ *
225
+ * ⚠️ Returns first-party PII (emails/phones) — never expose this to the browser.
226
+ */
227
+ getLinkedIdentities(query: LinkedIdentitiesQuery, opts?: CallOptions): Promise<LinkedIdentitiesResult>;
219
228
  private toInitiateResult;
220
229
  }
221
230
 
222
- export { CallOptions, ConfirmPaymentResult, CreateCheckoutInput, CreateCheckoutResult, InboundPendingQuery, InboundPendingResult, InitiateResult, InitiateSendInput, InitiateTransferInput, InvoError, InvoServer, type InvoWebhookEvent, ItemHistoryQuery, ItemHistoryResult, ItemOrderQuery, type ItemPurchasedData, OrderDetailsResult, PlayerBalanceQuery, PlayerBalanceResult, PlayerToken, type PurchaseCompletedData, type PurchaseEventData, PurchaseInput, PurchaseItemInput, PurchaseItemResult, PurchaseResult, ServerConfig, type TransferEventData, type VerifyWebhookOptions, type WebhookHandlerOptions, createWebhookHandler, verifyWebhook, verifyWebhookAsync };
231
+ export { CallOptions, ConfirmPaymentResult, CreateCheckoutInput, CreateCheckoutResult, InboundPendingQuery, InboundPendingResult, InitiateResult, InitiateSendInput, InitiateTransferInput, InvoError, InvoServer, type InvoWebhookEvent, ItemHistoryQuery, ItemHistoryResult, ItemOrderQuery, type ItemPurchasedData, LinkedIdentitiesQuery, LinkedIdentitiesResult, OrderDetailsResult, PlayerBalanceQuery, PlayerBalanceResult, PlayerToken, type PurchaseCompletedData, type PurchaseEventData, PurchaseInput, PurchaseItemInput, PurchaseItemResult, PurchaseResult, ServerConfig, type TransferEventData, type VerifyWebhookOptions, type WebhookHandlerOptions, createWebhookHandler, verifyWebhook, verifyWebhookAsync };
package/dist/server.js CHANGED
@@ -161,7 +161,7 @@ async function hmacHexSubtle(secret, message) {
161
161
  }
162
162
 
163
163
  // src/server.ts
164
- var DEFAULT_UA = "invonetwork-web-sdk/0.4.0 (+https://invo.network)";
164
+ var DEFAULT_UA = "invonetwork-web-sdk/0.8.0 (+https://invo.network)";
165
165
  var MAX_USD_AMOUNT = 999.99;
166
166
  var MAX_ITEM_PRICE = 999999.99;
167
167
  function invalidInput(label, value, why) {
@@ -201,6 +201,13 @@ function toOrderDetails(raw) {
201
201
  raw
202
202
  };
203
203
  }
204
+ function toLinkedEmail(row) {
205
+ return {
206
+ email: String(row["email"] ?? ""),
207
+ primary: row["primary"] === true,
208
+ verifiedAt: row["verified_at"] ?? null
209
+ };
210
+ }
204
211
  function toInboundPendingItem(row) {
205
212
  return {
206
213
  transactionId: String(row["transaction_id"] ?? ""),
@@ -620,6 +627,48 @@ var InvoServer = class {
620
627
  const rows = Array.isArray(raw["inbound_pending"]) ? raw["inbound_pending"] : [];
621
628
  return { inboundPending: rows.map(toInboundPendingItem), raw };
622
629
  }
630
+ /**
631
+ * Look up a player's linked wallet identities (SERVER-only, game-secret). Game-scoped:
632
+ * returns wallet members that also have a Player row on this game. Provide a `playerEmail`
633
+ * or `playerPhone` (phone wins if both). No in-game match (backend 404) returns
634
+ * `notFound: true` with empty `emails` — not an error.
635
+ *
636
+ * ⚠️ Returns first-party PII (emails/phones) — never expose this to the browser.
637
+ */
638
+ async getLinkedIdentities(query, opts) {
639
+ const phone = typeof query.playerPhone === "string" && query.playerPhone.trim() ? query.playerPhone.trim() : "";
640
+ const email = typeof query.playerEmail === "string" && query.playerEmail.trim() ? query.playerEmail.trim() : "";
641
+ if (!phone && !email) {
642
+ throw invalidInput("query", query, "requires a playerEmail or playerPhone");
643
+ }
644
+ const q = new URLSearchParams();
645
+ if (phone) q.set("player_phone", phone);
646
+ else q.set("player_email", email);
647
+ let raw;
648
+ try {
649
+ raw = await this.http.get(
650
+ `/api/wallet/identities?${q.toString()}`,
651
+ this.auth,
652
+ { signal: opts?.signal }
653
+ );
654
+ } catch (e) {
655
+ if (e instanceof InvoError && e.status === 404) {
656
+ const raw404 = e.body && typeof e.body === "object" ? e.body : {};
657
+ return { walletUserId: null, primaryEmail: "", primaryPhone: "", isMinor: false, emails: [], notFound: true, raw: raw404 };
658
+ }
659
+ throw e;
660
+ }
661
+ const emails = Array.isArray(raw["emails"]) ? raw["emails"].map(toLinkedEmail) : [];
662
+ return {
663
+ walletUserId: raw["wallet_user_id"] ?? null,
664
+ primaryEmail: String(raw["primary_email"] ?? ""),
665
+ primaryPhone: String(raw["primary_phone"] ?? ""),
666
+ isMinor: raw["is_minor"] === true,
667
+ emails,
668
+ notFound: false,
669
+ raw
670
+ };
671
+ }
623
672
  toInitiateResult(raw) {
624
673
  const vm = raw["verification_method"];
625
674
  const guardian = raw["guardian_approval"];
@@ -357,6 +357,50 @@ interface PendingCollectResult {
357
357
  pending: PendingCollectItem[];
358
358
  raw: Record<string, unknown>;
359
359
  }
360
+ interface BalanceRow {
361
+ currencyId: string | number;
362
+ currencyName: string;
363
+ /** Short symbol (may be null). */
364
+ currencySymbol: string | null;
365
+ /** Icon URL (may be null). */
366
+ currencySymbolUrl: string | null;
367
+ /** Decimal strings (parseFloat as needed). */
368
+ availableBalance: string;
369
+ reservedBalance: string;
370
+ totalBalance: string;
371
+ raw: Record<string, unknown>;
372
+ }
373
+ interface BalanceResult {
374
+ gameId: string | number;
375
+ gameName: string;
376
+ /** Empty for a player who hasn't transacted on this game yet (a 200, not an error). */
377
+ balances: BalanceRow[];
378
+ /** Sum of total_balance across rows (decimal string). */
379
+ totalValue: string;
380
+ currencyCount: number;
381
+ hasFunds: boolean;
382
+ raw: Record<string, unknown>;
383
+ }
384
+ interface LinkedIdentitiesQuery {
385
+ /** Provide one; phone wins if both are given. */
386
+ playerEmail?: string;
387
+ playerPhone?: string;
388
+ }
389
+ interface LinkedIdentityEmail {
390
+ email: string;
391
+ primary: boolean;
392
+ verifiedAt: string | null;
393
+ }
394
+ interface LinkedIdentitiesResult {
395
+ walletUserId: string | null;
396
+ primaryEmail: string;
397
+ primaryPhone: string;
398
+ isMinor: boolean;
399
+ emails: LinkedIdentityEmail[];
400
+ /** true when there was no in-game match (backend 404) — treat as "no linked identities". */
401
+ notFound: boolean;
402
+ raw: Record<string, unknown>;
403
+ }
360
404
  interface DestinationsQuery {
361
405
  /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
362
406
  direction?: "transfer" | "send";
@@ -416,4 +460,4 @@ interface LinkDeviceResult {
416
460
  raw: Record<string, unknown>;
417
461
  }
418
462
 
419
- export { type ApproveResult as A, type InboundPendingQuery as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type InboundPendingResult as F, type CurrencyBalance as G, type InboundPendingItem as H, InvoError as I, type LinkDeviceResult as L, type OrderDetailsResult as O, type PendingCollectResult as P, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type DestinationGame as e, type InvoErrorInfo as f, type InvoHooks as g, type InvoRequestInfo as h, type InvoResponseInfo as i, type PendingCollectItem as j, type PlayerToken as k, type InitiateSendInput as l, type InitiateResult as m, type InitiateTransferInput as n, type CreateCheckoutInput as o, type CreateCheckoutResult as p, type PurchaseInput as q, type PurchaseResult as r, type ConfirmPaymentResult as s, type PurchaseItemInput as t, type PurchaseItemResult as u, type ItemHistoryQuery as v, type ItemHistoryResult as w, type ItemOrderQuery as x, type PlayerBalanceQuery as y, type PlayerBalanceResult as z };
463
+ export { type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type CurrencyBalance as M, type InboundPendingItem as N, type OrderDetailsResult as O, type PendingCollectResult as P, type LinkedIdentityEmail as Q, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
@@ -357,6 +357,50 @@ interface PendingCollectResult {
357
357
  pending: PendingCollectItem[];
358
358
  raw: Record<string, unknown>;
359
359
  }
360
+ interface BalanceRow {
361
+ currencyId: string | number;
362
+ currencyName: string;
363
+ /** Short symbol (may be null). */
364
+ currencySymbol: string | null;
365
+ /** Icon URL (may be null). */
366
+ currencySymbolUrl: string | null;
367
+ /** Decimal strings (parseFloat as needed). */
368
+ availableBalance: string;
369
+ reservedBalance: string;
370
+ totalBalance: string;
371
+ raw: Record<string, unknown>;
372
+ }
373
+ interface BalanceResult {
374
+ gameId: string | number;
375
+ gameName: string;
376
+ /** Empty for a player who hasn't transacted on this game yet (a 200, not an error). */
377
+ balances: BalanceRow[];
378
+ /** Sum of total_balance across rows (decimal string). */
379
+ totalValue: string;
380
+ currencyCount: number;
381
+ hasFunds: boolean;
382
+ raw: Record<string, unknown>;
383
+ }
384
+ interface LinkedIdentitiesQuery {
385
+ /** Provide one; phone wins if both are given. */
386
+ playerEmail?: string;
387
+ playerPhone?: string;
388
+ }
389
+ interface LinkedIdentityEmail {
390
+ email: string;
391
+ primary: boolean;
392
+ verifiedAt: string | null;
393
+ }
394
+ interface LinkedIdentitiesResult {
395
+ walletUserId: string | null;
396
+ primaryEmail: string;
397
+ primaryPhone: string;
398
+ isMinor: boolean;
399
+ emails: LinkedIdentityEmail[];
400
+ /** true when there was no in-game match (backend 404) — treat as "no linked identities". */
401
+ notFound: boolean;
402
+ raw: Record<string, unknown>;
403
+ }
360
404
  interface DestinationsQuery {
361
405
  /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
362
406
  direction?: "transfer" | "send";
@@ -416,4 +460,4 @@ interface LinkDeviceResult {
416
460
  raw: Record<string, unknown>;
417
461
  }
418
462
 
419
- export { type ApproveResult as A, type InboundPendingQuery as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type InboundPendingResult as F, type CurrencyBalance as G, type InboundPendingItem as H, InvoError as I, type LinkDeviceResult as L, type OrderDetailsResult as O, type PendingCollectResult as P, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type DestinationGame as e, type InvoErrorInfo as f, type InvoHooks as g, type InvoRequestInfo as h, type InvoResponseInfo as i, type PendingCollectItem as j, type PlayerToken as k, type InitiateSendInput as l, type InitiateResult as m, type InitiateTransferInput as n, type CreateCheckoutInput as o, type CreateCheckoutResult as p, type PurchaseInput as q, type PurchaseResult as r, type ConfirmPaymentResult as s, type PurchaseItemInput as t, type PurchaseItemResult as u, type ItemHistoryQuery as v, type ItemHistoryResult as w, type ItemOrderQuery as x, type PlayerBalanceQuery as y, type PlayerBalanceResult as z };
463
+ export { type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type CurrencyBalance as M, type InboundPendingItem as N, type OrderDetailsResult as O, type PendingCollectResult as P, type LinkedIdentityEmail as Q, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invonetwork/web-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "INVO Web SDK — currency purchase + passkey (WebAuthn) verification for partner web platforms.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,