@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/dist/index.cjs CHANGED
@@ -19,6 +19,14 @@ var InvoError = class _InvoError extends Error {
19
19
  get isTokenExpired() {
20
20
  return this.code === "SDK_TOKEN_EXPIRED";
21
21
  }
22
+ /** True if `enrollPasskey()` needs the OTP-grant flow (`enrollmentBegin`/`enrollmentVerify`). */
23
+ get isEnrollmentAuthorizationRequired() {
24
+ return this.code === "ENROLLMENT_REQUIRES_AUTHORIZATION";
25
+ }
26
+ /** True if enrolling is blocked because another method exists — use `linkDevice`. */
27
+ get isEnrollmentProofRequired() {
28
+ return this.code === "ENROLLMENT_REQUIRES_PROOF";
29
+ }
22
30
  /**
23
31
  * True if an item purchase failed because the player's balance was too low (§4.8 → 400).
24
32
  * The backend carries `required_amount` + `current_balance` on the body for the UI.
@@ -332,6 +340,44 @@ function assertionToJSON(cred) {
332
340
  }
333
341
 
334
342
  // src/index.ts
343
+ function toPendingCollectItem(row) {
344
+ return {
345
+ transferId: String(row["transfer_id"] ?? ""),
346
+ kind: String(row["kind"] ?? ""),
347
+ flow: String(row["flow"] ?? ""),
348
+ amount: row["amount"] ?? null,
349
+ currency: String(row["currency"] ?? ""),
350
+ counterpartyGame: String(row["counterparty_game"] ?? ""),
351
+ expiresAt: row["expires_at"] ?? null,
352
+ stepUpRequired: row["step_up_required"] === true,
353
+ held: row["held"] === true,
354
+ holdReason: row["hold_reason"] ?? null,
355
+ raw: row
356
+ };
357
+ }
358
+ function toDestinationGame(row) {
359
+ const s = (k) => row[k] === void 0 || row[k] === null ? void 0 : String(row[k]);
360
+ return {
361
+ gameId: row["game_id"] ?? "",
362
+ gameName: String(row["game_name"] ?? ""),
363
+ tenantType: s("tenant_type"),
364
+ developerName: s("developer_name"),
365
+ publisherName: s("publisher_name"),
366
+ genre: s("genre"),
367
+ platform: s("platform"),
368
+ gameStatus: s("game_status"),
369
+ gameIcon: s("game_icon"),
370
+ gamePoster: s("game_poster"),
371
+ gameUrl: s("game_url"),
372
+ gameDescription: s("game_description"),
373
+ currencyName: String(row["currency_name"] ?? ""),
374
+ currencySymbol: String(row["currency_symbol"] ?? ""),
375
+ currencySymbolUrl: s("currency_symbol_url"),
376
+ minimumTransfer: String(row["minimum_transfer"] ?? ""),
377
+ maximumTransfer: String(row["maximum_transfer"] ?? ""),
378
+ raw: row
379
+ };
380
+ }
335
381
  var InvoClient = class {
336
382
  constructor(config) {
337
383
  if (!config.token) throw new Error("InvoClient requires a player `token`.");
@@ -424,12 +470,95 @@ var InvoClient = class {
424
470
  return { status: String(raw["status"] ?? ""), raw };
425
471
  });
426
472
  }
473
+ /**
474
+ * List the player's own pending items to collect (browser, player-token). Each row's
475
+ * `kind` tells you which action to call: `"identity_gate"` → `approve*` (you initiated);
476
+ * `"receiving_confirm"` → `confirmReceipt*` (a peer sent to you). PII-free — no claim
477
+ * code or phone (those are only on the server-side `getInboundPending`).
478
+ */
479
+ async getPendingCollect(opts) {
480
+ return this.withTokenRetry(async () => {
481
+ const raw = await this.get(
482
+ "/api/sdk/transfers/pending",
483
+ opts?.signal
484
+ );
485
+ const rows = Array.isArray(raw["pending"]) ? raw["pending"] : [];
486
+ return { pending: rows.map(toPendingCollectItem), raw };
487
+ });
488
+ }
489
+ /**
490
+ * List the games/tenants this player can send/transfer to, with display metadata
491
+ * inline (name, icon, currency, min/max limits) — one call, no per-game lookup.
492
+ * Source game is the token's own game. Player-token (browser).
493
+ */
494
+ async getDestinations(query, opts) {
495
+ const direction = query?.direction ?? "transfer";
496
+ return this.withTokenRetry(async () => {
497
+ const raw = await this.get(
498
+ `/api/sdk/destinations?direction=${encodeURIComponent(direction)}`,
499
+ opts?.signal
500
+ );
501
+ const games = Array.isArray(raw["available_games"]) ? raw["available_games"] : [];
502
+ return {
503
+ status: String(raw["status"] ?? ""),
504
+ sourceGameId: raw["source_game_id"] ?? "",
505
+ sourceGameName: String(raw["source_game_name"] ?? ""),
506
+ sourceGameIcon: raw["source_game_icon"],
507
+ sourceCurrencyName: String(raw["source_currency_name"] ?? ""),
508
+ sourceCurrencyIcon: raw["source_currency_icon"],
509
+ universalTransfers: raw["universal_transfers"] === true,
510
+ transferMode: String(raw["transfer_mode"] ?? ""),
511
+ availableGames: games.map(toDestinationGame),
512
+ totalDestinations: Number(raw["total_destinations"] ?? games.length),
513
+ direction: String(raw["direction"] ?? direction),
514
+ linkedGameIds: Array.isArray(raw["linked_game_ids"]) ? raw["linked_game_ids"] : void 0,
515
+ raw
516
+ };
517
+ });
518
+ }
519
+ /**
520
+ * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
521
+ * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
522
+ * this to send a 6-digit code to the player's phone + email on file, collect the code,
523
+ * call `enrollmentVerify(code)`, then RETRY the SAME `enrollPasskey()` —
524
+ * `register/complete` auto-consumes the server-side grant (30-min TTL). For
525
+ * `ENROLLMENT_REQUIRES_PROOF` (`err.isEnrollmentProofRequired`) use `linkDevice` instead.
526
+ */
527
+ async enrollmentBegin(opts) {
528
+ return this.withTokenRetry(async () => {
529
+ const raw = await this.post(
530
+ "/api/sdk/device/enrollment/begin",
531
+ void 0,
532
+ opts?.signal
533
+ );
534
+ return {
535
+ status: String(raw["status"] ?? ""),
536
+ channels: Array.isArray(raw["channels"]) ? raw["channels"] : [],
537
+ raw
538
+ };
539
+ });
540
+ }
541
+ /** Verify the enrollment OTP (see {@link enrollmentBegin}); on success, retry `enrollPasskey()`. */
542
+ async enrollmentVerify(code, opts) {
543
+ if (!code) throw new Error("enrollmentVerify requires the OTP `code`.");
544
+ return this.withTokenRetry(async () => {
545
+ const raw = await this.post(
546
+ "/api/sdk/device/enrollment/verify",
547
+ { code },
548
+ opts?.signal
549
+ );
550
+ return { status: String(raw["status"] ?? ""), raw };
551
+ });
552
+ }
427
553
  // --- internals ---
428
554
  /** POST with the current player token. Token-expiry retry is handled one level
429
555
  * up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
430
556
  async post(path, body, signal) {
431
557
  return this.http.post(path, body, this.auth, { signal });
432
558
  }
559
+ async get(path, signal) {
560
+ return this.http.get(path, this.auth, { signal });
561
+ }
433
562
  /**
434
563
  * Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
435
564
  * and a `refreshToken` hook is configured. We re-run the entire begin→get→
@@ -483,12 +612,17 @@ var InvoClient = class {
483
612
  { webauthn_assertion: assertion },
484
613
  signal
485
614
  );
615
+ const holdReason = raw["error_code"] ?? raw["code"];
486
616
  return {
487
617
  status: String(raw["status"] ?? ""),
488
618
  next: String(raw["next"] ?? ""),
489
619
  transactionId: String(raw["transaction_id"] ?? transactionId),
490
620
  claimCode: raw["claim_code"],
491
621
  claimCodeExpiresAt: raw["claim_code_expires_at"],
622
+ holdReason: typeof holdReason === "string" ? holdReason : void 0,
623
+ risk: raw["risk"],
624
+ guardianApproval: raw["guardian_approval"] ?? void 0,
625
+ pollEndpoint: raw["poll_endpoint"],
492
626
  raw
493
627
  };
494
628
  });
@@ -507,7 +641,12 @@ var InvoClient = class {
507
641
  { webauthn_assertion: assertion },
508
642
  signal
509
643
  );
510
- return { status: String(raw["status"] ?? ""), raw };
644
+ const holdReason = raw["error_code"] ?? raw["code"];
645
+ return {
646
+ status: String(raw["status"] ?? ""),
647
+ holdReason: typeof holdReason === "string" ? holdReason : void 0,
648
+ raw
649
+ };
511
650
  });
512
651
  }
513
652
  };
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 } from './types-CZdmipNK.cjs';
2
- export { I as InvoError, c as InvoErrorInfo, d as InvoHooks, e as InvoRequestInfo, f as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-CZdmipNK.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, 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';
3
3
 
4
4
  declare class InvoClient {
5
5
  private readonly http;
@@ -32,9 +32,34 @@ declare class InvoClient {
32
32
  * begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
33
33
  */
34
34
  linkDevice(linkId: string, opts?: CallOptions): Promise<LinkDeviceResult>;
35
+ /**
36
+ * List the player's own pending items to collect (browser, player-token). Each row's
37
+ * `kind` tells you which action to call: `"identity_gate"` → `approve*` (you initiated);
38
+ * `"receiving_confirm"` → `confirmReceipt*` (a peer sent to you). PII-free — no claim
39
+ * code or phone (those are only on the server-side `getInboundPending`).
40
+ */
41
+ getPendingCollect(opts?: CallOptions): Promise<PendingCollectResult>;
42
+ /**
43
+ * List the games/tenants this player can send/transfer to, with display metadata
44
+ * inline (name, icon, currency, min/max limits) — one call, no per-game lookup.
45
+ * Source game is the token's own game. Player-token (browser).
46
+ */
47
+ getDestinations(query?: DestinationsQuery, opts?: CallOptions): Promise<DestinationsResult>;
48
+ /**
49
+ * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
50
+ * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
51
+ * this to send a 6-digit code to the player's phone + email on file, collect the code,
52
+ * call `enrollmentVerify(code)`, then RETRY the SAME `enrollPasskey()` —
53
+ * `register/complete` auto-consumes the server-side grant (30-min TTL). For
54
+ * `ENROLLMENT_REQUIRES_PROOF` (`err.isEnrollmentProofRequired`) use `linkDevice` instead.
55
+ */
56
+ enrollmentBegin(opts?: CallOptions): Promise<EnrollmentBeginResult>;
57
+ /** Verify the enrollment OTP (see {@link enrollmentBegin}); on success, retry `enrollPasskey()`. */
58
+ enrollmentVerify(code: string, opts?: CallOptions): Promise<EnrollmentVerifyResult>;
35
59
  /** POST with the current player token. Token-expiry retry is handled one level
36
60
  * up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
37
61
  private post;
62
+ private get;
38
63
  /**
39
64
  * Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
40
65
  * and a `refreshToken` hook is configured. We re-run the entire begin→get→
@@ -49,4 +74,4 @@ declare class InvoClient {
49
74
  private confirmReceipt;
50
75
  }
51
76
 
52
- export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
77
+ export { ApproveResult, 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 } from './types-CZdmipNK.js';
2
- export { I as InvoError, c as InvoErrorInfo, d as InvoHooks, e as InvoRequestInfo, f as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-CZdmipNK.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, 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';
3
3
 
4
4
  declare class InvoClient {
5
5
  private readonly http;
@@ -32,9 +32,34 @@ declare class InvoClient {
32
32
  * begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
33
33
  */
34
34
  linkDevice(linkId: string, opts?: CallOptions): Promise<LinkDeviceResult>;
35
+ /**
36
+ * List the player's own pending items to collect (browser, player-token). Each row's
37
+ * `kind` tells you which action to call: `"identity_gate"` → `approve*` (you initiated);
38
+ * `"receiving_confirm"` → `confirmReceipt*` (a peer sent to you). PII-free — no claim
39
+ * code or phone (those are only on the server-side `getInboundPending`).
40
+ */
41
+ getPendingCollect(opts?: CallOptions): Promise<PendingCollectResult>;
42
+ /**
43
+ * List the games/tenants this player can send/transfer to, with display metadata
44
+ * inline (name, icon, currency, min/max limits) — one call, no per-game lookup.
45
+ * Source game is the token's own game. Player-token (browser).
46
+ */
47
+ getDestinations(query?: DestinationsQuery, opts?: CallOptions): Promise<DestinationsResult>;
48
+ /**
49
+ * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
50
+ * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
51
+ * this to send a 6-digit code to the player's phone + email on file, collect the code,
52
+ * call `enrollmentVerify(code)`, then RETRY the SAME `enrollPasskey()` —
53
+ * `register/complete` auto-consumes the server-side grant (30-min TTL). For
54
+ * `ENROLLMENT_REQUIRES_PROOF` (`err.isEnrollmentProofRequired`) use `linkDevice` instead.
55
+ */
56
+ enrollmentBegin(opts?: CallOptions): Promise<EnrollmentBeginResult>;
57
+ /** Verify the enrollment OTP (see {@link enrollmentBegin}); on success, retry `enrollPasskey()`. */
58
+ enrollmentVerify(code: string, opts?: CallOptions): Promise<EnrollmentVerifyResult>;
35
59
  /** POST with the current player token. Token-expiry retry is handled one level
36
60
  * up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
37
61
  private post;
62
+ private get;
38
63
  /**
39
64
  * Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
40
65
  * and a `refreshToken` hook is configured. We re-run the entire begin→get→
@@ -49,4 +74,4 @@ declare class InvoClient {
49
74
  private confirmReceipt;
50
75
  }
51
76
 
52
- export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
77
+ export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, DestinationsQuery, DestinationsResult, EnrollmentBeginResult, EnrollmentVerifyResult, InvoClient, LinkDeviceResult, PendingCollectResult };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { assertSecureBaseUrl, Http, InvoError } from './chunk-JOVATUDY.js';
2
- export { InvoError } from './chunk-JOVATUDY.js';
1
+ import { assertSecureBaseUrl, Http, InvoError } from './chunk-D3XBTH4C.js';
2
+ export { InvoError } from './chunk-D3XBTH4C.js';
3
3
 
4
4
  // src/shared/webauthn.ts
5
5
  function b64urlToBuffer(value) {
@@ -83,6 +83,44 @@ function assertionToJSON(cred) {
83
83
  }
84
84
 
85
85
  // src/index.ts
86
+ function toPendingCollectItem(row) {
87
+ return {
88
+ transferId: String(row["transfer_id"] ?? ""),
89
+ kind: String(row["kind"] ?? ""),
90
+ flow: String(row["flow"] ?? ""),
91
+ amount: row["amount"] ?? null,
92
+ currency: String(row["currency"] ?? ""),
93
+ counterpartyGame: String(row["counterparty_game"] ?? ""),
94
+ expiresAt: row["expires_at"] ?? null,
95
+ stepUpRequired: row["step_up_required"] === true,
96
+ held: row["held"] === true,
97
+ holdReason: row["hold_reason"] ?? null,
98
+ raw: row
99
+ };
100
+ }
101
+ function toDestinationGame(row) {
102
+ const s = (k) => row[k] === void 0 || row[k] === null ? void 0 : String(row[k]);
103
+ return {
104
+ gameId: row["game_id"] ?? "",
105
+ gameName: String(row["game_name"] ?? ""),
106
+ tenantType: s("tenant_type"),
107
+ developerName: s("developer_name"),
108
+ publisherName: s("publisher_name"),
109
+ genre: s("genre"),
110
+ platform: s("platform"),
111
+ gameStatus: s("game_status"),
112
+ gameIcon: s("game_icon"),
113
+ gamePoster: s("game_poster"),
114
+ gameUrl: s("game_url"),
115
+ gameDescription: s("game_description"),
116
+ currencyName: String(row["currency_name"] ?? ""),
117
+ currencySymbol: String(row["currency_symbol"] ?? ""),
118
+ currencySymbolUrl: s("currency_symbol_url"),
119
+ minimumTransfer: String(row["minimum_transfer"] ?? ""),
120
+ maximumTransfer: String(row["maximum_transfer"] ?? ""),
121
+ raw: row
122
+ };
123
+ }
86
124
  var InvoClient = class {
87
125
  constructor(config) {
88
126
  if (!config.token) throw new Error("InvoClient requires a player `token`.");
@@ -175,12 +213,95 @@ var InvoClient = class {
175
213
  return { status: String(raw["status"] ?? ""), raw };
176
214
  });
177
215
  }
216
+ /**
217
+ * List the player's own pending items to collect (browser, player-token). Each row's
218
+ * `kind` tells you which action to call: `"identity_gate"` → `approve*` (you initiated);
219
+ * `"receiving_confirm"` → `confirmReceipt*` (a peer sent to you). PII-free — no claim
220
+ * code or phone (those are only on the server-side `getInboundPending`).
221
+ */
222
+ async getPendingCollect(opts) {
223
+ return this.withTokenRetry(async () => {
224
+ const raw = await this.get(
225
+ "/api/sdk/transfers/pending",
226
+ opts?.signal
227
+ );
228
+ const rows = Array.isArray(raw["pending"]) ? raw["pending"] : [];
229
+ return { pending: rows.map(toPendingCollectItem), raw };
230
+ });
231
+ }
232
+ /**
233
+ * List the games/tenants this player can send/transfer to, with display metadata
234
+ * inline (name, icon, currency, min/max limits) — one call, no per-game lookup.
235
+ * Source game is the token's own game. Player-token (browser).
236
+ */
237
+ async getDestinations(query, opts) {
238
+ const direction = query?.direction ?? "transfer";
239
+ return this.withTokenRetry(async () => {
240
+ const raw = await this.get(
241
+ `/api/sdk/destinations?direction=${encodeURIComponent(direction)}`,
242
+ opts?.signal
243
+ );
244
+ const games = Array.isArray(raw["available_games"]) ? raw["available_games"] : [];
245
+ return {
246
+ status: String(raw["status"] ?? ""),
247
+ sourceGameId: raw["source_game_id"] ?? "",
248
+ sourceGameName: String(raw["source_game_name"] ?? ""),
249
+ sourceGameIcon: raw["source_game_icon"],
250
+ sourceCurrencyName: String(raw["source_currency_name"] ?? ""),
251
+ sourceCurrencyIcon: raw["source_currency_icon"],
252
+ universalTransfers: raw["universal_transfers"] === true,
253
+ transferMode: String(raw["transfer_mode"] ?? ""),
254
+ availableGames: games.map(toDestinationGame),
255
+ totalDestinations: Number(raw["total_destinations"] ?? games.length),
256
+ direction: String(raw["direction"] ?? direction),
257
+ linkedGameIds: Array.isArray(raw["linked_game_ids"]) ? raw["linked_game_ids"] : void 0,
258
+ raw
259
+ };
260
+ });
261
+ }
262
+ /**
263
+ * First-enrollment OTP grant (§4.2). When `enrollPasskey()` throws
264
+ * `ENROLLMENT_REQUIRES_AUTHORIZATION` (`err.isEnrollmentAuthorizationRequired`), call
265
+ * this to send a 6-digit code to the player's phone + email on file, collect the code,
266
+ * call `enrollmentVerify(code)`, then RETRY the SAME `enrollPasskey()` —
267
+ * `register/complete` auto-consumes the server-side grant (30-min TTL). For
268
+ * `ENROLLMENT_REQUIRES_PROOF` (`err.isEnrollmentProofRequired`) use `linkDevice` instead.
269
+ */
270
+ async enrollmentBegin(opts) {
271
+ return this.withTokenRetry(async () => {
272
+ const raw = await this.post(
273
+ "/api/sdk/device/enrollment/begin",
274
+ void 0,
275
+ opts?.signal
276
+ );
277
+ return {
278
+ status: String(raw["status"] ?? ""),
279
+ channels: Array.isArray(raw["channels"]) ? raw["channels"] : [],
280
+ raw
281
+ };
282
+ });
283
+ }
284
+ /** Verify the enrollment OTP (see {@link enrollmentBegin}); on success, retry `enrollPasskey()`. */
285
+ async enrollmentVerify(code, opts) {
286
+ if (!code) throw new Error("enrollmentVerify requires the OTP `code`.");
287
+ return this.withTokenRetry(async () => {
288
+ const raw = await this.post(
289
+ "/api/sdk/device/enrollment/verify",
290
+ { code },
291
+ opts?.signal
292
+ );
293
+ return { status: String(raw["status"] ?? ""), raw };
294
+ });
295
+ }
178
296
  // --- internals ---
179
297
  /** POST with the current player token. Token-expiry retry is handled one level
180
298
  * up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
181
299
  async post(path, body, signal) {
182
300
  return this.http.post(path, body, this.auth, { signal });
183
301
  }
302
+ async get(path, signal) {
303
+ return this.http.get(path, this.auth, { signal });
304
+ }
184
305
  /**
185
306
  * Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
186
307
  * and a `refreshToken` hook is configured. We re-run the entire begin→get→
@@ -234,12 +355,17 @@ var InvoClient = class {
234
355
  { webauthn_assertion: assertion },
235
356
  signal
236
357
  );
358
+ const holdReason = raw["error_code"] ?? raw["code"];
237
359
  return {
238
360
  status: String(raw["status"] ?? ""),
239
361
  next: String(raw["next"] ?? ""),
240
362
  transactionId: String(raw["transaction_id"] ?? transactionId),
241
363
  claimCode: raw["claim_code"],
242
364
  claimCodeExpiresAt: raw["claim_code_expires_at"],
365
+ holdReason: typeof holdReason === "string" ? holdReason : void 0,
366
+ risk: raw["risk"],
367
+ guardianApproval: raw["guardian_approval"] ?? void 0,
368
+ pollEndpoint: raw["poll_endpoint"],
243
369
  raw
244
370
  };
245
371
  });
@@ -258,7 +384,12 @@ var InvoClient = class {
258
384
  { webauthn_assertion: assertion },
259
385
  signal
260
386
  );
261
- return { status: String(raw["status"] ?? ""), raw };
387
+ const holdReason = raw["error_code"] ?? raw["code"];
388
+ return {
389
+ status: String(raw["status"] ?? ""),
390
+ holdReason: typeof holdReason === "string" ? holdReason : void 0,
391
+ raw
392
+ };
262
393
  });
263
394
  }
264
395
  };
package/dist/server.cjs CHANGED
@@ -21,6 +21,14 @@ var InvoError = class _InvoError extends Error {
21
21
  get isTokenExpired() {
22
22
  return this.code === "SDK_TOKEN_EXPIRED";
23
23
  }
24
+ /** True if `enrollPasskey()` needs the OTP-grant flow (`enrollmentBegin`/`enrollmentVerify`). */
25
+ get isEnrollmentAuthorizationRequired() {
26
+ return this.code === "ENROLLMENT_REQUIRES_AUTHORIZATION";
27
+ }
28
+ /** True if enrolling is blocked because another method exists — use `linkDevice`. */
29
+ get isEnrollmentProofRequired() {
30
+ return this.code === "ENROLLMENT_REQUIRES_PROOF";
31
+ }
24
32
  /**
25
33
  * True if an item purchase failed because the player's balance was too low (§4.8 → 400).
26
34
  * The backend carries `required_amount` + `current_balance` on the body for the UI.
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InvoError, S as ServerConfig, a as CallOptions, P as PlayerToken, g as InitiateSendInput, h as InitiateResult, i as InitiateTransferInput, j as CreateCheckoutInput, k as CreateCheckoutResult, l as PurchaseInput, m as PurchaseResult, n as ConfirmPaymentResult, O as OrderDetailsResult, o as PurchaseItemInput, p as PurchaseItemResult, q as ItemHistoryQuery, r as ItemHistoryResult, s as ItemOrderQuery, t as PlayerBalanceQuery, u as PlayerBalanceResult, v as InboundPendingQuery, w as InboundPendingResult } from './types-CZdmipNK.cjs';
2
- export { x as CurrencyBalance, y as InboundPendingItem, c as InvoErrorInfo, d as InvoHooks, e as InvoRequestInfo, f as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-CZdmipNK.cjs';
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';
3
3
 
4
4
  interface VerifyWebhookOptions {
5
5
  /** Max age of the signed timestamp, in seconds. Default 300 (5 min). */
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InvoError, S as ServerConfig, a as CallOptions, P as PlayerToken, g as InitiateSendInput, h as InitiateResult, i as InitiateTransferInput, j as CreateCheckoutInput, k as CreateCheckoutResult, l as PurchaseInput, m as PurchaseResult, n as ConfirmPaymentResult, O as OrderDetailsResult, o as PurchaseItemInput, p as PurchaseItemResult, q as ItemHistoryQuery, r as ItemHistoryResult, s as ItemOrderQuery, t as PlayerBalanceQuery, u as PlayerBalanceResult, v as InboundPendingQuery, w as InboundPendingResult } from './types-CZdmipNK.js';
2
- export { x as CurrencyBalance, y as InboundPendingItem, c as InvoErrorInfo, d as InvoHooks, e as InvoRequestInfo, f as InvoResponseInfo, R as Rail, V as VerificationMethod } from './types-CZdmipNK.js';
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';
3
3
 
