@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.mjs CHANGED
@@ -1083,6 +1083,17 @@ function createLogger(level = "info", sink = console) {
1083
1083
  return { error: gate("error"), warn: gate("warn"), info: gate("info"), debug: gate("debug") };
1084
1084
  }
1085
1085
 
1086
+ // src/lib/logging.ts
1087
+ var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set(["email", "code", "walletAddress", "dpopJwk", "response", "refreshToken"]);
1088
+ function redactBody(body) {
1089
+ if (!body || typeof body !== "object") return body;
1090
+ const out = {};
1091
+ for (const [key, value] of Object.entries(body)) {
1092
+ out[key] = SENSITIVE_BODY_KEYS.has(key) ? "[redacted]" : value;
1093
+ }
1094
+ return out;
1095
+ }
1096
+
1086
1097
  // src/storage/web.ts
1087
1098
  var LOG_PREFIX = "[PollarClient:storage]";
1088
1099
  function createMemoryAdapter() {
@@ -1171,7 +1182,7 @@ function defaultStorage(options = {}) {
1171
1182
  }
1172
1183
 
1173
1184
  // src/version.ts
1174
- var POLLAR_CORE_VERSION = "0.9.0-rc.4" ;
1185
+ var POLLAR_CORE_VERSION = "0.9.1-rc.0" ;
1175
1186
 
1176
1187
  // src/visibility/noop.ts
1177
1188
  function createNoopVisibilityProvider() {
@@ -1343,10 +1354,13 @@ function openAlbedoPopup(url) {
1343
1354
  }
1344
1355
  function waitForAlbedoPopup() {
1345
1356
  return new Promise((resolve, reject) => {
1346
- const timeout = setTimeout(() => {
1347
- window.removeEventListener("message", handler);
1348
- reject(new Error("Albedo response timeout"));
1349
- }, 2 * 60 * 1e3);
1357
+ const timeout = setTimeout(
1358
+ () => {
1359
+ window.removeEventListener("message", handler);
1360
+ reject(new Error("Albedo response timeout"));
1361
+ },
1362
+ 2 * 60 * 1e3
1363
+ );
1350
1364
  function handler(event) {
1351
1365
  if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1352
1366
  clearTimeout(timeout);
@@ -1706,6 +1720,16 @@ function waitForSessionReady(args) {
1706
1720
  return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal, logger) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal, logger);
1707
1721
  }
1708
1722
 
1723
+ // src/client/auth/logging.ts
1724
+ function logApiError(logger, route, detail = {}, level = "error") {
1725
+ const { body, error, data } = detail;
1726
+ logger[level](`[PollarClient:auth] ${route} failed`, {
1727
+ route,
1728
+ ...body !== void 0 ? { body: redactBody(body) } : {},
1729
+ cause: error ?? data
1730
+ });
1731
+ }
1732
+
1709
1733
  // src/client/auth/authenticate.ts
1710
1734
  async function authenticate(clientSessionId, deps, expectedWallet) {
1711
1735
  const { api, logger, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
@@ -1723,6 +1747,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1723
1747
  } catch (err) {
1724
1748
  if (err instanceof SessionStatusError) {
1725
1749
  const expired = err.code === "EXPIRED_CLIENT_ID";
1750
+ logApiError(logger, "session status", { data: err });
1726
1751
  setAuthState({
1727
1752
  step: "error",
1728
1753
  previousStep: "authenticating",
@@ -1735,14 +1760,12 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1735
1760
  throw err;
1736
1761
  }
1737
1762
  const dpopJwk = await deps.getPublicJwk();
1738
- const { data } = await api.POST("/auth/login", {
1739
- body: {
1740
- clientSessionId,
1741
- dpopJwk,
1742
- ...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
1743
- },
1744
- signal
1745
- });
1763
+ const body = {
1764
+ clientSessionId,
1765
+ dpopJwk,
1766
+ ...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
1767
+ };
1768
+ const { data, error } = await api.POST("/auth/login", { body, signal });
1746
1769
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1747
1770
  const sessionWallet = data.content.data?.providers?.wallet?.address;
1748
1771
  if (expectedWallet && sessionWallet !== expectedWallet) {
@@ -1757,6 +1780,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1757
1780
  }
1758
1781
  await storeSession(data.content);
1759
1782
  } else {
1783
+ if (!error) logApiError(logger, "POST /auth/login", { body, data });
1760
1784
  setAuthState({
1761
1785
  step: "error",
1762
1786
  previousStep: "authenticating",
@@ -1769,10 +1793,11 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1769
1793
 
1770
1794
  // src/client/auth/deps.ts
1771
1795
  async function createAuthSession(deps) {
1772
- const { api, signal, setAuthState } = deps;
1796
+ const { api, logger, signal, setAuthState } = deps;
1773
1797
  setAuthState({ step: "creating_session" });
1774
1798
  const { data, error } = await api.POST("/auth/session", { signal });
1775
1799
  if (error || !data?.success) {
1800
+ if (!error) logApiError(logger, "POST /auth/session", { data });
1776
1801
  setAuthState({
1777
1802
  step: "error",
1778
1803
  previousStep: "creating_session",
@@ -1791,13 +1816,12 @@ async function initEmailSession(deps) {
1791
1816
  deps.setAuthState({ step: "entering_email", clientSessionId });
1792
1817
  }
1793
1818
  async function sendEmailCode(email, clientSessionId, deps) {
1794
- const { api, signal, setAuthState } = deps;
1819
+ const { api, logger, signal, setAuthState } = deps;
1795
1820
  setAuthState({ step: "sending_email", email });
1796
- const { data, error } = await api.POST("/auth/email", {
1797
- body: { clientSessionId, email },
1798
- signal
1799
- });
1821
+ const body = { clientSessionId, email };
1822
+ const { data, error } = await api.POST("/auth/email", { body, signal });
1800
1823
  if (error || !data?.success) {
1824
+ if (!error) logApiError(logger, "POST /auth/email", { body, data });
1801
1825
  setAuthState({
1802
1826
  step: "error",
1803
1827
  previousStep: "sending_email",
@@ -1809,18 +1833,17 @@ async function sendEmailCode(email, clientSessionId, deps) {
1809
1833
  setAuthState({ step: "entering_code", clientSessionId, email });
1810
1834
  }
1811
1835
  async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1812
- const { api, signal, setAuthState } = deps;
1836
+ const { api, logger, signal, setAuthState } = deps;
1813
1837
  setAuthState({ step: "verifying_email_code", clientSessionId, email });
1814
- const { data, error } = await api.POST("/auth/email/verify-code", {
1815
- body: { clientSessionId, code },
1816
- signal
1817
- });
1838
+ const body = { clientSessionId, code };
1839
+ const { data, error } = await api.POST("/auth/email/verify-code", { body, signal });
1818
1840
  if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
1819
1841
  await authenticate(clientSessionId, deps);
1820
1842
  return;
1821
1843
  }
1822
1844
  const errCode = error?.error ?? data?.code;
1823
1845
  if (errCode === "SDK_EMAIL_CODE_EXPIRED") {
1846
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1824
1847
  setAuthState({
1825
1848
  step: "error",
1826
1849
  previousStep: "verifying_email_code",
@@ -1832,6 +1855,7 @@ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1832
1855
  return;
1833
1856
  }
1834
1857
  if (errCode === "INVALID_EMAIL_CODE" || errCode === "SDK_EMAIL_CODE_INVALID") {
1858
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1835
1859
  setAuthState({
1836
1860
  step: "error",
1837
1861
  previousStep: "verifying_email_code",
@@ -1842,6 +1866,7 @@ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1842
1866
  });
1843
1867
  return;
1844
1868
  }
1869
+ if (!error) logApiError(logger, "POST /auth/email/verify-code", { body, data });
1845
1870
  setAuthState({
1846
1871
  step: "error",
1847
1872
  previousStep: "verifying_email_code",
@@ -1891,10 +1916,73 @@ async function loginOAuth(provider, deps) {
1891
1916
  await authenticate(clientSessionId, deps);
1892
1917
  }
1893
1918
 
1919
+ // src/client/auth/errorMessages.ts
1920
+ var CATALOG = {
1921
+ // ── Smart-account deploy / sponsor wallet ──────────────────────────────────
1922
+ SPONSOR_NOT_FUNDED: {
1923
+ message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
1924
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1925
+ },
1926
+ APP_WALLET_NOT_FOUND: {
1927
+ message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
1928
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1929
+ },
1930
+ WALLET_NOT_FOUND: {
1931
+ message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
1932
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1933
+ },
1934
+ PASSKEY_DEPLOY_FAILED: {
1935
+ message: "We couldn't finish creating your wallet. Please try again in a moment.",
1936
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1937
+ },
1938
+ // ── Passkey ceremony ────────────────────────────────────────────────────────
1939
+ PASSKEY_ALREADY_REGISTERED: {
1940
+ message: "A passkey is already registered for this account. Try signing in instead.",
1941
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1942
+ },
1943
+ PASSKEY_UNKNOWN_CREDENTIAL: {
1944
+ message: "We don't recognize this passkey. Try creating a new one.",
1945
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1946
+ },
1947
+ PASSKEY_VERIFICATION_FAILED: {
1948
+ message: "We couldn't verify your passkey. Please try again.",
1949
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1950
+ },
1951
+ PASSKEY_CHALLENGE_MISSING: {
1952
+ message: "Your passkey session expired. Please start again.",
1953
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1954
+ },
1955
+ // ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
1956
+ TX_INSUFFICIENT_BALANCE: {
1957
+ message: "Insufficient balance to complete this transaction.",
1958
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1959
+ },
1960
+ TX_DESTINATION_NOT_FOUND: {
1961
+ message: "The destination account doesn't exist on the network yet.",
1962
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1963
+ },
1964
+ TX_NO_TRUSTLINE: {
1965
+ message: "The destination can't receive this asset yet (no trustline).",
1966
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1967
+ },
1968
+ TX_BAD_SEQUENCE: {
1969
+ message: "Something went out of sync. Please try again.",
1970
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1971
+ }
1972
+ };
1973
+ function resolveAuthError(code, fallbackMessage) {
1974
+ if (code && CATALOG[code]) return CATALOG[code];
1975
+ return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
1976
+ }
1977
+ function extractErrorCode(error, data) {
1978
+ return error?.code ?? data?.code ?? void 0;
1979
+ }
1980
+
1894
1981
  // src/client/auth/passkeyFlow.ts
1895
1982
  async function smartWalletFlow(deps, mode) {
1896
- const { api, signal, setAuthState, passkey } = deps;
1983
+ const { api, logger, signal, setAuthState, passkey } = deps;
1897
1984
  if (!passkey) {
1985
+ logger.error("[PollarClient:auth] passkey ceremony not configured");
1898
1986
  setAuthState({
1899
1987
  step: "error",
1900
1988
  previousStep: "creating_session",
@@ -1906,38 +1994,44 @@ async function smartWalletFlow(deps, mode) {
1906
1994
  const clientSessionId = await createAuthSession(deps);
1907
1995
  if (!clientSessionId) return;
1908
1996
  try {
1909
- const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
1910
- body: { clientSessionId },
1997
+ const challengeBody = { clientSessionId };
1998
+ const { data: challengeData, error: challengeError } = await api.POST("/auth/passkey/challenge", {
1999
+ body: challengeBody,
1911
2000
  signal
1912
2001
  });
1913
2002
  const challenge = challengeData?.content?.challenge;
1914
2003
  if (!challengeData?.success || !challenge) {
1915
- return failPasskey(setAuthState, "Failed to start passkey");
2004
+ if (!challengeError) logApiError(logger, "POST /auth/passkey/challenge", { body: challengeBody, data: challengeData });
2005
+ return failPasskey(setAuthState, extractErrorCode(challengeError, challengeData), "Failed to start passkey");
1916
2006
  }
1917
2007
  setAuthState({ step: "creating_passkey" });
1918
2008
  const ceremony = await passkey({ challenge, mode });
1919
2009
  const response = ceremony.response;
1920
2010
  if (ceremony.kind === "register") {
1921
2011
  setAuthState({ step: "deploying_smart_account" });
1922
- const { data } = await api.POST("/auth/passkey/register", {
1923
- body: { clientSessionId, response },
1924
- signal
1925
- });
1926
- if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
2012
+ const body = { clientSessionId, response };
2013
+ const { data, error } = await api.POST("/auth/passkey/register", { body, signal });
2014
+ if (!data?.success) {
2015
+ if (!error) logApiError(logger, "POST /auth/passkey/register", { body, data });
2016
+ return failPasskey(setAuthState, extractErrorCode(error, data), "Passkey registration failed");
2017
+ }
1927
2018
  } else {
1928
- const { data } = await api.POST("/auth/passkey/login", {
1929
- body: { clientSessionId, response },
1930
- signal
1931
- });
1932
- if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
2019
+ const body = { clientSessionId, response };
2020
+ const { data, error } = await api.POST("/auth/passkey/login", { body, signal });
2021
+ if (!data?.success) {
2022
+ if (!error) logApiError(logger, "POST /auth/passkey/login", { body, data });
2023
+ return failPasskey(setAuthState, extractErrorCode(error, data), "Passkey authentication failed");
2024
+ }
1933
2025
  }
1934
- } catch {
1935
- return failPasskey(setAuthState, "Passkey login failed");
2026
+ } catch (err) {
2027
+ logApiError(logger, "passkey ceremony", { error: err });
2028
+ return failPasskey(setAuthState, void 0, "Passkey login failed");
1936
2029
  }
1937
2030
  await authenticate(clientSessionId, deps);
1938
2031
  }
1939
- function failPasskey(setAuthState, message) {
1940
- setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
2032
+ function failPasskey(setAuthState, code, fallbackMessage) {
2033
+ const { message, errorCode } = resolveAuthError(code, fallbackMessage);
2034
+ setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode });
1941
2035
  }
1942
2036
 
1943
2037
  // src/client/auth/walletFlow.ts
@@ -1954,7 +2048,7 @@ function withSignal(promise, signal) {
1954
2048
  ]);
1955
2049
  }
1956
2050
  async function loginWallet(type, deps) {
1957
- const { api, signal, setAuthState } = deps;
2051
+ const { api, logger, signal, setAuthState } = deps;
1958
2052
  const clientSessionId = await createAuthSession(deps);
1959
2053
  if (!clientSessionId) return;
1960
2054
  let connectedWallet;
@@ -1970,11 +2064,10 @@ async function loginWallet(type, deps) {
1970
2064
  connectedWallet = address;
1971
2065
  deps.storeWalletAdapter(adapter, type);
1972
2066
  setAuthState({ step: "authenticating_wallet" });
1973
- const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1974
- body: { clientSessionId, walletAddress: address },
1975
- signal
1976
- });
2067
+ const body = { clientSessionId, walletAddress: address };
2068
+ const { data: walletData, error: walletError } = await api.POST("/auth/wallet", { body, signal });
1977
2069
  if (walletError || !walletData?.success) {
2070
+ if (!walletError) logApiError(logger, "POST /auth/wallet", { body, data: walletData });
1978
2071
  setAuthState({
1979
2072
  step: "error",
1980
2073
  previousStep: "authenticating_wallet",
@@ -1983,7 +2076,8 @@ async function loginWallet(type, deps) {
1983
2076
  });
1984
2077
  return;
1985
2078
  }
1986
- } catch {
2079
+ } catch (err) {
2080
+ logApiError(logger, "wallet connect", { error: err });
1987
2081
  setAuthState({
1988
2082
  step: "error",
1989
2083
  previousStep: "connecting_wallet",
@@ -2189,28 +2283,77 @@ var PollarClient = class {
2189
2283
  onResponse: async ({ request, response }) => {
2190
2284
  const newNonce = response.headers.get("DPoP-Nonce");
2191
2285
  if (newNonce) self._dpopNonce = newNonce;
2192
- if (response.status !== 401) return response;
2286
+ if (response.status !== 401) return self._logHttp(request, response);
2193
2287
  const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
2194
2288
  const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
2195
2289
  if (request.url.includes("/auth/refresh")) {
2196
- if (isNonceChallenge) return self._retryRequest(request);
2197
- return response;
2290
+ if (isNonceChallenge) return self._logHttp(request, await self._retryRequest(request));
2291
+ return self._logHttp(request, response);
2198
2292
  }
2199
2293
  if (!isNonceChallenge) {
2200
2294
  try {
2201
2295
  await self.refresh();
2202
2296
  } catch {
2203
- return response;
2297
+ return self._logHttp(request, response);
2204
2298
  }
2205
2299
  const method = request.method.toUpperCase();
2206
2300
  if (method !== "GET" && method !== "HEAD") {
2207
- return response;
2301
+ return self._logHttp(request, response);
2208
2302
  }
2209
2303
  }
2210
- return self._retryRequest(request);
2304
+ return self._logHttp(request, await self._retryRequest(request));
2211
2305
  }
2212
2306
  });
2213
2307
  }
2308
+ /**
2309
+ * Logs the final outcome of an SDK API call exactly once: successes (`2xx`) at
2310
+ * `debug` (method + path + status, no body), failures (`4xx`/`5xx`) at `error`
2311
+ * with the redacted request body and the response error body. Returns the
2312
+ * response so it can be chained at the middleware's return points. The error
2313
+ * body is read off a synchronous `clone()` so it never disturbs the body the
2314
+ * caller consumes.
2315
+ */
2316
+ _logHttp(request, response) {
2317
+ const path = this._httpPath(request.url);
2318
+ const label = `[PollarClient:http] ${request.method.toUpperCase()} ${path} ${response.status}`;
2319
+ if (response.ok) {
2320
+ this._log.debug(label);
2321
+ } else {
2322
+ void this._logHttpError(label, request, response.clone());
2323
+ }
2324
+ return response;
2325
+ }
2326
+ /** Reads the redacted request body + JSON response body and logs at `error`. */
2327
+ async _logHttpError(label, request, response) {
2328
+ let requestBody;
2329
+ const cached = this._requestBodyCache.get(request);
2330
+ if (cached) {
2331
+ try {
2332
+ requestBody = redactBody(JSON.parse(new TextDecoder().decode(cached)));
2333
+ } catch {
2334
+ }
2335
+ }
2336
+ let responseBody;
2337
+ if ((response.headers.get("content-type") ?? "").includes("application/json")) {
2338
+ try {
2339
+ responseBody = await response.json();
2340
+ } catch {
2341
+ }
2342
+ }
2343
+ this._log.error(label, {
2344
+ ...requestBody !== void 0 ? { requestBody } : {},
2345
+ ...responseBody !== void 0 ? { responseBody } : {}
2346
+ });
2347
+ }
2348
+ /** Strips origin + `/v1` version prefix from a request URL for compact logs. */
2349
+ _httpPath(url) {
2350
+ try {
2351
+ const { pathname } = new URL(url);
2352
+ return pathname.startsWith("/v1/") ? pathname.slice(3) : pathname;
2353
+ } catch {
2354
+ return url;
2355
+ }
2356
+ }
2214
2357
  async _buildProofForRequest(request, accessToken) {
2215
2358
  try {
2216
2359
  const htu = request.url.split("?")[0].split("#")[0];