@pollar/core 0.9.0-rc.4 → 0.9.1-rc.0

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.rn.mjs CHANGED
@@ -1019,6 +1019,17 @@ function createLogger(level = "info", sink = console) {
1019
1019
  return { error: gate("error"), warn: gate("warn"), info: gate("info"), debug: gate("debug") };
1020
1020
  }
1021
1021
 
1022
+ // src/lib/logging.ts
1023
+ var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set(["email", "code", "walletAddress", "dpopJwk", "response", "refreshToken"]);
1024
+ function redactBody(body) {
1025
+ if (!body || typeof body !== "object") return body;
1026
+ const out = {};
1027
+ for (const [key, value] of Object.entries(body)) {
1028
+ out[key] = SENSITIVE_BODY_KEYS.has(key) ? "[redacted]" : value;
1029
+ }
1030
+ return out;
1031
+ }
1032
+
1022
1033
  // src/storage/web.ts
1023
1034
  var LOG_PREFIX = "[PollarClient:storage]";
1024
1035
  function createMemoryAdapter() {
@@ -1107,7 +1118,7 @@ function defaultStorage(options = {}) {
1107
1118
  }
1108
1119
 
1109
1120
  // src/version.ts
1110
- var POLLAR_CORE_VERSION = "0.9.0-rc.4" ;
1121
+ var POLLAR_CORE_VERSION = "0.9.1-rc.0" ;
1111
1122
 
1112
1123
  // src/visibility/noop.ts
1113
1124
  function createNoopVisibilityProvider() {
@@ -1279,10 +1290,13 @@ function openAlbedoPopup(url) {
1279
1290
  }
1280
1291
  function waitForAlbedoPopup() {
1281
1292
  return new Promise((resolve, reject) => {
1282
- const timeout = setTimeout(() => {
1283
- window.removeEventListener("message", handler);
1284
- reject(new Error("Albedo response timeout"));
1285
- }, 2 * 60 * 1e3);
1293
+ const timeout = setTimeout(
1294
+ () => {
1295
+ window.removeEventListener("message", handler);
1296
+ reject(new Error("Albedo response timeout"));
1297
+ },
1298
+ 2 * 60 * 1e3
1299
+ );
1286
1300
  function handler(event) {
1287
1301
  if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1288
1302
  clearTimeout(timeout);
@@ -1642,6 +1656,16 @@ function waitForSessionReady(args) {
1642
1656
  return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal, logger) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal, logger);
1643
1657
  }
1644
1658
 
1659
+ // src/client/auth/logging.ts
1660
+ function logApiError(logger, route, detail = {}, level = "error") {
1661
+ const { body, error, data } = detail;
1662
+ logger[level](`[PollarClient:auth] ${route} failed`, {
1663
+ route,
1664
+ ...body !== void 0 ? { body: redactBody(body) } : {},
1665
+ cause: error ?? data
1666
+ });
1667
+ }
1668
+
1645
1669
  // src/client/auth/authenticate.ts
1646
1670
  async function authenticate(clientSessionId, deps, expectedWallet) {
1647
1671
  const { api, logger, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
@@ -1659,6 +1683,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1659
1683
  } catch (err) {
1660
1684
  if (err instanceof SessionStatusError) {
1661
1685
  const expired = err.code === "EXPIRED_CLIENT_ID";
1686
+ logApiError(logger, "session status", { data: err });
1662
1687
  setAuthState({
1663
1688
  step: "error",
1664
1689
  previousStep: "authenticating",
@@ -1671,14 +1696,12 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1671
1696
  throw err;
1672
1697
  }
1673
1698
  const dpopJwk = await deps.getPublicJwk();
1674
- const { data } = await api.POST("/auth/login", {
1675
- body: {
1676
- clientSessionId,
1677
- dpopJwk,
1678
- ...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
1679
- },
1680
- signal
1681
- });
1699
+ const body = {
1700
+ clientSessionId,
1701
+ dpopJwk,
1702
+ ...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
1703
+ };
1704
+ const { data, error } = await api.POST("/auth/login", { body, signal });
1682
1705
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1683
1706
  const sessionWallet = data.content.data?.providers?.wallet?.address;
1684
1707
  if (expectedWallet && sessionWallet !== expectedWallet) {
@@ -1693,6 +1716,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1693
1716
  }
1694
1717
  await storeSession(data.content);
1695
1718
  } else {
1719
+ if (!error) logApiError(logger, "POST /auth/login", { body, data });
1696
1720
  setAuthState({
1697
1721
  step: "error",
1698
1722
  previousStep: "authenticating",
@@ -1705,10 +1729,11 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1705
1729
 
1706
1730
  // src/client/auth/deps.ts
1707
1731
  async function createAuthSession(deps) {
1708
- const { api, signal, setAuthState } = deps;
1732
+ const { api, logger, signal, setAuthState } = deps;
1709
1733
  setAuthState({ step: "creating_session" });
1710
1734
  const { data, error } = await api.POST("/auth/session", { signal });
1711
1735
  if (error || !data?.success) {
1736
+ if (!error) logApiError(logger, "POST /auth/session", { data });
1712
1737
  setAuthState({
1713
1738
  step: "error",
1714
1739
  previousStep: "creating_session",
@@ -1727,13 +1752,12 @@ async function initEmailSession(deps) {
1727
1752
  deps.setAuthState({ step: "entering_email", clientSessionId });
1728
1753
  }
1729
1754
  async function sendEmailCode(email, clientSessionId, deps) {
1730
- const { api, signal, setAuthState } = deps;
1755
+ const { api, logger, signal, setAuthState } = deps;
1731
1756
  setAuthState({ step: "sending_email", email });
1732
- const { data, error } = await api.POST("/auth/email", {
1733
- body: { clientSessionId, email },
1734
- signal
1735
- });
1757
+ const body = { clientSessionId, email };
1758
+ const { data, error } = await api.POST("/auth/email", { body, signal });
1736
1759
  if (error || !data?.success) {
1760
+ if (!error) logApiError(logger, "POST /auth/email", { body, data });
1737
1761
  setAuthState({
1738
1762
  step: "error",
1739
1763
  previousStep: "sending_email",
@@ -1745,18 +1769,17 @@ async function sendEmailCode(email, clientSessionId, deps) {
1745
1769
  setAuthState({ step: "entering_code", clientSessionId, email });
1746
1770
  }
1747
1771
  async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1748
- const { api, signal, setAuthState } = deps;
1772
+ const { api, logger, signal, setAuthState } = deps;
1749
1773
  setAuthState({ step: "verifying_email_code", clientSessionId, email });
1750
- const { data, error } = await api.POST("/auth/email/verify-code", {
1751
- body: { clientSessionId, code },
1752
- signal
1753
- });
1774
+ const body = { clientSessionId, code };
1775
+ const { data, error } = await api.POST("/auth/email/verify-code", { body, signal });
1754
1776
  if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
1755
1777
  await authenticate(clientSessionId, deps);
1756
1778
  return;
1757
1779
  }
1758
1780
  const errCode = error?.error ?? data?.code;
1759
1781
  if (errCode === "SDK_EMAIL_CODE_EXPIRED") {
1782
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1760
1783
  setAuthState({
1761
1784
  step: "error",
1762
1785
  previousStep: "verifying_email_code",
@@ -1768,6 +1791,7 @@ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1768
1791
  return;
1769
1792
  }
1770
1793
  if (errCode === "INVALID_EMAIL_CODE" || errCode === "SDK_EMAIL_CODE_INVALID") {
1794
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1771
1795
  setAuthState({
1772
1796
  step: "error",
1773
1797
  previousStep: "verifying_email_code",
@@ -1778,6 +1802,7 @@ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1778
1802
  });
1779
1803
  return;
1780
1804
  }
1805
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1781
1806
  setAuthState({
1782
1807
  step: "error",
1783
1808
  previousStep: "verifying_email_code",
@@ -1827,10 +1852,73 @@ async function loginOAuth(provider, deps) {
1827
1852
  await authenticate(clientSessionId, deps);
1828
1853
  }
1829
1854
 
1855
+ // src/client/auth/errorMessages.ts
1856
+ var CATALOG = {
1857
+ // ── Smart-account deploy / sponsor wallet ──────────────────────────────────
1858
+ SPONSOR_NOT_FUNDED: {
1859
+ message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
1860
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1861
+ },
1862
+ APP_WALLET_NOT_FOUND: {
1863
+ message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
1864
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1865
+ },
1866
+ WALLET_NOT_FOUND: {
1867
+ message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
1868
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1869
+ },
1870
+ PASSKEY_DEPLOY_FAILED: {
1871
+ message: "We couldn't finish creating your wallet. Please try again in a moment.",
1872
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1873
+ },
1874
+ // ── Passkey ceremony ────────────────────────────────────────────────────────
1875
+ PASSKEY_ALREADY_REGISTERED: {
1876
+ message: "A passkey is already registered for this account. Try signing in instead.",
1877
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1878
+ },
1879
+ PASSKEY_UNKNOWN_CREDENTIAL: {
1880
+ message: "We don't recognize this passkey. Try creating a new one.",
1881
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1882
+ },
1883
+ PASSKEY_VERIFICATION_FAILED: {
1884
+ message: "We couldn't verify your passkey. Please try again.",
1885
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1886
+ },
1887
+ PASSKEY_CHALLENGE_MISSING: {
1888
+ message: "Your passkey session expired. Please start again.",
1889
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1890
+ },
1891
+ // ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
1892
+ TX_INSUFFICIENT_BALANCE: {
1893
+ message: "Insufficient balance to complete this transaction.",
1894
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1895
+ },
1896
+ TX_DESTINATION_NOT_FOUND: {
1897
+ message: "The destination account doesn't exist on the network yet.",
1898
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1899
+ },
1900
+ TX_NO_TRUSTLINE: {
1901
+ message: "The destination can't receive this asset yet (no trustline).",
1902
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1903
+ },
1904
+ TX_BAD_SEQUENCE: {
1905
+ message: "Something went out of sync. Please try again.",
1906
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1907
+ }
1908
+ };
1909
+ function resolveAuthError(code, fallbackMessage) {
1910
+ if (code && CATALOG[code]) return CATALOG[code];
1911
+ return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
1912
+ }
1913
+ function extractErrorCode(error, data) {
1914
+ return error?.code ?? data?.code ?? void 0;
1915
+ }
1916
+
1830
1917
  // src/client/auth/passkeyFlow.ts
1831
1918
  async function smartWalletFlow(deps, mode) {
1832
- const { api, signal, setAuthState, passkey } = deps;
1919
+ const { api, logger, signal, setAuthState, passkey } = deps;
1833
1920
  if (!passkey) {
1921
+ logger.error("[PollarClient:auth] passkey ceremony not configured");
1834
1922
  setAuthState({
1835
1923
  step: "error",
1836
1924
  previousStep: "creating_session",
@@ -1842,38 +1930,44 @@ async function smartWalletFlow(deps, mode) {
1842
1930
  const clientSessionId = await createAuthSession(deps);
1843
1931
  if (!clientSessionId) return;
1844
1932
  try {
1845
- const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
1846
- body: { clientSessionId },
1933
+ const challengeBody = { clientSessionId };
1934
+ const { data: challengeData, error: challengeError } = await api.POST("/auth/passkey/challenge", {
1935
+ body: challengeBody,
1847
1936
  signal
1848
1937
  });
1849
1938
  const challenge = challengeData?.content?.challenge;
1850
1939
  if (!challengeData?.success || !challenge) {
1851
- return failPasskey(setAuthState, "Failed to start passkey");
1940
+ if (!challengeError) logApiError(logger, "POST /auth/passkey/challenge", { body: challengeBody, data: challengeData });
1941
+ return failPasskey(setAuthState, extractErrorCode(challengeError, challengeData), "Failed to start passkey");
1852
1942
  }
1853
1943
  setAuthState({ step: "creating_passkey" });
1854
1944
  const ceremony = await passkey({ challenge, mode });
1855
1945
  const response = ceremony.response;
1856
1946
  if (ceremony.kind === "register") {
1857
1947
  setAuthState({ step: "deploying_smart_account" });
1858
- const { data } = await api.POST("/auth/passkey/register", {
1859
- body: { clientSessionId, response },
1860
- signal
1861
- });
1862
- if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
1948
+ const body = { clientSessionId, response };
1949
+ const { data, error } = await api.POST("/auth/passkey/register", { body, signal });
1950
+ if (!data?.success) {
1951
+ if (!error) logApiError(logger, "POST /auth/passkey/register", { body, data });
1952
+ return failPasskey(setAuthState, extractErrorCode(error, data), "Passkey registration failed");
1953
+ }
1863
1954
  } else {
1864
- const { data } = await api.POST("/auth/passkey/login", {
1865
- body: { clientSessionId, response },
1866
- signal
1867
- });
1868
- if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
1955
+ const body = { clientSessionId, response };
1956
+ const { data, error } = await api.POST("/auth/passkey/login", { body, signal });
1957
+ if (!data?.success) {
1958
+ if (!error) logApiError(logger, "POST /auth/passkey/login", { body, data });
1959
+ return failPasskey(setAuthState, extractErrorCode(error, data), "Passkey authentication failed");
1960
+ }
1869
1961
  }
1870
- } catch {
1871
- return failPasskey(setAuthState, "Passkey login failed");
1962
+ } catch (err) {
1963
+ logApiError(logger, "passkey ceremony", { error: err });
1964
+ return failPasskey(setAuthState, void 0, "Passkey login failed");
1872
1965
  }
1873
1966
  await authenticate(clientSessionId, deps);
1874
1967
  }
1875
- function failPasskey(setAuthState, message) {
1876
- setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
1968
+ function failPasskey(setAuthState, code, fallbackMessage) {
1969
+ const { message, errorCode } = resolveAuthError(code, fallbackMessage);
1970
+ setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode });
1877
1971
  }
1878
1972
 
1879
1973
  // src/client/auth/walletFlow.ts
@@ -1890,7 +1984,7 @@ function withSignal(promise, signal) {
1890
1984
  ]);
1891
1985
  }
1892
1986
  async function loginWallet(type, deps) {
1893
- const { api, signal, setAuthState } = deps;
1987
+ const { api, logger, signal, setAuthState } = deps;
1894
1988
  const clientSessionId = await createAuthSession(deps);
1895
1989
  if (!clientSessionId) return;
1896
1990
  let connectedWallet;
@@ -1906,11 +2000,10 @@ async function loginWallet(type, deps) {
1906
2000
  connectedWallet = address;
1907
2001
  deps.storeWalletAdapter(adapter, type);
1908
2002
  setAuthState({ step: "authenticating_wallet" });
1909
- const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1910
- body: { clientSessionId, walletAddress: address },
1911
- signal
1912
- });
2003
+ const body = { clientSessionId, walletAddress: address };
2004
+ const { data: walletData, error: walletError } = await api.POST("/auth/wallet", { body, signal });
1913
2005
  if (walletError || !walletData?.success) {
2006
+ if (!walletError) logApiError(logger, "POST /auth/wallet", { body, data: walletData });
1914
2007
  setAuthState({
1915
2008
  step: "error",
1916
2009
  previousStep: "authenticating_wallet",
@@ -1919,7 +2012,8 @@ async function loginWallet(type, deps) {
1919
2012
  });
1920
2013
  return;
1921
2014
  }
1922
- } catch {
2015
+ } catch (err) {
2016
+ logApiError(logger, "wallet connect", { error: err });
1923
2017
  setAuthState({
1924
2018
  step: "error",
1925
2019
  previousStep: "connecting_wallet",
@@ -2125,28 +2219,77 @@ var PollarClient = class {
2125
2219
  onResponse: async ({ request, response }) => {
2126
2220
  const newNonce = response.headers.get("DPoP-Nonce");
2127
2221
  if (newNonce) self._dpopNonce = newNonce;
2128
- if (response.status !== 401) return response;
2222
+ if (response.status !== 401) return self._logHttp(request, response);
2129
2223
  const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
2130
2224
  const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
2131
2225
  if (request.url.includes("/auth/refresh")) {
2132
- if (isNonceChallenge) return self._retryRequest(request);
2133
- return response;
2226
+ if (isNonceChallenge) return self._logHttp(request, await self._retryRequest(request));
2227
+ return self._logHttp(request, response);
2134
2228
  }
2135
2229
  if (!isNonceChallenge) {
2136
2230
  try {
2137
2231
  await self.refresh();
2138
2232
  } catch {
2139
- return response;
2233
+ return self._logHttp(request, response);
2140
2234
  }
2141
2235
  const method = request.method.toUpperCase();
2142
2236
  if (method !== "GET" && method !== "HEAD") {
2143
- return response;
2237
+ return self._logHttp(request, response);
2144
2238
  }
2145
2239
  }
2146
- return self._retryRequest(request);
2240
+ return self._logHttp(request, await self._retryRequest(request));
2147
2241
  }
2148
2242
  });
2149
2243
  }
2244
+ /**
2245
+ * Logs the final outcome of an SDK API call exactly once: successes (`2xx`) at
2246
+ * `debug` (method + path + status, no body), failures (`4xx`/`5xx`) at `error`
2247
+ * with the redacted request body and the response error body. Returns the
2248
+ * response so it can be chained at the middleware's return points. The error
2249
+ * body is read off a synchronous `clone()` so it never disturbs the body the
2250
+ * caller consumes.
2251
+ */
2252
+ _logHttp(request, response) {
2253
+ const path = this._httpPath(request.url);
2254
+ const label = `[PollarClient:http] ${request.method.toUpperCase()} ${path} ${response.status}`;
2255
+ if (response.ok) {
2256
+ this._log.debug(label);
2257
+ } else {
2258
+ void this._logHttpError(label, request, response.clone());
2259
+ }
2260
+ return response;
2261
+ }
2262
+ /** Reads the redacted request body + JSON response body and logs at `error`. */
2263
+ async _logHttpError(label, request, response) {
2264
+ let requestBody;
2265
+ const cached = this._requestBodyCache.get(request);
2266
+ if (cached) {
2267
+ try {
2268
+ requestBody = redactBody(JSON.parse(new TextDecoder().decode(cached)));
2269
+ } catch {
2270
+ }
2271
+ }
2272
+ let responseBody;
2273
+ if ((response.headers.get("content-type") ?? "").includes("application/json")) {
2274
+ try {
2275
+ responseBody = await response.json();
2276
+ } catch {
2277
+ }
2278
+ }
2279
+ this._log.error(label, {
2280
+ ...requestBody !== void 0 ? { requestBody } : {},
2281
+ ...responseBody !== void 0 ? { responseBody } : {}
2282
+ });
2283
+ }
2284
+ /** Strips origin + `/v1` version prefix from a request URL for compact logs. */
2285
+ _httpPath(url) {
2286
+ try {
2287
+ const { pathname } = new URL(url);
2288
+ return pathname.startsWith("/v1/") ? pathname.slice(3) : pathname;
2289
+ } catch {
2290
+ return url;
2291
+ }
2292
+ }
2150
2293
  async _buildProofForRequest(request, accessToken) {
2151
2294
  try {
2152
2295
  const htu = request.url.split("?")[0].split("#")[0];