@pollar/core 0.9.1-rc.0 → 0.10.0-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.d.mts +2275 -1426
- package/dist/index.d.ts +2275 -1426
- package/dist/index.js +498 -137
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +498 -137
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +2 -2
- package/dist/index.rn.d.ts +2 -2
- package/dist/index.rn.js +475 -136
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +475 -136
- package/dist/index.rn.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.rn.mjs
CHANGED
|
@@ -1020,7 +1020,19 @@ function createLogger(level = "info", sink = console) {
|
|
|
1020
1020
|
}
|
|
1021
1021
|
|
|
1022
1022
|
// src/lib/logging.ts
|
|
1023
|
-
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1023
|
+
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1024
|
+
"email",
|
|
1025
|
+
"code",
|
|
1026
|
+
"walletAddress",
|
|
1027
|
+
"dpopJwk",
|
|
1028
|
+
"response",
|
|
1029
|
+
"refreshToken",
|
|
1030
|
+
// SEP-10 challenge envelopes: a counter-signed challenge is a live, replayable
|
|
1031
|
+
// auth credential — never log it in the clear.
|
|
1032
|
+
"signedChallengeXdr",
|
|
1033
|
+
"challengeXdr",
|
|
1034
|
+
"signedTxXdr"
|
|
1035
|
+
]);
|
|
1024
1036
|
function redactBody(body) {
|
|
1025
1037
|
if (!body || typeof body !== "object") return body;
|
|
1026
1038
|
const out = {};
|
|
@@ -1117,8 +1129,36 @@ function defaultStorage(options = {}) {
|
|
|
1117
1129
|
return createLocalStorageAdapter(options);
|
|
1118
1130
|
}
|
|
1119
1131
|
|
|
1132
|
+
// src/types.ts
|
|
1133
|
+
var AUTH_ERROR_CODES = {
|
|
1134
|
+
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1135
|
+
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1136
|
+
SESSION_INVALID: "SESSION_INVALID",
|
|
1137
|
+
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1138
|
+
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1139
|
+
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1140
|
+
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1141
|
+
AUTH_FAILED: "AUTH_FAILED",
|
|
1142
|
+
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1143
|
+
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1144
|
+
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1145
|
+
EXTERNAL_AUTH_FAILED: "EXTERNAL_AUTH_FAILED",
|
|
1146
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1147
|
+
// Generic bucket for on-chain transaction failures; the precise reason is the
|
|
1148
|
+
// backend `code` (e.g. TX_FEE_LIMIT_EXCEEDED) carried alongside on the outcome.
|
|
1149
|
+
TX_FAILED: "TX_FAILED",
|
|
1150
|
+
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1151
|
+
};
|
|
1152
|
+
var PollarFlowError = class extends Error {
|
|
1153
|
+
constructor(message) {
|
|
1154
|
+
super(message);
|
|
1155
|
+
this.code = "INVALID_FLOW";
|
|
1156
|
+
this.name = "PollarFlowError";
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1120
1160
|
// src/version.ts
|
|
1121
|
-
var POLLAR_CORE_VERSION = "0.
|
|
1161
|
+
var POLLAR_CORE_VERSION = "0.10.0-rc.0" ;
|
|
1122
1162
|
|
|
1123
1163
|
// src/visibility/noop.ts
|
|
1124
1164
|
function createNoopVisibilityProvider() {
|
|
@@ -1171,30 +1211,6 @@ function defaultVisibilityProvider() {
|
|
|
1171
1211
|
return createNoopVisibilityProvider();
|
|
1172
1212
|
}
|
|
1173
1213
|
|
|
1174
|
-
// src/types.ts
|
|
1175
|
-
var AUTH_ERROR_CODES = {
|
|
1176
|
-
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1177
|
-
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1178
|
-
SESSION_INVALID: "SESSION_INVALID",
|
|
1179
|
-
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1180
|
-
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1181
|
-
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1182
|
-
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1183
|
-
AUTH_FAILED: "AUTH_FAILED",
|
|
1184
|
-
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1185
|
-
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1186
|
-
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1187
|
-
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1188
|
-
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1189
|
-
};
|
|
1190
|
-
var PollarFlowError = class extends Error {
|
|
1191
|
-
constructor(message) {
|
|
1192
|
-
super(message);
|
|
1193
|
-
this.code = "INVALID_FLOW";
|
|
1194
|
-
this.name = "PollarFlowError";
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
|
|
1198
1214
|
// src/wallets/FreighterAdapter.ts
|
|
1199
1215
|
var import_freighter_api = __toESM(require_index_min());
|
|
1200
1216
|
|
|
@@ -1450,6 +1466,10 @@ function isValidSession(value, logger = console) {
|
|
|
1450
1466
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be internal|smart|external");
|
|
1451
1467
|
return false;
|
|
1452
1468
|
}
|
|
1469
|
+
if (w["provider"] !== void 0 && typeof w["provider"] !== "string") {
|
|
1470
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.provider must be a string if present");
|
|
1471
|
+
return false;
|
|
1472
|
+
}
|
|
1453
1473
|
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1454
1474
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1455
1475
|
return false;
|
|
@@ -1745,14 +1765,91 @@ async function createAuthSession(deps) {
|
|
|
1745
1765
|
return data.content.clientSessionId;
|
|
1746
1766
|
}
|
|
1747
1767
|
|
|
1768
|
+
// src/client/auth/errorMessages.ts
|
|
1769
|
+
var CATALOG = {
|
|
1770
|
+
// ── Smart-account deploy / sponsor wallet ──────────────────────────────────
|
|
1771
|
+
SPONSOR_NOT_FUNDED: {
|
|
1772
|
+
message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
|
|
1773
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1774
|
+
},
|
|
1775
|
+
APP_WALLET_NOT_FOUND: {
|
|
1776
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1777
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1778
|
+
},
|
|
1779
|
+
WALLET_NOT_FOUND: {
|
|
1780
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1781
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1782
|
+
},
|
|
1783
|
+
PASSKEY_DEPLOY_FAILED: {
|
|
1784
|
+
message: "We couldn't finish creating your wallet. Please try again in a moment.",
|
|
1785
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1786
|
+
},
|
|
1787
|
+
// ── Passkey ceremony ────────────────────────────────────────────────────────
|
|
1788
|
+
PASSKEY_ALREADY_REGISTERED: {
|
|
1789
|
+
message: "A passkey is already registered for this account. Try signing in instead.",
|
|
1790
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1791
|
+
},
|
|
1792
|
+
PASSKEY_UNKNOWN_CREDENTIAL: {
|
|
1793
|
+
message: "We don't recognize this passkey. Try creating a new one.",
|
|
1794
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1795
|
+
},
|
|
1796
|
+
PASSKEY_VERIFICATION_FAILED: {
|
|
1797
|
+
message: "We couldn't verify your passkey. Please try again.",
|
|
1798
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1799
|
+
},
|
|
1800
|
+
PASSKEY_CHALLENGE_MISSING: {
|
|
1801
|
+
message: "Your passkey session expired. Please start again.",
|
|
1802
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1803
|
+
},
|
|
1804
|
+
// ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
|
|
1805
|
+
// These map to the TX_FAILED bucket (not PASSKEY_FAILED) — the precise reason
|
|
1806
|
+
// is the entry key itself, surfaced as the raw `code` on the tx outcome.
|
|
1807
|
+
TX_INSUFFICIENT_BALANCE: {
|
|
1808
|
+
message: "Insufficient balance to complete this transaction.",
|
|
1809
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1810
|
+
},
|
|
1811
|
+
TX_INSUFFICIENT_FEE: {
|
|
1812
|
+
message: "Not enough XLM to cover the network fee. Add more XLM to your wallet and try again.",
|
|
1813
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1814
|
+
},
|
|
1815
|
+
TX_FEE_LIMIT_EXCEEDED: {
|
|
1816
|
+
message: "The transaction fee is above the allowed limit. Please try again.",
|
|
1817
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1818
|
+
},
|
|
1819
|
+
TX_CONTRACT_FAILED: {
|
|
1820
|
+
message: "The contract rejected this operation. Check the operation is allowed right now and try again.",
|
|
1821
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1822
|
+
},
|
|
1823
|
+
TX_DESTINATION_NOT_FOUND: {
|
|
1824
|
+
message: "The destination account doesn't exist on the network yet.",
|
|
1825
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1826
|
+
},
|
|
1827
|
+
TX_NO_TRUSTLINE: {
|
|
1828
|
+
message: "The destination can't receive this asset yet (no trustline).",
|
|
1829
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1830
|
+
},
|
|
1831
|
+
TX_BAD_SEQUENCE: {
|
|
1832
|
+
message: "Something went out of sync. Please try again.",
|
|
1833
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
function resolveAuthError(code, fallbackMessage) {
|
|
1837
|
+
if (code && CATALOG[code]) return CATALOG[code];
|
|
1838
|
+
return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
|
|
1839
|
+
}
|
|
1840
|
+
function extractErrorCode(error, data) {
|
|
1841
|
+
return error?.code ?? data?.code ?? void 0;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1748
1844
|
// src/client/auth/emailFlow.ts
|
|
1749
|
-
async function initEmailSession(
|
|
1750
|
-
const clientSessionId = await
|
|
1751
|
-
if (!clientSessionId) return;
|
|
1752
|
-
|
|
1845
|
+
async function initEmailSession(ctx) {
|
|
1846
|
+
const clientSessionId = await ctx.createSession();
|
|
1847
|
+
if (!clientSessionId) return null;
|
|
1848
|
+
ctx.setAuthState({ step: "entering_email", clientSessionId });
|
|
1849
|
+
return clientSessionId;
|
|
1753
1850
|
}
|
|
1754
|
-
async function sendEmailCode(email, clientSessionId,
|
|
1755
|
-
const { api, logger, signal, setAuthState } =
|
|
1851
|
+
async function sendEmailCode(email, clientSessionId, ctx) {
|
|
1852
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1756
1853
|
setAuthState({ step: "sending_email", email });
|
|
1757
1854
|
const body = { clientSessionId, email };
|
|
1758
1855
|
const { data, error } = await api.POST("/auth/email", { body, signal });
|
|
@@ -1768,13 +1865,13 @@ async function sendEmailCode(email, clientSessionId, deps) {
|
|
|
1768
1865
|
}
|
|
1769
1866
|
setAuthState({ step: "entering_code", clientSessionId, email });
|
|
1770
1867
|
}
|
|
1771
|
-
async function verifyAndAuthenticate(code, clientSessionId, email,
|
|
1772
|
-
const { api, logger, signal, setAuthState } =
|
|
1868
|
+
async function verifyAndAuthenticate(code, clientSessionId, email, ctx) {
|
|
1869
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1773
1870
|
setAuthState({ step: "verifying_email_code", clientSessionId, email });
|
|
1774
1871
|
const body = { clientSessionId, code };
|
|
1775
1872
|
const { data, error } = await api.POST("/auth/email/verify-code", { body, signal });
|
|
1776
1873
|
if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
|
|
1777
|
-
await authenticate(clientSessionId
|
|
1874
|
+
await ctx.authenticate(clientSessionId);
|
|
1778
1875
|
return;
|
|
1779
1876
|
}
|
|
1780
1877
|
const errCode = error?.error ?? data?.code;
|
|
@@ -1852,68 +1949,6 @@ async function loginOAuth(provider, deps) {
|
|
|
1852
1949
|
await authenticate(clientSessionId, deps);
|
|
1853
1950
|
}
|
|
1854
1951
|
|
|
1855
|
-
// src/client/auth/errorMessages.ts
|
|
1856
|
-
var CATALOG = {
|
|
1857
|
-
// ── Smart-account deploy / sponsor wallet ──────────────────────────────────
|
|
1858
|
-
SPONSOR_NOT_FUNDED: {
|
|
1859
|
-
message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
|
|
1860
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1861
|
-
},
|
|
1862
|
-
APP_WALLET_NOT_FOUND: {
|
|
1863
|
-
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1864
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1865
|
-
},
|
|
1866
|
-
WALLET_NOT_FOUND: {
|
|
1867
|
-
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1868
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1869
|
-
},
|
|
1870
|
-
PASSKEY_DEPLOY_FAILED: {
|
|
1871
|
-
message: "We couldn't finish creating your wallet. Please try again in a moment.",
|
|
1872
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1873
|
-
},
|
|
1874
|
-
// ── Passkey ceremony ────────────────────────────────────────────────────────
|
|
1875
|
-
PASSKEY_ALREADY_REGISTERED: {
|
|
1876
|
-
message: "A passkey is already registered for this account. Try signing in instead.",
|
|
1877
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1878
|
-
},
|
|
1879
|
-
PASSKEY_UNKNOWN_CREDENTIAL: {
|
|
1880
|
-
message: "We don't recognize this passkey. Try creating a new one.",
|
|
1881
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1882
|
-
},
|
|
1883
|
-
PASSKEY_VERIFICATION_FAILED: {
|
|
1884
|
-
message: "We couldn't verify your passkey. Please try again.",
|
|
1885
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1886
|
-
},
|
|
1887
|
-
PASSKEY_CHALLENGE_MISSING: {
|
|
1888
|
-
message: "Your passkey session expired. Please start again.",
|
|
1889
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1890
|
-
},
|
|
1891
|
-
// ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
|
|
1892
|
-
TX_INSUFFICIENT_BALANCE: {
|
|
1893
|
-
message: "Insufficient balance to complete this transaction.",
|
|
1894
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1895
|
-
},
|
|
1896
|
-
TX_DESTINATION_NOT_FOUND: {
|
|
1897
|
-
message: "The destination account doesn't exist on the network yet.",
|
|
1898
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1899
|
-
},
|
|
1900
|
-
TX_NO_TRUSTLINE: {
|
|
1901
|
-
message: "The destination can't receive this asset yet (no trustline).",
|
|
1902
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1903
|
-
},
|
|
1904
|
-
TX_BAD_SEQUENCE: {
|
|
1905
|
-
message: "Something went out of sync. Please try again.",
|
|
1906
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1907
|
-
}
|
|
1908
|
-
};
|
|
1909
|
-
function resolveAuthError(code, fallbackMessage) {
|
|
1910
|
-
if (code && CATALOG[code]) return CATALOG[code];
|
|
1911
|
-
return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
|
|
1912
|
-
}
|
|
1913
|
-
function extractErrorCode(error, data) {
|
|
1914
|
-
return error?.code ?? data?.code ?? void 0;
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
1952
|
// src/client/auth/passkeyFlow.ts
|
|
1918
1953
|
async function smartWalletFlow(deps, mode) {
|
|
1919
1954
|
const { api, logger, signal, setAuthState, passkey } = deps;
|
|
@@ -1970,6 +2005,71 @@ function failPasskey(setAuthState, code, fallbackMessage) {
|
|
|
1970
2005
|
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode });
|
|
1971
2006
|
}
|
|
1972
2007
|
|
|
2008
|
+
// src/client/auth/providers.ts
|
|
2009
|
+
function oauthProvider(provider) {
|
|
2010
|
+
return {
|
|
2011
|
+
id: provider,
|
|
2012
|
+
login: (ctx) => ctx.startHostedOAuth(provider)
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
function emailProvider() {
|
|
2016
|
+
return {
|
|
2017
|
+
id: "email",
|
|
2018
|
+
login: async (ctx, options) => {
|
|
2019
|
+
const email = options.email ?? "";
|
|
2020
|
+
const clientSessionId = await initEmailSession(ctx);
|
|
2021
|
+
if (clientSessionId) await sendEmailCode(email, clientSessionId, ctx);
|
|
2022
|
+
},
|
|
2023
|
+
actions: {
|
|
2024
|
+
begin: async (ctx) => {
|
|
2025
|
+
await initEmailSession(ctx);
|
|
2026
|
+
},
|
|
2027
|
+
sendCode: (ctx, payload) => {
|
|
2028
|
+
const { email, clientSessionId } = payload ?? {};
|
|
2029
|
+
return sendEmailCode(email, clientSessionId, ctx);
|
|
2030
|
+
},
|
|
2031
|
+
verifyCode: (ctx, payload) => {
|
|
2032
|
+
const { code, clientSessionId, email } = payload ?? {};
|
|
2033
|
+
return verifyAndAuthenticate(code, clientSessionId, email, ctx);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// src/client/auth/sep10-challenge.ts
|
|
2040
|
+
var ENVELOPE_TYPE_TX_V0 = 0;
|
|
2041
|
+
var ENVELOPE_TYPE_TX = 2;
|
|
2042
|
+
var KEY_TYPE_ED25519 = 0;
|
|
2043
|
+
var SEQ_OFFSET_V1 = 44;
|
|
2044
|
+
var SEQ_OFFSET_V0 = 40;
|
|
2045
|
+
function base64ToBytes(b64) {
|
|
2046
|
+
return base64urlDecode(b64.replace(/\+/g, "-").replace(/\//g, "_"));
|
|
2047
|
+
}
|
|
2048
|
+
function isI64Zero(view, offset) {
|
|
2049
|
+
return view.getUint32(offset, false) === 0 && view.getUint32(offset + 4, false) === 0;
|
|
2050
|
+
}
|
|
2051
|
+
function isValidSep10Challenge(challengeXdr) {
|
|
2052
|
+
try {
|
|
2053
|
+
const bytes = base64ToBytes(challengeXdr.trim());
|
|
2054
|
+
if (bytes.length < 8) return false;
|
|
2055
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2056
|
+
const envelopeType = view.getUint32(0, false);
|
|
2057
|
+
let seqOffset;
|
|
2058
|
+
if (envelopeType === ENVELOPE_TYPE_TX) {
|
|
2059
|
+
if (view.getUint32(4, false) !== KEY_TYPE_ED25519) return false;
|
|
2060
|
+
seqOffset = SEQ_OFFSET_V1;
|
|
2061
|
+
} else if (envelopeType === ENVELOPE_TYPE_TX_V0) {
|
|
2062
|
+
seqOffset = SEQ_OFFSET_V0;
|
|
2063
|
+
} else {
|
|
2064
|
+
return false;
|
|
2065
|
+
}
|
|
2066
|
+
if (bytes.length < seqOffset + 8) return false;
|
|
2067
|
+
return isI64Zero(view, seqOffset);
|
|
2068
|
+
} catch {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1973
2073
|
// src/client/auth/walletFlow.ts
|
|
1974
2074
|
function withSignal(promise, signal) {
|
|
1975
2075
|
return Promise.race([
|
|
@@ -1983,6 +2083,16 @@ function withSignal(promise, signal) {
|
|
|
1983
2083
|
})
|
|
1984
2084
|
]);
|
|
1985
2085
|
}
|
|
2086
|
+
async function requestWalletChallenge(clientSessionId, walletAddress, deps) {
|
|
2087
|
+
const { api, logger, signal } = deps;
|
|
2088
|
+
const body = { clientSessionId, walletAddress };
|
|
2089
|
+
const { data, error } = await api.POST("/auth/wallet/challenge", { body, signal });
|
|
2090
|
+
if (error || !data?.success) {
|
|
2091
|
+
if (!error) logApiError(logger, "POST /auth/wallet/challenge", { body, data });
|
|
2092
|
+
return null;
|
|
2093
|
+
}
|
|
2094
|
+
return data.content.challengeXdr;
|
|
2095
|
+
}
|
|
1986
2096
|
async function loginWallet(type, deps) {
|
|
1987
2097
|
const { api, logger, signal, setAuthState } = deps;
|
|
1988
2098
|
const clientSessionId = await createAuthSession(deps);
|
|
@@ -1999,8 +2109,33 @@ async function loginWallet(type, deps) {
|
|
|
1999
2109
|
const { address } = await withSignal(adapter.connect(), signal);
|
|
2000
2110
|
connectedWallet = address;
|
|
2001
2111
|
deps.storeWalletAdapter(adapter, type);
|
|
2112
|
+
setAuthState({ step: "signing_wallet_challenge", walletType: type });
|
|
2113
|
+
const challengeXdr = await requestWalletChallenge(clientSessionId, address, deps);
|
|
2114
|
+
if (!challengeXdr) {
|
|
2115
|
+
setAuthState({
|
|
2116
|
+
step: "error",
|
|
2117
|
+
previousStep: "signing_wallet_challenge",
|
|
2118
|
+
message: "Failed to obtain wallet challenge",
|
|
2119
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2120
|
+
});
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (!isValidSep10Challenge(challengeXdr)) {
|
|
2124
|
+
logApiError(logger, "SEP-10 challenge validation", { error: "unexpected challenge structure (sequence != 0?)" });
|
|
2125
|
+
setAuthState({
|
|
2126
|
+
step: "error",
|
|
2127
|
+
previousStep: "signing_wallet_challenge",
|
|
2128
|
+
message: "Invalid wallet challenge",
|
|
2129
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2130
|
+
});
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const { signedTxXdr } = await withSignal(
|
|
2134
|
+
adapter.signTransaction(challengeXdr, { networkPassphrase: deps.networkPassphrase }),
|
|
2135
|
+
signal
|
|
2136
|
+
);
|
|
2002
2137
|
setAuthState({ step: "authenticating_wallet" });
|
|
2003
|
-
const body = { clientSessionId, walletAddress: address };
|
|
2138
|
+
const body = { clientSessionId, walletAddress: address, signedChallengeXdr: signedTxXdr };
|
|
2004
2139
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", { body, signal });
|
|
2005
2140
|
if (walletError || !walletData?.success) {
|
|
2006
2141
|
if (!walletError) logApiError(logger, "POST /auth/wallet", { body, data: walletData });
|
|
@@ -2089,6 +2224,13 @@ var PollarClient = class {
|
|
|
2089
2224
|
this._loginController = null;
|
|
2090
2225
|
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2091
2226
|
this._resumeController = null;
|
|
2227
|
+
/**
|
|
2228
|
+
* Registry of pluggable login strategies, keyed by provider id. Seeded with
|
|
2229
|
+
* the built-ins (`google`, `github`, `email`) and then any `config.providers`
|
|
2230
|
+
* (which can override a built-in by reusing its id). `wallet` is deliberately
|
|
2231
|
+
* absent — it keeps its own dedicated flow. See {@link PollarAuthProvider}.
|
|
2232
|
+
*/
|
|
2233
|
+
this._providers = /* @__PURE__ */ new Map();
|
|
2092
2234
|
this.apiKey = config.apiKey;
|
|
2093
2235
|
this.id = randomUUID();
|
|
2094
2236
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -2110,6 +2252,12 @@ var PollarClient = class {
|
|
|
2110
2252
|
this._maxIdleMs = config.maxIdleMs;
|
|
2111
2253
|
this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
|
|
2112
2254
|
this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location?.origin ?? "" : "");
|
|
2255
|
+
for (const provider of [oauthProvider("google"), oauthProvider("github"), emailProvider()]) {
|
|
2256
|
+
this._providers.set(provider.id, provider);
|
|
2257
|
+
}
|
|
2258
|
+
for (const provider of config.providers ?? []) {
|
|
2259
|
+
this._providers.set(provider.id, provider);
|
|
2260
|
+
}
|
|
2113
2261
|
this._api = createApiClient(this.basePath);
|
|
2114
2262
|
this._wireMiddlewares();
|
|
2115
2263
|
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
@@ -2516,28 +2664,42 @@ var PollarClient = class {
|
|
|
2516
2664
|
warnServerSide("login");
|
|
2517
2665
|
return;
|
|
2518
2666
|
}
|
|
2519
|
-
if (options.provider === "
|
|
2520
|
-
const controller = this._newController();
|
|
2521
|
-
const deps = this._flowDeps(controller.signal);
|
|
2522
|
-
if (options.provider === "google" || options.provider === "github") {
|
|
2523
|
-
loginOAuth(options.provider, {
|
|
2524
|
-
...deps,
|
|
2525
|
-
basePath: this.basePath,
|
|
2526
|
-
apiKey: this.apiKey,
|
|
2527
|
-
openAuthUrl: this._openAuthUrl,
|
|
2528
|
-
redirectUri: this._oauthRedirectUri
|
|
2529
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2530
|
-
} else if (options.provider === "email") {
|
|
2531
|
-
const { email } = options;
|
|
2532
|
-
initEmailSession(deps).then(() => {
|
|
2533
|
-
if (this._authState.step === "entering_email") {
|
|
2534
|
-
return sendEmailCode(email, this._authState.clientSessionId, deps);
|
|
2535
|
-
}
|
|
2536
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2537
|
-
}
|
|
2538
|
-
} else if (options.provider === "wallet") {
|
|
2667
|
+
if (options.provider === "wallet") {
|
|
2539
2668
|
this.loginWallet(options.type);
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
const provider = this._providers.get(options.provider);
|
|
2672
|
+
if (!provider?.login) {
|
|
2673
|
+
this._setAuthState({
|
|
2674
|
+
step: "error",
|
|
2675
|
+
previousStep: this._authState.step,
|
|
2676
|
+
message: `No auth provider registered for '${options.provider}'`,
|
|
2677
|
+
errorCode: AUTH_ERROR_CODES.AUTH_FAILED
|
|
2678
|
+
});
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
const controller = this._newController();
|
|
2682
|
+
provider.login(this._providerContext(controller.signal), options).catch((err) => this._handleFlowError(err));
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Invoke a named secondary step on a registered provider (e.g. email's
|
|
2686
|
+
* `sendCode` / `verifyCode`, or a custom provider's multi-step continuation).
|
|
2687
|
+
* Reuses the in-flight login `AbortController` when one exists so the step
|
|
2688
|
+
* stays cancellable via `cancelLogin()`; otherwise starts a fresh one. The
|
|
2689
|
+
* built-in email steps also have dedicated typed methods
|
|
2690
|
+
* ({@link sendEmailCode} / {@link verifyEmailCode}) — prefer those for email.
|
|
2691
|
+
*/
|
|
2692
|
+
providerAction(provider, action, payload) {
|
|
2693
|
+
if (!isClientRuntime) {
|
|
2694
|
+
warnServerSide("providerAction");
|
|
2695
|
+
return;
|
|
2540
2696
|
}
|
|
2697
|
+
const fn = this._providers.get(provider)?.actions?.[action];
|
|
2698
|
+
if (!fn) {
|
|
2699
|
+
throw new PollarFlowError(`Auth provider '${provider}' has no action '${action}'`);
|
|
2700
|
+
}
|
|
2701
|
+
const signal = this._loginController?.signal ?? this._newController().signal;
|
|
2702
|
+
fn(this._providerContext(signal), payload).catch((err) => this._handleFlowError(err));
|
|
2541
2703
|
}
|
|
2542
2704
|
// ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
|
|
2543
2705
|
beginEmailLogin() {
|
|
@@ -2546,7 +2708,7 @@ var PollarClient = class {
|
|
|
2546
2708
|
return;
|
|
2547
2709
|
}
|
|
2548
2710
|
const controller = this._newController();
|
|
2549
|
-
initEmailSession(this.
|
|
2711
|
+
initEmailSession(this._providerContext(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2550
2712
|
}
|
|
2551
2713
|
sendEmailCode(email) {
|
|
2552
2714
|
if (!isClientRuntime) {
|
|
@@ -2558,7 +2720,7 @@ var PollarClient = class {
|
|
|
2558
2720
|
}
|
|
2559
2721
|
const { clientSessionId } = this._authState;
|
|
2560
2722
|
const signal = this._loginController.signal;
|
|
2561
|
-
sendEmailCode(email, clientSessionId, this.
|
|
2723
|
+
sendEmailCode(email, clientSessionId, this._providerContext(signal)).catch((err) => this._handleFlowError(err));
|
|
2562
2724
|
}
|
|
2563
2725
|
verifyEmailCode(code) {
|
|
2564
2726
|
if (!isClientRuntime) {
|
|
@@ -2573,7 +2735,7 @@ var PollarClient = class {
|
|
|
2573
2735
|
const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
|
|
2574
2736
|
const email = state.step === "entering_code" ? state.email : state.email ?? "";
|
|
2575
2737
|
const controller = this._newController();
|
|
2576
|
-
verifyAndAuthenticate(code, clientSessionId, email, this.
|
|
2738
|
+
verifyAndAuthenticate(code, clientSessionId, email, this._providerContext(controller.signal)).catch(
|
|
2577
2739
|
(err) => this._handleFlowError(err)
|
|
2578
2740
|
);
|
|
2579
2741
|
}
|
|
@@ -2938,6 +3100,29 @@ var PollarClient = class {
|
|
|
2938
3100
|
getWalletType() {
|
|
2939
3101
|
return this._walletAdapter?.type ?? null;
|
|
2940
3102
|
}
|
|
3103
|
+
/**
|
|
3104
|
+
* The authenticated user's wallet as a {@link WalletInfo} discriminated union,
|
|
3105
|
+
* or `null` when there's no session (or the session carries no address yet).
|
|
3106
|
+
*
|
|
3107
|
+
* `custody` strictly determines `provider` (the mapping is 1:1 and fixed at
|
|
3108
|
+
* account creation server-side): `external` reports the connected adapter id
|
|
3109
|
+
* (`getWalletType()`), `smart` is always `'passkey'`, and `internal` reports
|
|
3110
|
+
* the login method the backend recorded (`null` for pre-provider sessions).
|
|
3111
|
+
*/
|
|
3112
|
+
getWallet() {
|
|
3113
|
+
const w = this._session?.wallet;
|
|
3114
|
+
if (!w || !w.address) return null;
|
|
3115
|
+
switch (w.type) {
|
|
3116
|
+
case "external":
|
|
3117
|
+
return { custody: "external", address: w.address, provider: this._walletAdapter?.type ?? null };
|
|
3118
|
+
case "smart":
|
|
3119
|
+
return { custody: "smart", address: w.address, provider: "passkey" };
|
|
3120
|
+
case "internal":
|
|
3121
|
+
return { custody: "internal", address: w.address, provider: w.provider ?? null };
|
|
3122
|
+
default:
|
|
3123
|
+
return null;
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
2941
3126
|
/**
|
|
2942
3127
|
* Signs the given unsigned XDR and returns the signed XDR.
|
|
2943
3128
|
*
|
|
@@ -2991,14 +3176,16 @@ var PollarClient = class {
|
|
|
2991
3176
|
});
|
|
2992
3177
|
return { status: "signed", signedXdr, submissionToken: idempotencyKey };
|
|
2993
3178
|
}
|
|
2994
|
-
const details = error
|
|
3179
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
2995
3180
|
this._setTransactionState({
|
|
2996
3181
|
step: "error",
|
|
2997
3182
|
phase: "signing",
|
|
2998
3183
|
...buildData && { buildData },
|
|
2999
|
-
...details && { details }
|
|
3184
|
+
...details && { details },
|
|
3185
|
+
...code && { code },
|
|
3186
|
+
...message && { message }
|
|
3000
3187
|
});
|
|
3001
|
-
return { status: "error", ...details && { details } };
|
|
3188
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3002
3189
|
} catch (err) {
|
|
3003
3190
|
const details = err instanceof Error ? err.message : void 0;
|
|
3004
3191
|
this._setTransactionState({
|
|
@@ -3010,6 +3197,54 @@ var PollarClient = class {
|
|
|
3010
3197
|
return { status: "error", ...details && { details } };
|
|
3011
3198
|
}
|
|
3012
3199
|
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Sign a single Soroban authorization entry (`SorobanAuthorizationEntry`).
|
|
3202
|
+
*
|
|
3203
|
+
* Use this when a contract is the transaction source (e.g. it sponsors the
|
|
3204
|
+
* gas and swaps the fee out of the user's token) and only needs the user's
|
|
3205
|
+
* address-credentials authorization, not a full signed envelope. The signed
|
|
3206
|
+
* entry is returned as base64 XDR for the caller to compose into its tx.
|
|
3207
|
+
*
|
|
3208
|
+
* - External wallets (Freighter/Albedo) sign the entry via the provider.
|
|
3209
|
+
* - Custodial wallets are signed by the backend, which FIRST validates the
|
|
3210
|
+
* entry's invocation tree against the app's contract/function allowlist and
|
|
3211
|
+
* caps the validity window — entries touching a non-allowlisted contract or
|
|
3212
|
+
* function, or expiring too far ahead, are rejected.
|
|
3213
|
+
*
|
|
3214
|
+
* @param entryXdr base64 XDR of the unsigned `SorobanAuthorizationEntry`.
|
|
3215
|
+
* @param options.validUntilLedger absolute ledger the signature expires at
|
|
3216
|
+
* (computed from the network's latest ledger). Ignored on the external-wallet
|
|
3217
|
+
* path, where the provider sets its own expiration.
|
|
3218
|
+
*/
|
|
3219
|
+
async signAuthEntry(entryXdr, options) {
|
|
3220
|
+
if (this._walletAdapter) {
|
|
3221
|
+
const accountToSign = this._session?.wallet?.address;
|
|
3222
|
+
try {
|
|
3223
|
+
const { signedAuthEntry } = await this._walletAdapter.signAuthEntry(
|
|
3224
|
+
entryXdr,
|
|
3225
|
+
accountToSign ? { accountToSign } : void 0
|
|
3226
|
+
);
|
|
3227
|
+
return { status: "signed", signedAuthEntry };
|
|
3228
|
+
} catch (err) {
|
|
3229
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3230
|
+
return { status: "error", ...details && { details } };
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
const address = this._session?.wallet?.address ?? "";
|
|
3234
|
+
try {
|
|
3235
|
+
const { data, error } = await this._api.POST("/tx/sign-auth-entry", {
|
|
3236
|
+
body: { network: this.getNetwork(), address, entryXdr, validUntilLedger: options.validUntilLedger }
|
|
3237
|
+
});
|
|
3238
|
+
if (!error && data?.success && data.content?.signedAuthEntry) {
|
|
3239
|
+
return { status: "signed", signedAuthEntry: data.content.signedAuthEntry };
|
|
3240
|
+
}
|
|
3241
|
+
const details = error?.details;
|
|
3242
|
+
return { status: "error", ...details && { details } };
|
|
3243
|
+
} catch (err) {
|
|
3244
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3245
|
+
return { status: "error", ...details && { details } };
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3013
3248
|
/**
|
|
3014
3249
|
* Submits a signed XDR via `/tx/submit` regardless of wallet type
|
|
3015
3250
|
* (custodial or external). Routing through sdk-api gives us:
|
|
@@ -3028,6 +3263,21 @@ var PollarClient = class {
|
|
|
3028
3263
|
* `submitted` on Horizon ack (pending), `success` on ledger confirmation,
|
|
3029
3264
|
* or `error[phase: 'submitting']` on failure.
|
|
3030
3265
|
*/
|
|
3266
|
+
/**
|
|
3267
|
+
* Normalize a backend API error into { details, code, message }. `code` is the
|
|
3268
|
+
* precise backend ErrorCode (e.g. `TX_FEE_LIMIT_EXCEEDED`) for programmatic
|
|
3269
|
+
* handling; `message` is a friendly string from the error catalog; `details`
|
|
3270
|
+
* is the raw diagnostic. Lets tx flows surface a typed reason instead of an
|
|
3271
|
+
* opaque details string.
|
|
3272
|
+
*/
|
|
3273
|
+
_resolveTxApiError(error) {
|
|
3274
|
+
const e = error;
|
|
3275
|
+
const details = e?.details ?? e?.message;
|
|
3276
|
+
const code = e?.code;
|
|
3277
|
+
if (!code) return details ? { details } : {};
|
|
3278
|
+
const { message } = resolveAuthError(code, details ?? code);
|
|
3279
|
+
return { code, message, ...details && { details } };
|
|
3280
|
+
}
|
|
3031
3281
|
async submitTx(signedXdr, opts) {
|
|
3032
3282
|
const buildData = this._currentBuildData();
|
|
3033
3283
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
@@ -3065,14 +3315,22 @@ var PollarClient = class {
|
|
|
3065
3315
|
...resultCode && { details: resultCode, resultCode }
|
|
3066
3316
|
};
|
|
3067
3317
|
}
|
|
3068
|
-
const details = error
|
|
3318
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3069
3319
|
this._setTransactionState({
|
|
3070
3320
|
step: "error",
|
|
3071
3321
|
phase: "submitting",
|
|
3072
3322
|
...buildData && { buildData },
|
|
3073
|
-
...details && { details }
|
|
3323
|
+
...details && { details },
|
|
3324
|
+
...code && { code },
|
|
3325
|
+
...message && { message }
|
|
3074
3326
|
});
|
|
3075
|
-
return {
|
|
3327
|
+
return {
|
|
3328
|
+
status: "error",
|
|
3329
|
+
...outcomeExtra,
|
|
3330
|
+
...details && { details },
|
|
3331
|
+
...code && { code },
|
|
3332
|
+
...message && { message }
|
|
3333
|
+
};
|
|
3076
3334
|
} catch (err) {
|
|
3077
3335
|
const details = err instanceof Error ? err.message : void 0;
|
|
3078
3336
|
this._setTransactionState({
|
|
@@ -3159,14 +3417,22 @@ var PollarClient = class {
|
|
|
3159
3417
|
...resultCode && { details: resultCode, resultCode }
|
|
3160
3418
|
};
|
|
3161
3419
|
}
|
|
3162
|
-
const details = error
|
|
3420
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3163
3421
|
this._setTransactionState({
|
|
3164
3422
|
step: "error",
|
|
3165
3423
|
phase: "signing-submitting",
|
|
3166
3424
|
...buildData && { buildData },
|
|
3167
|
-
...details && { details }
|
|
3425
|
+
...details && { details },
|
|
3426
|
+
...code && { code },
|
|
3427
|
+
...message && { message }
|
|
3168
3428
|
});
|
|
3169
|
-
return {
|
|
3429
|
+
return {
|
|
3430
|
+
status: "error",
|
|
3431
|
+
...outcomeExtra,
|
|
3432
|
+
...details && { details },
|
|
3433
|
+
...code && { code },
|
|
3434
|
+
...message && { message }
|
|
3435
|
+
};
|
|
3170
3436
|
} catch (err) {
|
|
3171
3437
|
const details = err instanceof Error ? err.message : void 0;
|
|
3172
3438
|
this._setTransactionState({
|
|
@@ -3241,13 +3507,15 @@ var PollarClient = class {
|
|
|
3241
3507
|
});
|
|
3242
3508
|
return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
|
|
3243
3509
|
}
|
|
3244
|
-
const details = error
|
|
3510
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3245
3511
|
this._setTransactionState({
|
|
3246
3512
|
step: "error",
|
|
3247
3513
|
phase: "building-signing-submitting",
|
|
3248
|
-
...details && { details }
|
|
3514
|
+
...details && { details },
|
|
3515
|
+
...code && { code },
|
|
3516
|
+
...message && { message }
|
|
3249
3517
|
});
|
|
3250
|
-
return { status: "error", ...details && { details } };
|
|
3518
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3251
3519
|
} catch (err) {
|
|
3252
3520
|
const details = err instanceof Error ? err.message : void 0;
|
|
3253
3521
|
this._setTransactionState({
|
|
@@ -3360,9 +3628,22 @@ var PollarClient = class {
|
|
|
3360
3628
|
});
|
|
3361
3629
|
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3362
3630
|
}
|
|
3363
|
-
const details = error
|
|
3364
|
-
this._setTransactionState({
|
|
3365
|
-
|
|
3631
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3632
|
+
this._setTransactionState({
|
|
3633
|
+
step: "error",
|
|
3634
|
+
phase: "submitting",
|
|
3635
|
+
buildData,
|
|
3636
|
+
...details && { details },
|
|
3637
|
+
...code && { code },
|
|
3638
|
+
...message && { message }
|
|
3639
|
+
});
|
|
3640
|
+
return {
|
|
3641
|
+
status: "error",
|
|
3642
|
+
...outcomeExtra,
|
|
3643
|
+
...details && { details },
|
|
3644
|
+
...code && { code },
|
|
3645
|
+
...message && { message }
|
|
3646
|
+
};
|
|
3366
3647
|
} catch (err) {
|
|
3367
3648
|
const details = err instanceof Error ? err.message : void 0;
|
|
3368
3649
|
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
@@ -3440,11 +3721,67 @@ var PollarClient = class {
|
|
|
3440
3721
|
this._loginController = new AbortController();
|
|
3441
3722
|
return this._loginController;
|
|
3442
3723
|
}
|
|
3724
|
+
/**
|
|
3725
|
+
* Build the {@link AuthProviderContext} facade for one login attempt. Wraps
|
|
3726
|
+
* the internal `FlowDeps` so providers get only the curated primitives —
|
|
3727
|
+
* `createSession`, `authenticate`, `exchangeExternalToken`, `startHostedOAuth`
|
|
3728
|
+
* — while storage / wallet-adapter / key-manager internals stay private. All
|
|
3729
|
+
* legs share the same `signal`, so `cancelLogin()` aborts the whole chain.
|
|
3730
|
+
*/
|
|
3731
|
+
_providerContext(signal) {
|
|
3732
|
+
const deps = this._flowDeps(signal);
|
|
3733
|
+
return {
|
|
3734
|
+
signal,
|
|
3735
|
+
api: this._api,
|
|
3736
|
+
basePath: this.basePath,
|
|
3737
|
+
apiKey: this.apiKey,
|
|
3738
|
+
logger: this._log,
|
|
3739
|
+
setAuthState: this._setAuthState.bind(this),
|
|
3740
|
+
createSession: () => createAuthSession(deps),
|
|
3741
|
+
authenticate: (clientSessionId) => authenticate(clientSessionId, deps),
|
|
3742
|
+
requestChallenge: (clientSessionId, walletAddress) => requestWalletChallenge(clientSessionId, walletAddress, deps),
|
|
3743
|
+
exchangeExternalToken: (clientSessionId, body) => this._exchangeExternalToken(clientSessionId, body, signal),
|
|
3744
|
+
startHostedOAuth: (provider) => loginOAuth(provider, {
|
|
3745
|
+
...deps,
|
|
3746
|
+
basePath: this.basePath,
|
|
3747
|
+
apiKey: this.apiKey,
|
|
3748
|
+
openAuthUrl: this._openAuthUrl,
|
|
3749
|
+
redirectUri: this._oauthRedirectUri
|
|
3750
|
+
})
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* Generic external-provider exchange leg (`POST /auth/external`). Custom
|
|
3755
|
+
* providers call this (via the context) after their own SDK has authenticated
|
|
3756
|
+
* the user and the wallet has counter-signed the SEP-10 challenge
|
|
3757
|
+
* (`{ provider, walletAddress, signedChallengeXdr }`). On success the session
|
|
3758
|
+
* is marked READY server-side and the provider should then call
|
|
3759
|
+
* `ctx.authenticate(clientSessionId)`. Returns `false` (and sets an error
|
|
3760
|
+
* state) on failure.
|
|
3761
|
+
*/
|
|
3762
|
+
async _exchangeExternalToken(clientSessionId, body, signal) {
|
|
3763
|
+
const { data, error } = await this._api.POST("/auth/external", {
|
|
3764
|
+
body: { clientSessionId, ...body },
|
|
3765
|
+
signal
|
|
3766
|
+
});
|
|
3767
|
+
if (error || !data?.success) {
|
|
3768
|
+
this._log.error("[PollarClient] External provider authentication failed", { error });
|
|
3769
|
+
this._setAuthState({
|
|
3770
|
+
step: "error",
|
|
3771
|
+
previousStep: this._authState.step,
|
|
3772
|
+
message: "External provider authentication failed",
|
|
3773
|
+
errorCode: AUTH_ERROR_CODES.EXTERNAL_AUTH_FAILED
|
|
3774
|
+
});
|
|
3775
|
+
return false;
|
|
3776
|
+
}
|
|
3777
|
+
return true;
|
|
3778
|
+
}
|
|
3443
3779
|
_flowDeps(signal) {
|
|
3444
3780
|
return {
|
|
3445
3781
|
api: this._api,
|
|
3446
3782
|
logger: this._log,
|
|
3447
3783
|
basePath: this.basePath,
|
|
3784
|
+
networkPassphrase: this._networkPassphrase(),
|
|
3448
3785
|
// SSE status streaming works on web; React Native's `fetch` has no
|
|
3449
3786
|
// readable `response.body`, so those clients poll the non-streaming
|
|
3450
3787
|
// status endpoint instead. `isBrowser` is false in RN and SSR alike.
|
|
@@ -3576,6 +3913,7 @@ var PollarClient = class {
|
|
|
3576
3913
|
async _storeSession(session) {
|
|
3577
3914
|
this._log.info("[PollarClient] Session stored");
|
|
3578
3915
|
const w = session.wallet;
|
|
3916
|
+
const wireProvider = w.provider;
|
|
3579
3917
|
const persisted = {
|
|
3580
3918
|
clientSessionId: session.clientSessionId,
|
|
3581
3919
|
userId: session.userId ?? null,
|
|
@@ -3589,6 +3927,7 @@ var PollarClient = class {
|
|
|
3589
3927
|
// persisted session speak one vocabulary while the wire stays compatible.
|
|
3590
3928
|
wallet: {
|
|
3591
3929
|
type: w.type === "custodial" ? "internal" : w.type,
|
|
3930
|
+
...wireProvider ? { provider: wireProvider } : {},
|
|
3592
3931
|
address: w.address ?? w.publicKey ?? null,
|
|
3593
3932
|
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3594
3933
|
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|