@bounded-sh/client 0.0.103 → 0.0.105
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/README.md +15 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/oidc-auth.d.ts +17 -13
- package/dist/auth/providers/offchain-auth-provider.d.ts +7 -9
- package/dist/{email-auth-provider-Cr3I4Po5.js → email-auth-provider-CB5ZBjJL.js} +2 -2
- package/dist/{email-auth-provider-Cr3I4Po5.js.map → email-auth-provider-CB5ZBjJL.js.map} +1 -1
- package/dist/{email-auth-provider-DCZ93FYg.js → email-auth-provider-Czo-xM6n.js} +2 -2
- package/dist/{email-auth-provider-DCZ93FYg.js.map → email-auth-provider-Czo-xM6n.js.map} +1 -1
- package/dist/{email-auth-provider-Ct3YA68i.mjs → email-auth-provider-D9SQu9c1.mjs} +2 -2
- package/dist/{email-auth-provider-Ct3YA68i.mjs.map → email-auth-provider-D9SQu9c1.mjs.map} +1 -1
- package/dist/{email-auth-provider-BJXeBUwp.mjs → email-auth-provider-sA_9RTRl.mjs} +2 -2
- package/dist/{email-auth-provider-BJXeBUwp.mjs.map → email-auth-provider-sA_9RTRl.mjs.map} +1 -1
- package/dist/global.d.ts +2 -0
- package/dist/{guest-auth-provider-CUo6sgop.mjs → guest-auth-provider-1mrlNF2G.mjs} +2 -2
- package/dist/{guest-auth-provider-CUo6sgop.mjs.map → guest-auth-provider-1mrlNF2G.mjs.map} +1 -1
- package/dist/{guest-auth-provider--Sqlucfr.js → guest-auth-provider-DJ5nCGjq.js} +2 -2
- package/dist/{guest-auth-provider--Sqlucfr.js.map → guest-auth-provider-DJ5nCGjq.js.map} +1 -1
- package/dist/{guest-auth-provider-CPj-w7_R.js → guest-auth-provider-DXNCZ0R6.js} +2 -2
- package/dist/{guest-auth-provider-CPj-w7_R.js.map → guest-auth-provider-DXNCZ0R6.js.map} +1 -1
- package/dist/{guest-auth-provider-DMtDTZeA.mjs → guest-auth-provider-DsFTB844.mjs} +2 -2
- package/dist/{guest-auth-provider-DMtDTZeA.mjs.map → guest-auth-provider-DsFTB844.mjs.map} +1 -1
- package/dist/{index-CaZx7sd8.mjs → index-Bu6vnwxM.mjs} +385 -471
- package/dist/index-Bu6vnwxM.mjs.map +1 -0
- package/dist/{index-Pyb2SLgv.js → index-yb-jFt5B.js} +385 -471
- package/dist/index-yb-jFt5B.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{index.native-3T0IcOed.mjs → index.native-BSOqaKIz.mjs} +321 -447
- package/dist/index.native-BSOqaKIz.mjs.map +1 -0
- package/dist/{index.native-PGrxu7RO.js → index.native-BpcNwWQX.js} +321 -447
- package/dist/index.native-BpcNwWQX.js.map +1 -0
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.js +2 -2
- package/dist/index.native.mjs +1 -1
- package/dist/{injected-evm-wallet-provider-B3Mtkdnu.mjs → injected-evm-wallet-provider-BuDISWSq.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-B3Mtkdnu.mjs.map → injected-evm-wallet-provider-BuDISWSq.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-U0XJu5HM.js → injected-evm-wallet-provider-D7tQrDaO.js} +2 -2
- package/dist/{injected-evm-wallet-provider-U0XJu5HM.js.map → injected-evm-wallet-provider-D7tQrDaO.js.map} +1 -1
- package/dist/{injected-evm-wallet-provider-C8rBnyO1.mjs → injected-evm-wallet-provider-Xb2G-ZGI.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-C8rBnyO1.mjs.map → injected-evm-wallet-provider-Xb2G-ZGI.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-BNu6Z-iN.js → injected-evm-wallet-provider-_UNpc_hM.js} +2 -2
- package/dist/{injected-evm-wallet-provider-BNu6Z-iN.js.map → injected-evm-wallet-provider-_UNpc_hM.js.map} +1 -1
- package/dist/{injected-wallet-provider-DR8xVudg.mjs → injected-wallet-provider-BDCLkdR_.mjs} +2 -2
- package/dist/{injected-wallet-provider-DR8xVudg.mjs.map → injected-wallet-provider-BDCLkdR_.mjs.map} +1 -1
- package/dist/{injected-wallet-provider-29eTkM-A.js → injected-wallet-provider-C7Q0haGY.js} +2 -2
- package/dist/{injected-wallet-provider-29eTkM-A.js.map → injected-wallet-provider-C7Q0haGY.js.map} +1 -1
- package/dist/{injected-wallet-provider-Cv9RELO6.js → injected-wallet-provider-CFwJYxh2.js} +2 -2
- package/dist/{injected-wallet-provider-Cv9RELO6.js.map → injected-wallet-provider-CFwJYxh2.js.map} +1 -1
- package/dist/{injected-wallet-provider-BnmYbm79.mjs → injected-wallet-provider-DZOwvKgd.mjs} +2 -2
- package/dist/{injected-wallet-provider-BnmYbm79.mjs.map → injected-wallet-provider-DZOwvKgd.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/index-CaZx7sd8.mjs.map +0 -1
- package/dist/index-Pyb2SLgv.js.map +0 -1
- package/dist/index.native-3T0IcOed.mjs.map +0 -1
- package/dist/index.native-PGrxu7RO.js.map +0 -1
|
@@ -202,10 +202,14 @@ function getPlatform() {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
* OffchainAuthProvider wraps a real auth provider (
|
|
205
|
+
* OffchainAuthProvider wraps a real auth provider (Turnkey, Phantom, ...) for the
|
|
206
|
+
* poofnet environment.
|
|
206
207
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
208
|
+
* Poofnet has no chain, but its onchain writes are still signed: the worker
|
|
209
|
+
* issues an unsendable approval message and verifies the wallet's signature
|
|
210
|
+
* before committing. So `signTransaction` and `signMessage` go straight to the
|
|
211
|
+
* wrapped wallet, whose own prompt is the only one the user sees. Only a raw
|
|
212
|
+
* submit - which needs a real cluster - is refused here.
|
|
209
213
|
*/
|
|
210
214
|
class OffchainAuthProvider {
|
|
211
215
|
constructor(wrappedProvider, config = {}) {
|
|
@@ -240,10 +244,7 @@ class OffchainAuthProvider {
|
|
|
240
244
|
return null;
|
|
241
245
|
}
|
|
242
246
|
async signTransaction(tx) {
|
|
243
|
-
|
|
244
|
-
await this.showUnsupportedTransactionModal();
|
|
245
|
-
}
|
|
246
|
-
throw new Error('Poofnet does not support real Solana transactions. Deploy your project to mainnet to use this feature.');
|
|
247
|
+
return this.wrappedProvider.signTransaction(tx);
|
|
247
248
|
}
|
|
248
249
|
/**
|
|
249
250
|
* Sign and submit transaction - not supported in poofnet environment.
|
|
@@ -268,21 +269,11 @@ class OffchainAuthProvider {
|
|
|
268
269
|
}
|
|
269
270
|
await this.wrappedProvider.exportWallet(address);
|
|
270
271
|
}
|
|
271
|
-
// ============ signMessage delegates to wrapped provider ============
|
|
272
272
|
async signMessage(message) {
|
|
273
273
|
var _a, _b, _c, _d, _e, _f;
|
|
274
274
|
(_b = (_a = this.config).onSigningStart) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
275
275
|
try {
|
|
276
|
-
// On RN (no DOM), auto-confirm — there's no modal to show.
|
|
277
|
-
// On web, show the confirmation modal.
|
|
278
|
-
if (getPlatform().hasDOM) {
|
|
279
|
-
const confirmed = await this.showTransactionModal(message);
|
|
280
|
-
if (!confirmed) {
|
|
281
|
-
throw new Error("Transaction rejected by user");
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
276
|
const signature = await this.wrappedProvider.signMessage(message);
|
|
285
|
-
// Callback: signing complete
|
|
286
277
|
(_d = (_c = this.config).onSigningComplete) === null || _d === void 0 ? void 0 : _d.call(_c, signature);
|
|
287
278
|
return signature;
|
|
288
279
|
}
|
|
@@ -373,99 +364,6 @@ class OffchainAuthProvider {
|
|
|
373
364
|
document.addEventListener("keydown", escHandler);
|
|
374
365
|
});
|
|
375
366
|
}
|
|
376
|
-
async showTransactionModal(message) {
|
|
377
|
-
return new Promise((resolve) => {
|
|
378
|
-
// Parse the transaction message for display
|
|
379
|
-
let parsed = {};
|
|
380
|
-
try {
|
|
381
|
-
parsed = JSON.parse(message);
|
|
382
|
-
}
|
|
383
|
-
catch (_a) {
|
|
384
|
-
// If not JSON, just show raw message
|
|
385
|
-
}
|
|
386
|
-
// Create modal container. Captured in this closure so every teardown
|
|
387
|
-
// path acts on exactly this prompt, never on a newer one that may have
|
|
388
|
-
// replaced a shared field.
|
|
389
|
-
const container = document.createElement("div");
|
|
390
|
-
container.id = "poofnet-tx-modal";
|
|
391
|
-
container.appendChild(this.buildTransactionModalContent(parsed, message));
|
|
392
|
-
document.body.appendChild(container);
|
|
393
|
-
// Add styles
|
|
394
|
-
const style = document.createElement("style");
|
|
395
|
-
style.textContent = this.getModalStyles();
|
|
396
|
-
container.appendChild(style);
|
|
397
|
-
// Mark all OTHER body children as inert to disable focus traps from
|
|
398
|
-
// other modals, recording only the ones this modal changed (skipping
|
|
399
|
-
// any already-inert) so cleanup restores exactly what we set and never
|
|
400
|
-
// re-enables app-owned inert regions.
|
|
401
|
-
const selfInerted = [];
|
|
402
|
-
document.body.childNodes.forEach((child) => {
|
|
403
|
-
if (child !== container &&
|
|
404
|
-
child instanceof HTMLElement &&
|
|
405
|
-
!child.hasAttribute("inert")) {
|
|
406
|
-
child.setAttribute("inert", "");
|
|
407
|
-
selfInerted.push(child);
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
// Animate in
|
|
411
|
-
requestAnimationFrame(() => {
|
|
412
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
413
|
-
const content = container.querySelector(".poofnet-modal-content");
|
|
414
|
-
if (overlay)
|
|
415
|
-
overlay.style.opacity = "1";
|
|
416
|
-
if (content) {
|
|
417
|
-
content.style.opacity = "1";
|
|
418
|
-
content.style.transform = "translateY(0)";
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
// Single idempotent teardown for every settlement path: removes the key
|
|
422
|
-
// handler, restores only self-added inert, and removes exactly this
|
|
423
|
-
// captured container after the exit animation.
|
|
424
|
-
let settled = false;
|
|
425
|
-
const escHandler = (e) => {
|
|
426
|
-
if (e.key === "Escape")
|
|
427
|
-
settle(false);
|
|
428
|
-
};
|
|
429
|
-
const cleanup = () => {
|
|
430
|
-
if (settled)
|
|
431
|
-
return;
|
|
432
|
-
settled = true;
|
|
433
|
-
document.removeEventListener("keydown", escHandler);
|
|
434
|
-
for (const el of selfInerted)
|
|
435
|
-
el.removeAttribute("inert");
|
|
436
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
437
|
-
const content = container.querySelector(".poofnet-modal-content");
|
|
438
|
-
if (overlay)
|
|
439
|
-
overlay.style.opacity = "0";
|
|
440
|
-
if (content) {
|
|
441
|
-
content.style.opacity = "0";
|
|
442
|
-
content.style.transform = "translateY(20px)";
|
|
443
|
-
}
|
|
444
|
-
setTimeout(() => container.remove(), 200);
|
|
445
|
-
};
|
|
446
|
-
const settle = (confirmed) => {
|
|
447
|
-
cleanup();
|
|
448
|
-
resolve(confirmed);
|
|
449
|
-
};
|
|
450
|
-
// Handle confirm
|
|
451
|
-
const confirmBtn = container.querySelector("#poofnet-confirm-btn");
|
|
452
|
-
confirmBtn === null || confirmBtn === void 0 ? void 0 : confirmBtn.addEventListener("click", () => settle(true));
|
|
453
|
-
// Handle cancel
|
|
454
|
-
const cancelBtn = container.querySelector("#poofnet-cancel-btn");
|
|
455
|
-
cancelBtn === null || cancelBtn === void 0 ? void 0 : cancelBtn.addEventListener("click", () => settle(false));
|
|
456
|
-
// Handle close button
|
|
457
|
-
const closeBtn = container.querySelector("#poofnet-close-btn");
|
|
458
|
-
closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener("click", () => settle(false));
|
|
459
|
-
// Handle overlay click
|
|
460
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
461
|
-
overlay === null || overlay === void 0 ? void 0 : overlay.addEventListener("click", (e) => {
|
|
462
|
-
if (e.target === overlay)
|
|
463
|
-
settle(false);
|
|
464
|
-
});
|
|
465
|
-
// Handle escape key
|
|
466
|
-
document.addEventListener("keydown", escHandler);
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
367
|
createElement(tag, className, text) {
|
|
470
368
|
const el = document.createElement(tag);
|
|
471
369
|
if (className)
|
|
@@ -520,113 +418,6 @@ class OffchainAuthProvider {
|
|
|
520
418
|
footer.appendChild(close);
|
|
521
419
|
return overlay;
|
|
522
420
|
}
|
|
523
|
-
formatPath(path) {
|
|
524
|
-
const parts = path.split("/").filter(Boolean);
|
|
525
|
-
if (parts.length >= 2) {
|
|
526
|
-
return {
|
|
527
|
-
collection: parts[0],
|
|
528
|
-
documentId: parts[parts.length - 1],
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
return { collection: parts[0] || path, documentId: null };
|
|
532
|
-
}
|
|
533
|
-
formatFieldValue(value) {
|
|
534
|
-
if (value === null || value === undefined)
|
|
535
|
-
return "null";
|
|
536
|
-
if (typeof value === "string") {
|
|
537
|
-
return value.length > 30
|
|
538
|
-
? `"${value.slice(0, 30)}..."`
|
|
539
|
-
: `"${value}"`;
|
|
540
|
-
}
|
|
541
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
542
|
-
return String(value);
|
|
543
|
-
if (Array.isArray(value))
|
|
544
|
-
return `[${value.length} items]`;
|
|
545
|
-
if (typeof value === "object")
|
|
546
|
-
return `{${Object.keys(value).length} fields}`;
|
|
547
|
-
return String(value);
|
|
548
|
-
}
|
|
549
|
-
getActionDescription(type, collection, isNew) {
|
|
550
|
-
if (type === "delete")
|
|
551
|
-
return `Delete from ${collection}`;
|
|
552
|
-
return isNew ? `Create in ${collection}` : `Update ${collection}`;
|
|
553
|
-
}
|
|
554
|
-
buildTransactionModalContent(parsed, rawMessage) {
|
|
555
|
-
const instructions = parsed.instructions || [];
|
|
556
|
-
// Group instructions by type for summary
|
|
557
|
-
const sets = instructions.filter((i) => i.type === "set");
|
|
558
|
-
const deletes = instructions.filter((i) => i.type === "delete");
|
|
559
|
-
// Create summary text
|
|
560
|
-
let summaryText = "";
|
|
561
|
-
if (sets.length > 0 && deletes.length > 0) {
|
|
562
|
-
summaryText = `${sets.length} update${sets.length > 1 ? "s" : ""}, ${deletes.length} delete${deletes.length > 1 ? "s" : ""}`;
|
|
563
|
-
}
|
|
564
|
-
else if (sets.length > 0) {
|
|
565
|
-
summaryText = `${sets.length} update${sets.length > 1 ? "s" : ""}`;
|
|
566
|
-
}
|
|
567
|
-
else if (deletes.length > 0) {
|
|
568
|
-
summaryText = `${deletes.length} delete${deletes.length > 1 ? "s" : ""}`;
|
|
569
|
-
}
|
|
570
|
-
const { overlay, body, footer } = this.buildModalShell("🔐", "Confirm Transaction");
|
|
571
|
-
if (instructions.length > 0) {
|
|
572
|
-
if (summaryText) {
|
|
573
|
-
this.appendTextElement(body, "div", "poofnet-summary", summaryText);
|
|
574
|
-
}
|
|
575
|
-
const list = this.createElement("div", "poofnet-instructions-list");
|
|
576
|
-
for (const inst of instructions) {
|
|
577
|
-
list.appendChild(this.buildInstructionElement(inst));
|
|
578
|
-
}
|
|
579
|
-
body.appendChild(list);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
const section = this.createElement("div", "poofnet-section");
|
|
583
|
-
this.appendTextElement(section, "h3", "poofnet-section-title", "Message");
|
|
584
|
-
const raw = this.createElement("pre", "poofnet-raw-message", rawMessage.slice(0, 200) + (rawMessage.length > 200 ? "..." : ""));
|
|
585
|
-
section.appendChild(raw);
|
|
586
|
-
body.appendChild(section);
|
|
587
|
-
}
|
|
588
|
-
body.appendChild(this.buildInfoBox("This is a simulated transaction. No real fees will be charged."));
|
|
589
|
-
const cancel = this.createElement("button", "poofnet-btn poofnet-btn-cancel", "Cancel");
|
|
590
|
-
cancel.id = "poofnet-cancel-btn";
|
|
591
|
-
cancel.type = "button";
|
|
592
|
-
footer.appendChild(cancel);
|
|
593
|
-
const confirm = this.createElement("button", "poofnet-btn poofnet-btn-confirm", "Confirm");
|
|
594
|
-
confirm.id = "poofnet-confirm-btn";
|
|
595
|
-
confirm.type = "button";
|
|
596
|
-
footer.appendChild(confirm);
|
|
597
|
-
return overlay;
|
|
598
|
-
}
|
|
599
|
-
buildInstructionElement(inst) {
|
|
600
|
-
const { collection, documentId } = this.formatPath(inst.path);
|
|
601
|
-
const isDelete = inst.type === "delete";
|
|
602
|
-
const card = this.createElement("div", `poofnet-instruction ${isDelete ? "poofnet-action-delete" : "poofnet-action-set"}`);
|
|
603
|
-
const header = this.createElement("div", "poofnet-instruction-header");
|
|
604
|
-
this.appendTextElement(header, "span", "poofnet-instruction-icon", isDelete ? "🗑️" : "✏️");
|
|
605
|
-
this.appendTextElement(header, "span", "poofnet-instruction-action", isDelete ? "Delete" : "Save");
|
|
606
|
-
this.appendTextElement(header, "span", "poofnet-instruction-collection", collection);
|
|
607
|
-
card.appendChild(header);
|
|
608
|
-
if (documentId) {
|
|
609
|
-
this.appendTextElement(card, "div", "poofnet-instruction-id", `ID: ${documentId}`);
|
|
610
|
-
}
|
|
611
|
-
if (inst.data && !isDelete) {
|
|
612
|
-
const fieldKeys = Object.keys(inst.data).filter((key) => !key.startsWith("_"));
|
|
613
|
-
const visibleFields = fieldKeys.slice(0, 4);
|
|
614
|
-
if (visibleFields.length > 0) {
|
|
615
|
-
const fields = this.createElement("div", "poofnet-fields");
|
|
616
|
-
for (const key of visibleFields) {
|
|
617
|
-
const row = this.createElement("div", "poofnet-field");
|
|
618
|
-
this.appendTextElement(row, "span", "poofnet-field-key", `${key}:`);
|
|
619
|
-
this.appendTextElement(row, "span", "poofnet-field-value", this.formatFieldValue(inst.data[key]));
|
|
620
|
-
fields.appendChild(row);
|
|
621
|
-
}
|
|
622
|
-
if (fieldKeys.length > 4) {
|
|
623
|
-
this.appendTextElement(fields, "div", "poofnet-field-more", `+${fieldKeys.length - 4} more fields`);
|
|
624
|
-
}
|
|
625
|
-
card.appendChild(fields);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return card;
|
|
629
|
-
}
|
|
630
421
|
getModalStyles() {
|
|
631
422
|
return `
|
|
632
423
|
/* Ensure inert elements remain visible while being non-interactive */
|
|
@@ -723,130 +514,6 @@ class OffchainAuthProvider {
|
|
|
723
514
|
flex: 1;
|
|
724
515
|
}
|
|
725
516
|
|
|
726
|
-
.poofnet-summary {
|
|
727
|
-
font-size: 13px;
|
|
728
|
-
color: #6b7280;
|
|
729
|
-
margin-bottom: 12px;
|
|
730
|
-
text-align: center;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
.poofnet-section {
|
|
734
|
-
margin-bottom: 16px;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.poofnet-section-title {
|
|
738
|
-
font-size: 12px;
|
|
739
|
-
font-weight: 600;
|
|
740
|
-
color: #666;
|
|
741
|
-
text-transform: uppercase;
|
|
742
|
-
letter-spacing: 0.5px;
|
|
743
|
-
margin: 0 0 8px;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
.poofnet-instructions-list {
|
|
747
|
-
display: flex;
|
|
748
|
-
flex-direction: column;
|
|
749
|
-
gap: 10px;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
.poofnet-instruction {
|
|
753
|
-
background: #f9fafb;
|
|
754
|
-
border-radius: 10px;
|
|
755
|
-
padding: 12px;
|
|
756
|
-
border-left: 3px solid #8b5cf6;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.poofnet-instruction.poofnet-action-delete {
|
|
760
|
-
border-left-color: #ef4444;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.poofnet-instruction-header {
|
|
764
|
-
display: flex;
|
|
765
|
-
align-items: center;
|
|
766
|
-
gap: 8px;
|
|
767
|
-
margin-bottom: 4px;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
.poofnet-instruction-icon {
|
|
771
|
-
font-size: 16px;
|
|
772
|
-
flex-shrink: 0;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
.poofnet-instruction-action {
|
|
776
|
-
font-size: 13px;
|
|
777
|
-
font-weight: 600;
|
|
778
|
-
color: #8b5cf6;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
.poofnet-action-delete .poofnet-instruction-action {
|
|
782
|
-
color: #ef4444;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
.poofnet-instruction-collection {
|
|
786
|
-
font-size: 13px;
|
|
787
|
-
font-weight: 500;
|
|
788
|
-
color: #374151;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
.poofnet-instruction-id {
|
|
792
|
-
font-size: 11px;
|
|
793
|
-
color: #9ca3af;
|
|
794
|
-
font-family: monospace;
|
|
795
|
-
margin-bottom: 8px;
|
|
796
|
-
word-break: break-all;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
.poofnet-fields {
|
|
800
|
-
background: white;
|
|
801
|
-
border-radius: 6px;
|
|
802
|
-
padding: 8px 10px;
|
|
803
|
-
margin-top: 8px;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
.poofnet-field {
|
|
807
|
-
display: flex;
|
|
808
|
-
gap: 8px;
|
|
809
|
-
padding: 4px 0;
|
|
810
|
-
font-size: 12px;
|
|
811
|
-
border-bottom: 1px solid #f3f4f6;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
.poofnet-field:last-child {
|
|
815
|
-
border-bottom: none;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
.poofnet-field-key {
|
|
819
|
-
color: #6b7280;
|
|
820
|
-
font-weight: 500;
|
|
821
|
-
flex-shrink: 0;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
.poofnet-field-value {
|
|
825
|
-
color: #374151;
|
|
826
|
-
word-break: break-all;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
.poofnet-field-more {
|
|
830
|
-
font-size: 11px;
|
|
831
|
-
color: #9ca3af;
|
|
832
|
-
padding-top: 4px;
|
|
833
|
-
font-style: italic;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.poofnet-raw-message {
|
|
837
|
-
background: #f9fafb;
|
|
838
|
-
border-radius: 8px;
|
|
839
|
-
padding: 12px;
|
|
840
|
-
font-size: 11px;
|
|
841
|
-
font-family: monospace;
|
|
842
|
-
color: #4b5563;
|
|
843
|
-
white-space: pre-wrap;
|
|
844
|
-
word-break: break-all;
|
|
845
|
-
margin: 0;
|
|
846
|
-
max-height: 120px;
|
|
847
|
-
overflow-y: auto;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
517
|
.poofnet-info-box {
|
|
851
518
|
display: flex;
|
|
852
519
|
align-items: center;
|
|
@@ -895,15 +562,6 @@ class OffchainAuthProvider {
|
|
|
895
562
|
.poofnet-btn-cancel:hover {
|
|
896
563
|
background: #e5e7eb;
|
|
897
564
|
}
|
|
898
|
-
|
|
899
|
-
.poofnet-btn-confirm {
|
|
900
|
-
background: #8b5cf6;
|
|
901
|
-
color: white;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
.poofnet-btn-confirm:hover {
|
|
905
|
-
background: #7c3aed;
|
|
906
|
-
}
|
|
907
565
|
`;
|
|
908
566
|
}
|
|
909
567
|
}
|
|
@@ -5172,9 +4830,18 @@ const OIDC_NO_WALLET = 'This session has no Turnkey wallet. Sign in again to pro
|
|
|
5172
4830
|
// same tab (redirect + popup, or two popups) each own a distinct record, so a second
|
|
5173
4831
|
// attempt cannot clobber the first's verifier/state/redirectUri.
|
|
5174
4832
|
const PKCE_STORE_PREFIX = 'bounded.oidc.pkce';
|
|
4833
|
+
const STATE_PREFIX = 'bounded.';
|
|
4834
|
+
const LOGIN_TIMEOUT_MS = 10 * 60000;
|
|
4835
|
+
const CALLBACK_PARAMS = ['code', 'state', 'error', 'error_description', 'iss'];
|
|
5175
4836
|
function pkceKey(state) {
|
|
5176
4837
|
return `${PKCE_STORE_PREFIX}.${state}`;
|
|
5177
4838
|
}
|
|
4839
|
+
function removeAttempt(state) {
|
|
4840
|
+
try {
|
|
4841
|
+
sessionStorage.removeItem(pkceKey(state));
|
|
4842
|
+
}
|
|
4843
|
+
catch ( /* Storage may have been disabled mid-login. */_a) { /* Storage may have been disabled mid-login. */ }
|
|
4844
|
+
}
|
|
5178
4845
|
const ISSUER_SESSION_KIND_KEY = 'bounded_issuer_session_kind';
|
|
5179
4846
|
// Active session store (Web on web, RN on React Native) — selected by config,
|
|
5180
4847
|
// not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
|
|
@@ -5283,6 +4950,165 @@ async function makePkce() {
|
|
|
5283
4950
|
function randomState() {
|
|
5284
4951
|
return b64url(getPlatform().getRandomBytes(16));
|
|
5285
4952
|
}
|
|
4953
|
+
let pendingCompletion;
|
|
4954
|
+
let loginReturnError;
|
|
4955
|
+
/** The last OAuth return failure in this initialization, or null. Initialization
|
|
4956
|
+
* remains usable so the UI can display this error and offer a fresh sign-in. */
|
|
4957
|
+
function getLoginReturnError() {
|
|
4958
|
+
return (loginReturnError === null || loginReturnError === void 0 ? void 0 : loginReturnError.generation) === getInitializationGeneration() ? loginReturnError.error : null;
|
|
4959
|
+
}
|
|
4960
|
+
// A hosted page can contain both an app bundle and the platform widget's SDK.
|
|
4961
|
+
// They share one code exchange, while adopting the session in their own registries.
|
|
4962
|
+
const COMPLETIONS = Symbol.for('bounded.oidc.completions');
|
|
4963
|
+
function cleanCallbackUrl(url) {
|
|
4964
|
+
const clean = new URL(url);
|
|
4965
|
+
for (const key of CALLBACK_PARAMS)
|
|
4966
|
+
clean.searchParams.delete(key);
|
|
4967
|
+
return clean;
|
|
4968
|
+
}
|
|
4969
|
+
async function validateAttempt(pkce, callback) {
|
|
4970
|
+
const cfg = await core.getConfig();
|
|
4971
|
+
if (!pkce || typeof pkce.verifier !== 'string' || !pkce.verifier ||
|
|
4972
|
+
pkce.appId !== cfg.appId || pkce.issuer !== await issuerBase()) {
|
|
4973
|
+
throw new Error('Login belongs to a different app or auth server. Start sign-in again.');
|
|
4974
|
+
}
|
|
4975
|
+
if (!Number.isFinite(pkce.createdAt) || Date.now() - pkce.createdAt > LOGIN_TIMEOUT_MS || pkce.createdAt > Date.now()) {
|
|
4976
|
+
throw new Error('Login expired. Start sign-in again.');
|
|
4977
|
+
}
|
|
4978
|
+
if (callback && (cleanCallbackUrl(callback).href !== cleanCallbackUrl(new URL(pkce.redirectUri)).href ||
|
|
4979
|
+
(callback.searchParams.has('iss') && callback.searchParams.get('iss') !== pkce.issuer))) {
|
|
4980
|
+
throw new Error('Login returned to a different address or from a different auth server. Start sign-in again.');
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
// The receiver acknowledges only after checking origin, window and state. An
|
|
4984
|
+
// absent/disconnected opener must never leave a popup claiming success.
|
|
4985
|
+
async function handOffPopup(url, state) {
|
|
4986
|
+
const cfg = await core.getConfig();
|
|
4987
|
+
const issuer = await issuerBase();
|
|
4988
|
+
const opener = window.opener;
|
|
4989
|
+
if (!opener || opener === window || opener.closed) {
|
|
4990
|
+
throw new Error('The original sign-in window is unavailable. Close this window and sign in again in the original tab.');
|
|
4991
|
+
}
|
|
4992
|
+
return new Promise((resolve, reject) => {
|
|
4993
|
+
const cleanup = () => { clearTimeout(timer); window.removeEventListener('message', onMessage); };
|
|
4994
|
+
const onMessage = (event) => {
|
|
4995
|
+
var _a;
|
|
4996
|
+
if (event.source !== opener || event.origin !== url.origin ||
|
|
4997
|
+
((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) !== 'bounded-oidc-accepted' || event.data.state !== state)
|
|
4998
|
+
return;
|
|
4999
|
+
cleanup();
|
|
5000
|
+
try {
|
|
5001
|
+
window.close();
|
|
5002
|
+
}
|
|
5003
|
+
catch ( /* The opener also closes its own popup handle. */_b) { /* The opener also closes its own popup handle. */ }
|
|
5004
|
+
resolve();
|
|
5005
|
+
};
|
|
5006
|
+
const timer = setTimeout(() => {
|
|
5007
|
+
cleanup();
|
|
5008
|
+
reject(new Error('Could not reach the original sign-in tab. Close this window and start sign-in again.'));
|
|
5009
|
+
}, 10000);
|
|
5010
|
+
window.addEventListener('message', onMessage);
|
|
5011
|
+
try {
|
|
5012
|
+
opener.postMessage({ type: 'bounded-oidc-code', state, code: url.searchParams.get('code'),
|
|
5013
|
+
error: url.searchParams.get('error'), errorDescription: url.searchParams.get('error_description'),
|
|
5014
|
+
callbackUrl: url.href, appId: cfg.appId, issuer }, url.origin);
|
|
5015
|
+
}
|
|
5016
|
+
catch (error) {
|
|
5017
|
+
cleanup();
|
|
5018
|
+
reject(error);
|
|
5019
|
+
}
|
|
5020
|
+
});
|
|
5021
|
+
}
|
|
5022
|
+
async function finishLoginReturn(url, state) {
|
|
5023
|
+
try {
|
|
5024
|
+
if (!/^bounded\.(popup|redirect)\.[A-Za-z0-9_-]{22}$/.test(state)) {
|
|
5025
|
+
throw new Error('Invalid login state. Start sign-in again.');
|
|
5026
|
+
}
|
|
5027
|
+
if (state.startsWith('bounded.popup.')) {
|
|
5028
|
+
await handOffPopup(url, state);
|
|
5029
|
+
return { handled: true, user: null };
|
|
5030
|
+
}
|
|
5031
|
+
const raw = sessionStorage.getItem(pkceKey(state));
|
|
5032
|
+
if (!raw)
|
|
5033
|
+
throw new Error('Missing login attempt. Start sign-in again in this tab.');
|
|
5034
|
+
const pkce = JSON.parse(raw);
|
|
5035
|
+
if (pkce.state !== state)
|
|
5036
|
+
throw new Error('OAuth state mismatch. Start sign-in again.');
|
|
5037
|
+
await validateAttempt(pkce, url);
|
|
5038
|
+
const error = url.searchParams.get('error');
|
|
5039
|
+
if (error)
|
|
5040
|
+
throw new Error(url.searchParams.get('error_description') || `Login failed: ${error}`);
|
|
5041
|
+
const user = await exchangeCode(url.searchParams.get('code'), pkce);
|
|
5042
|
+
return { handled: true, user, idToken: sessionManager().getIdToken() };
|
|
5043
|
+
}
|
|
5044
|
+
finally {
|
|
5045
|
+
removeAttempt(state);
|
|
5046
|
+
const clean = cleanCallbackUrl(url);
|
|
5047
|
+
if (window.location.href === url.href) {
|
|
5048
|
+
window.history.replaceState(window.history.state, document.title, clean.pathname + clean.search + clean.hash);
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
/** Internal initialization step. Unrelated OAuth callbacks are left untouched. */
|
|
5053
|
+
function completePendingLogin() {
|
|
5054
|
+
if (!hasDOM$1())
|
|
5055
|
+
return Promise.resolve({ handled: false, user: null });
|
|
5056
|
+
const url = new URL(window.location.href);
|
|
5057
|
+
const state = url.searchParams.get('state');
|
|
5058
|
+
if (!(state === null || state === void 0 ? void 0 : state.startsWith(STATE_PREFIX)) || (!url.searchParams.get('code') && !url.searchParams.get('error'))) {
|
|
5059
|
+
return Promise.resolve({ handled: false, user: null });
|
|
5060
|
+
}
|
|
5061
|
+
const generation = getInitializationGeneration();
|
|
5062
|
+
if ((pendingCompletion === null || pendingCompletion === void 0 ? void 0 : pendingCompletion.href) === url.href && pendingCompletion.generation === generation)
|
|
5063
|
+
return pendingCompletion.promise;
|
|
5064
|
+
const promise = finishSharedLoginReturn(url, state).catch((error) => {
|
|
5065
|
+
loginReturnError = { generation, error: error instanceof Error ? error : new Error(String(error)) };
|
|
5066
|
+
throw loginReturnError.error;
|
|
5067
|
+
}).finally(() => {
|
|
5068
|
+
if ((pendingCompletion === null || pendingCompletion === void 0 ? void 0 : pendingCompletion.promise) === promise)
|
|
5069
|
+
pendingCompletion = undefined;
|
|
5070
|
+
});
|
|
5071
|
+
pendingCompletion = { href: url.href, generation, promise };
|
|
5072
|
+
return promise;
|
|
5073
|
+
}
|
|
5074
|
+
async function finishSharedLoginReturn(url, state) {
|
|
5075
|
+
var _a;
|
|
5076
|
+
var _b;
|
|
5077
|
+
const cfg = await core.getConfig();
|
|
5078
|
+
const issuer = await issuerBase();
|
|
5079
|
+
if (state.startsWith('bounded.redirect.')) {
|
|
5080
|
+
const raw = sessionStorage.getItem(pkceKey(state));
|
|
5081
|
+
// A second SDK configured for another app/issuer on this page does not own
|
|
5082
|
+
// this return. Leave its URL and proof available to the initiating app.
|
|
5083
|
+
try {
|
|
5084
|
+
const attempt = raw && JSON.parse(raw);
|
|
5085
|
+
if (attempt && (attempt.appId !== cfg.appId || attempt.issuer !== issuer))
|
|
5086
|
+
return { handled: false, user: null };
|
|
5087
|
+
}
|
|
5088
|
+
catch ( /* The owning completion reports malformed attempt data. */_c) { /* The owning completion reports malformed attempt data. */ }
|
|
5089
|
+
}
|
|
5090
|
+
const shared = (_a = (_b = window)[COMPLETIONS]) !== null && _a !== void 0 ? _a : (_b[COMPLETIONS] = new Map());
|
|
5091
|
+
const key = JSON.stringify([url.href, cfg.appId, issuer]);
|
|
5092
|
+
const existing = shared.get(key);
|
|
5093
|
+
if (!existing) {
|
|
5094
|
+
const completion = finishLoginReturn(url, state);
|
|
5095
|
+
shared.set(key, completion);
|
|
5096
|
+
return completion;
|
|
5097
|
+
}
|
|
5098
|
+
const generation = getInitializationGeneration();
|
|
5099
|
+
const result = await existing;
|
|
5100
|
+
if (result.user) {
|
|
5101
|
+
if (!result.idToken || sessionManager().getIdToken() !== result.idToken) {
|
|
5102
|
+
throw new Error('The completed login session has changed. Start sign-in again.');
|
|
5103
|
+
}
|
|
5104
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc',
|
|
5105
|
+
user: Object.assign(Object.assign({}, result.user), { provider: oidcProviderSingleton }), beforeCommit: () => {
|
|
5106
|
+
if (generation !== getInitializationGeneration())
|
|
5107
|
+
throw new Error('Login was superseded by SDK reinitialization. Start sign-in again.');
|
|
5108
|
+
} });
|
|
5109
|
+
}
|
|
5110
|
+
return result;
|
|
5111
|
+
}
|
|
5286
5112
|
function normalizeMethod(value) {
|
|
5287
5113
|
const normalized = String(value || '').trim().toLowerCase();
|
|
5288
5114
|
return /^[a-z0-9_-]{1,40}$/.test(normalized) ? normalized : null;
|
|
@@ -5325,7 +5151,8 @@ function appendHostedLoginHints(params, opts) {
|
|
|
5325
5151
|
// native (expo-web-browser) path. The caller decides where the PkceState lives
|
|
5326
5152
|
// (web → sessionStorage across the full-page redirect; native → in-memory across
|
|
5327
5153
|
// the awaited browser session).
|
|
5328
|
-
async function buildAuthorize(opts) {
|
|
5154
|
+
async function buildAuthorize(opts, mode = 'redirect') {
|
|
5155
|
+
loginReturnError = undefined;
|
|
5329
5156
|
const cfg = await core.getConfig();
|
|
5330
5157
|
if (!cfg.appId)
|
|
5331
5158
|
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
@@ -5334,15 +5161,23 @@ async function buildAuthorize(opts) {
|
|
|
5334
5161
|
const redirectUri = opts.redirectUri || (hasDOM$1() ? window.location.origin + window.location.pathname : undefined);
|
|
5335
5162
|
if (!redirectUri)
|
|
5336
5163
|
throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
|
|
5164
|
+
if (hasDOM$1()) {
|
|
5165
|
+
const redirect = new URL(redirectUri);
|
|
5166
|
+
if (redirect.origin !== window.location.origin || redirect.hash || CALLBACK_PARAMS.some((key) => redirect.searchParams.has(key))) {
|
|
5167
|
+
throw new Error('redirectUri must be on this app origin, without a fragment or OAuth callback parameters.');
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
5337
5170
|
const { verifier, challenge } = await makePkce();
|
|
5338
|
-
const state = randomState()
|
|
5171
|
+
const state = `${STATE_PREFIX}${mode}.${randomState()}`;
|
|
5339
5172
|
const base = await issuerBase();
|
|
5340
5173
|
const params = new URLSearchParams({
|
|
5341
5174
|
client_id: cfg.appId, redirect_uri: redirectUri, response_type: 'code', scope: 'openid profile email',
|
|
5342
5175
|
code_challenge: challenge, code_challenge_method: 'S256', state,
|
|
5343
5176
|
});
|
|
5344
5177
|
appendHostedLoginHints(params, opts);
|
|
5345
|
-
return { url: `${base}/api/auth/oauth2/authorize?${params.toString()}`, pkce: {
|
|
5178
|
+
return { url: `${base}/api/auth/oauth2/authorize?${params.toString()}`, pkce: {
|
|
5179
|
+
verifier, state, redirectUri, appId: cfg.appId, issuer: base, createdAt: Date.now(),
|
|
5180
|
+
} };
|
|
5346
5181
|
}
|
|
5347
5182
|
// Are we on React Native (no DOM)? OIDC dispatch uses this to choose the native
|
|
5348
5183
|
// expo-web-browser driver vs. the web full-page-redirect/popup driver.
|
|
@@ -5391,8 +5226,7 @@ function parseCallbackParams(callbackUrl) {
|
|
|
5391
5226
|
}
|
|
5392
5227
|
// Full native login: open the hosted page in the system auth session, await the
|
|
5393
5228
|
// universal-link callback, verify state, and run the SAME PKCE code→token exchange
|
|
5394
|
-
// as web. Returns the signed-in User
|
|
5395
|
-
// completeLoginFromRedirect() round-trip the way the web full-page redirect needs).
|
|
5229
|
+
// as web. Returns the signed-in User inline; web redirects complete on the next init().
|
|
5396
5230
|
async function runNativeLogin(opts) {
|
|
5397
5231
|
var _a, _b;
|
|
5398
5232
|
const { url, pkce } = await buildAuthorize(opts);
|
|
@@ -5419,7 +5253,7 @@ async function runNativeLogin(opts) {
|
|
|
5419
5253
|
if (!returnedState || returnedState !== pkce.state) {
|
|
5420
5254
|
throw new Error('OAuth state mismatch — possible CSRF; aborting');
|
|
5421
5255
|
}
|
|
5422
|
-
return await exchangeCode(code, pkce
|
|
5256
|
+
return await exchangeCode(code, pkce);
|
|
5423
5257
|
}
|
|
5424
5258
|
finally {
|
|
5425
5259
|
setAuthLoading(false);
|
|
@@ -5428,7 +5262,7 @@ async function runNativeLogin(opts) {
|
|
|
5428
5262
|
/** Start hosted login (Google / Apple / email / hosted phone). Works on WEB and REACT NATIVE.
|
|
5429
5263
|
*
|
|
5430
5264
|
* WEB: navigates the whole page to the hosted Bounded login and resolves (void) as the page
|
|
5431
|
-
* unloads;
|
|
5265
|
+
* unloads; init() completes the return to redirectUri automatically.
|
|
5432
5266
|
*
|
|
5433
5267
|
* REACT NATIVE: opens the hosted page via expo-web-browser, awaits the https universal-link
|
|
5434
5268
|
* callback, runs the PKCE exchange INLINE, and resolves with the signed-in User. No
|
|
@@ -5446,63 +5280,32 @@ async function loginWithRedirect(opts) {
|
|
|
5446
5280
|
// is per-tab and cleared on close — appropriate for a short-lived auth handshake. Keyed by
|
|
5447
5281
|
// this attempt's own state so a second concurrent login cannot overwrite it.
|
|
5448
5282
|
sessionStorage.setItem(pkceKey(pkce.state), JSON.stringify(pkce));
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
/** Finish login on the redirect_uri page (WEB ONLY): exchange ?code= for a token (PKCE-verified),
|
|
5453
|
-
* store the session, and return the User. Idempotent: returns null if there's no ?code= in the URL.
|
|
5454
|
-
*
|
|
5455
|
-
* On REACT NATIVE this returns null and is a no-op: native login completes INLINE inside
|
|
5456
|
-
* loginWithRedirect() (which resolves with the User), so there is no separate redirect
|
|
5457
|
-
* page to finish from. Calling it on RN is harmless. */
|
|
5458
|
-
async function completeLoginFromRedirect() {
|
|
5459
|
-
if (!hasDOM$1())
|
|
5460
|
-
return null;
|
|
5461
|
-
const url = new URL(window.location.href);
|
|
5462
|
-
const code = url.searchParams.get('code');
|
|
5463
|
-
const returnedState = url.searchParams.get('state');
|
|
5464
|
-
if (!code)
|
|
5465
|
-
return null;
|
|
5466
|
-
// POPUP path: if we're the redirect_uri page running INSIDE the loginWithPopup() window,
|
|
5467
|
-
// hand the code to the opener and close — no separate completeLoginInPopup() call needed.
|
|
5468
|
-
// The opener is the same app/origin, so post to our own origin (the opener's message
|
|
5469
|
-
// handler checks ev.origin === redirect_uri origin). One call now finishes BOTH flows.
|
|
5470
|
-
if (window.opener && window.opener !== window) {
|
|
5471
|
-
try {
|
|
5472
|
-
window.opener.postMessage({ type: 'bounded-oidc-code', code, state: returnedState }, window.location.origin);
|
|
5473
|
-
}
|
|
5474
|
-
catch ( /* ignore */_a) { /* ignore */ }
|
|
5475
|
-
try {
|
|
5476
|
-
window.close();
|
|
5477
|
-
}
|
|
5478
|
-
catch ( /* ignore */_b) { /* ignore */ }
|
|
5479
|
-
return null;
|
|
5480
|
-
}
|
|
5283
|
+
// This promise represents dispatch, not authentication on the next page.
|
|
5284
|
+
// Browsers can cancel navigation (for example at beforeunload) without firing
|
|
5285
|
+
// pageshow. Never leave this document's SDK loading after dispatch settles.
|
|
5481
5286
|
setAuthLoading(true);
|
|
5482
5287
|
try {
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
const pkce = JSON.parse(raw);
|
|
5489
|
-
if (!returnedState || returnedState !== pkce.state)
|
|
5490
|
-
throw new Error('OAuth state mismatch — possible CSRF; aborting');
|
|
5491
|
-
const user = await exchangeCode(code, pkce.verifier, pkce.redirectUri);
|
|
5492
|
-
sessionStorage.removeItem(pkceKey(returnedState));
|
|
5493
|
-
// Strip code/state from the URL so a reload doesn't re-exchange (codes are single-use anyway).
|
|
5494
|
-
url.searchParams.delete('code');
|
|
5495
|
-
url.searchParams.delete('state');
|
|
5496
|
-
window.history.replaceState({}, document.title, url.pathname + url.search + url.hash);
|
|
5497
|
-
return user;
|
|
5288
|
+
window.location.href = url;
|
|
5289
|
+
}
|
|
5290
|
+
catch (error) {
|
|
5291
|
+
removeAttempt(pkce.state);
|
|
5292
|
+
throw error;
|
|
5498
5293
|
}
|
|
5499
5294
|
finally {
|
|
5500
5295
|
setAuthLoading(false);
|
|
5501
5296
|
}
|
|
5502
5297
|
}
|
|
5298
|
+
/** Optional explicit completion. Normal apps only await init(), which already finishes login.
|
|
5299
|
+
* Concurrent calls share one exchange. Returns null when no Bounded return is present. */
|
|
5300
|
+
async function completeLoginFromRedirect() {
|
|
5301
|
+
const result = await completePendingLogin();
|
|
5302
|
+
const error = getLoginReturnError();
|
|
5303
|
+
if (error)
|
|
5304
|
+
throw error;
|
|
5305
|
+
return result.user;
|
|
5306
|
+
}
|
|
5503
5307
|
/** Popup variant: open the hosted login in a popup; resolves with the User once the popup's
|
|
5504
|
-
* redirect_uri page
|
|
5505
|
-
* completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
|
|
5308
|
+
* redirect_uri page initializes the SDK, which hands the code back automatically. */
|
|
5506
5309
|
async function loginWithPopup(opts) {
|
|
5507
5310
|
if (!hasDOM$1())
|
|
5508
5311
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
@@ -5521,7 +5324,7 @@ async function loginWithPopup(opts) {
|
|
|
5521
5324
|
throw new Error('Popup blocked — use loginWithRedirect() instead');
|
|
5522
5325
|
let authorization;
|
|
5523
5326
|
try {
|
|
5524
|
-
authorization = await buildAuthorize(opts);
|
|
5327
|
+
authorization = await buildAuthorize(opts, 'popup');
|
|
5525
5328
|
}
|
|
5526
5329
|
catch (error) {
|
|
5527
5330
|
try {
|
|
@@ -5556,6 +5359,7 @@ async function loginWithPopup(opts) {
|
|
|
5556
5359
|
popup.location.href = url;
|
|
5557
5360
|
}
|
|
5558
5361
|
catch (_d) {
|
|
5362
|
+
removeAttempt(pkce.state);
|
|
5559
5363
|
try {
|
|
5560
5364
|
popup.close();
|
|
5561
5365
|
}
|
|
@@ -5571,17 +5375,21 @@ async function loginWithPopup(opts) {
|
|
|
5571
5375
|
let settled = false;
|
|
5572
5376
|
let timer;
|
|
5573
5377
|
let navigationTimer;
|
|
5378
|
+
let completionTimer;
|
|
5574
5379
|
const cleanup = () => {
|
|
5575
5380
|
settled = true;
|
|
5576
5381
|
clearInterval(timer);
|
|
5577
5382
|
clearTimeout(navigationTimer);
|
|
5383
|
+
clearTimeout(completionTimer);
|
|
5578
5384
|
window.removeEventListener('message', onMsg);
|
|
5385
|
+
removeAttempt(pkce.state);
|
|
5579
5386
|
};
|
|
5580
5387
|
const onMsg = async (ev) => {
|
|
5581
|
-
if (ev.origin !== expectedOrigin)
|
|
5582
|
-
return;
|
|
5388
|
+
if (settled || ev.origin !== expectedOrigin || ev.source !== popup)
|
|
5389
|
+
return;
|
|
5583
5390
|
const d = ev.data;
|
|
5584
|
-
if (!d || d.type !== 'bounded-oidc-code' ||
|
|
5391
|
+
if (!d || d.type !== 'bounded-oidc-code' ||
|
|
5392
|
+
(typeof d.code !== 'string' && typeof d.error !== 'string'))
|
|
5585
5393
|
return;
|
|
5586
5394
|
// Validate state BEFORE tearing down this listener, against the record THIS
|
|
5587
5395
|
// attempt captured in the closure (`pkce`), not a shared sessionStorage reread.
|
|
@@ -5589,10 +5397,15 @@ async function loginWithPopup(opts) {
|
|
|
5589
5397
|
// stay registered for our own code, instead of cleaning up on another's message.
|
|
5590
5398
|
if (d.state !== pkce.state)
|
|
5591
5399
|
return;
|
|
5400
|
+
if (d.appId !== pkce.appId || d.issuer !== pkce.issuer)
|
|
5401
|
+
return;
|
|
5592
5402
|
cleanup();
|
|
5593
5403
|
try {
|
|
5594
|
-
|
|
5595
|
-
|
|
5404
|
+
await validateAttempt(pkce, new URL(d.callbackUrl));
|
|
5405
|
+
popup.postMessage({ type: 'bounded-oidc-accepted', state: pkce.state }, expectedOrigin);
|
|
5406
|
+
if (d.error)
|
|
5407
|
+
throw new Error(typeof d.errorDescription === 'string' ? d.errorDescription : `Login failed: ${d.error}`);
|
|
5408
|
+
const user = await exchangeCode(d.code, pkce);
|
|
5596
5409
|
try {
|
|
5597
5410
|
popup.close();
|
|
5598
5411
|
}
|
|
@@ -5600,10 +5413,22 @@ async function loginWithPopup(opts) {
|
|
|
5600
5413
|
resolve(user);
|
|
5601
5414
|
}
|
|
5602
5415
|
catch (e) {
|
|
5416
|
+
try {
|
|
5417
|
+
popup.close();
|
|
5418
|
+
}
|
|
5419
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
5603
5420
|
reject(e);
|
|
5604
5421
|
}
|
|
5605
5422
|
};
|
|
5606
5423
|
window.addEventListener('message', onMsg);
|
|
5424
|
+
completionTimer = setTimeout(() => {
|
|
5425
|
+
cleanup();
|
|
5426
|
+
try {
|
|
5427
|
+
popup.close();
|
|
5428
|
+
}
|
|
5429
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
5430
|
+
reject(new Error('Login expired or its popup disconnected. Start sign-in again.'));
|
|
5431
|
+
}, LOGIN_TIMEOUT_MS);
|
|
5607
5432
|
// Some browser/extension combinations can return a real popup handle but
|
|
5608
5433
|
// strand the window at about:blank instead of navigating to the hosted
|
|
5609
5434
|
// authorize URL. The old code then waited forever because the popup was
|
|
@@ -5661,33 +5486,20 @@ async function loginWithPopup(opts) {
|
|
|
5661
5486
|
setAuthLoading(false);
|
|
5662
5487
|
}
|
|
5663
5488
|
}
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
return;
|
|
5674
|
-
try {
|
|
5675
|
-
window.opener.postMessage({ type: 'bounded-oidc-code', code, state }, openerOrigin);
|
|
5676
|
-
}
|
|
5677
|
-
catch ( /* ignore */_a) { /* ignore */ }
|
|
5678
|
-
try {
|
|
5679
|
-
window.close();
|
|
5680
|
-
}
|
|
5681
|
-
catch ( /* ignore */_b) { /* ignore */ }
|
|
5682
|
-
}
|
|
5683
|
-
async function exchangeCode(code, verifier, redirectUri) {
|
|
5684
|
-
const cfg = await core.getConfig();
|
|
5685
|
-
const base = await issuerBase();
|
|
5489
|
+
async function exchangeCode(code, pkce) {
|
|
5490
|
+
const generation = getInitializationGeneration();
|
|
5491
|
+
const beforeCommit = () => {
|
|
5492
|
+
if (generation !== getInitializationGeneration())
|
|
5493
|
+
throw new Error('Login was superseded by SDK reinitialization. Start sign-in again.');
|
|
5494
|
+
};
|
|
5495
|
+
await validateAttempt(pkce);
|
|
5496
|
+
beforeCommit();
|
|
5497
|
+
const base = pkce.issuer;
|
|
5686
5498
|
// OIDC token endpoint: application/x-www-form-urlencoded (the standard). Public client (no
|
|
5687
5499
|
// secret) authenticated by PKCE. The code is single-use + short-TTL on the server.
|
|
5688
5500
|
const body = new URLSearchParams({
|
|
5689
|
-
grant_type: 'authorization_code', code, redirect_uri: redirectUri,
|
|
5690
|
-
client_id:
|
|
5501
|
+
grant_type: 'authorization_code', code, redirect_uri: pkce.redirectUri,
|
|
5502
|
+
client_id: pkce.appId, code_verifier: pkce.verifier,
|
|
5691
5503
|
});
|
|
5692
5504
|
let res;
|
|
5693
5505
|
try {
|
|
@@ -5711,10 +5523,20 @@ async function exchangeCode(code, verifier, redirectUri) {
|
|
|
5711
5523
|
const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
|
|
5712
5524
|
const refreshToken = boundedRefreshTokenField(data);
|
|
5713
5525
|
const user = userFromIdToken(idToken);
|
|
5714
|
-
|
|
5526
|
+
// Reinitialization while the request was in flight must not install this
|
|
5527
|
+
// app's result under a different app's session manager/configuration.
|
|
5528
|
+
beforeCommit();
|
|
5529
|
+
const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, beforeCommit);
|
|
5715
5530
|
// Adopt on EVERY session surface (see establishInlineSession) - hosted
|
|
5716
5531
|
// popup/redirect completions all land here, so this closes their gap too.
|
|
5717
|
-
|
|
5532
|
+
try {
|
|
5533
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit });
|
|
5534
|
+
}
|
|
5535
|
+
catch (error) {
|
|
5536
|
+
if (typeof rollback === 'function')
|
|
5537
|
+
await rollback();
|
|
5538
|
+
throw error;
|
|
5539
|
+
}
|
|
5718
5540
|
// OIDC login does establish the issuer's Better Auth browser cookie. Record
|
|
5719
5541
|
// that fact separately from the provider name so logout clears the cookie
|
|
5720
5542
|
// and returns through the registered app origin. Stamped AFTER the adoption
|
|
@@ -6065,7 +5887,7 @@ async function getAuthProvider(config) {
|
|
|
6065
5887
|
// manager as legacy email sessions. Human login should be started
|
|
6066
5888
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
6067
5889
|
// logout surface for those sessions.
|
|
6068
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-
|
|
5890
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-Czo-xM6n.js'); })).EmailAuthProvider();
|
|
6069
5891
|
break;
|
|
6070
5892
|
case "phantom": {
|
|
6071
5893
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -6107,7 +5929,7 @@ async function getAuthProvider(config) {
|
|
|
6107
5929
|
// (bounded-login-widget.ts), so registration never counts as
|
|
6108
5930
|
// evidence the user has a wallet.
|
|
6109
5931
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
6110
|
-
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
5932
|
+
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-C7Q0haGY.js'); });
|
|
6111
5933
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
6112
5934
|
break;
|
|
6113
5935
|
}
|
|
@@ -6120,7 +5942,7 @@ async function getAuthProvider(config) {
|
|
|
6120
5942
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
6121
5943
|
// to point at a specific wallet or set chainId/domain.
|
|
6122
5944
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
6123
|
-
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-
|
|
5945
|
+
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-_UNpc_hM.js'); });
|
|
6124
5946
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
6125
5947
|
break;
|
|
6126
5948
|
}
|
|
@@ -6145,7 +5967,7 @@ async function getAuthProvider(config) {
|
|
|
6145
5967
|
break;
|
|
6146
5968
|
case "guest":
|
|
6147
5969
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
6148
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider
|
|
5970
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-DJ5nCGjq.js'); })).GuestAuthProvider();
|
|
6149
5971
|
break;
|
|
6150
5972
|
}
|
|
6151
5973
|
if (!currentAuthProvider) {
|
|
@@ -6207,7 +6029,7 @@ async function signInAnonymously() {
|
|
|
6207
6029
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
6208
6030
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
6209
6031
|
// so bundle-splitting can see it.
|
|
6210
|
-
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider
|
|
6032
|
+
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-DJ5nCGjq.js'); })).GuestAuthProvider();
|
|
6211
6033
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
6212
6034
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
6213
6035
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6378,7 +6200,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6378
6200
|
if (!sharedWalletReady) {
|
|
6379
6201
|
sharedWalletReady = (async () => {
|
|
6380
6202
|
await core.getConfig();
|
|
6381
|
-
await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6203
|
+
await Promise.resolve().then(function () { return require('./injected-wallet-provider-C7Q0haGY.js'); });
|
|
6382
6204
|
})().catch((error) => {
|
|
6383
6205
|
sharedWalletReady = null;
|
|
6384
6206
|
throw error;
|
|
@@ -6661,6 +6483,10 @@ let authLoadingListeners = [];
|
|
|
6661
6483
|
let initCompleted = false;
|
|
6662
6484
|
let isAuthLoading = false;
|
|
6663
6485
|
let authReadyPromise = null;
|
|
6486
|
+
let initializingConfig = null;
|
|
6487
|
+
let initializationGeneration = 0;
|
|
6488
|
+
/** Internal guard for a login result arriving after the app reconfigured the SDK. */
|
|
6489
|
+
function getInitializationGeneration() { return initializationGeneration; }
|
|
6664
6490
|
// This file acts as a middleware for the global state of the SDK
|
|
6665
6491
|
// This mostly involves setting up the AuthProvider and managing the current user
|
|
6666
6492
|
async function initialize(newConfig) {
|
|
@@ -6686,6 +6512,19 @@ async function initialize(newConfig) {
|
|
|
6686
6512
|
authProviderInstance = await getAuthProvider(newConfig);
|
|
6687
6513
|
// Initialize config with auth provider
|
|
6688
6514
|
await core.init(Object.assign(Object.assign({}, newConfig), { authProvider: authProviderInstance }));
|
|
6515
|
+
// A return establishes its own provider/session, or hands off a popup.
|
|
6516
|
+
// Restoring the old provider afterwards would overwrite the new login.
|
|
6517
|
+
try {
|
|
6518
|
+
if ((await completePendingLogin()).handled) {
|
|
6519
|
+
initCompleted = true;
|
|
6520
|
+
return;
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
catch (_a) {
|
|
6524
|
+
// A cancelled/failed return is a login failure, not a broken SDK.
|
|
6525
|
+
// The completion layer keeps it in getLoginReturnError(); restore
|
|
6526
|
+
// any existing session and leave sign-in/readiness usable for retry.
|
|
6527
|
+
}
|
|
6689
6528
|
// Now that core is initialized, update the auth method on the config
|
|
6690
6529
|
const authMethod = getCurrentAuthMethod();
|
|
6691
6530
|
if (authMethod) {
|
|
@@ -6717,7 +6556,42 @@ async function initialize(newConfig) {
|
|
|
6717
6556
|
setAuthLoading(false);
|
|
6718
6557
|
}
|
|
6719
6558
|
}
|
|
6559
|
+
// Compare data options by value, but preserve identity for callbacks and class
|
|
6560
|
+
// instances such as providers. Comparing only app/endpoints would silently drop
|
|
6561
|
+
// a concurrent change to wallet policy or provider configuration.
|
|
6562
|
+
function sameConfigValue(a, b, seen = new WeakMap()) {
|
|
6563
|
+
var _a;
|
|
6564
|
+
if (Object.is(a, b))
|
|
6565
|
+
return true;
|
|
6566
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object')
|
|
6567
|
+
return false;
|
|
6568
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
6569
|
+
return false;
|
|
6570
|
+
if (Array.isArray(a) && a.length !== b.length)
|
|
6571
|
+
return false;
|
|
6572
|
+
if (!Array.isArray(a)) {
|
|
6573
|
+
for (const value of [a, b]) {
|
|
6574
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6575
|
+
if (prototype !== null && ((_a = prototype.constructor) === null || _a === void 0 ? void 0 : _a.name) !== 'Object')
|
|
6576
|
+
return false;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
if (seen.has(a))
|
|
6580
|
+
return seen.get(a) === b;
|
|
6581
|
+
seen.set(a, b);
|
|
6582
|
+
const left = Object.keys(a), right = Object.keys(b);
|
|
6583
|
+
return left.length === right.length && left.every((key) => Object.prototype.hasOwnProperty.call(b, key)
|
|
6584
|
+
&& sameConfigValue(a[key], b[key], seen));
|
|
6585
|
+
}
|
|
6720
6586
|
function init(newConfig) {
|
|
6587
|
+
if (initializingConfig) {
|
|
6588
|
+
if (sameConfigValue(initializingConfig, newConfig)) {
|
|
6589
|
+
return authReadyPromise;
|
|
6590
|
+
}
|
|
6591
|
+
return Promise.reject(new Error('SDK initialization is in progress. Await init() before changing its configuration.'));
|
|
6592
|
+
}
|
|
6593
|
+
initializingConfig = Object.assign({}, newConfig);
|
|
6594
|
+
initializationGeneration++;
|
|
6721
6595
|
let resolveReady;
|
|
6722
6596
|
let rejectReady;
|
|
6723
6597
|
const ready = new Promise((resolve, reject) => {
|
|
@@ -6728,7 +6602,7 @@ function init(newConfig) {
|
|
|
6728
6602
|
// auth-loading listener to call waitForAuthReady() synchronously when init()
|
|
6729
6603
|
// announces that session restoration has begun.
|
|
6730
6604
|
authReadyPromise = ready;
|
|
6731
|
-
void initialize(
|
|
6605
|
+
void initialize(initializingConfig).then(() => { initializingConfig = null; resolveReady(); }, (error) => { initializingConfig = null; rejectReady(error); });
|
|
6732
6606
|
return ready;
|
|
6733
6607
|
}
|
|
6734
6608
|
/**
|
|
@@ -8722,12 +8596,12 @@ exports.bs58 = bs58;
|
|
|
8722
8596
|
exports.bufferExports = bufferExports;
|
|
8723
8597
|
exports.builds = builds;
|
|
8724
8598
|
exports.completeLoginFromRedirect = completeLoginFromRedirect;
|
|
8725
|
-
exports.completeLoginInPopup = completeLoginInPopup;
|
|
8726
8599
|
exports.confirmAndCheckTransaction = confirmAndCheckTransaction;
|
|
8727
8600
|
exports.getAuthLoading = getAuthLoading;
|
|
8728
8601
|
exports.getAuthProvider = getAuthProvider;
|
|
8729
8602
|
exports.getCurrentUser = getCurrentUser;
|
|
8730
8603
|
exports.getIdToken = getIdToken;
|
|
8604
|
+
exports.getLoginReturnError = getLoginReturnError;
|
|
8731
8605
|
exports.getPlatform = getPlatform;
|
|
8732
8606
|
exports.getWallets = getWallets;
|
|
8733
8607
|
exports.hasTurnkeyWallet = hasTurnkeyWallet;
|
|
@@ -8762,4 +8636,4 @@ exports.useAuth = useAuth;
|
|
|
8762
8636
|
exports.useBuildRun = useBuildRun;
|
|
8763
8637
|
exports.useQuery = useQuery;
|
|
8764
8638
|
exports.waitForAuthReady = waitForAuthReady;
|
|
8765
|
-
//# sourceMappingURL=index.native-
|
|
8639
|
+
//# sourceMappingURL=index.native-BpcNwWQX.js.map
|