@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.js
CHANGED
|
@@ -189,7 +189,7 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
189
189
|
|
|
190
190
|
// src/lib/base64url.ts
|
|
191
191
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
192
|
-
(() => {
|
|
192
|
+
var REVERSE = (() => {
|
|
193
193
|
const m = /* @__PURE__ */ new Map();
|
|
194
194
|
for (let i = 0; i < ALPHABET.length; i++) m.set(ALPHABET[i], i);
|
|
195
195
|
return m;
|
|
@@ -220,6 +220,28 @@ function base64urlEncode(bytes) {
|
|
|
220
220
|
}
|
|
221
221
|
return result;
|
|
222
222
|
}
|
|
223
|
+
function base64urlDecode(input) {
|
|
224
|
+
const clean = input.replace(/=+$/, "");
|
|
225
|
+
const out = new Uint8Array(Math.floor(clean.length * 3 / 4));
|
|
226
|
+
let byteIdx = 0;
|
|
227
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
228
|
+
const c1 = REVERSE.get(clean[i]);
|
|
229
|
+
const c2 = REVERSE.get(clean[i + 1]);
|
|
230
|
+
const c3 = i + 2 < clean.length ? REVERSE.get(clean[i + 2]) : void 0;
|
|
231
|
+
const c4 = i + 3 < clean.length ? REVERSE.get(clean[i + 3]) : void 0;
|
|
232
|
+
if (c1 === void 0 || c2 === void 0) {
|
|
233
|
+
throw new Error("[PollarClient] Invalid base64url input");
|
|
234
|
+
}
|
|
235
|
+
out[byteIdx++] = c1 << 2 | c2 >> 4;
|
|
236
|
+
if (c3 !== void 0) {
|
|
237
|
+
out[byteIdx++] = (c2 & 15) << 4 | c3 >> 2;
|
|
238
|
+
if (c4 !== void 0) {
|
|
239
|
+
out[byteIdx++] = (c3 & 3) << 6 | c4;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return out.slice(0, byteIdx);
|
|
244
|
+
}
|
|
223
245
|
function base64urlEncodeString(s) {
|
|
224
246
|
return base64urlEncode(new TextEncoder().encode(s));
|
|
225
247
|
}
|
|
@@ -1086,7 +1108,19 @@ function createLogger(level = "info", sink = console) {
|
|
|
1086
1108
|
}
|
|
1087
1109
|
|
|
1088
1110
|
// src/lib/logging.ts
|
|
1089
|
-
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1111
|
+
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1112
|
+
"email",
|
|
1113
|
+
"code",
|
|
1114
|
+
"walletAddress",
|
|
1115
|
+
"dpopJwk",
|
|
1116
|
+
"response",
|
|
1117
|
+
"refreshToken",
|
|
1118
|
+
// SEP-10 challenge envelopes: a counter-signed challenge is a live, replayable
|
|
1119
|
+
// auth credential — never log it in the clear.
|
|
1120
|
+
"signedChallengeXdr",
|
|
1121
|
+
"challengeXdr",
|
|
1122
|
+
"signedTxXdr"
|
|
1123
|
+
]);
|
|
1090
1124
|
function redactBody(body) {
|
|
1091
1125
|
if (!body || typeof body !== "object") return body;
|
|
1092
1126
|
const out = {};
|
|
@@ -1183,8 +1217,36 @@ function defaultStorage(options = {}) {
|
|
|
1183
1217
|
return createLocalStorageAdapter(options);
|
|
1184
1218
|
}
|
|
1185
1219
|
|
|
1220
|
+
// src/types.ts
|
|
1221
|
+
var AUTH_ERROR_CODES = {
|
|
1222
|
+
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1223
|
+
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1224
|
+
SESSION_INVALID: "SESSION_INVALID",
|
|
1225
|
+
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1226
|
+
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1227
|
+
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1228
|
+
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1229
|
+
AUTH_FAILED: "AUTH_FAILED",
|
|
1230
|
+
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1231
|
+
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1232
|
+
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1233
|
+
EXTERNAL_AUTH_FAILED: "EXTERNAL_AUTH_FAILED",
|
|
1234
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1235
|
+
// Generic bucket for on-chain transaction failures; the precise reason is the
|
|
1236
|
+
// backend `code` (e.g. TX_FEE_LIMIT_EXCEEDED) carried alongside on the outcome.
|
|
1237
|
+
TX_FAILED: "TX_FAILED",
|
|
1238
|
+
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1239
|
+
};
|
|
1240
|
+
var PollarFlowError = class extends Error {
|
|
1241
|
+
constructor(message) {
|
|
1242
|
+
super(message);
|
|
1243
|
+
this.code = "INVALID_FLOW";
|
|
1244
|
+
this.name = "PollarFlowError";
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1186
1248
|
// src/version.ts
|
|
1187
|
-
var POLLAR_CORE_VERSION = "0.
|
|
1249
|
+
var POLLAR_CORE_VERSION = "0.10.0-rc.0" ;
|
|
1188
1250
|
|
|
1189
1251
|
// src/visibility/noop.ts
|
|
1190
1252
|
function createNoopVisibilityProvider() {
|
|
@@ -1237,30 +1299,6 @@ function defaultVisibilityProvider() {
|
|
|
1237
1299
|
return createNoopVisibilityProvider();
|
|
1238
1300
|
}
|
|
1239
1301
|
|
|
1240
|
-
// src/types.ts
|
|
1241
|
-
var AUTH_ERROR_CODES = {
|
|
1242
|
-
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1243
|
-
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1244
|
-
SESSION_INVALID: "SESSION_INVALID",
|
|
1245
|
-
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1246
|
-
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1247
|
-
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1248
|
-
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1249
|
-
AUTH_FAILED: "AUTH_FAILED",
|
|
1250
|
-
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1251
|
-
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1252
|
-
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1253
|
-
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1254
|
-
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1255
|
-
};
|
|
1256
|
-
var PollarFlowError = class extends Error {
|
|
1257
|
-
constructor(message) {
|
|
1258
|
-
super(message);
|
|
1259
|
-
this.code = "INVALID_FLOW";
|
|
1260
|
-
this.name = "PollarFlowError";
|
|
1261
|
-
}
|
|
1262
|
-
};
|
|
1263
|
-
|
|
1264
1302
|
// src/wallets/FreighterAdapter.ts
|
|
1265
1303
|
var import_freighter_api = __toESM(require_index_min());
|
|
1266
1304
|
|
|
@@ -1516,6 +1554,10 @@ function isValidSession(value, logger = console) {
|
|
|
1516
1554
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be internal|smart|external");
|
|
1517
1555
|
return false;
|
|
1518
1556
|
}
|
|
1557
|
+
if (w["provider"] !== void 0 && typeof w["provider"] !== "string") {
|
|
1558
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.provider must be a string if present");
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1519
1561
|
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1520
1562
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1521
1563
|
return false;
|
|
@@ -1811,14 +1853,91 @@ async function createAuthSession(deps) {
|
|
|
1811
1853
|
return data.content.clientSessionId;
|
|
1812
1854
|
}
|
|
1813
1855
|
|
|
1856
|
+
// src/client/auth/errorMessages.ts
|
|
1857
|
+
var CATALOG = {
|
|
1858
|
+
// ── Smart-account deploy / sponsor wallet ──────────────────────────────────
|
|
1859
|
+
SPONSOR_NOT_FUNDED: {
|
|
1860
|
+
message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
|
|
1861
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1862
|
+
},
|
|
1863
|
+
APP_WALLET_NOT_FOUND: {
|
|
1864
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1865
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1866
|
+
},
|
|
1867
|
+
WALLET_NOT_FOUND: {
|
|
1868
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1869
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1870
|
+
},
|
|
1871
|
+
PASSKEY_DEPLOY_FAILED: {
|
|
1872
|
+
message: "We couldn't finish creating your wallet. Please try again in a moment.",
|
|
1873
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1874
|
+
},
|
|
1875
|
+
// ── Passkey ceremony ────────────────────────────────────────────────────────
|
|
1876
|
+
PASSKEY_ALREADY_REGISTERED: {
|
|
1877
|
+
message: "A passkey is already registered for this account. Try signing in instead.",
|
|
1878
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1879
|
+
},
|
|
1880
|
+
PASSKEY_UNKNOWN_CREDENTIAL: {
|
|
1881
|
+
message: "We don't recognize this passkey. Try creating a new one.",
|
|
1882
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1883
|
+
},
|
|
1884
|
+
PASSKEY_VERIFICATION_FAILED: {
|
|
1885
|
+
message: "We couldn't verify your passkey. Please try again.",
|
|
1886
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1887
|
+
},
|
|
1888
|
+
PASSKEY_CHALLENGE_MISSING: {
|
|
1889
|
+
message: "Your passkey session expired. Please start again.",
|
|
1890
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1891
|
+
},
|
|
1892
|
+
// ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
|
|
1893
|
+
// These map to the TX_FAILED bucket (not PASSKEY_FAILED) — the precise reason
|
|
1894
|
+
// is the entry key itself, surfaced as the raw `code` on the tx outcome.
|
|
1895
|
+
TX_INSUFFICIENT_BALANCE: {
|
|
1896
|
+
message: "Insufficient balance to complete this transaction.",
|
|
1897
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1898
|
+
},
|
|
1899
|
+
TX_INSUFFICIENT_FEE: {
|
|
1900
|
+
message: "Not enough XLM to cover the network fee. Add more XLM to your wallet and try again.",
|
|
1901
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1902
|
+
},
|
|
1903
|
+
TX_FEE_LIMIT_EXCEEDED: {
|
|
1904
|
+
message: "The transaction fee is above the allowed limit. Please try again.",
|
|
1905
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1906
|
+
},
|
|
1907
|
+
TX_CONTRACT_FAILED: {
|
|
1908
|
+
message: "The contract rejected this operation. Check the operation is allowed right now and try again.",
|
|
1909
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1910
|
+
},
|
|
1911
|
+
TX_DESTINATION_NOT_FOUND: {
|
|
1912
|
+
message: "The destination account doesn't exist on the network yet.",
|
|
1913
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1914
|
+
},
|
|
1915
|
+
TX_NO_TRUSTLINE: {
|
|
1916
|
+
message: "The destination can't receive this asset yet (no trustline).",
|
|
1917
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1918
|
+
},
|
|
1919
|
+
TX_BAD_SEQUENCE: {
|
|
1920
|
+
message: "Something went out of sync. Please try again.",
|
|
1921
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
function resolveAuthError(code, fallbackMessage) {
|
|
1925
|
+
if (code && CATALOG[code]) return CATALOG[code];
|
|
1926
|
+
return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
|
|
1927
|
+
}
|
|
1928
|
+
function extractErrorCode(error, data) {
|
|
1929
|
+
return error?.code ?? data?.code ?? void 0;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1814
1932
|
// src/client/auth/emailFlow.ts
|
|
1815
|
-
async function initEmailSession(
|
|
1816
|
-
const clientSessionId = await
|
|
1817
|
-
if (!clientSessionId) return;
|
|
1818
|
-
|
|
1933
|
+
async function initEmailSession(ctx) {
|
|
1934
|
+
const clientSessionId = await ctx.createSession();
|
|
1935
|
+
if (!clientSessionId) return null;
|
|
1936
|
+
ctx.setAuthState({ step: "entering_email", clientSessionId });
|
|
1937
|
+
return clientSessionId;
|
|
1819
1938
|
}
|
|
1820
|
-
async function sendEmailCode(email, clientSessionId,
|
|
1821
|
-
const { api, logger, signal, setAuthState } =
|
|
1939
|
+
async function sendEmailCode(email, clientSessionId, ctx) {
|
|
1940
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1822
1941
|
setAuthState({ step: "sending_email", email });
|
|
1823
1942
|
const body = { clientSessionId, email };
|
|
1824
1943
|
const { data, error } = await api.POST("/auth/email", { body, signal });
|
|
@@ -1834,13 +1953,13 @@ async function sendEmailCode(email, clientSessionId, deps) {
|
|
|
1834
1953
|
}
|
|
1835
1954
|
setAuthState({ step: "entering_code", clientSessionId, email });
|
|
1836
1955
|
}
|
|
1837
|
-
async function verifyAndAuthenticate(code, clientSessionId, email,
|
|
1838
|
-
const { api, logger, signal, setAuthState } =
|
|
1956
|
+
async function verifyAndAuthenticate(code, clientSessionId, email, ctx) {
|
|
1957
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1839
1958
|
setAuthState({ step: "verifying_email_code", clientSessionId, email });
|
|
1840
1959
|
const body = { clientSessionId, code };
|
|
1841
1960
|
const { data, error } = await api.POST("/auth/email/verify-code", { body, signal });
|
|
1842
1961
|
if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
|
|
1843
|
-
await authenticate(clientSessionId
|
|
1962
|
+
await ctx.authenticate(clientSessionId);
|
|
1844
1963
|
return;
|
|
1845
1964
|
}
|
|
1846
1965
|
const errCode = error?.error ?? data?.code;
|
|
@@ -1918,68 +2037,6 @@ async function loginOAuth(provider, deps) {
|
|
|
1918
2037
|
await authenticate(clientSessionId, deps);
|
|
1919
2038
|
}
|
|
1920
2039
|
|
|
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
|
-
|
|
1983
2040
|
// src/client/auth/passkeyFlow.ts
|
|
1984
2041
|
async function smartWalletFlow(deps, mode) {
|
|
1985
2042
|
const { api, logger, signal, setAuthState, passkey } = deps;
|
|
@@ -2036,6 +2093,71 @@ function failPasskey(setAuthState, code, fallbackMessage) {
|
|
|
2036
2093
|
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode });
|
|
2037
2094
|
}
|
|
2038
2095
|
|
|
2096
|
+
// src/client/auth/providers.ts
|
|
2097
|
+
function oauthProvider(provider) {
|
|
2098
|
+
return {
|
|
2099
|
+
id: provider,
|
|
2100
|
+
login: (ctx) => ctx.startHostedOAuth(provider)
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
function emailProvider() {
|
|
2104
|
+
return {
|
|
2105
|
+
id: "email",
|
|
2106
|
+
login: async (ctx, options) => {
|
|
2107
|
+
const email = options.email ?? "";
|
|
2108
|
+
const clientSessionId = await initEmailSession(ctx);
|
|
2109
|
+
if (clientSessionId) await sendEmailCode(email, clientSessionId, ctx);
|
|
2110
|
+
},
|
|
2111
|
+
actions: {
|
|
2112
|
+
begin: async (ctx) => {
|
|
2113
|
+
await initEmailSession(ctx);
|
|
2114
|
+
},
|
|
2115
|
+
sendCode: (ctx, payload) => {
|
|
2116
|
+
const { email, clientSessionId } = payload ?? {};
|
|
2117
|
+
return sendEmailCode(email, clientSessionId, ctx);
|
|
2118
|
+
},
|
|
2119
|
+
verifyCode: (ctx, payload) => {
|
|
2120
|
+
const { code, clientSessionId, email } = payload ?? {};
|
|
2121
|
+
return verifyAndAuthenticate(code, clientSessionId, email, ctx);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/client/auth/sep10-challenge.ts
|
|
2128
|
+
var ENVELOPE_TYPE_TX_V0 = 0;
|
|
2129
|
+
var ENVELOPE_TYPE_TX = 2;
|
|
2130
|
+
var KEY_TYPE_ED25519 = 0;
|
|
2131
|
+
var SEQ_OFFSET_V1 = 44;
|
|
2132
|
+
var SEQ_OFFSET_V0 = 40;
|
|
2133
|
+
function base64ToBytes(b64) {
|
|
2134
|
+
return base64urlDecode(b64.replace(/\+/g, "-").replace(/\//g, "_"));
|
|
2135
|
+
}
|
|
2136
|
+
function isI64Zero(view, offset) {
|
|
2137
|
+
return view.getUint32(offset, false) === 0 && view.getUint32(offset + 4, false) === 0;
|
|
2138
|
+
}
|
|
2139
|
+
function isValidSep10Challenge(challengeXdr) {
|
|
2140
|
+
try {
|
|
2141
|
+
const bytes = base64ToBytes(challengeXdr.trim());
|
|
2142
|
+
if (bytes.length < 8) return false;
|
|
2143
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2144
|
+
const envelopeType = view.getUint32(0, false);
|
|
2145
|
+
let seqOffset;
|
|
2146
|
+
if (envelopeType === ENVELOPE_TYPE_TX) {
|
|
2147
|
+
if (view.getUint32(4, false) !== KEY_TYPE_ED25519) return false;
|
|
2148
|
+
seqOffset = SEQ_OFFSET_V1;
|
|
2149
|
+
} else if (envelopeType === ENVELOPE_TYPE_TX_V0) {
|
|
2150
|
+
seqOffset = SEQ_OFFSET_V0;
|
|
2151
|
+
} else {
|
|
2152
|
+
return false;
|
|
2153
|
+
}
|
|
2154
|
+
if (bytes.length < seqOffset + 8) return false;
|
|
2155
|
+
return isI64Zero(view, seqOffset);
|
|
2156
|
+
} catch {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2039
2161
|
// src/client/auth/walletFlow.ts
|
|
2040
2162
|
function withSignal(promise, signal) {
|
|
2041
2163
|
return Promise.race([
|
|
@@ -2049,6 +2171,16 @@ function withSignal(promise, signal) {
|
|
|
2049
2171
|
})
|
|
2050
2172
|
]);
|
|
2051
2173
|
}
|
|
2174
|
+
async function requestWalletChallenge(clientSessionId, walletAddress, deps) {
|
|
2175
|
+
const { api, logger, signal } = deps;
|
|
2176
|
+
const body = { clientSessionId, walletAddress };
|
|
2177
|
+
const { data, error } = await api.POST("/auth/wallet/challenge", { body, signal });
|
|
2178
|
+
if (error || !data?.success) {
|
|
2179
|
+
if (!error) logApiError(logger, "POST /auth/wallet/challenge", { body, data });
|
|
2180
|
+
return null;
|
|
2181
|
+
}
|
|
2182
|
+
return data.content.challengeXdr;
|
|
2183
|
+
}
|
|
2052
2184
|
async function loginWallet(type, deps) {
|
|
2053
2185
|
const { api, logger, signal, setAuthState } = deps;
|
|
2054
2186
|
const clientSessionId = await createAuthSession(deps);
|
|
@@ -2065,8 +2197,33 @@ async function loginWallet(type, deps) {
|
|
|
2065
2197
|
const { address } = await withSignal(adapter.connect(), signal);
|
|
2066
2198
|
connectedWallet = address;
|
|
2067
2199
|
deps.storeWalletAdapter(adapter, type);
|
|
2200
|
+
setAuthState({ step: "signing_wallet_challenge", walletType: type });
|
|
2201
|
+
const challengeXdr = await requestWalletChallenge(clientSessionId, address, deps);
|
|
2202
|
+
if (!challengeXdr) {
|
|
2203
|
+
setAuthState({
|
|
2204
|
+
step: "error",
|
|
2205
|
+
previousStep: "signing_wallet_challenge",
|
|
2206
|
+
message: "Failed to obtain wallet challenge",
|
|
2207
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2208
|
+
});
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (!isValidSep10Challenge(challengeXdr)) {
|
|
2212
|
+
logApiError(logger, "SEP-10 challenge validation", { error: "unexpected challenge structure (sequence != 0?)" });
|
|
2213
|
+
setAuthState({
|
|
2214
|
+
step: "error",
|
|
2215
|
+
previousStep: "signing_wallet_challenge",
|
|
2216
|
+
message: "Invalid wallet challenge",
|
|
2217
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2218
|
+
});
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const { signedTxXdr } = await withSignal(
|
|
2222
|
+
adapter.signTransaction(challengeXdr, { networkPassphrase: deps.networkPassphrase }),
|
|
2223
|
+
signal
|
|
2224
|
+
);
|
|
2068
2225
|
setAuthState({ step: "authenticating_wallet" });
|
|
2069
|
-
const body = { clientSessionId, walletAddress: address };
|
|
2226
|
+
const body = { clientSessionId, walletAddress: address, signedChallengeXdr: signedTxXdr };
|
|
2070
2227
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", { body, signal });
|
|
2071
2228
|
if (walletError || !walletData?.success) {
|
|
2072
2229
|
if (!walletError) logApiError(logger, "POST /auth/wallet", { body, data: walletData });
|
|
@@ -2155,6 +2312,13 @@ var PollarClient = class {
|
|
|
2155
2312
|
this._loginController = null;
|
|
2156
2313
|
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2157
2314
|
this._resumeController = null;
|
|
2315
|
+
/**
|
|
2316
|
+
* Registry of pluggable login strategies, keyed by provider id. Seeded with
|
|
2317
|
+
* the built-ins (`google`, `github`, `email`) and then any `config.providers`
|
|
2318
|
+
* (which can override a built-in by reusing its id). `wallet` is deliberately
|
|
2319
|
+
* absent — it keeps its own dedicated flow. See {@link PollarAuthProvider}.
|
|
2320
|
+
*/
|
|
2321
|
+
this._providers = /* @__PURE__ */ new Map();
|
|
2158
2322
|
this.apiKey = config.apiKey;
|
|
2159
2323
|
this.id = randomUUID();
|
|
2160
2324
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -2176,6 +2340,12 @@ var PollarClient = class {
|
|
|
2176
2340
|
this._maxIdleMs = config.maxIdleMs;
|
|
2177
2341
|
this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
|
|
2178
2342
|
this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location?.origin ?? "" : "");
|
|
2343
|
+
for (const provider of [oauthProvider("google"), oauthProvider("github"), emailProvider()]) {
|
|
2344
|
+
this._providers.set(provider.id, provider);
|
|
2345
|
+
}
|
|
2346
|
+
for (const provider of config.providers ?? []) {
|
|
2347
|
+
this._providers.set(provider.id, provider);
|
|
2348
|
+
}
|
|
2179
2349
|
this._api = createApiClient(this.basePath);
|
|
2180
2350
|
this._wireMiddlewares();
|
|
2181
2351
|
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
@@ -2582,28 +2752,42 @@ var PollarClient = class {
|
|
|
2582
2752
|
warnServerSide("login");
|
|
2583
2753
|
return;
|
|
2584
2754
|
}
|
|
2585
|
-
if (options.provider === "
|
|
2586
|
-
const controller = this._newController();
|
|
2587
|
-
const deps = this._flowDeps(controller.signal);
|
|
2588
|
-
if (options.provider === "google" || options.provider === "github") {
|
|
2589
|
-
loginOAuth(options.provider, {
|
|
2590
|
-
...deps,
|
|
2591
|
-
basePath: this.basePath,
|
|
2592
|
-
apiKey: this.apiKey,
|
|
2593
|
-
openAuthUrl: this._openAuthUrl,
|
|
2594
|
-
redirectUri: this._oauthRedirectUri
|
|
2595
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2596
|
-
} else if (options.provider === "email") {
|
|
2597
|
-
const { email } = options;
|
|
2598
|
-
initEmailSession(deps).then(() => {
|
|
2599
|
-
if (this._authState.step === "entering_email") {
|
|
2600
|
-
return sendEmailCode(email, this._authState.clientSessionId, deps);
|
|
2601
|
-
}
|
|
2602
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2603
|
-
}
|
|
2604
|
-
} else if (options.provider === "wallet") {
|
|
2755
|
+
if (options.provider === "wallet") {
|
|
2605
2756
|
this.loginWallet(options.type);
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
const provider = this._providers.get(options.provider);
|
|
2760
|
+
if (!provider?.login) {
|
|
2761
|
+
this._setAuthState({
|
|
2762
|
+
step: "error",
|
|
2763
|
+
previousStep: this._authState.step,
|
|
2764
|
+
message: `No auth provider registered for '${options.provider}'`,
|
|
2765
|
+
errorCode: AUTH_ERROR_CODES.AUTH_FAILED
|
|
2766
|
+
});
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
const controller = this._newController();
|
|
2770
|
+
provider.login(this._providerContext(controller.signal), options).catch((err) => this._handleFlowError(err));
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Invoke a named secondary step on a registered provider (e.g. email's
|
|
2774
|
+
* `sendCode` / `verifyCode`, or a custom provider's multi-step continuation).
|
|
2775
|
+
* Reuses the in-flight login `AbortController` when one exists so the step
|
|
2776
|
+
* stays cancellable via `cancelLogin()`; otherwise starts a fresh one. The
|
|
2777
|
+
* built-in email steps also have dedicated typed methods
|
|
2778
|
+
* ({@link sendEmailCode} / {@link verifyEmailCode}) — prefer those for email.
|
|
2779
|
+
*/
|
|
2780
|
+
providerAction(provider, action, payload) {
|
|
2781
|
+
if (!isClientRuntime) {
|
|
2782
|
+
warnServerSide("providerAction");
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
const fn = this._providers.get(provider)?.actions?.[action];
|
|
2786
|
+
if (!fn) {
|
|
2787
|
+
throw new PollarFlowError(`Auth provider '${provider}' has no action '${action}'`);
|
|
2606
2788
|
}
|
|
2789
|
+
const signal = this._loginController?.signal ?? this._newController().signal;
|
|
2790
|
+
fn(this._providerContext(signal), payload).catch((err) => this._handleFlowError(err));
|
|
2607
2791
|
}
|
|
2608
2792
|
// ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
|
|
2609
2793
|
beginEmailLogin() {
|
|
@@ -2612,7 +2796,7 @@ var PollarClient = class {
|
|
|
2612
2796
|
return;
|
|
2613
2797
|
}
|
|
2614
2798
|
const controller = this._newController();
|
|
2615
|
-
initEmailSession(this.
|
|
2799
|
+
initEmailSession(this._providerContext(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2616
2800
|
}
|
|
2617
2801
|
sendEmailCode(email) {
|
|
2618
2802
|
if (!isClientRuntime) {
|
|
@@ -2624,7 +2808,7 @@ var PollarClient = class {
|
|
|
2624
2808
|
}
|
|
2625
2809
|
const { clientSessionId } = this._authState;
|
|
2626
2810
|
const signal = this._loginController.signal;
|
|
2627
|
-
sendEmailCode(email, clientSessionId, this.
|
|
2811
|
+
sendEmailCode(email, clientSessionId, this._providerContext(signal)).catch((err) => this._handleFlowError(err));
|
|
2628
2812
|
}
|
|
2629
2813
|
verifyEmailCode(code) {
|
|
2630
2814
|
if (!isClientRuntime) {
|
|
@@ -2639,7 +2823,7 @@ var PollarClient = class {
|
|
|
2639
2823
|
const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
|
|
2640
2824
|
const email = state.step === "entering_code" ? state.email : state.email ?? "";
|
|
2641
2825
|
const controller = this._newController();
|
|
2642
|
-
verifyAndAuthenticate(code, clientSessionId, email, this.
|
|
2826
|
+
verifyAndAuthenticate(code, clientSessionId, email, this._providerContext(controller.signal)).catch(
|
|
2643
2827
|
(err) => this._handleFlowError(err)
|
|
2644
2828
|
);
|
|
2645
2829
|
}
|
|
@@ -3004,6 +3188,29 @@ var PollarClient = class {
|
|
|
3004
3188
|
getWalletType() {
|
|
3005
3189
|
return this._walletAdapter?.type ?? null;
|
|
3006
3190
|
}
|
|
3191
|
+
/**
|
|
3192
|
+
* The authenticated user's wallet as a {@link WalletInfo} discriminated union,
|
|
3193
|
+
* or `null` when there's no session (or the session carries no address yet).
|
|
3194
|
+
*
|
|
3195
|
+
* `custody` strictly determines `provider` (the mapping is 1:1 and fixed at
|
|
3196
|
+
* account creation server-side): `external` reports the connected adapter id
|
|
3197
|
+
* (`getWalletType()`), `smart` is always `'passkey'`, and `internal` reports
|
|
3198
|
+
* the login method the backend recorded (`null` for pre-provider sessions).
|
|
3199
|
+
*/
|
|
3200
|
+
getWallet() {
|
|
3201
|
+
const w = this._session?.wallet;
|
|
3202
|
+
if (!w || !w.address) return null;
|
|
3203
|
+
switch (w.type) {
|
|
3204
|
+
case "external":
|
|
3205
|
+
return { custody: "external", address: w.address, provider: this._walletAdapter?.type ?? null };
|
|
3206
|
+
case "smart":
|
|
3207
|
+
return { custody: "smart", address: w.address, provider: "passkey" };
|
|
3208
|
+
case "internal":
|
|
3209
|
+
return { custody: "internal", address: w.address, provider: w.provider ?? null };
|
|
3210
|
+
default:
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3007
3214
|
/**
|
|
3008
3215
|
* Signs the given unsigned XDR and returns the signed XDR.
|
|
3009
3216
|
*
|
|
@@ -3057,14 +3264,16 @@ var PollarClient = class {
|
|
|
3057
3264
|
});
|
|
3058
3265
|
return { status: "signed", signedXdr, submissionToken: idempotencyKey };
|
|
3059
3266
|
}
|
|
3060
|
-
const details = error
|
|
3267
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3061
3268
|
this._setTransactionState({
|
|
3062
3269
|
step: "error",
|
|
3063
3270
|
phase: "signing",
|
|
3064
3271
|
...buildData && { buildData },
|
|
3065
|
-
...details && { details }
|
|
3272
|
+
...details && { details },
|
|
3273
|
+
...code && { code },
|
|
3274
|
+
...message && { message }
|
|
3066
3275
|
});
|
|
3067
|
-
return { status: "error", ...details && { details } };
|
|
3276
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3068
3277
|
} catch (err) {
|
|
3069
3278
|
const details = err instanceof Error ? err.message : void 0;
|
|
3070
3279
|
this._setTransactionState({
|
|
@@ -3076,6 +3285,54 @@ var PollarClient = class {
|
|
|
3076
3285
|
return { status: "error", ...details && { details } };
|
|
3077
3286
|
}
|
|
3078
3287
|
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Sign a single Soroban authorization entry (`SorobanAuthorizationEntry`).
|
|
3290
|
+
*
|
|
3291
|
+
* Use this when a contract is the transaction source (e.g. it sponsors the
|
|
3292
|
+
* gas and swaps the fee out of the user's token) and only needs the user's
|
|
3293
|
+
* address-credentials authorization, not a full signed envelope. The signed
|
|
3294
|
+
* entry is returned as base64 XDR for the caller to compose into its tx.
|
|
3295
|
+
*
|
|
3296
|
+
* - External wallets (Freighter/Albedo) sign the entry via the provider.
|
|
3297
|
+
* - Custodial wallets are signed by the backend, which FIRST validates the
|
|
3298
|
+
* entry's invocation tree against the app's contract/function allowlist and
|
|
3299
|
+
* caps the validity window — entries touching a non-allowlisted contract or
|
|
3300
|
+
* function, or expiring too far ahead, are rejected.
|
|
3301
|
+
*
|
|
3302
|
+
* @param entryXdr base64 XDR of the unsigned `SorobanAuthorizationEntry`.
|
|
3303
|
+
* @param options.validUntilLedger absolute ledger the signature expires at
|
|
3304
|
+
* (computed from the network's latest ledger). Ignored on the external-wallet
|
|
3305
|
+
* path, where the provider sets its own expiration.
|
|
3306
|
+
*/
|
|
3307
|
+
async signAuthEntry(entryXdr, options) {
|
|
3308
|
+
if (this._walletAdapter) {
|
|
3309
|
+
const accountToSign = this._session?.wallet?.address;
|
|
3310
|
+
try {
|
|
3311
|
+
const { signedAuthEntry } = await this._walletAdapter.signAuthEntry(
|
|
3312
|
+
entryXdr,
|
|
3313
|
+
accountToSign ? { accountToSign } : void 0
|
|
3314
|
+
);
|
|
3315
|
+
return { status: "signed", signedAuthEntry };
|
|
3316
|
+
} catch (err) {
|
|
3317
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3318
|
+
return { status: "error", ...details && { details } };
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
const address = this._session?.wallet?.address ?? "";
|
|
3322
|
+
try {
|
|
3323
|
+
const { data, error } = await this._api.POST("/tx/sign-auth-entry", {
|
|
3324
|
+
body: { network: this.getNetwork(), address, entryXdr, validUntilLedger: options.validUntilLedger }
|
|
3325
|
+
});
|
|
3326
|
+
if (!error && data?.success && data.content?.signedAuthEntry) {
|
|
3327
|
+
return { status: "signed", signedAuthEntry: data.content.signedAuthEntry };
|
|
3328
|
+
}
|
|
3329
|
+
const details = error?.details;
|
|
3330
|
+
return { status: "error", ...details && { details } };
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3333
|
+
return { status: "error", ...details && { details } };
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3079
3336
|
/**
|
|
3080
3337
|
* Submits a signed XDR via `/tx/submit` regardless of wallet type
|
|
3081
3338
|
* (custodial or external). Routing through sdk-api gives us:
|
|
@@ -3094,6 +3351,21 @@ var PollarClient = class {
|
|
|
3094
3351
|
* `submitted` on Horizon ack (pending), `success` on ledger confirmation,
|
|
3095
3352
|
* or `error[phase: 'submitting']` on failure.
|
|
3096
3353
|
*/
|
|
3354
|
+
/**
|
|
3355
|
+
* Normalize a backend API error into { details, code, message }. `code` is the
|
|
3356
|
+
* precise backend ErrorCode (e.g. `TX_FEE_LIMIT_EXCEEDED`) for programmatic
|
|
3357
|
+
* handling; `message` is a friendly string from the error catalog; `details`
|
|
3358
|
+
* is the raw diagnostic. Lets tx flows surface a typed reason instead of an
|
|
3359
|
+
* opaque details string.
|
|
3360
|
+
*/
|
|
3361
|
+
_resolveTxApiError(error) {
|
|
3362
|
+
const e = error;
|
|
3363
|
+
const details = e?.details ?? e?.message;
|
|
3364
|
+
const code = e?.code;
|
|
3365
|
+
if (!code) return details ? { details } : {};
|
|
3366
|
+
const { message } = resolveAuthError(code, details ?? code);
|
|
3367
|
+
return { code, message, ...details && { details } };
|
|
3368
|
+
}
|
|
3097
3369
|
async submitTx(signedXdr, opts) {
|
|
3098
3370
|
const buildData = this._currentBuildData();
|
|
3099
3371
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
@@ -3131,14 +3403,22 @@ var PollarClient = class {
|
|
|
3131
3403
|
...resultCode && { details: resultCode, resultCode }
|
|
3132
3404
|
};
|
|
3133
3405
|
}
|
|
3134
|
-
const details = error
|
|
3406
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3135
3407
|
this._setTransactionState({
|
|
3136
3408
|
step: "error",
|
|
3137
3409
|
phase: "submitting",
|
|
3138
3410
|
...buildData && { buildData },
|
|
3139
|
-
...details && { details }
|
|
3411
|
+
...details && { details },
|
|
3412
|
+
...code && { code },
|
|
3413
|
+
...message && { message }
|
|
3140
3414
|
});
|
|
3141
|
-
return {
|
|
3415
|
+
return {
|
|
3416
|
+
status: "error",
|
|
3417
|
+
...outcomeExtra,
|
|
3418
|
+
...details && { details },
|
|
3419
|
+
...code && { code },
|
|
3420
|
+
...message && { message }
|
|
3421
|
+
};
|
|
3142
3422
|
} catch (err) {
|
|
3143
3423
|
const details = err instanceof Error ? err.message : void 0;
|
|
3144
3424
|
this._setTransactionState({
|
|
@@ -3225,14 +3505,22 @@ var PollarClient = class {
|
|
|
3225
3505
|
...resultCode && { details: resultCode, resultCode }
|
|
3226
3506
|
};
|
|
3227
3507
|
}
|
|
3228
|
-
const details = error
|
|
3508
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3229
3509
|
this._setTransactionState({
|
|
3230
3510
|
step: "error",
|
|
3231
3511
|
phase: "signing-submitting",
|
|
3232
3512
|
...buildData && { buildData },
|
|
3233
|
-
...details && { details }
|
|
3513
|
+
...details && { details },
|
|
3514
|
+
...code && { code },
|
|
3515
|
+
...message && { message }
|
|
3234
3516
|
});
|
|
3235
|
-
return {
|
|
3517
|
+
return {
|
|
3518
|
+
status: "error",
|
|
3519
|
+
...outcomeExtra,
|
|
3520
|
+
...details && { details },
|
|
3521
|
+
...code && { code },
|
|
3522
|
+
...message && { message }
|
|
3523
|
+
};
|
|
3236
3524
|
} catch (err) {
|
|
3237
3525
|
const details = err instanceof Error ? err.message : void 0;
|
|
3238
3526
|
this._setTransactionState({
|
|
@@ -3307,13 +3595,15 @@ var PollarClient = class {
|
|
|
3307
3595
|
});
|
|
3308
3596
|
return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
|
|
3309
3597
|
}
|
|
3310
|
-
const details = error
|
|
3598
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3311
3599
|
this._setTransactionState({
|
|
3312
3600
|
step: "error",
|
|
3313
3601
|
phase: "building-signing-submitting",
|
|
3314
|
-
...details && { details }
|
|
3602
|
+
...details && { details },
|
|
3603
|
+
...code && { code },
|
|
3604
|
+
...message && { message }
|
|
3315
3605
|
});
|
|
3316
|
-
return { status: "error", ...details && { details } };
|
|
3606
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3317
3607
|
} catch (err) {
|
|
3318
3608
|
const details = err instanceof Error ? err.message : void 0;
|
|
3319
3609
|
this._setTransactionState({
|
|
@@ -3426,9 +3716,22 @@ var PollarClient = class {
|
|
|
3426
3716
|
});
|
|
3427
3717
|
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3428
3718
|
}
|
|
3429
|
-
const details = error
|
|
3430
|
-
this._setTransactionState({
|
|
3431
|
-
|
|
3719
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3720
|
+
this._setTransactionState({
|
|
3721
|
+
step: "error",
|
|
3722
|
+
phase: "submitting",
|
|
3723
|
+
buildData,
|
|
3724
|
+
...details && { details },
|
|
3725
|
+
...code && { code },
|
|
3726
|
+
...message && { message }
|
|
3727
|
+
});
|
|
3728
|
+
return {
|
|
3729
|
+
status: "error",
|
|
3730
|
+
...outcomeExtra,
|
|
3731
|
+
...details && { details },
|
|
3732
|
+
...code && { code },
|
|
3733
|
+
...message && { message }
|
|
3734
|
+
};
|
|
3432
3735
|
} catch (err) {
|
|
3433
3736
|
const details = err instanceof Error ? err.message : void 0;
|
|
3434
3737
|
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
@@ -3506,11 +3809,67 @@ var PollarClient = class {
|
|
|
3506
3809
|
this._loginController = new AbortController();
|
|
3507
3810
|
return this._loginController;
|
|
3508
3811
|
}
|
|
3812
|
+
/**
|
|
3813
|
+
* Build the {@link AuthProviderContext} facade for one login attempt. Wraps
|
|
3814
|
+
* the internal `FlowDeps` so providers get only the curated primitives —
|
|
3815
|
+
* `createSession`, `authenticate`, `exchangeExternalToken`, `startHostedOAuth`
|
|
3816
|
+
* — while storage / wallet-adapter / key-manager internals stay private. All
|
|
3817
|
+
* legs share the same `signal`, so `cancelLogin()` aborts the whole chain.
|
|
3818
|
+
*/
|
|
3819
|
+
_providerContext(signal) {
|
|
3820
|
+
const deps = this._flowDeps(signal);
|
|
3821
|
+
return {
|
|
3822
|
+
signal,
|
|
3823
|
+
api: this._api,
|
|
3824
|
+
basePath: this.basePath,
|
|
3825
|
+
apiKey: this.apiKey,
|
|
3826
|
+
logger: this._log,
|
|
3827
|
+
setAuthState: this._setAuthState.bind(this),
|
|
3828
|
+
createSession: () => createAuthSession(deps),
|
|
3829
|
+
authenticate: (clientSessionId) => authenticate(clientSessionId, deps),
|
|
3830
|
+
requestChallenge: (clientSessionId, walletAddress) => requestWalletChallenge(clientSessionId, walletAddress, deps),
|
|
3831
|
+
exchangeExternalToken: (clientSessionId, body) => this._exchangeExternalToken(clientSessionId, body, signal),
|
|
3832
|
+
startHostedOAuth: (provider) => loginOAuth(provider, {
|
|
3833
|
+
...deps,
|
|
3834
|
+
basePath: this.basePath,
|
|
3835
|
+
apiKey: this.apiKey,
|
|
3836
|
+
openAuthUrl: this._openAuthUrl,
|
|
3837
|
+
redirectUri: this._oauthRedirectUri
|
|
3838
|
+
})
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
/**
|
|
3842
|
+
* Generic external-provider exchange leg (`POST /auth/external`). Custom
|
|
3843
|
+
* providers call this (via the context) after their own SDK has authenticated
|
|
3844
|
+
* the user and the wallet has counter-signed the SEP-10 challenge
|
|
3845
|
+
* (`{ provider, walletAddress, signedChallengeXdr }`). On success the session
|
|
3846
|
+
* is marked READY server-side and the provider should then call
|
|
3847
|
+
* `ctx.authenticate(clientSessionId)`. Returns `false` (and sets an error
|
|
3848
|
+
* state) on failure.
|
|
3849
|
+
*/
|
|
3850
|
+
async _exchangeExternalToken(clientSessionId, body, signal) {
|
|
3851
|
+
const { data, error } = await this._api.POST("/auth/external", {
|
|
3852
|
+
body: { clientSessionId, ...body },
|
|
3853
|
+
signal
|
|
3854
|
+
});
|
|
3855
|
+
if (error || !data?.success) {
|
|
3856
|
+
this._log.error("[PollarClient] External provider authentication failed", { error });
|
|
3857
|
+
this._setAuthState({
|
|
3858
|
+
step: "error",
|
|
3859
|
+
previousStep: this._authState.step,
|
|
3860
|
+
message: "External provider authentication failed",
|
|
3861
|
+
errorCode: AUTH_ERROR_CODES.EXTERNAL_AUTH_FAILED
|
|
3862
|
+
});
|
|
3863
|
+
return false;
|
|
3864
|
+
}
|
|
3865
|
+
return true;
|
|
3866
|
+
}
|
|
3509
3867
|
_flowDeps(signal) {
|
|
3510
3868
|
return {
|
|
3511
3869
|
api: this._api,
|
|
3512
3870
|
logger: this._log,
|
|
3513
3871
|
basePath: this.basePath,
|
|
3872
|
+
networkPassphrase: this._networkPassphrase(),
|
|
3514
3873
|
// SSE status streaming works on web; React Native's `fetch` has no
|
|
3515
3874
|
// readable `response.body`, so those clients poll the non-streaming
|
|
3516
3875
|
// status endpoint instead. `isBrowser` is false in RN and SSR alike.
|
|
@@ -3642,6 +4001,7 @@ var PollarClient = class {
|
|
|
3642
4001
|
async _storeSession(session) {
|
|
3643
4002
|
this._log.info("[PollarClient] Session stored");
|
|
3644
4003
|
const w = session.wallet;
|
|
4004
|
+
const wireProvider = w.provider;
|
|
3645
4005
|
const persisted = {
|
|
3646
4006
|
clientSessionId: session.clientSessionId,
|
|
3647
4007
|
userId: session.userId ?? null,
|
|
@@ -3655,6 +4015,7 @@ var PollarClient = class {
|
|
|
3655
4015
|
// persisted session speak one vocabulary while the wire stays compatible.
|
|
3656
4016
|
wallet: {
|
|
3657
4017
|
type: w.type === "custodial" ? "internal" : w.type,
|
|
4018
|
+
...wireProvider ? { provider: wireProvider } : {},
|
|
3658
4019
|
address: w.address ?? w.publicKey ?? null,
|
|
3659
4020
|
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3660
4021
|
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|