4
4
  interface VerifyWebhookOptions {
5
5
  /** Max age of the signed timestamp, in seconds. Default 300 (5 min). */
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { InvoError, assertSecureBaseUrl, Http } from './chunk-JOVATUDY.js';
2
- export { InvoError } from './chunk-JOVATUDY.js';
1
+ import { InvoError, assertSecureBaseUrl, Http } from './chunk-D3XBTH4C.js';
2
+ export { InvoError } from './chunk-D3XBTH4C.js';
3
3
  import { createHmac } from 'crypto';
4
4
 
5
5
  var DEFAULT_TOLERANCE_SEC = 300;
@@ -26,6 +26,10 @@ declare class InvoError extends Error {
26
26
  get isReceiverNotEnrolled(): boolean;
27
27
  /** True if the session/SDK token has expired and the caller should re-mint + retry. */
28
28
  get isTokenExpired(): boolean;
29
+ /** True if `enrollPasskey()` needs the OTP-grant flow (`enrollmentBegin`/`enrollmentVerify`). */
30
+ get isEnrollmentAuthorizationRequired(): boolean;
31
+ /** True if enrolling is blocked because another method exists — use `linkDevice`. */
32
+ get isEnrollmentProofRequired(): boolean;
29
33
  /**
30
34
  * True if an item purchase failed because the player's balance was too low (§4.8 → 400).
31
35
  * The backend carries `required_amount` + `current_balance` on the body for the UI.
@@ -308,9 +312,101 @@ interface ApproveResult {
308
312
  /** transfer-approve returns the sender's claim code; send-approve does not. */
309
313
  claimCode?: string;
310
314
  claimCodeExpiresAt?: string;
315
+ /**
316
+ * Present when the approve came back as an HTTP 202 **hold** rather than success —
317
+ * e.g. `"RISK_HOLD"`, `"GUARDIAN_APPROVAL_PENDING"`, `"STEP_UP_REQUIRED"` (the last
318
+ * only on non-passkey approvals). Success carries no holdReason. Terminal guardian
319
+ * outcomes (`GUARDIAN_APPROVAL_REJECTED`/`_EXPIRED` 410, `..._CHECK_UNAVAILABLE` 503)
320
+ * are thrown as `InvoError` instead.
321
+ */
322
+ holdReason?: string;
323
+ /** Risk-engine detail on a RISK_HOLD: `{ decision, reasons[] }`. */
324
+ risk?: Record<string, unknown>;
325
+ /** The guardian_approval audit object on GUARDIAN_APPROVAL_PENDING. */
326
+ guardianApproval?: unknown;
327
+ /** Poll endpoint for guardian status (transfer holds only). */
328
+ pollEndpoint?: string;
311
329
  raw: Record<string, unknown>;
312
330
  }
313
331
  interface ConfirmReceiptResult {
332
+ status: string;
333
+ /** Present on a 202 hold — e.g. `"RECIPIENT_IDENTITY_PENDING"`. */
334
+ holdReason?: string;
335
+ raw: Record<string, unknown>;
336
+ }
337
+ interface PendingCollectItem {
338
+ /** The transaction id — note the backend key is `transfer_id`. */
339
+ transferId: string;
340
+ /** "identity_gate" (you initiated → call approve*) | "receiving_confirm" (peer send → call confirmReceipt*). */
341
+ kind: string;
342
+ /** "transfer" | "send". */
343
+ flow: string;
344
+ amount: string | null;
345
+ /** Currency name (not id). */
346
+ currency: string;
347
+ /** Tenant name — destination (identity_gate) or source (receiving_confirm). */
348
+ counterpartyGame: string;
349
+ expiresAt: string | null;
350
+ stepUpRequired: boolean;
351
+ held: boolean;
352
+ /** "guardian_pending" | "device_requires_elevation" | null. */
353
+ holdReason: string | null;
354
+ raw: Record<string, unknown>;
355
+ }
356
+ interface PendingCollectResult {
357
+ pending: PendingCollectItem[];
358
+ raw: Record<string, unknown>;
359
+ }
360
+ interface DestinationsQuery {
361
+ /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
362
+ direction?: "transfer" | "send";
363
+ }
364
+ /** A game/tenant this player can send/transfer to, with display metadata inline. */
365
+ interface DestinationGame {
366
+ gameId: string | number;
367
+ gameName: string;
368
+ tenantType?: string;
369
+ developerName?: string;
370
+ publisherName?: string;
371
+ genre?: string;
372
+ platform?: string;
373
+ gameStatus?: string;
374
+ gameIcon?: string;
375
+ gamePoster?: string;
376
+ gameUrl?: string;
377
+ gameDescription?: string;
378
+ currencyName: string;
379
+ currencySymbol: string;
380
+ currencySymbolUrl?: string;
381
+ /** Limits are decimal strings. */
382
+ minimumTransfer: string;
383
+ maximumTransfer: string;
384
+ raw: Record<string, unknown>;
385
+ }
386
+ interface DestinationsResult {
387
+ status: string;
388
+ sourceGameId: string | number;
389
+ sourceGameName: string;
390
+ sourceGameIcon?: string;
391
+ sourceCurrencyName: string;
392
+ sourceCurrencyIcon?: string;
393
+ universalTransfers: boolean;
394
+ /** "universal" (every live tenant) | "linked" (intersect linkedGameIds). */
395
+ transferMode: string;
396
+ availableGames: DestinationGame[];
397
+ totalDestinations: number;
398
+ direction: string;
399
+ /** Present in "linked" mode. */
400
+ linkedGameIds?: (string | number)[];
401
+ raw: Record<string, unknown>;
402
+ }
403
+ interface EnrollmentBeginResult {
404
+ status: string;
405
+ /** Channels the OTP was sent to, e.g. ["sms","email"]. */
406
+ channels: string[];
407
+ raw: Record<string, unknown>;
408
+ }
409
+ interface EnrollmentVerifyResult {
314
410
  status: string;
315
411
  raw: Record<string, unknown>;
316
412
  }
@@ -320,4 +416,4 @@ interface LinkDeviceResult {
320
416
  raw: Record<string, unknown>;
321
417
  }
322
418
 
323
- export { type ApproveResult as A, type ClientConfig as C, InvoError as I, type LinkDeviceResult as L, type OrderDetailsResult as O, type PlayerToken as P, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type InvoErrorInfo as c, type InvoHooks as d, type InvoRequestInfo as e, type InvoResponseInfo as f, type InitiateSendInput as g, type InitiateResult as h, type InitiateTransferInput as i, type CreateCheckoutInput as j, type CreateCheckoutResult as k, type PurchaseInput as l, type PurchaseResult as m, type ConfirmPaymentResult as n, type PurchaseItemInput as o, type PurchaseItemResult as p, type ItemHistoryQuery as q, type ItemHistoryResult as r, type ItemOrderQuery as s, type PlayerBalanceQuery as t, type PlayerBalanceResult as u, type InboundPendingQuery as v, type InboundPendingResult as w, type CurrencyBalance as x, type InboundPendingItem as y };
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 };