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