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