@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
|
@@ -181,10 +181,14 @@ function getPlatform() {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
|
-
* OffchainAuthProvider wraps a real auth provider (
|
|
184
|
+
* OffchainAuthProvider wraps a real auth provider (Turnkey, Phantom, ...) for the
|
|
185
|
+
* poofnet environment.
|
|
185
186
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
187
|
+
* Poofnet has no chain, but its onchain writes are still signed: the worker
|
|
188
|
+
* issues an unsendable approval message and verifies the wallet's signature
|
|
189
|
+
* before committing. So `signTransaction` and `signMessage` go straight to the
|
|
190
|
+
* wrapped wallet, whose own prompt is the only one the user sees. Only a raw
|
|
191
|
+
* submit - which needs a real cluster - is refused here.
|
|
188
192
|
*/
|
|
189
193
|
class OffchainAuthProvider {
|
|
190
194
|
constructor(wrappedProvider, config = {}) {
|
|
@@ -219,10 +223,7 @@ class OffchainAuthProvider {
|
|
|
219
223
|
return null;
|
|
220
224
|
}
|
|
221
225
|
async signTransaction(tx) {
|
|
222
|
-
|
|
223
|
-
await this.showUnsupportedTransactionModal();
|
|
224
|
-
}
|
|
225
|
-
throw new Error('Poofnet does not support real Solana transactions. Deploy your project to mainnet to use this feature.');
|
|
226
|
+
return this.wrappedProvider.signTransaction(tx);
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
228
229
|
* Sign and submit transaction - not supported in poofnet environment.
|
|
@@ -247,21 +248,11 @@ class OffchainAuthProvider {
|
|
|
247
248
|
}
|
|
248
249
|
await this.wrappedProvider.exportWallet(address);
|
|
249
250
|
}
|
|
250
|
-
// ============ signMessage delegates to wrapped provider ============
|
|
251
251
|
async signMessage(message) {
|
|
252
252
|
var _a, _b, _c, _d, _e, _f;
|
|
253
253
|
(_b = (_a = this.config).onSigningStart) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
254
254
|
try {
|
|
255
|
-
// On RN (no DOM), auto-confirm — there's no modal to show.
|
|
256
|
-
// On web, show the confirmation modal.
|
|
257
|
-
if (getPlatform().hasDOM) {
|
|
258
|
-
const confirmed = await this.showTransactionModal(message);
|
|
259
|
-
if (!confirmed) {
|
|
260
|
-
throw new Error("Transaction rejected by user");
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
255
|
const signature = await this.wrappedProvider.signMessage(message);
|
|
264
|
-
// Callback: signing complete
|
|
265
256
|
(_d = (_c = this.config).onSigningComplete) === null || _d === void 0 ? void 0 : _d.call(_c, signature);
|
|
266
257
|
return signature;
|
|
267
258
|
}
|
|
@@ -352,99 +343,6 @@ class OffchainAuthProvider {
|
|
|
352
343
|
document.addEventListener("keydown", escHandler);
|
|
353
344
|
});
|
|
354
345
|
}
|
|
355
|
-
async showTransactionModal(message) {
|
|
356
|
-
return new Promise((resolve) => {
|
|
357
|
-
// Parse the transaction message for display
|
|
358
|
-
let parsed = {};
|
|
359
|
-
try {
|
|
360
|
-
parsed = JSON.parse(message);
|
|
361
|
-
}
|
|
362
|
-
catch (_a) {
|
|
363
|
-
// If not JSON, just show raw message
|
|
364
|
-
}
|
|
365
|
-
// Create modal container. Captured in this closure so every teardown
|
|
366
|
-
// path acts on exactly this prompt, never on a newer one that may have
|
|
367
|
-
// replaced a shared field.
|
|
368
|
-
const container = document.createElement("div");
|
|
369
|
-
container.id = "poofnet-tx-modal";
|
|
370
|
-
container.appendChild(this.buildTransactionModalContent(parsed, message));
|
|
371
|
-
document.body.appendChild(container);
|
|
372
|
-
// Add styles
|
|
373
|
-
const style = document.createElement("style");
|
|
374
|
-
style.textContent = this.getModalStyles();
|
|
375
|
-
container.appendChild(style);
|
|
376
|
-
// Mark all OTHER body children as inert to disable focus traps from
|
|
377
|
-
// other modals, recording only the ones this modal changed (skipping
|
|
378
|
-
// any already-inert) so cleanup restores exactly what we set and never
|
|
379
|
-
// re-enables app-owned inert regions.
|
|
380
|
-
const selfInerted = [];
|
|
381
|
-
document.body.childNodes.forEach((child) => {
|
|
382
|
-
if (child !== container &&
|
|
383
|
-
child instanceof HTMLElement &&
|
|
384
|
-
!child.hasAttribute("inert")) {
|
|
385
|
-
child.setAttribute("inert", "");
|
|
386
|
-
selfInerted.push(child);
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
// Animate in
|
|
390
|
-
requestAnimationFrame(() => {
|
|
391
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
392
|
-
const content = container.querySelector(".poofnet-modal-content");
|
|
393
|
-
if (overlay)
|
|
394
|
-
overlay.style.opacity = "1";
|
|
395
|
-
if (content) {
|
|
396
|
-
content.style.opacity = "1";
|
|
397
|
-
content.style.transform = "translateY(0)";
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
// Single idempotent teardown for every settlement path: removes the key
|
|
401
|
-
// handler, restores only self-added inert, and removes exactly this
|
|
402
|
-
// captured container after the exit animation.
|
|
403
|
-
let settled = false;
|
|
404
|
-
const escHandler = (e) => {
|
|
405
|
-
if (e.key === "Escape")
|
|
406
|
-
settle(false);
|
|
407
|
-
};
|
|
408
|
-
const cleanup = () => {
|
|
409
|
-
if (settled)
|
|
410
|
-
return;
|
|
411
|
-
settled = true;
|
|
412
|
-
document.removeEventListener("keydown", escHandler);
|
|
413
|
-
for (const el of selfInerted)
|
|
414
|
-
el.removeAttribute("inert");
|
|
415
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
416
|
-
const content = container.querySelector(".poofnet-modal-content");
|
|
417
|
-
if (overlay)
|
|
418
|
-
overlay.style.opacity = "0";
|
|
419
|
-
if (content) {
|
|
420
|
-
content.style.opacity = "0";
|
|
421
|
-
content.style.transform = "translateY(20px)";
|
|
422
|
-
}
|
|
423
|
-
setTimeout(() => container.remove(), 200);
|
|
424
|
-
};
|
|
425
|
-
const settle = (confirmed) => {
|
|
426
|
-
cleanup();
|
|
427
|
-
resolve(confirmed);
|
|
428
|
-
};
|
|
429
|
-
// Handle confirm
|
|
430
|
-
const confirmBtn = container.querySelector("#poofnet-confirm-btn");
|
|
431
|
-
confirmBtn === null || confirmBtn === void 0 ? void 0 : confirmBtn.addEventListener("click", () => settle(true));
|
|
432
|
-
// Handle cancel
|
|
433
|
-
const cancelBtn = container.querySelector("#poofnet-cancel-btn");
|
|
434
|
-
cancelBtn === null || cancelBtn === void 0 ? void 0 : cancelBtn.addEventListener("click", () => settle(false));
|
|
435
|
-
// Handle close button
|
|
436
|
-
const closeBtn = container.querySelector("#poofnet-close-btn");
|
|
437
|
-
closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener("click", () => settle(false));
|
|
438
|
-
// Handle overlay click
|
|
439
|
-
const overlay = container.querySelector(".poofnet-modal-overlay");
|
|
440
|
-
overlay === null || overlay === void 0 ? void 0 : overlay.addEventListener("click", (e) => {
|
|
441
|
-
if (e.target === overlay)
|
|
442
|
-
settle(false);
|
|
443
|
-
});
|
|
444
|
-
// Handle escape key
|
|
445
|
-
document.addEventListener("keydown", escHandler);
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
346
|
createElement(tag, className, text) {
|
|
449
347
|
const el = document.createElement(tag);
|
|
450
348
|
if (className)
|
|
@@ -499,113 +397,6 @@ class OffchainAuthProvider {
|
|
|
499
397
|
footer.appendChild(close);
|
|
500
398
|
return overlay;
|
|
501
399
|
}
|
|
502
|
-
formatPath(path) {
|
|
503
|
-
const parts = path.split("/").filter(Boolean);
|
|
504
|
-
if (parts.length >= 2) {
|
|
505
|
-
return {
|
|
506
|
-
collection: parts[0],
|
|
507
|
-
documentId: parts[parts.length - 1],
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
return { collection: parts[0] || path, documentId: null };
|
|
511
|
-
}
|
|
512
|
-
formatFieldValue(value) {
|
|
513
|
-
if (value === null || value === undefined)
|
|
514
|
-
return "null";
|
|
515
|
-
if (typeof value === "string") {
|
|
516
|
-
return value.length > 30
|
|
517
|
-
? `"${value.slice(0, 30)}..."`
|
|
518
|
-
: `"${value}"`;
|
|
519
|
-
}
|
|
520
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
521
|
-
return String(value);
|
|
522
|
-
if (Array.isArray(value))
|
|
523
|
-
return `[${value.length} items]`;
|
|
524
|
-
if (typeof value === "object")
|
|
525
|
-
return `{${Object.keys(value).length} fields}`;
|
|
526
|
-
return String(value);
|
|
527
|
-
}
|
|
528
|
-
getActionDescription(type, collection, isNew) {
|
|
529
|
-
if (type === "delete")
|
|
530
|
-
return `Delete from ${collection}`;
|
|
531
|
-
return isNew ? `Create in ${collection}` : `Update ${collection}`;
|
|
532
|
-
}
|
|
533
|
-
buildTransactionModalContent(parsed, rawMessage) {
|
|
534
|
-
const instructions = parsed.instructions || [];
|
|
535
|
-
// Group instructions by type for summary
|
|
536
|
-
const sets = instructions.filter((i) => i.type === "set");
|
|
537
|
-
const deletes = instructions.filter((i) => i.type === "delete");
|
|
538
|
-
// Create summary text
|
|
539
|
-
let summaryText = "";
|
|
540
|
-
if (sets.length > 0 && deletes.length > 0) {
|
|
541
|
-
summaryText = `${sets.length} update${sets.length > 1 ? "s" : ""}, ${deletes.length} delete${deletes.length > 1 ? "s" : ""}`;
|
|
542
|
-
}
|
|
543
|
-
else if (sets.length > 0) {
|
|
544
|
-
summaryText = `${sets.length} update${sets.length > 1 ? "s" : ""}`;
|
|
545
|
-
}
|
|
546
|
-
else if (deletes.length > 0) {
|
|
547
|
-
summaryText = `${deletes.length} delete${deletes.length > 1 ? "s" : ""}`;
|
|
548
|
-
}
|
|
549
|
-
const { overlay, body, footer } = this.buildModalShell("🔐", "Confirm Transaction");
|
|
550
|
-
if (instructions.length > 0) {
|
|
551
|
-
if (summaryText) {
|
|
552
|
-
this.appendTextElement(body, "div", "poofnet-summary", summaryText);
|
|
553
|
-
}
|
|
554
|
-
const list = this.createElement("div", "poofnet-instructions-list");
|
|
555
|
-
for (const inst of instructions) {
|
|
556
|
-
list.appendChild(this.buildInstructionElement(inst));
|
|
557
|
-
}
|
|
558
|
-
body.appendChild(list);
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
const section = this.createElement("div", "poofnet-section");
|
|
562
|
-
this.appendTextElement(section, "h3", "poofnet-section-title", "Message");
|
|
563
|
-
const raw = this.createElement("pre", "poofnet-raw-message", rawMessage.slice(0, 200) + (rawMessage.length > 200 ? "..." : ""));
|
|
564
|
-
section.appendChild(raw);
|
|
565
|
-
body.appendChild(section);
|
|
566
|
-
}
|
|
567
|
-
body.appendChild(this.buildInfoBox("This is a simulated transaction. No real fees will be charged."));
|
|
568
|
-
const cancel = this.createElement("button", "poofnet-btn poofnet-btn-cancel", "Cancel");
|
|
569
|
-
cancel.id = "poofnet-cancel-btn";
|
|
570
|
-
cancel.type = "button";
|
|
571
|
-
footer.appendChild(cancel);
|
|
572
|
-
const confirm = this.createElement("button", "poofnet-btn poofnet-btn-confirm", "Confirm");
|
|
573
|
-
confirm.id = "poofnet-confirm-btn";
|
|
574
|
-
confirm.type = "button";
|
|
575
|
-
footer.appendChild(confirm);
|
|
576
|
-
return overlay;
|
|
577
|
-
}
|
|
578
|
-
buildInstructionElement(inst) {
|
|
579
|
-
const { collection, documentId } = this.formatPath(inst.path);
|
|
580
|
-
const isDelete = inst.type === "delete";
|
|
581
|
-
const card = this.createElement("div", `poofnet-instruction ${isDelete ? "poofnet-action-delete" : "poofnet-action-set"}`);
|
|
582
|
-
const header = this.createElement("div", "poofnet-instruction-header");
|
|
583
|
-
this.appendTextElement(header, "span", "poofnet-instruction-icon", isDelete ? "🗑️" : "✏️");
|
|
584
|
-
this.appendTextElement(header, "span", "poofnet-instruction-action", isDelete ? "Delete" : "Save");
|
|
585
|
-
this.appendTextElement(header, "span", "poofnet-instruction-collection", collection);
|
|
586
|
-
card.appendChild(header);
|
|
587
|
-
if (documentId) {
|
|
588
|
-
this.appendTextElement(card, "div", "poofnet-instruction-id", `ID: ${documentId}`);
|
|
589
|
-
}
|
|
590
|
-
if (inst.data && !isDelete) {
|
|
591
|
-
const fieldKeys = Object.keys(inst.data).filter((key) => !key.startsWith("_"));
|
|
592
|
-
const visibleFields = fieldKeys.slice(0, 4);
|
|
593
|
-
if (visibleFields.length > 0) {
|
|
594
|
-
const fields = this.createElement("div", "poofnet-fields");
|
|
595
|
-
for (const key of visibleFields) {
|
|
596
|
-
const row = this.createElement("div", "poofnet-field");
|
|
597
|
-
this.appendTextElement(row, "span", "poofnet-field-key", `${key}:`);
|
|
598
|
-
this.appendTextElement(row, "span", "poofnet-field-value", this.formatFieldValue(inst.data[key]));
|
|
599
|
-
fields.appendChild(row);
|
|
600
|
-
}
|
|
601
|
-
if (fieldKeys.length > 4) {
|
|
602
|
-
this.appendTextElement(fields, "div", "poofnet-field-more", `+${fieldKeys.length - 4} more fields`);
|
|
603
|
-
}
|
|
604
|
-
card.appendChild(fields);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return card;
|
|
608
|
-
}
|
|
609
400
|
getModalStyles() {
|
|
610
401
|
return `
|
|
611
402
|
/* Ensure inert elements remain visible while being non-interactive */
|
|
@@ -702,130 +493,6 @@ class OffchainAuthProvider {
|
|
|
702
493
|
flex: 1;
|
|
703
494
|
}
|
|
704
495
|
|
|
705
|
-
.poofnet-summary {
|
|
706
|
-
font-size: 13px;
|
|
707
|
-
color: #6b7280;
|
|
708
|
-
margin-bottom: 12px;
|
|
709
|
-
text-align: center;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
.poofnet-section {
|
|
713
|
-
margin-bottom: 16px;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.poofnet-section-title {
|
|
717
|
-
font-size: 12px;
|
|
718
|
-
font-weight: 600;
|
|
719
|
-
color: #666;
|
|
720
|
-
text-transform: uppercase;
|
|
721
|
-
letter-spacing: 0.5px;
|
|
722
|
-
margin: 0 0 8px;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
.poofnet-instructions-list {
|
|
726
|
-
display: flex;
|
|
727
|
-
flex-direction: column;
|
|
728
|
-
gap: 10px;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
.poofnet-instruction {
|
|
732
|
-
background: #f9fafb;
|
|
733
|
-
border-radius: 10px;
|
|
734
|
-
padding: 12px;
|
|
735
|
-
border-left: 3px solid #8b5cf6;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
.poofnet-instruction.poofnet-action-delete {
|
|
739
|
-
border-left-color: #ef4444;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
.poofnet-instruction-header {
|
|
743
|
-
display: flex;
|
|
744
|
-
align-items: center;
|
|
745
|
-
gap: 8px;
|
|
746
|
-
margin-bottom: 4px;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
.poofnet-instruction-icon {
|
|
750
|
-
font-size: 16px;
|
|
751
|
-
flex-shrink: 0;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
.poofnet-instruction-action {
|
|
755
|
-
font-size: 13px;
|
|
756
|
-
font-weight: 600;
|
|
757
|
-
color: #8b5cf6;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
.poofnet-action-delete .poofnet-instruction-action {
|
|
761
|
-
color: #ef4444;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
.poofnet-instruction-collection {
|
|
765
|
-
font-size: 13px;
|
|
766
|
-
font-weight: 500;
|
|
767
|
-
color: #374151;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
.poofnet-instruction-id {
|
|
771
|
-
font-size: 11px;
|
|
772
|
-
color: #9ca3af;
|
|
773
|
-
font-family: monospace;
|
|
774
|
-
margin-bottom: 8px;
|
|
775
|
-
word-break: break-all;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
.poofnet-fields {
|
|
779
|
-
background: white;
|
|
780
|
-
border-radius: 6px;
|
|
781
|
-
padding: 8px 10px;
|
|
782
|
-
margin-top: 8px;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
.poofnet-field {
|
|
786
|
-
display: flex;
|
|
787
|
-
gap: 8px;
|
|
788
|
-
padding: 4px 0;
|
|
789
|
-
font-size: 12px;
|
|
790
|
-
border-bottom: 1px solid #f3f4f6;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.poofnet-field:last-child {
|
|
794
|
-
border-bottom: none;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
.poofnet-field-key {
|
|
798
|
-
color: #6b7280;
|
|
799
|
-
font-weight: 500;
|
|
800
|
-
flex-shrink: 0;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
.poofnet-field-value {
|
|
804
|
-
color: #374151;
|
|
805
|
-
word-break: break-all;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
.poofnet-field-more {
|
|
809
|
-
font-size: 11px;
|
|
810
|
-
color: #9ca3af;
|
|
811
|
-
padding-top: 4px;
|
|
812
|
-
font-style: italic;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
.poofnet-raw-message {
|
|
816
|
-
background: #f9fafb;
|
|
817
|
-
border-radius: 8px;
|
|
818
|
-
padding: 12px;
|
|
819
|
-
font-size: 11px;
|
|
820
|
-
font-family: monospace;
|
|
821
|
-
color: #4b5563;
|
|
822
|
-
white-space: pre-wrap;
|
|
823
|
-
word-break: break-all;
|
|
824
|
-
margin: 0;
|
|
825
|
-
max-height: 120px;
|
|
826
|
-
overflow-y: auto;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
496
|
.poofnet-info-box {
|
|
830
497
|
display: flex;
|
|
831
498
|
align-items: center;
|
|
@@ -874,15 +541,6 @@ class OffchainAuthProvider {
|
|
|
874
541
|
.poofnet-btn-cancel:hover {
|
|
875
542
|
background: #e5e7eb;
|
|
876
543
|
}
|
|
877
|
-
|
|
878
|
-
.poofnet-btn-confirm {
|
|
879
|
-
background: #8b5cf6;
|
|
880
|
-
color: white;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
.poofnet-btn-confirm:hover {
|
|
884
|
-
background: #7c3aed;
|
|
885
|
-
}
|
|
886
544
|
`;
|
|
887
545
|
}
|
|
888
546
|
}
|
|
@@ -5151,9 +4809,18 @@ const OIDC_NO_WALLET = 'This session has no Turnkey wallet. Sign in again to pro
|
|
|
5151
4809
|
// same tab (redirect + popup, or two popups) each own a distinct record, so a second
|
|
5152
4810
|
// attempt cannot clobber the first's verifier/state/redirectUri.
|
|
5153
4811
|
const PKCE_STORE_PREFIX = 'bounded.oidc.pkce';
|
|
4812
|
+
const STATE_PREFIX = 'bounded.';
|
|
4813
|
+
const LOGIN_TIMEOUT_MS = 10 * 60000;
|
|
4814
|
+
const CALLBACK_PARAMS = ['code', 'state', 'error', 'error_description', 'iss'];
|
|
5154
4815
|
function pkceKey(state) {
|
|
5155
4816
|
return `${PKCE_STORE_PREFIX}.${state}`;
|
|
5156
4817
|
}
|
|
4818
|
+
function removeAttempt(state) {
|
|
4819
|
+
try {
|
|
4820
|
+
sessionStorage.removeItem(pkceKey(state));
|
|
4821
|
+
}
|
|
4822
|
+
catch ( /* Storage may have been disabled mid-login. */_a) { /* Storage may have been disabled mid-login. */ }
|
|
4823
|
+
}
|
|
5157
4824
|
const ISSUER_SESSION_KIND_KEY = 'bounded_issuer_session_kind';
|
|
5158
4825
|
// Active session store (Web on web, RN on React Native) — selected by config,
|
|
5159
4826
|
// not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
|
|
@@ -5262,6 +4929,165 @@ async function makePkce() {
|
|
|
5262
4929
|
function randomState() {
|
|
5263
4930
|
return b64url(getPlatform().getRandomBytes(16));
|
|
5264
4931
|
}
|
|
4932
|
+
let pendingCompletion;
|
|
4933
|
+
let loginReturnError;
|
|
4934
|
+
/** The last OAuth return failure in this initialization, or null. Initialization
|
|
4935
|
+
* remains usable so the UI can display this error and offer a fresh sign-in. */
|
|
4936
|
+
function getLoginReturnError() {
|
|
4937
|
+
return (loginReturnError === null || loginReturnError === void 0 ? void 0 : loginReturnError.generation) === getInitializationGeneration() ? loginReturnError.error : null;
|
|
4938
|
+
}
|
|
4939
|
+
// A hosted page can contain both an app bundle and the platform widget's SDK.
|
|
4940
|
+
// They share one code exchange, while adopting the session in their own registries.
|
|
4941
|
+
const COMPLETIONS = Symbol.for('bounded.oidc.completions');
|
|
4942
|
+
function cleanCallbackUrl(url) {
|
|
4943
|
+
const clean = new URL(url);
|
|
4944
|
+
for (const key of CALLBACK_PARAMS)
|
|
4945
|
+
clean.searchParams.delete(key);
|
|
4946
|
+
return clean;
|
|
4947
|
+
}
|
|
4948
|
+
async function validateAttempt(pkce, callback) {
|
|
4949
|
+
const cfg = await getConfig();
|
|
4950
|
+
if (!pkce || typeof pkce.verifier !== 'string' || !pkce.verifier ||
|
|
4951
|
+
pkce.appId !== cfg.appId || pkce.issuer !== await issuerBase()) {
|
|
4952
|
+
throw new Error('Login belongs to a different app or auth server. Start sign-in again.');
|
|
4953
|
+
}
|
|
4954
|
+
if (!Number.isFinite(pkce.createdAt) || Date.now() - pkce.createdAt > LOGIN_TIMEOUT_MS || pkce.createdAt > Date.now()) {
|
|
4955
|
+
throw new Error('Login expired. Start sign-in again.');
|
|
4956
|
+
}
|
|
4957
|
+
if (callback && (cleanCallbackUrl(callback).href !== cleanCallbackUrl(new URL(pkce.redirectUri)).href ||
|
|
4958
|
+
(callback.searchParams.has('iss') && callback.searchParams.get('iss') !== pkce.issuer))) {
|
|
4959
|
+
throw new Error('Login returned to a different address or from a different auth server. Start sign-in again.');
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
// The receiver acknowledges only after checking origin, window and state. An
|
|
4963
|
+
// absent/disconnected opener must never leave a popup claiming success.
|
|
4964
|
+
async function handOffPopup(url, state) {
|
|
4965
|
+
const cfg = await getConfig();
|
|
4966
|
+
const issuer = await issuerBase();
|
|
4967
|
+
const opener = window.opener;
|
|
4968
|
+
if (!opener || opener === window || opener.closed) {
|
|
4969
|
+
throw new Error('The original sign-in window is unavailable. Close this window and sign in again in the original tab.');
|
|
4970
|
+
}
|
|
4971
|
+
return new Promise((resolve, reject) => {
|
|
4972
|
+
const cleanup = () => { clearTimeout(timer); window.removeEventListener('message', onMessage); };
|
|
4973
|
+
const onMessage = (event) => {
|
|
4974
|
+
var _a;
|
|
4975
|
+
if (event.source !== opener || event.origin !== url.origin ||
|
|
4976
|
+
((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) !== 'bounded-oidc-accepted' || event.data.state !== state)
|
|
4977
|
+
return;
|
|
4978
|
+
cleanup();
|
|
4979
|
+
try {
|
|
4980
|
+
window.close();
|
|
4981
|
+
}
|
|
4982
|
+
catch ( /* The opener also closes its own popup handle. */_b) { /* The opener also closes its own popup handle. */ }
|
|
4983
|
+
resolve();
|
|
4984
|
+
};
|
|
4985
|
+
const timer = setTimeout(() => {
|
|
4986
|
+
cleanup();
|
|
4987
|
+
reject(new Error('Could not reach the original sign-in tab. Close this window and start sign-in again.'));
|
|
4988
|
+
}, 10000);
|
|
4989
|
+
window.addEventListener('message', onMessage);
|
|
4990
|
+
try {
|
|
4991
|
+
opener.postMessage({ type: 'bounded-oidc-code', state, code: url.searchParams.get('code'),
|
|
4992
|
+
error: url.searchParams.get('error'), errorDescription: url.searchParams.get('error_description'),
|
|
4993
|
+
callbackUrl: url.href, appId: cfg.appId, issuer }, url.origin);
|
|
4994
|
+
}
|
|
4995
|
+
catch (error) {
|
|
4996
|
+
cleanup();
|
|
4997
|
+
reject(error);
|
|
4998
|
+
}
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
async function finishLoginReturn(url, state) {
|
|
5002
|
+
try {
|
|
5003
|
+
if (!/^bounded\.(popup|redirect)\.[A-Za-z0-9_-]{22}$/.test(state)) {
|
|
5004
|
+
throw new Error('Invalid login state. Start sign-in again.');
|
|
5005
|
+
}
|
|
5006
|
+
if (state.startsWith('bounded.popup.')) {
|
|
5007
|
+
await handOffPopup(url, state);
|
|
5008
|
+
return { handled: true, user: null };
|
|
5009
|
+
}
|
|
5010
|
+
const raw = sessionStorage.getItem(pkceKey(state));
|
|
5011
|
+
if (!raw)
|
|
5012
|
+
throw new Error('Missing login attempt. Start sign-in again in this tab.');
|
|
5013
|
+
const pkce = JSON.parse(raw);
|
|
5014
|
+
if (pkce.state !== state)
|
|
5015
|
+
throw new Error('OAuth state mismatch. Start sign-in again.');
|
|
5016
|
+
await validateAttempt(pkce, url);
|
|
5017
|
+
const error = url.searchParams.get('error');
|
|
5018
|
+
if (error)
|
|
5019
|
+
throw new Error(url.searchParams.get('error_description') || `Login failed: ${error}`);
|
|
5020
|
+
const user = await exchangeCode(url.searchParams.get('code'), pkce);
|
|
5021
|
+
return { handled: true, user, idToken: sessionManager().getIdToken() };
|
|
5022
|
+
}
|
|
5023
|
+
finally {
|
|
5024
|
+
removeAttempt(state);
|
|
5025
|
+
const clean = cleanCallbackUrl(url);
|
|
5026
|
+
if (window.location.href === url.href) {
|
|
5027
|
+
window.history.replaceState(window.history.state, document.title, clean.pathname + clean.search + clean.hash);
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
/** Internal initialization step. Unrelated OAuth callbacks are left untouched. */
|
|
5032
|
+
function completePendingLogin() {
|
|
5033
|
+
if (!hasDOM$1())
|
|
5034
|
+
return Promise.resolve({ handled: false, user: null });
|
|
5035
|
+
const url = new URL(window.location.href);
|
|
5036
|
+
const state = url.searchParams.get('state');
|
|
5037
|
+
if (!(state === null || state === void 0 ? void 0 : state.startsWith(STATE_PREFIX)) || (!url.searchParams.get('code') && !url.searchParams.get('error'))) {
|
|
5038
|
+
return Promise.resolve({ handled: false, user: null });
|
|
5039
|
+
}
|
|
5040
|
+
const generation = getInitializationGeneration();
|
|
5041
|
+
if ((pendingCompletion === null || pendingCompletion === void 0 ? void 0 : pendingCompletion.href) === url.href && pendingCompletion.generation === generation)
|
|
5042
|
+
return pendingCompletion.promise;
|
|
5043
|
+
const promise = finishSharedLoginReturn(url, state).catch((error) => {
|
|
5044
|
+
loginReturnError = { generation, error: error instanceof Error ? error : new Error(String(error)) };
|
|
5045
|
+
throw loginReturnError.error;
|
|
5046
|
+
}).finally(() => {
|
|
5047
|
+
if ((pendingCompletion === null || pendingCompletion === void 0 ? void 0 : pendingCompletion.promise) === promise)
|
|
5048
|
+
pendingCompletion = undefined;
|
|
5049
|
+
});
|
|
5050
|
+
pendingCompletion = { href: url.href, generation, promise };
|
|
5051
|
+
return promise;
|
|
5052
|
+
}
|
|
5053
|
+
async function finishSharedLoginReturn(url, state) {
|
|
5054
|
+
var _a;
|
|
5055
|
+
var _b;
|
|
5056
|
+
const cfg = await getConfig();
|
|
5057
|
+
const issuer = await issuerBase();
|
|
5058
|
+
if (state.startsWith('bounded.redirect.')) {
|
|
5059
|
+
const raw = sessionStorage.getItem(pkceKey(state));
|
|
5060
|
+
// A second SDK configured for another app/issuer on this page does not own
|
|
5061
|
+
// this return. Leave its URL and proof available to the initiating app.
|
|
5062
|
+
try {
|
|
5063
|
+
const attempt = raw && JSON.parse(raw);
|
|
5064
|
+
if (attempt && (attempt.appId !== cfg.appId || attempt.issuer !== issuer))
|
|
5065
|
+
return { handled: false, user: null };
|
|
5066
|
+
}
|
|
5067
|
+
catch ( /* The owning completion reports malformed attempt data. */_c) { /* The owning completion reports malformed attempt data. */ }
|
|
5068
|
+
}
|
|
5069
|
+
const shared = (_a = (_b = window)[COMPLETIONS]) !== null && _a !== void 0 ? _a : (_b[COMPLETIONS] = new Map());
|
|
5070
|
+
const key = JSON.stringify([url.href, cfg.appId, issuer]);
|
|
5071
|
+
const existing = shared.get(key);
|
|
5072
|
+
if (!existing) {
|
|
5073
|
+
const completion = finishLoginReturn(url, state);
|
|
5074
|
+
shared.set(key, completion);
|
|
5075
|
+
return completion;
|
|
5076
|
+
}
|
|
5077
|
+
const generation = getInitializationGeneration();
|
|
5078
|
+
const result = await existing;
|
|
5079
|
+
if (result.user) {
|
|
5080
|
+
if (!result.idToken || sessionManager().getIdToken() !== result.idToken) {
|
|
5081
|
+
throw new Error('The completed login session has changed. Start sign-in again.');
|
|
5082
|
+
}
|
|
5083
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc',
|
|
5084
|
+
user: Object.assign(Object.assign({}, result.user), { provider: oidcProviderSingleton }), beforeCommit: () => {
|
|
5085
|
+
if (generation !== getInitializationGeneration())
|
|
5086
|
+
throw new Error('Login was superseded by SDK reinitialization. Start sign-in again.');
|
|
5087
|
+
} });
|
|
5088
|
+
}
|
|
5089
|
+
return result;
|
|
5090
|
+
}
|
|
5265
5091
|
function normalizeMethod(value) {
|
|
5266
5092
|
const normalized = String(value || '').trim().toLowerCase();
|
|
5267
5093
|
return /^[a-z0-9_-]{1,40}$/.test(normalized) ? normalized : null;
|
|
@@ -5304,7 +5130,8 @@ function appendHostedLoginHints(params, opts) {
|
|
|
5304
5130
|
// native (expo-web-browser) path. The caller decides where the PkceState lives
|
|
5305
5131
|
// (web → sessionStorage across the full-page redirect; native → in-memory across
|
|
5306
5132
|
// the awaited browser session).
|
|
5307
|
-
async function buildAuthorize(opts) {
|
|
5133
|
+
async function buildAuthorize(opts, mode = 'redirect') {
|
|
5134
|
+
loginReturnError = undefined;
|
|
5308
5135
|
const cfg = await getConfig();
|
|
5309
5136
|
if (!cfg.appId)
|
|
5310
5137
|
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
@@ -5313,15 +5140,23 @@ async function buildAuthorize(opts) {
|
|
|
5313
5140
|
const redirectUri = opts.redirectUri || (hasDOM$1() ? window.location.origin + window.location.pathname : undefined);
|
|
5314
5141
|
if (!redirectUri)
|
|
5315
5142
|
throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
|
|
5143
|
+
if (hasDOM$1()) {
|
|
5144
|
+
const redirect = new URL(redirectUri);
|
|
5145
|
+
if (redirect.origin !== window.location.origin || redirect.hash || CALLBACK_PARAMS.some((key) => redirect.searchParams.has(key))) {
|
|
5146
|
+
throw new Error('redirectUri must be on this app origin, without a fragment or OAuth callback parameters.');
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5316
5149
|
const { verifier, challenge } = await makePkce();
|
|
5317
|
-
const state = randomState()
|
|
5150
|
+
const state = `${STATE_PREFIX}${mode}.${randomState()}`;
|
|
5318
5151
|
const base = await issuerBase();
|
|
5319
5152
|
const params = new URLSearchParams({
|
|
5320
5153
|
client_id: cfg.appId, redirect_uri: redirectUri, response_type: 'code', scope: 'openid profile email',
|
|
5321
5154
|
code_challenge: challenge, code_challenge_method: 'S256', state,
|
|
5322
5155
|
});
|
|
5323
5156
|
appendHostedLoginHints(params, opts);
|
|
5324
|
-
return { url: `${base}/api/auth/oauth2/authorize?${params.toString()}`, pkce: {
|
|
5157
|
+
return { url: `${base}/api/auth/oauth2/authorize?${params.toString()}`, pkce: {
|
|
5158
|
+
verifier, state, redirectUri, appId: cfg.appId, issuer: base, createdAt: Date.now(),
|
|
5159
|
+
} };
|
|
5325
5160
|
}
|
|
5326
5161
|
// Are we on React Native (no DOM)? OIDC dispatch uses this to choose the native
|
|
5327
5162
|
// expo-web-browser driver vs. the web full-page-redirect/popup driver.
|
|
@@ -5370,8 +5205,7 @@ function parseCallbackParams(callbackUrl) {
|
|
|
5370
5205
|
}
|
|
5371
5206
|
// Full native login: open the hosted page in the system auth session, await the
|
|
5372
5207
|
// universal-link callback, verify state, and run the SAME PKCE code→token exchange
|
|
5373
|
-
// as web. Returns the signed-in User
|
|
5374
|
-
// completeLoginFromRedirect() round-trip the way the web full-page redirect needs).
|
|
5208
|
+
// as web. Returns the signed-in User inline; web redirects complete on the next init().
|
|
5375
5209
|
async function runNativeLogin(opts) {
|
|
5376
5210
|
var _a, _b;
|
|
5377
5211
|
const { url, pkce } = await buildAuthorize(opts);
|
|
@@ -5398,7 +5232,7 @@ async function runNativeLogin(opts) {
|
|
|
5398
5232
|
if (!returnedState || returnedState !== pkce.state) {
|
|
5399
5233
|
throw new Error('OAuth state mismatch — possible CSRF; aborting');
|
|
5400
5234
|
}
|
|
5401
|
-
return await exchangeCode(code, pkce
|
|
5235
|
+
return await exchangeCode(code, pkce);
|
|
5402
5236
|
}
|
|
5403
5237
|
finally {
|
|
5404
5238
|
setAuthLoading(false);
|
|
@@ -5407,7 +5241,7 @@ async function runNativeLogin(opts) {
|
|
|
5407
5241
|
/** Start hosted login (Google / Apple / email / hosted phone). Works on WEB and REACT NATIVE.
|
|
5408
5242
|
*
|
|
5409
5243
|
* WEB: navigates the whole page to the hosted Bounded login and resolves (void) as the page
|
|
5410
|
-
* unloads;
|
|
5244
|
+
* unloads; init() completes the return to redirectUri automatically.
|
|
5411
5245
|
*
|
|
5412
5246
|
* REACT NATIVE: opens the hosted page via expo-web-browser, awaits the https universal-link
|
|
5413
5247
|
* callback, runs the PKCE exchange INLINE, and resolves with the signed-in User. No
|
|
@@ -5425,63 +5259,32 @@ async function loginWithRedirect(opts) {
|
|
|
5425
5259
|
// is per-tab and cleared on close — appropriate for a short-lived auth handshake. Keyed by
|
|
5426
5260
|
// this attempt's own state so a second concurrent login cannot overwrite it.
|
|
5427
5261
|
sessionStorage.setItem(pkceKey(pkce.state), JSON.stringify(pkce));
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
/** Finish login on the redirect_uri page (WEB ONLY): exchange ?code= for a token (PKCE-verified),
|
|
5432
|
-
* store the session, and return the User. Idempotent: returns null if there's no ?code= in the URL.
|
|
5433
|
-
*
|
|
5434
|
-
* On REACT NATIVE this returns null and is a no-op: native login completes INLINE inside
|
|
5435
|
-
* loginWithRedirect() (which resolves with the User), so there is no separate redirect
|
|
5436
|
-
* page to finish from. Calling it on RN is harmless. */
|
|
5437
|
-
async function completeLoginFromRedirect() {
|
|
5438
|
-
if (!hasDOM$1())
|
|
5439
|
-
return null;
|
|
5440
|
-
const url = new URL(window.location.href);
|
|
5441
|
-
const code = url.searchParams.get('code');
|
|
5442
|
-
const returnedState = url.searchParams.get('state');
|
|
5443
|
-
if (!code)
|
|
5444
|
-
return null;
|
|
5445
|
-
// POPUP path: if we're the redirect_uri page running INSIDE the loginWithPopup() window,
|
|
5446
|
-
// hand the code to the opener and close — no separate completeLoginInPopup() call needed.
|
|
5447
|
-
// The opener is the same app/origin, so post to our own origin (the opener's message
|
|
5448
|
-
// handler checks ev.origin === redirect_uri origin). One call now finishes BOTH flows.
|
|
5449
|
-
if (window.opener && window.opener !== window) {
|
|
5450
|
-
try {
|
|
5451
|
-
window.opener.postMessage({ type: 'bounded-oidc-code', code, state: returnedState }, window.location.origin);
|
|
5452
|
-
}
|
|
5453
|
-
catch ( /* ignore */_a) { /* ignore */ }
|
|
5454
|
-
try {
|
|
5455
|
-
window.close();
|
|
5456
|
-
}
|
|
5457
|
-
catch ( /* ignore */_b) { /* ignore */ }
|
|
5458
|
-
return null;
|
|
5459
|
-
}
|
|
5262
|
+
// This promise represents dispatch, not authentication on the next page.
|
|
5263
|
+
// Browsers can cancel navigation (for example at beforeunload) without firing
|
|
5264
|
+
// pageshow. Never leave this document's SDK loading after dispatch settles.
|
|
5460
5265
|
setAuthLoading(true);
|
|
5461
5266
|
try {
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
const pkce = JSON.parse(raw);
|
|
5468
|
-
if (!returnedState || returnedState !== pkce.state)
|
|
5469
|
-
throw new Error('OAuth state mismatch — possible CSRF; aborting');
|
|
5470
|
-
const user = await exchangeCode(code, pkce.verifier, pkce.redirectUri);
|
|
5471
|
-
sessionStorage.removeItem(pkceKey(returnedState));
|
|
5472
|
-
// Strip code/state from the URL so a reload doesn't re-exchange (codes are single-use anyway).
|
|
5473
|
-
url.searchParams.delete('code');
|
|
5474
|
-
url.searchParams.delete('state');
|
|
5475
|
-
window.history.replaceState({}, document.title, url.pathname + url.search + url.hash);
|
|
5476
|
-
return user;
|
|
5267
|
+
window.location.href = url;
|
|
5268
|
+
}
|
|
5269
|
+
catch (error) {
|
|
5270
|
+
removeAttempt(pkce.state);
|
|
5271
|
+
throw error;
|
|
5477
5272
|
}
|
|
5478
5273
|
finally {
|
|
5479
5274
|
setAuthLoading(false);
|
|
5480
5275
|
}
|
|
5481
5276
|
}
|
|
5277
|
+
/** Optional explicit completion. Normal apps only await init(), which already finishes login.
|
|
5278
|
+
* Concurrent calls share one exchange. Returns null when no Bounded return is present. */
|
|
5279
|
+
async function completeLoginFromRedirect() {
|
|
5280
|
+
const result = await completePendingLogin();
|
|
5281
|
+
const error = getLoginReturnError();
|
|
5282
|
+
if (error)
|
|
5283
|
+
throw error;
|
|
5284
|
+
return result.user;
|
|
5285
|
+
}
|
|
5482
5286
|
/** Popup variant: open the hosted login in a popup; resolves with the User once the popup's
|
|
5483
|
-
* redirect_uri page
|
|
5484
|
-
* completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
|
|
5287
|
+
* redirect_uri page initializes the SDK, which hands the code back automatically. */
|
|
5485
5288
|
async function loginWithPopup(opts) {
|
|
5486
5289
|
if (!hasDOM$1())
|
|
5487
5290
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
@@ -5500,7 +5303,7 @@ async function loginWithPopup(opts) {
|
|
|
5500
5303
|
throw new Error('Popup blocked — use loginWithRedirect() instead');
|
|
5501
5304
|
let authorization;
|
|
5502
5305
|
try {
|
|
5503
|
-
authorization = await buildAuthorize(opts);
|
|
5306
|
+
authorization = await buildAuthorize(opts, 'popup');
|
|
5504
5307
|
}
|
|
5505
5308
|
catch (error) {
|
|
5506
5309
|
try {
|
|
@@ -5535,6 +5338,7 @@ async function loginWithPopup(opts) {
|
|
|
5535
5338
|
popup.location.href = url;
|
|
5536
5339
|
}
|
|
5537
5340
|
catch (_d) {
|
|
5341
|
+
removeAttempt(pkce.state);
|
|
5538
5342
|
try {
|
|
5539
5343
|
popup.close();
|
|
5540
5344
|
}
|
|
@@ -5550,17 +5354,21 @@ async function loginWithPopup(opts) {
|
|
|
5550
5354
|
let settled = false;
|
|
5551
5355
|
let timer;
|
|
5552
5356
|
let navigationTimer;
|
|
5357
|
+
let completionTimer;
|
|
5553
5358
|
const cleanup = () => {
|
|
5554
5359
|
settled = true;
|
|
5555
5360
|
clearInterval(timer);
|
|
5556
5361
|
clearTimeout(navigationTimer);
|
|
5362
|
+
clearTimeout(completionTimer);
|
|
5557
5363
|
window.removeEventListener('message', onMsg);
|
|
5364
|
+
removeAttempt(pkce.state);
|
|
5558
5365
|
};
|
|
5559
5366
|
const onMsg = async (ev) => {
|
|
5560
|
-
if (ev.origin !== expectedOrigin)
|
|
5561
|
-
return;
|
|
5367
|
+
if (settled || ev.origin !== expectedOrigin || ev.source !== popup)
|
|
5368
|
+
return;
|
|
5562
5369
|
const d = ev.data;
|
|
5563
|
-
if (!d || d.type !== 'bounded-oidc-code' ||
|
|
5370
|
+
if (!d || d.type !== 'bounded-oidc-code' ||
|
|
5371
|
+
(typeof d.code !== 'string' && typeof d.error !== 'string'))
|
|
5564
5372
|
return;
|
|
5565
5373
|
// Validate state BEFORE tearing down this listener, against the record THIS
|
|
5566
5374
|
// attempt captured in the closure (`pkce`), not a shared sessionStorage reread.
|
|
@@ -5568,10 +5376,15 @@ async function loginWithPopup(opts) {
|
|
|
5568
5376
|
// stay registered for our own code, instead of cleaning up on another's message.
|
|
5569
5377
|
if (d.state !== pkce.state)
|
|
5570
5378
|
return;
|
|
5379
|
+
if (d.appId !== pkce.appId || d.issuer !== pkce.issuer)
|
|
5380
|
+
return;
|
|
5571
5381
|
cleanup();
|
|
5572
5382
|
try {
|
|
5573
|
-
|
|
5574
|
-
|
|
5383
|
+
await validateAttempt(pkce, new URL(d.callbackUrl));
|
|
5384
|
+
popup.postMessage({ type: 'bounded-oidc-accepted', state: pkce.state }, expectedOrigin);
|
|
5385
|
+
if (d.error)
|
|
5386
|
+
throw new Error(typeof d.errorDescription === 'string' ? d.errorDescription : `Login failed: ${d.error}`);
|
|
5387
|
+
const user = await exchangeCode(d.code, pkce);
|
|
5575
5388
|
try {
|
|
5576
5389
|
popup.close();
|
|
5577
5390
|
}
|
|
@@ -5579,10 +5392,22 @@ async function loginWithPopup(opts) {
|
|
|
5579
5392
|
resolve(user);
|
|
5580
5393
|
}
|
|
5581
5394
|
catch (e) {
|
|
5395
|
+
try {
|
|
5396
|
+
popup.close();
|
|
5397
|
+
}
|
|
5398
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
5582
5399
|
reject(e);
|
|
5583
5400
|
}
|
|
5584
5401
|
};
|
|
5585
5402
|
window.addEventListener('message', onMsg);
|
|
5403
|
+
completionTimer = setTimeout(() => {
|
|
5404
|
+
cleanup();
|
|
5405
|
+
try {
|
|
5406
|
+
popup.close();
|
|
5407
|
+
}
|
|
5408
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
5409
|
+
reject(new Error('Login expired or its popup disconnected. Start sign-in again.'));
|
|
5410
|
+
}, LOGIN_TIMEOUT_MS);
|
|
5586
5411
|
// Some browser/extension combinations can return a real popup handle but
|
|
5587
5412
|
// strand the window at about:blank instead of navigating to the hosted
|
|
5588
5413
|
// authorize URL. The old code then waited forever because the popup was
|
|
@@ -5640,33 +5465,20 @@ async function loginWithPopup(opts) {
|
|
|
5640
5465
|
setAuthLoading(false);
|
|
5641
5466
|
}
|
|
5642
5467
|
}
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
return;
|
|
5653
|
-
try {
|
|
5654
|
-
window.opener.postMessage({ type: 'bounded-oidc-code', code, state }, openerOrigin);
|
|
5655
|
-
}
|
|
5656
|
-
catch ( /* ignore */_a) { /* ignore */ }
|
|
5657
|
-
try {
|
|
5658
|
-
window.close();
|
|
5659
|
-
}
|
|
5660
|
-
catch ( /* ignore */_b) { /* ignore */ }
|
|
5661
|
-
}
|
|
5662
|
-
async function exchangeCode(code, verifier, redirectUri) {
|
|
5663
|
-
const cfg = await getConfig();
|
|
5664
|
-
const base = await issuerBase();
|
|
5468
|
+
async function exchangeCode(code, pkce) {
|
|
5469
|
+
const generation = getInitializationGeneration();
|
|
5470
|
+
const beforeCommit = () => {
|
|
5471
|
+
if (generation !== getInitializationGeneration())
|
|
5472
|
+
throw new Error('Login was superseded by SDK reinitialization. Start sign-in again.');
|
|
5473
|
+
};
|
|
5474
|
+
await validateAttempt(pkce);
|
|
5475
|
+
beforeCommit();
|
|
5476
|
+
const base = pkce.issuer;
|
|
5665
5477
|
// OIDC token endpoint: application/x-www-form-urlencoded (the standard). Public client (no
|
|
5666
5478
|
// secret) authenticated by PKCE. The code is single-use + short-TTL on the server.
|
|
5667
5479
|
const body = new URLSearchParams({
|
|
5668
|
-
grant_type: 'authorization_code', code, redirect_uri: redirectUri,
|
|
5669
|
-
client_id:
|
|
5480
|
+
grant_type: 'authorization_code', code, redirect_uri: pkce.redirectUri,
|
|
5481
|
+
client_id: pkce.appId, code_verifier: pkce.verifier,
|
|
5670
5482
|
});
|
|
5671
5483
|
let res;
|
|
5672
5484
|
try {
|
|
@@ -5690,10 +5502,20 @@ async function exchangeCode(code, verifier, redirectUri) {
|
|
|
5690
5502
|
const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
|
|
5691
5503
|
const refreshToken = boundedRefreshTokenField(data);
|
|
5692
5504
|
const user = userFromIdToken(idToken);
|
|
5693
|
-
|
|
5505
|
+
// Reinitialization while the request was in flight must not install this
|
|
5506
|
+
// app's result under a different app's session manager/configuration.
|
|
5507
|
+
beforeCommit();
|
|
5508
|
+
const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, beforeCommit);
|
|
5694
5509
|
// Adopt on EVERY session surface (see establishInlineSession) - hosted
|
|
5695
5510
|
// popup/redirect completions all land here, so this closes their gap too.
|
|
5696
|
-
|
|
5511
|
+
try {
|
|
5512
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit });
|
|
5513
|
+
}
|
|
5514
|
+
catch (error) {
|
|
5515
|
+
if (typeof rollback === 'function')
|
|
5516
|
+
await rollback();
|
|
5517
|
+
throw error;
|
|
5518
|
+
}
|
|
5697
5519
|
// OIDC login does establish the issuer's Better Auth browser cookie. Record
|
|
5698
5520
|
// that fact separately from the provider name so logout clears the cookie
|
|
5699
5521
|
// and returns through the registered app origin. Stamped AFTER the adoption
|
|
@@ -6044,7 +5866,7 @@ async function getAuthProvider(config) {
|
|
|
6044
5866
|
// manager as legacy email sessions. Human login should be started
|
|
6045
5867
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
6046
5868
|
// logout surface for those sessions.
|
|
6047
|
-
currentAuthProvider = new (await import('./email-auth-provider-
|
|
5869
|
+
currentAuthProvider = new (await import('./email-auth-provider-D9SQu9c1.mjs')).EmailAuthProvider();
|
|
6048
5870
|
break;
|
|
6049
5871
|
case "phantom": {
|
|
6050
5872
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -6086,7 +5908,7 @@ async function getAuthProvider(config) {
|
|
|
6086
5908
|
// (bounded-login-widget.ts), so registration never counts as
|
|
6087
5909
|
// evidence the user has a wallet.
|
|
6088
5910
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
6089
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
5911
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-DZOwvKgd.mjs');
|
|
6090
5912
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
6091
5913
|
break;
|
|
6092
5914
|
}
|
|
@@ -6099,7 +5921,7 @@ async function getAuthProvider(config) {
|
|
|
6099
5921
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
6100
5922
|
// to point at a specific wallet or set chainId/domain.
|
|
6101
5923
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
6102
|
-
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-
|
|
5924
|
+
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-Xb2G-ZGI.mjs');
|
|
6103
5925
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
6104
5926
|
break;
|
|
6105
5927
|
}
|
|
@@ -6124,7 +5946,7 @@ async function getAuthProvider(config) {
|
|
|
6124
5946
|
break;
|
|
6125
5947
|
case "guest":
|
|
6126
5948
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
6127
|
-
currentAuthProvider = new (await import('./guest-auth-provider-
|
|
5949
|
+
currentAuthProvider = new (await import('./guest-auth-provider-DsFTB844.mjs')).GuestAuthProvider();
|
|
6128
5950
|
break;
|
|
6129
5951
|
}
|
|
6130
5952
|
if (!currentAuthProvider) {
|
|
@@ -6186,7 +6008,7 @@ async function signInAnonymously() {
|
|
|
6186
6008
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
6187
6009
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
6188
6010
|
// so bundle-splitting can see it.
|
|
6189
|
-
const provider = new (await import('./guest-auth-provider-
|
|
6011
|
+
const provider = new (await import('./guest-auth-provider-DsFTB844.mjs')).GuestAuthProvider();
|
|
6190
6012
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
6191
6013
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
6192
6014
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6357,7 +6179,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6357
6179
|
if (!sharedWalletReady) {
|
|
6358
6180
|
sharedWalletReady = (async () => {
|
|
6359
6181
|
await getConfig();
|
|
6360
|
-
await import('./injected-wallet-provider-
|
|
6182
|
+
await import('./injected-wallet-provider-DZOwvKgd.mjs');
|
|
6361
6183
|
})().catch((error) => {
|
|
6362
6184
|
sharedWalletReady = null;
|
|
6363
6185
|
throw error;
|
|
@@ -6640,6 +6462,10 @@ let authLoadingListeners = [];
|
|
|
6640
6462
|
let initCompleted = false;
|
|
6641
6463
|
let isAuthLoading = false;
|
|
6642
6464
|
let authReadyPromise = null;
|
|
6465
|
+
let initializingConfig = null;
|
|
6466
|
+
let initializationGeneration = 0;
|
|
6467
|
+
/** Internal guard for a login result arriving after the app reconfigured the SDK. */
|
|
6468
|
+
function getInitializationGeneration() { return initializationGeneration; }
|
|
6643
6469
|
// This file acts as a middleware for the global state of the SDK
|
|
6644
6470
|
// This mostly involves setting up the AuthProvider and managing the current user
|
|
6645
6471
|
async function initialize(newConfig) {
|
|
@@ -6665,6 +6491,19 @@ async function initialize(newConfig) {
|
|
|
6665
6491
|
authProviderInstance = await getAuthProvider(newConfig);
|
|
6666
6492
|
// Initialize config with auth provider
|
|
6667
6493
|
await init$1(Object.assign(Object.assign({}, newConfig), { authProvider: authProviderInstance }));
|
|
6494
|
+
// A return establishes its own provider/session, or hands off a popup.
|
|
6495
|
+
// Restoring the old provider afterwards would overwrite the new login.
|
|
6496
|
+
try {
|
|
6497
|
+
if ((await completePendingLogin()).handled) {
|
|
6498
|
+
initCompleted = true;
|
|
6499
|
+
return;
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
catch (_a) {
|
|
6503
|
+
// A cancelled/failed return is a login failure, not a broken SDK.
|
|
6504
|
+
// The completion layer keeps it in getLoginReturnError(); restore
|
|
6505
|
+
// any existing session and leave sign-in/readiness usable for retry.
|
|
6506
|
+
}
|
|
6668
6507
|
// Now that core is initialized, update the auth method on the config
|
|
6669
6508
|
const authMethod = getCurrentAuthMethod();
|
|
6670
6509
|
if (authMethod) {
|
|
@@ -6696,7 +6535,42 @@ async function initialize(newConfig) {
|
|
|
6696
6535
|
setAuthLoading(false);
|
|
6697
6536
|
}
|
|
6698
6537
|
}
|
|
6538
|
+
// Compare data options by value, but preserve identity for callbacks and class
|
|
6539
|
+
// instances such as providers. Comparing only app/endpoints would silently drop
|
|
6540
|
+
// a concurrent change to wallet policy or provider configuration.
|
|
6541
|
+
function sameConfigValue(a, b, seen = new WeakMap()) {
|
|
6542
|
+
var _a;
|
|
6543
|
+
if (Object.is(a, b))
|
|
6544
|
+
return true;
|
|
6545
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object')
|
|
6546
|
+
return false;
|
|
6547
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
6548
|
+
return false;
|
|
6549
|
+
if (Array.isArray(a) && a.length !== b.length)
|
|
6550
|
+
return false;
|
|
6551
|
+
if (!Array.isArray(a)) {
|
|
6552
|
+
for (const value of [a, b]) {
|
|
6553
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6554
|
+
if (prototype !== null && ((_a = prototype.constructor) === null || _a === void 0 ? void 0 : _a.name) !== 'Object')
|
|
6555
|
+
return false;
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
if (seen.has(a))
|
|
6559
|
+
return seen.get(a) === b;
|
|
6560
|
+
seen.set(a, b);
|
|
6561
|
+
const left = Object.keys(a), right = Object.keys(b);
|
|
6562
|
+
return left.length === right.length && left.every((key) => Object.prototype.hasOwnProperty.call(b, key)
|
|
6563
|
+
&& sameConfigValue(a[key], b[key], seen));
|
|
6564
|
+
}
|
|
6699
6565
|
function init(newConfig) {
|
|
6566
|
+
if (initializingConfig) {
|
|
6567
|
+
if (sameConfigValue(initializingConfig, newConfig)) {
|
|
6568
|
+
return authReadyPromise;
|
|
6569
|
+
}
|
|
6570
|
+
return Promise.reject(new Error('SDK initialization is in progress. Await init() before changing its configuration.'));
|
|
6571
|
+
}
|
|
6572
|
+
initializingConfig = Object.assign({}, newConfig);
|
|
6573
|
+
initializationGeneration++;
|
|
6700
6574
|
let resolveReady;
|
|
6701
6575
|
let rejectReady;
|
|
6702
6576
|
const ready = new Promise((resolve, reject) => {
|
|
@@ -6707,7 +6581,7 @@ function init(newConfig) {
|
|
|
6707
6581
|
// auth-loading listener to call waitForAuthReady() synchronously when init()
|
|
6708
6582
|
// announces that session restoration has begun.
|
|
6709
6583
|
authReadyPromise = ready;
|
|
6710
|
-
void initialize(
|
|
6584
|
+
void initialize(initializingConfig).then(() => { initializingConfig = null; resolveReady(); }, (error) => { initializingConfig = null; rejectReady(error); });
|
|
6711
6585
|
return ready;
|
|
6712
6586
|
}
|
|
6713
6587
|
/**
|
|
@@ -8685,5 +8559,5 @@ class PrivyExpoProvider {
|
|
|
8685
8559
|
}
|
|
8686
8560
|
}
|
|
8687
8561
|
|
|
8688
|
-
export { BUILD_REVIEW_CAPABILITY_SCHEMA as $, logout as A, getAuthProvider as B, signInAnonymously as C, loginWithPrivy as D, loginWithRedirect as E, completeLoginFromRedirect as F,
|
|
8689
|
-
//# sourceMappingURL=index.native-
|
|
8562
|
+
export { BUILD_REVIEW_CAPABILITY_SCHEMA as $, logout as A, getAuthProvider as B, signInAnonymously as C, loginWithPrivy as D, loginWithRedirect as E, completeLoginFromRedirect as F, getLoginReturnError as G, loginWithPopup as H, useAuth as I, useQuery as J, useBuildRun as K, apps as L, getIdToken as M, onramp as N, onrampQuote as O, onrampStatus as P, setPlatform as Q, OffchainAuthProvider as R, PrivyExpoProvider as S, builds as T, BuildsApiError as U, mediaAgentTools as V, NON_TERMINAL_STATES as W, TERMINAL_STATES as X, RUN_STATES as Y, PARK_REASONS as Z, FAILURE_REASONS as _, signMessageWithTurnkey as a, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as a0, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as a1, signTransactionWithTurnkey as b, signTransactionV1WithTurnkey as c, signAndSubmitTransactionWithTurnkey as d, bs58 as e, getWallets as f, getPlatform as g, hasTurnkeyWallet as h, isSolanaMobileWallet as i, solanaMobileWalletLeavesPage as j, setAuthLoading as k, bufferExports as l, setStoredAuthMethod as m, normalizeSolanaRpcUrl as n, confirmAndCheckTransaction as o, resolveSolanaRpcUrl as p, init as q, resolveSolanaWalletStandardChain as r, setCurrentUser as s, transactionHasSignature as t, getCurrentUser as u, onAuthStateChanged as v, onAuthLoadingChanged as w, getAuthLoading as x, waitForAuthReady as y, login as z };
|
|
8563
|
+
//# sourceMappingURL=index.native-BSOqaKIz.mjs.map
|