@invonetwork/web-sdk 0.3.0 → 0.4.1
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 +122 -96
- package/README.md +42 -20
- package/dist/{chunk-DV3WZGMH.js → chunk-EEWOAUXO.js} +28 -10
- package/dist/index.cjs +74 -36
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +50 -30
- package/dist/server.cjs +210 -39
- package/dist/server.d.cts +78 -19
- package/dist/server.d.ts +78 -19
- package/dist/server.js +185 -34
- package/dist/{types-CBkoUymV.d.cts → types-CBMLNwbe.d.cts} +32 -1
- package/dist/{types-CBkoUymV.d.ts → types-CBMLNwbe.d.ts} +32 -1
- package/package.json +76 -68
package/dist/index.cjs
CHANGED
|
@@ -98,11 +98,11 @@ var _Http = class _Http {
|
|
|
98
98
|
* (e.g. single-use WebAuthn assertions) are NEVER auto-retried.
|
|
99
99
|
*/
|
|
100
100
|
async post(path, body, auth, opts) {
|
|
101
|
-
return this.request("POST", path, body, auth, opts?.idempotent ?? false);
|
|
101
|
+
return this.request("POST", path, body, auth, opts?.idempotent ?? false, opts?.signal);
|
|
102
102
|
}
|
|
103
103
|
// GET is always idempotent → safe to retry.
|
|
104
|
-
async get(path, auth) {
|
|
105
|
-
return this.request("GET", path, void 0, auth, true);
|
|
104
|
+
async get(path, auth, opts) {
|
|
105
|
+
return this.request("GET", path, void 0, auth, true, opts?.signal);
|
|
106
106
|
}
|
|
107
107
|
authHeaders(auth) {
|
|
108
108
|
switch (auth.kind) {
|
|
@@ -114,7 +114,7 @@ var _Http = class _Http {
|
|
|
114
114
|
return {};
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
async request(method, path, body, auth, idempotent) {
|
|
117
|
+
async request(method, path, body, auth, idempotent, signal) {
|
|
118
118
|
const url = `${this.baseUrl}${path}`;
|
|
119
119
|
const headers = {
|
|
120
120
|
Accept: "application/json",
|
|
@@ -124,10 +124,13 @@ var _Http = class _Http {
|
|
|
124
124
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
125
125
|
const payload = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
126
126
|
for (let attempt = 0; ; attempt++) {
|
|
127
|
+
if (signal?.aborted) throw abortError(path);
|
|
127
128
|
const start = Date.now();
|
|
128
129
|
this.fire("onRequest", { method, url, attempt });
|
|
129
130
|
const controller = new AbortController();
|
|
130
131
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
132
|
+
const onAbort = () => controller.abort();
|
|
133
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
131
134
|
let res;
|
|
132
135
|
let networkError;
|
|
133
136
|
try {
|
|
@@ -140,12 +143,14 @@ var _Http = class _Http {
|
|
|
140
143
|
});
|
|
141
144
|
} finally {
|
|
142
145
|
clearTimeout(timer);
|
|
146
|
+
signal?.removeEventListener("abort", onAbort);
|
|
143
147
|
}
|
|
144
148
|
if (networkError) {
|
|
149
|
+
if (signal?.aborted) throw abortError(path);
|
|
145
150
|
const willRetry = idempotent && attempt < this.maxRetries;
|
|
146
151
|
this.fire("onError", { method, url, attempt, error: networkError, willRetry });
|
|
147
152
|
if (willRetry) {
|
|
148
|
-
await sleep(this.backoff(attempt));
|
|
153
|
+
await sleep(this.backoff(attempt), signal);
|
|
149
154
|
continue;
|
|
150
155
|
}
|
|
151
156
|
throw networkError;
|
|
@@ -181,7 +186,7 @@ var _Http = class _Http {
|
|
|
181
186
|
}
|
|
182
187
|
this.fire("onError", { method, url, attempt, error: err, willRetry: wait !== void 0 });
|
|
183
188
|
if (wait !== void 0) {
|
|
184
|
-
await sleep(wait);
|
|
189
|
+
await sleep(wait, signal);
|
|
185
190
|
continue;
|
|
186
191
|
}
|
|
187
192
|
throw err;
|
|
@@ -207,8 +212,21 @@ var _Http = class _Http {
|
|
|
207
212
|
/** HTTP statuses worth retrying — rate limit + transient gateway/server errors. */
|
|
208
213
|
_Http.RETRIABLE_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
209
214
|
var Http = _Http;
|
|
210
|
-
function sleep(ms) {
|
|
211
|
-
return new Promise((resolve) =>
|
|
215
|
+
function sleep(ms, signal) {
|
|
216
|
+
return new Promise((resolve) => {
|
|
217
|
+
if (signal?.aborted) return resolve();
|
|
218
|
+
const timer = setTimeout(done, ms);
|
|
219
|
+
const onAbort = () => done();
|
|
220
|
+
function done() {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
signal?.removeEventListener("abort", onAbort);
|
|
223
|
+
resolve();
|
|
224
|
+
}
|
|
225
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function abortError(path) {
|
|
229
|
+
return new InvoError({ message: `Request to ${path} was aborted`, code: "ABORTED", status: 0 });
|
|
212
230
|
}
|
|
213
231
|
function pickRequestId(headers) {
|
|
214
232
|
if (!headers || typeof headers.get !== "function") return void 0;
|
|
@@ -333,38 +351,43 @@ var InvoClient = class {
|
|
|
333
351
|
}
|
|
334
352
|
}
|
|
335
353
|
/** Enroll a passkey for the token's identity (register/begin -> create() -> register/complete). */
|
|
336
|
-
async enrollPasskey() {
|
|
354
|
+
async enrollPasskey(opts) {
|
|
337
355
|
this.assertWebAuthn();
|
|
356
|
+
const signal = opts?.signal;
|
|
338
357
|
return this.withTokenRetry(async () => {
|
|
339
358
|
const options = await this.post(
|
|
340
|
-
"/api/sdk/webauthn/register/begin"
|
|
359
|
+
"/api/sdk/webauthn/register/begin",
|
|
360
|
+
void 0,
|
|
361
|
+
signal
|
|
341
362
|
);
|
|
342
363
|
const cred = await navigator.credentials.create({
|
|
343
|
-
publicKey: toCreationOptions(options)
|
|
364
|
+
publicKey: toCreationOptions(options),
|
|
365
|
+
signal
|
|
344
366
|
});
|
|
345
367
|
if (!cred) throw new Error("Passkey creation was cancelled or returned no credential.");
|
|
346
368
|
const raw = await this.post(
|
|
347
369
|
"/api/sdk/webauthn/register/complete",
|
|
348
|
-
{ credential: registrationToJSON(cred) }
|
|
370
|
+
{ credential: registrationToJSON(cred) },
|
|
371
|
+
signal
|
|
349
372
|
);
|
|
350
373
|
return { status: String(raw["status"] ?? ""), device: raw["device"] ?? null, raw };
|
|
351
374
|
});
|
|
352
375
|
}
|
|
353
376
|
/** Approve a SEND with the player's passkey. */
|
|
354
|
-
async approveSend(transactionId) {
|
|
355
|
-
return this.approve("send", transactionId);
|
|
377
|
+
async approveSend(transactionId, opts) {
|
|
378
|
+
return this.approve("send", transactionId, opts);
|
|
356
379
|
}
|
|
357
380
|
/** Approve a TRANSFER with the player's passkey (returns the sender's claim code). */
|
|
358
|
-
async approveTransfer(transactionId) {
|
|
359
|
-
return this.approve("transfers", transactionId);
|
|
381
|
+
async approveTransfer(transactionId, opts) {
|
|
382
|
+
return this.approve("transfers", transactionId, opts);
|
|
360
383
|
}
|
|
361
384
|
/** Recipient self-claims a SEND with their passkey. */
|
|
362
|
-
async confirmReceiptSend(transactionId) {
|
|
363
|
-
return this.confirmReceipt("send", transactionId);
|
|
385
|
+
async confirmReceiptSend(transactionId, opts) {
|
|
386
|
+
return this.confirmReceipt("send", transactionId, opts);
|
|
364
387
|
}
|
|
365
388
|
/** Recipient self-claims a TRANSFER with their passkey. */
|
|
366
|
-
async confirmReceiptTransfer(transactionId) {
|
|
367
|
-
return this.confirmReceipt("transfers", transactionId);
|
|
389
|
+
async confirmReceiptTransfer(transactionId, opts) {
|
|
390
|
+
return this.confirmReceipt("transfers", transactionId, opts);
|
|
368
391
|
}
|
|
369
392
|
/**
|
|
370
393
|
* Interchangeable methods (§4.6): prove an *already-enrolled* method (e.g. the
|
|
@@ -374,15 +397,19 @@ var InvoClient = class {
|
|
|
374
397
|
*
|
|
375
398
|
* begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
|
|
376
399
|
*/
|
|
377
|
-
async linkDevice(linkId) {
|
|
400
|
+
async linkDevice(linkId, opts) {
|
|
378
401
|
if (!linkId) throw new Error("linkDevice requires a `linkId`.");
|
|
402
|
+
const signal = opts?.signal;
|
|
379
403
|
return this.withTokenRetry(async () => {
|
|
380
|
-
const assertion = await this.runAssertion(
|
|
381
|
-
|
|
382
|
-
|
|
404
|
+
const assertion = await this.runAssertion(
|
|
405
|
+
"/api/sdk/device/link/webauthn/begin",
|
|
406
|
+
{ link_id: linkId },
|
|
407
|
+
signal
|
|
408
|
+
);
|
|
383
409
|
const raw = await this.post(
|
|
384
410
|
"/api/sdk/device/link/webauthn/complete",
|
|
385
|
-
{ link_id: linkId, webauthn_assertion: assertion }
|
|
411
|
+
{ link_id: linkId, webauthn_assertion: assertion },
|
|
412
|
+
signal
|
|
386
413
|
);
|
|
387
414
|
return { status: String(raw["status"] ?? ""), raw };
|
|
388
415
|
});
|
|
@@ -390,8 +417,8 @@ var InvoClient = class {
|
|
|
390
417
|
// --- internals ---
|
|
391
418
|
/** POST with the current player token. Token-expiry retry is handled one level
|
|
392
419
|
* up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
|
|
393
|
-
async post(path, body) {
|
|
394
|
-
return this.http.post(path, body, this.auth);
|
|
420
|
+
async post(path, body, signal) {
|
|
421
|
+
return this.http.post(path, body, this.auth, { signal });
|
|
395
422
|
}
|
|
396
423
|
/**
|
|
397
424
|
* Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
|
|
@@ -422,22 +449,29 @@ var InvoClient = class {
|
|
|
422
449
|
this.auth = { kind: "bearer", token: fresh };
|
|
423
450
|
return true;
|
|
424
451
|
}
|
|
425
|
-
async runAssertion(beginPath, beginBody) {
|
|
452
|
+
async runAssertion(beginPath, beginBody, signal) {
|
|
426
453
|
this.assertWebAuthn();
|
|
427
|
-
const options = await this.post(beginPath, beginBody);
|
|
454
|
+
const options = await this.post(beginPath, beginBody, signal);
|
|
428
455
|
const cred = await navigator.credentials.get({
|
|
429
|
-
publicKey: toRequestOptions(options)
|
|
456
|
+
publicKey: toRequestOptions(options),
|
|
457
|
+
signal
|
|
430
458
|
});
|
|
431
459
|
if (!cred) throw new Error("Passkey assertion was cancelled or returned no credential.");
|
|
432
460
|
return assertionToJSON(cred);
|
|
433
461
|
}
|
|
434
|
-
async approve(flow, transactionId) {
|
|
462
|
+
async approve(flow, transactionId, opts) {
|
|
435
463
|
const id = encodeURIComponent(transactionId);
|
|
464
|
+
const signal = opts?.signal;
|
|
436
465
|
return this.withTokenRetry(async () => {
|
|
437
|
-
const assertion = await this.runAssertion(
|
|
466
|
+
const assertion = await this.runAssertion(
|
|
467
|
+
`/api/sdk/${flow}/${id}/approve/webauthn/begin`,
|
|
468
|
+
void 0,
|
|
469
|
+
signal
|
|
470
|
+
);
|
|
438
471
|
const raw = await this.post(
|
|
439
472
|
`/api/sdk/${flow}/${id}/approve`,
|
|
440
|
-
{ webauthn_assertion: assertion }
|
|
473
|
+
{ webauthn_assertion: assertion },
|
|
474
|
+
signal
|
|
441
475
|
);
|
|
442
476
|
return {
|
|
443
477
|
status: String(raw["status"] ?? ""),
|
|
@@ -449,15 +483,19 @@ var InvoClient = class {
|
|
|
449
483
|
};
|
|
450
484
|
});
|
|
451
485
|
}
|
|
452
|
-
async confirmReceipt(flow, transactionId) {
|
|
486
|
+
async confirmReceipt(flow, transactionId, opts) {
|
|
453
487
|
const id = encodeURIComponent(transactionId);
|
|
488
|
+
const signal = opts?.signal;
|
|
454
489
|
return this.withTokenRetry(async () => {
|
|
455
490
|
const assertion = await this.runAssertion(
|
|
456
|
-
`/api/sdk/${flow}/${id}/confirm-receipt/webauthn/begin
|
|
491
|
+
`/api/sdk/${flow}/${id}/confirm-receipt/webauthn/begin`,
|
|
492
|
+
void 0,
|
|
493
|
+
signal
|
|
457
494
|
);
|
|
458
495
|
const raw = await this.post(
|
|
459
496
|
`/api/sdk/${flow}/${id}/confirm-receipt`,
|
|
460
|
-
{ webauthn_assertion: assertion }
|
|
497
|
+
{ webauthn_assertion: assertion },
|
|
498
|
+
signal
|
|
461
499
|
);
|
|
462
500
|
return { status: String(raw["status"] ?? ""), raw };
|
|
463
501
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ClientConfig, A as ApproveResult,
|
|
2
|
-
export { I as InvoError,
|
|
1
|
+
import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult } from './types-CBMLNwbe.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-CBMLNwbe.cjs';
|
|
3
3
|
|
|
4
4
|
declare class InvoClient {
|
|
5
5
|
private readonly http;
|
|
@@ -10,19 +10,19 @@ declare class InvoClient {
|
|
|
10
10
|
constructor(config: ClientConfig);
|
|
11
11
|
private assertWebAuthn;
|
|
12
12
|
/** Enroll a passkey for the token's identity (register/begin -> create() -> register/complete). */
|
|
13
|
-
enrollPasskey(): Promise<{
|
|
13
|
+
enrollPasskey(opts?: CallOptions): Promise<{
|
|
14
14
|
status: string;
|
|
15
15
|
device: unknown;
|
|
16
16
|
raw: Record<string, unknown>;
|
|
17
17
|
}>;
|
|
18
18
|
/** Approve a SEND with the player's passkey. */
|
|
19
|
-
approveSend(transactionId: string): Promise<ApproveResult>;
|
|
19
|
+
approveSend(transactionId: string, opts?: CallOptions): Promise<ApproveResult>;
|
|
20
20
|
/** Approve a TRANSFER with the player's passkey (returns the sender's claim code). */
|
|
21
|
-
approveTransfer(transactionId: string): Promise<ApproveResult>;
|
|
21
|
+
approveTransfer(transactionId: string, opts?: CallOptions): Promise<ApproveResult>;
|
|
22
22
|
/** Recipient self-claims a SEND with their passkey. */
|
|
23
|
-
confirmReceiptSend(transactionId: string): Promise<ConfirmReceiptResult>;
|
|
23
|
+
confirmReceiptSend(transactionId: string, opts?: CallOptions): Promise<ConfirmReceiptResult>;
|
|
24
24
|
/** Recipient self-claims a TRANSFER with their passkey. */
|
|
25
|
-
confirmReceiptTransfer(transactionId: string): Promise<ConfirmReceiptResult>;
|
|
25
|
+
confirmReceiptTransfer(transactionId: string, opts?: CallOptions): Promise<ConfirmReceiptResult>;
|
|
26
26
|
/**
|
|
27
27
|
* Interchangeable methods (§4.6): prove an *already-enrolled* method (e.g. the
|
|
28
28
|
* INVO app device key) to authorize adding a new partner passkey. The returned
|
|
@@ -31,7 +31,7 @@ declare class InvoClient {
|
|
|
31
31
|
*
|
|
32
32
|
* begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
|
|
33
33
|
*/
|
|
34
|
-
linkDevice(linkId: string): Promise<LinkDeviceResult>;
|
|
34
|
+
linkDevice(linkId: string, opts?: CallOptions): Promise<LinkDeviceResult>;
|
|
35
35
|
/** POST with the current player token. Token-expiry retry is handled one level
|
|
36
36
|
* up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
|
|
37
37
|
private post;
|
|
@@ -49,4 +49,4 @@ declare class InvoClient {
|
|
|
49
49
|
private confirmReceipt;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export { ApproveResult, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
|
|
52
|
+
export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ClientConfig, A as ApproveResult,
|
|
2
|
-
export { I as InvoError,
|
|
1
|
+
import { C as ClientConfig, a as CallOptions, A as ApproveResult, b as ConfirmReceiptResult, L as LinkDeviceResult } from './types-CBMLNwbe.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-CBMLNwbe.js';
|
|
3
3
|
|
|
4
4
|
declare class InvoClient {
|
|
5
5
|
private readonly http;
|
|
@@ -10,19 +10,19 @@ declare class InvoClient {
|
|
|
10
10
|
constructor(config: ClientConfig);
|
|
11
11
|
private assertWebAuthn;
|
|
12
12
|
/** Enroll a passkey for the token's identity (register/begin -> create() -> register/complete). */
|
|
13
|
-
enrollPasskey(): Promise<{
|
|
13
|
+
enrollPasskey(opts?: CallOptions): Promise<{
|
|
14
14
|
status: string;
|
|
15
15
|
device: unknown;
|
|
16
16
|
raw: Record<string, unknown>;
|
|
17
17
|
}>;
|
|
18
18
|
/** Approve a SEND with the player's passkey. */
|
|
19
|
-
approveSend(transactionId: string): Promise<ApproveResult>;
|
|
19
|
+
approveSend(transactionId: string, opts?: CallOptions): Promise<ApproveResult>;
|
|
20
20
|
/** Approve a TRANSFER with the player's passkey (returns the sender's claim code). */
|
|
21
|
-
approveTransfer(transactionId: string): Promise<ApproveResult>;
|
|
21
|
+
approveTransfer(transactionId: string, opts?: CallOptions): Promise<ApproveResult>;
|
|
22
22
|
/** Recipient self-claims a SEND with their passkey. */
|
|
23
|
-
confirmReceiptSend(transactionId: string): Promise<ConfirmReceiptResult>;
|
|
23
|
+
confirmReceiptSend(transactionId: string, opts?: CallOptions): Promise<ConfirmReceiptResult>;
|
|
24
24
|
/** Recipient self-claims a TRANSFER with their passkey. */
|
|
25
|
-
confirmReceiptTransfer(transactionId: string): Promise<ConfirmReceiptResult>;
|
|
25
|
+
confirmReceiptTransfer(transactionId: string, opts?: CallOptions): Promise<ConfirmReceiptResult>;
|
|
26
26
|
/**
|
|
27
27
|
* Interchangeable methods (§4.6): prove an *already-enrolled* method (e.g. the
|
|
28
28
|
* INVO app device key) to authorize adding a new partner passkey. The returned
|
|
@@ -31,7 +31,7 @@ declare class InvoClient {
|
|
|
31
31
|
*
|
|
32
32
|
* begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
|
|
33
33
|
*/
|
|
34
|
-
linkDevice(linkId: string): Promise<LinkDeviceResult>;
|
|
34
|
+
linkDevice(linkId: string, opts?: CallOptions): Promise<LinkDeviceResult>;
|
|
35
35
|
/** POST with the current player token. Token-expiry retry is handled one level
|
|
36
36
|
* up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
|
|
37
37
|
private post;
|
|
@@ -49,4 +49,4 @@ declare class InvoClient {
|
|
|
49
49
|
private confirmReceipt;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export { ApproveResult, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
|
|
52
|
+
export { ApproveResult, CallOptions, ClientConfig, ConfirmReceiptResult, InvoClient, LinkDeviceResult };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertSecureBaseUrl, Http, InvoError } from './chunk-
|
|
2
|
-
export { InvoError } from './chunk-
|
|
1
|
+
import { assertSecureBaseUrl, Http, InvoError } from './chunk-EEWOAUXO.js';
|
|
2
|
+
export { InvoError } from './chunk-EEWOAUXO.js';
|
|
3
3
|
|
|
4
4
|
// src/shared/webauthn.ts
|
|
5
5
|
function b64urlToBuffer(value) {
|
|
@@ -106,38 +106,43 @@ var InvoClient = class {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
/** Enroll a passkey for the token's identity (register/begin -> create() -> register/complete). */
|
|
109
|
-
async enrollPasskey() {
|
|
109
|
+
async enrollPasskey(opts) {
|
|
110
110
|
this.assertWebAuthn();
|
|
111
|
+
const signal = opts?.signal;
|
|
111
112
|
return this.withTokenRetry(async () => {
|
|
112
113
|
const options = await this.post(
|
|
113
|
-
"/api/sdk/webauthn/register/begin"
|
|
114
|
+
"/api/sdk/webauthn/register/begin",
|
|
115
|
+
void 0,
|
|
116
|
+
signal
|
|
114
117
|
);
|
|
115
118
|
const cred = await navigator.credentials.create({
|
|
116
|
-
publicKey: toCreationOptions(options)
|
|
119
|
+
publicKey: toCreationOptions(options),
|
|
120
|
+
signal
|
|
117
121
|
});
|
|
118
122
|
if (!cred) throw new Error("Passkey creation was cancelled or returned no credential.");
|
|
119
123
|
const raw = await this.post(
|
|
120
124
|
"/api/sdk/webauthn/register/complete",
|
|
121
|
-
{ credential: registrationToJSON(cred) }
|
|
125
|
+
{ credential: registrationToJSON(cred) },
|
|
126
|
+
signal
|
|
122
127
|
);
|
|
123
128
|
return { status: String(raw["status"] ?? ""), device: raw["device"] ?? null, raw };
|
|
124
129
|
});
|
|
125
130
|
}
|
|
126
131
|
/** Approve a SEND with the player's passkey. */
|
|
127
|
-
async approveSend(transactionId) {
|
|
128
|
-
return this.approve("send", transactionId);
|
|
132
|
+
async approveSend(transactionId, opts) {
|
|
133
|
+
return this.approve("send", transactionId, opts);
|
|
129
134
|
}
|
|
130
135
|
/** Approve a TRANSFER with the player's passkey (returns the sender's claim code). */
|
|
131
|
-
async approveTransfer(transactionId) {
|
|
132
|
-
return this.approve("transfers", transactionId);
|
|
136
|
+
async approveTransfer(transactionId, opts) {
|
|
137
|
+
return this.approve("transfers", transactionId, opts);
|
|
133
138
|
}
|
|
134
139
|
/** Recipient self-claims a SEND with their passkey. */
|
|
135
|
-
async confirmReceiptSend(transactionId) {
|
|
136
|
-
return this.confirmReceipt("send", transactionId);
|
|
140
|
+
async confirmReceiptSend(transactionId, opts) {
|
|
141
|
+
return this.confirmReceipt("send", transactionId, opts);
|
|
137
142
|
}
|
|
138
143
|
/** Recipient self-claims a TRANSFER with their passkey. */
|
|
139
|
-
async confirmReceiptTransfer(transactionId) {
|
|
140
|
-
return this.confirmReceipt("transfers", transactionId);
|
|
144
|
+
async confirmReceiptTransfer(transactionId, opts) {
|
|
145
|
+
return this.confirmReceipt("transfers", transactionId, opts);
|
|
141
146
|
}
|
|
142
147
|
/**
|
|
143
148
|
* Interchangeable methods (§4.6): prove an *already-enrolled* method (e.g. the
|
|
@@ -147,15 +152,19 @@ var InvoClient = class {
|
|
|
147
152
|
*
|
|
148
153
|
* begin -> navigator.credentials.get() -> complete with { link_id, webauthn_assertion }.
|
|
149
154
|
*/
|
|
150
|
-
async linkDevice(linkId) {
|
|
155
|
+
async linkDevice(linkId, opts) {
|
|
151
156
|
if (!linkId) throw new Error("linkDevice requires a `linkId`.");
|
|
157
|
+
const signal = opts?.signal;
|
|
152
158
|
return this.withTokenRetry(async () => {
|
|
153
|
-
const assertion = await this.runAssertion(
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
const assertion = await this.runAssertion(
|
|
160
|
+
"/api/sdk/device/link/webauthn/begin",
|
|
161
|
+
{ link_id: linkId },
|
|
162
|
+
signal
|
|
163
|
+
);
|
|
156
164
|
const raw = await this.post(
|
|
157
165
|
"/api/sdk/device/link/webauthn/complete",
|
|
158
|
-
{ link_id: linkId, webauthn_assertion: assertion }
|
|
166
|
+
{ link_id: linkId, webauthn_assertion: assertion },
|
|
167
|
+
signal
|
|
159
168
|
);
|
|
160
169
|
return { status: String(raw["status"] ?? ""), raw };
|
|
161
170
|
});
|
|
@@ -163,8 +172,8 @@ var InvoClient = class {
|
|
|
163
172
|
// --- internals ---
|
|
164
173
|
/** POST with the current player token. Token-expiry retry is handled one level
|
|
165
174
|
* up by withTokenRetry (which re-runs the whole ceremony, not a single call). */
|
|
166
|
-
async post(path, body) {
|
|
167
|
-
return this.http.post(path, body, this.auth);
|
|
175
|
+
async post(path, body, signal) {
|
|
176
|
+
return this.http.post(path, body, this.auth, { signal });
|
|
168
177
|
}
|
|
169
178
|
/**
|
|
170
179
|
* Run a whole flow, retrying it ONCE if any call fails with SDK_TOKEN_EXPIRED
|
|
@@ -195,22 +204,29 @@ var InvoClient = class {
|
|
|
195
204
|
this.auth = { kind: "bearer", token: fresh };
|
|
196
205
|
return true;
|
|
197
206
|
}
|
|
198
|
-
async runAssertion(beginPath, beginBody) {
|
|
207
|
+
async runAssertion(beginPath, beginBody, signal) {
|
|
199
208
|
this.assertWebAuthn();
|
|
200
|
-
const options = await this.post(beginPath, beginBody);
|
|
209
|
+
const options = await this.post(beginPath, beginBody, signal);
|
|
201
210
|
const cred = await navigator.credentials.get({
|
|
202
|
-
publicKey: toRequestOptions(options)
|
|
211
|
+
publicKey: toRequestOptions(options),
|
|
212
|
+
signal
|
|
203
213
|
});
|
|
204
214
|
if (!cred) throw new Error("Passkey assertion was cancelled or returned no credential.");
|
|
205
215
|
return assertionToJSON(cred);
|
|
206
216
|
}
|
|
207
|
-
async approve(flow, transactionId) {
|
|
217
|
+
async approve(flow, transactionId, opts) {
|
|
208
218
|
const id = encodeURIComponent(transactionId);
|
|
219
|
+
const signal = opts?.signal;
|
|
209
220
|
return this.withTokenRetry(async () => {
|
|
210
|
-
const assertion = await this.runAssertion(
|
|
221
|
+
const assertion = await this.runAssertion(
|
|
222
|
+
`/api/sdk/${flow}/${id}/approve/webauthn/begin`,
|
|
223
|
+
void 0,
|
|
224
|
+
signal
|
|
225
|
+
);
|
|
211
226
|
const raw = await this.post(
|
|
212
227
|
`/api/sdk/${flow}/${id}/approve`,
|
|
213
|
-
{ webauthn_assertion: assertion }
|
|
228
|
+
{ webauthn_assertion: assertion },
|
|
229
|
+
signal
|
|
214
230
|
);
|
|
215
231
|
return {
|
|
216
232
|
status: String(raw["status"] ?? ""),
|
|
@@ -222,15 +238,19 @@ var InvoClient = class {
|
|
|
222
238
|
};
|
|
223
239
|
});
|
|
224
240
|
}
|
|
225
|
-
async confirmReceipt(flow, transactionId) {
|
|
241
|
+
async confirmReceipt(flow, transactionId, opts) {
|
|
226
242
|
const id = encodeURIComponent(transactionId);
|
|
243
|
+
const signal = opts?.signal;
|
|
227
244
|
return this.withTokenRetry(async () => {
|
|
228
245
|
const assertion = await this.runAssertion(
|
|
229
|
-
`/api/sdk/${flow}/${id}/confirm-receipt/webauthn/begin
|
|
246
|
+
`/api/sdk/${flow}/${id}/confirm-receipt/webauthn/begin`,
|
|
247
|
+
void 0,
|
|
248
|
+
signal
|
|
230
249
|
);
|
|
231
250
|
const raw = await this.post(
|
|
232
251
|
`/api/sdk/${flow}/${id}/confirm-receipt`,
|
|
233
|
-
{ webauthn_assertion: assertion }
|
|
252
|
+
{ webauthn_assertion: assertion },
|
|
253
|
+
signal
|
|
234
254
|
);
|
|
235
255
|
return { status: String(raw["status"] ?? ""), raw };
|
|
236
256
|
});
|