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