@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/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.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" ;
|
|
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
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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 {
|
|
1797
|
-
|
|
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 {
|
|
1815
|
-
|
|
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
|
|
1910
|
-
|
|
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
|
-
|
|
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
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
-
|
|
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,
|
|
1940
|
-
|
|
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 {
|
|
1974
|
-
|
|
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];
|