@pollar/core 0.9.0-rc.1 → 0.9.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1171,7 +1171,7 @@ function defaultStorage(options = {}) {
1171
1171
  }
1172
1172
 
1173
1173
  // src/version.ts
1174
- var POLLAR_CORE_VERSION = "0.9.0-rc.1" ;
1174
+ var POLLAR_CORE_VERSION = "0.9.0-rc.3" ;
1175
1175
 
1176
1176
  // src/visibility/noop.ts
1177
1177
  function createNoopVisibilityProvider() {
@@ -1343,7 +1343,10 @@ function openAlbedoPopup(url) {
1343
1343
  }
1344
1344
  function waitForAlbedoPopup() {
1345
1345
  return new Promise((resolve, reject) => {
1346
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1346
+ const timeout = setTimeout(() => {
1347
+ window.removeEventListener("message", handler);
1348
+ reject(new Error("Albedo response timeout"));
1349
+ }, 2 * 60 * 1e3);
1347
1350
  function handler(event) {
1348
1351
  if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1349
1352
  clearTimeout(timeout);
@@ -1353,24 +1356,6 @@ function waitForAlbedoPopup() {
1353
1356
  window.addEventListener("message", handler);
1354
1357
  });
1355
1358
  }
1356
- function waitForAlbedoResult() {
1357
- return new Promise((resolve, reject) => {
1358
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1359
- const parseResult = () => {
1360
- const params = new URLSearchParams(window.location.search);
1361
- if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
1362
- clearTimeout(timeout);
1363
- const result = {};
1364
- params.forEach((value, key) => {
1365
- result[key] = value;
1366
- });
1367
- window.history.replaceState({}, document.title, window.location.pathname);
1368
- resolve(result);
1369
- };
1370
- parseResult();
1371
- window.addEventListener("popstate", parseResult);
1372
- });
1373
- }
1374
1359
  var AlbedoAdapter = class {
1375
1360
  /**
1376
1361
  * Network used for `connect` and `signAuthEntry` (which carry no per-call
@@ -1412,10 +1397,10 @@ var AlbedoAdapter = class {
1412
1397
  url.searchParams.set("xdr", xdr);
1413
1398
  url.searchParams.set("app_name", "Pollar");
1414
1399
  url.searchParams.set("network", albedoNetwork(options, this.network));
1415
- url.searchParams.set("callback", window.location.href);
1400
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1416
1401
  url.searchParams.set("origin", window.location.origin);
1417
- window.location.href = url.toString();
1418
- const result = await waitForAlbedoResult();
1402
+ openAlbedoPopup(url.toString());
1403
+ const result = await waitForAlbedoPopup();
1419
1404
  if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
1420
1405
  return { signedTxXdr: result.signed_envelope_xdr };
1421
1406
  }
@@ -1425,10 +1410,10 @@ var AlbedoAdapter = class {
1425
1410
  url.searchParams.set("xdr", entryXdr);
1426
1411
  url.searchParams.set("app_name", "Pollar");
1427
1412
  url.searchParams.set("network", this.network);
1428
- url.searchParams.set("callback", window.location.href);
1413
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1429
1414
  url.searchParams.set("origin", window.location.origin);
1430
- window.location.href = url.toString();
1431
- const result = await waitForAlbedoResult();
1415
+ openAlbedoPopup(url.toString());
1416
+ const result = await waitForAlbedoPopup();
1432
1417
  if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
1433
1418
  return { signedAuthEntry: result.signed_xdr };
1434
1419
  }
@@ -1511,8 +1496,8 @@ function isValidSession(value, logger = console) {
1511
1496
  return false;
1512
1497
  }
1513
1498
  const w = wallet;
1514
- if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
1515
- logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
1499
+ if (w["type"] !== "internal" && w["type"] !== "smart" && w["type"] !== "external") {
1500
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be internal|smart|external");
1516
1501
  return false;
1517
1502
  }
1518
1503
  if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
@@ -1543,6 +1528,9 @@ async function readStorage(storage, apiKeyHash, logger = console) {
1543
1528
  if (w && w["address"] == null && typeof w["publicKey"] === "string") {
1544
1529
  w["address"] = w["publicKey"];
1545
1530
  }
1531
+ if (w && w["type"] === "custodial") {
1532
+ w["type"] = "internal";
1533
+ }
1546
1534
  }
1547
1535
  if (!isValidSession(session, logger)) {
1548
1536
  await storage.remove(sessionStorageKey(apiKeyHash));
@@ -1643,16 +1631,12 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1643
1631
  }
1644
1632
  const reader = data.getReader();
1645
1633
  const decoder = new TextDecoder();
1646
- let streamDone = false;
1647
1634
  let sawAnyChunk = false;
1648
1635
  try {
1649
1636
  while (true) {
1650
1637
  throwIfAborted(signal);
1651
1638
  const { done, value } = await reader.read();
1652
- if (done) {
1653
- streamDone = true;
1654
- break;
1655
- }
1639
+ if (done) break;
1656
1640
  sawAnyChunk = true;
1657
1641
  const chunk = decoder.decode(value);
1658
1642
  for (const message of chunk.split("\n\n").filter(Boolean)) {
@@ -1680,8 +1664,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1680
1664
  }
1681
1665
  if (sawAnyChunk) backoff = retryDelayMs;
1682
1666
  else backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
1683
- const delay = streamDone ? backoff : 0;
1684
- if (delay) await sleep(delay);
1667
+ await sleep(backoff);
1685
1668
  }
1686
1669
  }
1687
1670
  async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal, logger = console) {
@@ -1761,17 +1744,18 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1761
1744
  signal
1762
1745
  });
1763
1746
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1764
- if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
1747
+ const sessionWallet = data.content.data?.providers?.wallet?.address;
1748
+ if (expectedWallet && sessionWallet !== expectedWallet) {
1765
1749
  setAuthState({
1766
1750
  step: "error",
1767
1751
  previousStep: "authenticating",
1768
1752
  message: "Wallet mismatch: session wallet does not match connected wallet",
1769
1753
  errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1770
1754
  });
1771
- clearSession();
1755
+ await clearSession();
1772
1756
  return;
1773
1757
  }
1774
- storeSession(data.content);
1758
+ await storeSession(data.content);
1775
1759
  } else {
1776
1760
  setAuthState({
1777
1761
  step: "error",
@@ -1779,7 +1763,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1779
1763
  message: "Failed to load session",
1780
1764
  errorCode: AUTH_ERROR_CODES.AUTH_FAILED
1781
1765
  });
1782
- clearSession();
1766
+ await clearSession();
1783
1767
  }
1784
1768
  }
1785
1769
 
@@ -1908,7 +1892,7 @@ async function loginOAuth(provider, deps) {
1908
1892
  }
1909
1893
 
1910
1894
  // src/client/auth/passkeyFlow.ts
1911
- async function loginSmartWallet(deps) {
1895
+ async function smartWalletFlow(deps, mode) {
1912
1896
  const { api, signal, setAuthState, passkey } = deps;
1913
1897
  if (!passkey) {
1914
1898
  setAuthState({
@@ -1931,7 +1915,7 @@ async function loginSmartWallet(deps) {
1931
1915
  return failPasskey(setAuthState, "Failed to start passkey");
1932
1916
  }
1933
1917
  setAuthState({ step: "creating_passkey" });
1934
- const ceremony = await passkey({ challenge });
1918
+ const ceremony = await passkey({ challenge, mode });
1935
1919
  const response = ceremony.response;
1936
1920
  if (ceremony.kind === "register") {
1937
1921
  setAuthState({ step: "deploying_smart_account" });
@@ -2095,7 +2079,7 @@ var PollarClient = class {
2095
2079
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
2096
2080
  this._maxIdleMs = config.maxIdleMs;
2097
2081
  this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
2098
- this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location.origin : "");
2082
+ this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location?.origin ?? "" : "");
2099
2083
  this._api = createApiClient(this.basePath);
2100
2084
  this._wireMiddlewares();
2101
2085
  this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
@@ -2524,9 +2508,10 @@ var PollarClient = class {
2524
2508
  loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2525
2509
  }
2526
2510
  /**
2527
- * "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
2528
- * user, creates a sponsored smart-account C-address. Requires the `passkey`
2529
- * ceremony to be configured (e.g. via `@pollar/react`).
2511
+ * "Smart Wallet" login: runs the passkey (WebAuthn) `get()` ceremony for a
2512
+ * returning user and signs them in. Use {@link createSmartWallet} for a new
2513
+ * user. Requires the `passkey` ceremony to be configured (e.g. via
2514
+ * `@pollar/react`).
2530
2515
  */
2531
2516
  loginSmartWallet() {
2532
2517
  if (!isClientRuntime) {
@@ -2534,7 +2519,21 @@ var PollarClient = class {
2534
2519
  return;
2535
2520
  }
2536
2521
  const controller = this._newController();
2537
- loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2522
+ smartWalletFlow(this._flowDeps(controller.signal), "login").catch((err) => this._handleFlowError(err));
2523
+ }
2524
+ /**
2525
+ * "Smart Wallet" registration: runs the passkey (WebAuthn) `create()` ceremony
2526
+ * for a new user and deploys a sponsored smart-account C-address. Use
2527
+ * {@link loginSmartWallet} for a returning user. Requires the `passkey`
2528
+ * ceremony to be configured (e.g. via `@pollar/react`).
2529
+ */
2530
+ createSmartWallet() {
2531
+ if (!isClientRuntime) {
2532
+ warnServerSide("createSmartWallet");
2533
+ return;
2534
+ }
2535
+ const controller = this._newController();
2536
+ smartWalletFlow(this._flowDeps(controller.signal), "register").catch((err) => this._handleFlowError(err));
2538
2537
  }
2539
2538
  // ─── Cancel ───────────────────────────────────────────────────────────────
2540
2539
  cancelLogin() {
@@ -2774,6 +2773,54 @@ var PollarClient = class {
2774
2773
  this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2775
2774
  }
2776
2775
  }
2776
+ /**
2777
+ * Establishes (omit `limit`) or removes (`limit: '0'`) a trustline for an asset.
2778
+ *
2779
+ * Routing mirrors how the platform pays for the reserve:
2780
+ * - **Sponsored custodial** (`opts.sponsored` true, internal wallet) → the
2781
+ * server orchestrates a sponsored `changeTrust`: the app's wallets cover the
2782
+ * 0.5 XLM reserve and the fee, so the user pays nothing. Pass the asset's
2783
+ * `sponsored` flag (from {@link refreshAssets}) straight through.
2784
+ * - **Self-paid** (external/adapter wallet, sponsorship disabled, or a custom
2785
+ * asset not configured in the app) → a plain `change_trust` transaction the
2786
+ * user's own wallet signs and pays for, via {@link runTx}.
2787
+ *
2788
+ * Does not refresh on its own — callers should `refreshAssets()` afterwards.
2789
+ */
2790
+ async setTrustline(asset, opts) {
2791
+ const limit = opts?.limit;
2792
+ const walletType = this._session?.wallet?.type;
2793
+ if (!this._session?.wallet?.address) {
2794
+ return { status: "error", details: "No wallet connected" };
2795
+ }
2796
+ if (walletType === "smart") {
2797
+ return { status: "error", details: "Trustlines do not apply to smart wallets" };
2798
+ }
2799
+ if (opts?.sponsored && !this._walletAdapter && walletType === "internal") {
2800
+ try {
2801
+ const { data, error } = await this._api.POST("/wallet/assets/trustline", {
2802
+ body: { code: asset.code, issuer: asset.issuer, ...limit !== void 0 && { limit } }
2803
+ });
2804
+ if (!error && data?.success) {
2805
+ if (data.content) this._setEnabledAssetsState({ step: "loaded", data: data.content });
2806
+ return { status: "success" };
2807
+ }
2808
+ const details = error?.details ?? error?.code;
2809
+ return { status: "error", ...details && { details } };
2810
+ } catch (err) {
2811
+ const details = err instanceof Error ? err.message : void 0;
2812
+ return { status: "error", ...details && { details } };
2813
+ }
2814
+ }
2815
+ return this.runTx("change_trust", {
2816
+ asset: {
2817
+ type: asset.code.length <= 4 ? "credit_alphanum4" : "credit_alphanum12",
2818
+ code: asset.code,
2819
+ issuer: asset.issuer
2820
+ },
2821
+ ...limit !== void 0 && { limit }
2822
+ });
2823
+ }
2777
2824
  // ─── Transactions ─────────────────────────────────────────────────────────
2778
2825
  /**
2779
2826
  * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
@@ -3409,6 +3456,9 @@ var PollarClient = class {
3409
3456
  void this._resume();
3410
3457
  } else {
3411
3458
  this._log.info("[PollarClient] No session in storage");
3459
+ if (this._authState.step !== "idle") {
3460
+ await this._clearSession();
3461
+ }
3412
3462
  }
3413
3463
  }
3414
3464
  /**
@@ -3455,8 +3505,11 @@ var PollarClient = class {
3455
3505
  user: session.user,
3456
3506
  // The wire response still carries the legacy `publicKey` alias (kept for
3457
3507
  // older SDKs); the persisted session standardizes on `address` only.
3508
+ // The wire also still emits the legacy type `'custodial'` (unchanged for
3509
+ // SDKs ≤0.8.x); we remap it to `'internal'` here so the SDK surface and
3510
+ // persisted session speak one vocabulary while the wire stays compatible.
3458
3511
  wallet: {
3459
- type: w.type,
3512
+ type: w.type === "custodial" ? "internal" : w.type,
3460
3513
  address: w.address ?? w.publicKey ?? null,
3461
3514
  ...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
3462
3515
  ...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},