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