@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/README.md +17 -3
- package/dist/adapters/expo-secure-store.js +1 -1
- package/dist/adapters/expo-secure-store.js.map +1 -1
- package/dist/adapters/expo-secure-store.mjs +1 -1
- package/dist/adapters/expo-secure-store.mjs.map +1 -1
- package/dist/adapters/react-native-appstate.js +1 -1
- package/dist/adapters/react-native-appstate.js.map +1 -1
- package/dist/adapters/react-native-appstate.mjs +1 -1
- package/dist/adapters/react-native-appstate.mjs.map +1 -1
- package/dist/adapters/react-native-keychain.js +1 -1
- package/dist/adapters/react-native-keychain.js.map +1 -1
- package/dist/adapters/react-native-keychain.mjs +1 -1
- package/dist/adapters/react-native-keychain.mjs.map +1 -1
- package/dist/index.d.mts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +197 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +197 -54
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.js +197 -54
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +197 -54
- package/dist/index.rn.mjs.map +1 -1
- package/package.json +6 -6
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.
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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 {
|
|
1735
|
-
|
|
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 {
|
|
1753
|
-
|
|
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
|
|
1848
|
-
|
|
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
|
-
|
|
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
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
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
|
-
|
|
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,
|
|
1878
|
-
|
|
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 {
|
|
1912
|
-
|
|
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];
|