@pollar/core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -21
- package/dist/adapters/expo-secure-store.js.map +1 -1
- package/dist/adapters/expo-secure-store.mjs.map +1 -1
- package/dist/adapters/react-native-keychain.js.map +1 -1
- package/dist/adapters/react-native-keychain.mjs.map +1 -1
- package/dist/index.d.mts +1516 -142
- package/dist/index.d.ts +1516 -142
- package/dist/index.js +645 -91
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +644 -92
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +1 -1
- package/dist/index.rn.d.ts +1 -1
- package/dist/index.rn.js +645 -91
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +644 -92
- package/dist/index.rn.mjs.map +1 -1
- package/package.json +6 -3
package/dist/index.rn.mjs
CHANGED
|
@@ -28,10 +28,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
|
|
29
29
|
// ../../node_modules/@stellar/freighter-api/build/index.min.js
|
|
30
30
|
var require_index_min = __commonJS({
|
|
31
|
-
"../../node_modules/@stellar/freighter-api/build/index.min.js"(exports
|
|
31
|
+
"../../node_modules/@stellar/freighter-api/build/index.min.js"(exports, module) {
|
|
32
32
|
!(function(e, r) {
|
|
33
|
-
"object" == typeof exports
|
|
34
|
-
})(exports
|
|
33
|
+
"object" == typeof exports && "object" == typeof module ? module.exports = r() : "function" == typeof define && define.amd ? define([], r) : "object" == typeof exports ? exports.freighterApi = r() : e.freighterApi = r();
|
|
34
|
+
})(exports, (() => (() => {
|
|
35
35
|
var e, r, E = { d: (e2, r2) => {
|
|
36
36
|
for (var o2 in r2) E.o(r2, o2) && !E.o(e2, o2) && Object.defineProperty(e2, o2, { enumerable: true, get: r2[o2] });
|
|
37
37
|
}, o: (e2, r2) => Object.prototype.hasOwnProperty.call(e2, r2), r: (e2) => {
|
|
@@ -450,9 +450,7 @@ var WebCryptoKeyManager = class {
|
|
|
450
450
|
*/
|
|
451
451
|
this._initPromise = null;
|
|
452
452
|
if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
"[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
|
|
455
|
-
);
|
|
453
|
+
throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
|
|
456
454
|
}
|
|
457
455
|
this.apiKey = apiKey;
|
|
458
456
|
}
|
|
@@ -567,7 +565,7 @@ function createClient(clientOptions) {
|
|
|
567
565
|
const {
|
|
568
566
|
baseUrl: localBaseUrl,
|
|
569
567
|
fetch: fetch2 = baseFetch,
|
|
570
|
-
Request = CustomRequest,
|
|
568
|
+
Request: Request2 = CustomRequest,
|
|
571
569
|
headers,
|
|
572
570
|
params = {},
|
|
573
571
|
parseAs = "json",
|
|
@@ -619,7 +617,7 @@ function createClient(clientOptions) {
|
|
|
619
617
|
};
|
|
620
618
|
let id;
|
|
621
619
|
let options;
|
|
622
|
-
let request = new
|
|
620
|
+
let request = new Request2(
|
|
623
621
|
createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
|
|
624
622
|
requestInit
|
|
625
623
|
);
|
|
@@ -649,7 +647,7 @@ function createClient(clientOptions) {
|
|
|
649
647
|
id
|
|
650
648
|
});
|
|
651
649
|
if (result) {
|
|
652
|
-
if (result instanceof
|
|
650
|
+
if (result instanceof Request2) {
|
|
653
651
|
request = result;
|
|
654
652
|
} else if (result instanceof Response) {
|
|
655
653
|
response = result;
|
|
@@ -1021,24 +1019,44 @@ function createApiClient(baseUrl) {
|
|
|
1021
1019
|
return createClient({ baseUrl });
|
|
1022
1020
|
}
|
|
1023
1021
|
|
|
1022
|
+
// src/api/endpoints/distribution.ts
|
|
1023
|
+
async function listDistributionRules(api) {
|
|
1024
|
+
const { data, error } = await api.GET("/distribution/rules");
|
|
1025
|
+
if (!data?.content || error) {
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
error?.code ?? error?.error ?? "Failed to list distribution rules"
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
return data.content.rules;
|
|
1031
|
+
}
|
|
1032
|
+
async function claimDistributionRule(api, body) {
|
|
1033
|
+
const { data, error } = await api.POST("/distribution/claim", { body });
|
|
1034
|
+
if (!data?.content || error) {
|
|
1035
|
+
throw new Error(
|
|
1036
|
+
error?.code ?? error?.error ?? "Failed to claim distribution rule"
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
return data.content;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1024
1042
|
// src/api/endpoints/kyc.ts
|
|
1025
1043
|
async function getKycStatus(api, providerId) {
|
|
1026
1044
|
const { data, error } = await api.GET("/kyc/status", {
|
|
1027
1045
|
params: { query: providerId ? { providerId } : {} }
|
|
1028
1046
|
});
|
|
1029
1047
|
if (!data?.content || error) {
|
|
1030
|
-
throw new Error(error?.error ?? "Failed to get KYC status");
|
|
1048
|
+
throw new Error(error?.code ?? error?.error ?? "Failed to get KYC status");
|
|
1031
1049
|
}
|
|
1032
1050
|
return data.content;
|
|
1033
1051
|
}
|
|
1034
1052
|
async function getKycProviders(api, country) {
|
|
1035
1053
|
const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
|
|
1036
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
|
|
1054
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get KYC providers");
|
|
1037
1055
|
return data.content;
|
|
1038
1056
|
}
|
|
1039
1057
|
async function startKyc(api, body) {
|
|
1040
1058
|
const { data, error } = await api.POST("/kyc/start", { body });
|
|
1041
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
|
|
1059
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to start KYC");
|
|
1042
1060
|
return data.content;
|
|
1043
1061
|
}
|
|
1044
1062
|
async function resolveKyc(api, providerId, level = "basic") {
|
|
@@ -1060,22 +1078,22 @@ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e
|
|
|
1060
1078
|
// src/api/endpoints/ramps.ts
|
|
1061
1079
|
async function getRampsQuote(api, query) {
|
|
1062
1080
|
const { data, error } = await api.GET("/ramps/quote", { params: { query } });
|
|
1063
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
|
|
1081
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get ramp quotes");
|
|
1064
1082
|
return data.content;
|
|
1065
1083
|
}
|
|
1066
1084
|
async function createOnRamp(api, body) {
|
|
1067
1085
|
const { data, error } = await api.POST("/ramps/onramp", { body });
|
|
1068
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
|
|
1086
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create onramp");
|
|
1069
1087
|
return data.content;
|
|
1070
1088
|
}
|
|
1071
1089
|
async function createOffRamp(api, body) {
|
|
1072
1090
|
const { data, error } = await api.POST("/ramps/offramp", { body });
|
|
1073
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
|
|
1091
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create offramp");
|
|
1074
1092
|
return data.content;
|
|
1075
1093
|
}
|
|
1076
1094
|
async function getRampTransaction(api, txId) {
|
|
1077
1095
|
const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
|
|
1078
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
|
|
1096
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get transaction");
|
|
1079
1097
|
return data.content;
|
|
1080
1098
|
}
|
|
1081
1099
|
async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
|
|
@@ -1150,34 +1168,6 @@ function generateJti() {
|
|
|
1150
1168
|
);
|
|
1151
1169
|
}
|
|
1152
1170
|
|
|
1153
|
-
// src/stellar/StellarClient.ts
|
|
1154
|
-
var HORIZON_URLS = {
|
|
1155
|
-
mainnet: "https://horizon.stellar.org",
|
|
1156
|
-
testnet: "https://horizon-testnet.stellar.org"
|
|
1157
|
-
};
|
|
1158
|
-
var StellarClient = class {
|
|
1159
|
-
constructor(config) {
|
|
1160
|
-
this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
|
|
1161
|
-
}
|
|
1162
|
-
async submitTransaction(signedXdr) {
|
|
1163
|
-
try {
|
|
1164
|
-
const response = await fetch(`${this.horizonUrl}/transactions`, {
|
|
1165
|
-
method: "POST",
|
|
1166
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1167
|
-
body: new URLSearchParams({ tx: signedXdr })
|
|
1168
|
-
});
|
|
1169
|
-
if (!response.ok) {
|
|
1170
|
-
const body = await response.json().catch(() => ({}));
|
|
1171
|
-
return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
|
|
1172
|
-
}
|
|
1173
|
-
const data = await response.json();
|
|
1174
|
-
return { success: true, hash: data.hash };
|
|
1175
|
-
} catch {
|
|
1176
|
-
return { success: false, errorCode: "NETWORK_ERROR" };
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
|
|
1181
1171
|
// src/storage/web.ts
|
|
1182
1172
|
var LOG_PREFIX = "[PollarClient:storage]";
|
|
1183
1173
|
function createMemoryAdapter() {
|
|
@@ -1265,6 +1255,57 @@ function defaultStorage(options = {}) {
|
|
|
1265
1255
|
return createLocalStorageAdapter(options);
|
|
1266
1256
|
}
|
|
1267
1257
|
|
|
1258
|
+
// src/visibility/noop.ts
|
|
1259
|
+
function createNoopVisibilityProvider() {
|
|
1260
|
+
return {
|
|
1261
|
+
isVisible: () => true,
|
|
1262
|
+
onChange: () => () => {
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/visibility/web.ts
|
|
1268
|
+
function createWebVisibilityProvider() {
|
|
1269
|
+
const isVisibleNow = () => typeof document === "undefined" || document.visibilityState === "visible";
|
|
1270
|
+
return {
|
|
1271
|
+
isVisible: isVisibleNow,
|
|
1272
|
+
onChange: (cb) => {
|
|
1273
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1274
|
+
return () => {
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
let last = isVisibleNow();
|
|
1278
|
+
const handler = () => {
|
|
1279
|
+
const next = isVisibleNow();
|
|
1280
|
+
if (next !== last) {
|
|
1281
|
+
last = next;
|
|
1282
|
+
cb(next);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
document.addEventListener("visibilitychange", handler);
|
|
1286
|
+
window.addEventListener("pageshow", handler);
|
|
1287
|
+
window.addEventListener("pagehide", handler);
|
|
1288
|
+
window.addEventListener("focus", handler);
|
|
1289
|
+
window.addEventListener("blur", handler);
|
|
1290
|
+
return () => {
|
|
1291
|
+
document.removeEventListener("visibilitychange", handler);
|
|
1292
|
+
window.removeEventListener("pageshow", handler);
|
|
1293
|
+
window.removeEventListener("pagehide", handler);
|
|
1294
|
+
window.removeEventListener("focus", handler);
|
|
1295
|
+
window.removeEventListener("blur", handler);
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/visibility/autodetect.ts
|
|
1302
|
+
function defaultVisibilityProvider() {
|
|
1303
|
+
if (typeof document !== "undefined" && typeof window !== "undefined") {
|
|
1304
|
+
return createWebVisibilityProvider();
|
|
1305
|
+
}
|
|
1306
|
+
return createNoopVisibilityProvider();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1268
1309
|
// src/types.ts
|
|
1269
1310
|
var AUTH_ERROR_CODES = {
|
|
1270
1311
|
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
@@ -1275,6 +1316,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1275
1316
|
AUTH_FAILED: "AUTH_FAILED",
|
|
1276
1317
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1277
1318
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1319
|
+
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1278
1320
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1279
1321
|
};
|
|
1280
1322
|
var PollarFlowError = class extends Error {
|
|
@@ -1665,7 +1707,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
|
1665
1707
|
setAuthState({ step: "authenticating" });
|
|
1666
1708
|
await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
|
|
1667
1709
|
const dpopJwk = await deps.getPublicJwk();
|
|
1668
|
-
const { data
|
|
1710
|
+
const { data } = await api.POST("/auth/login", {
|
|
1669
1711
|
body: {
|
|
1670
1712
|
clientSessionId,
|
|
1671
1713
|
dpopJwk,
|
|
@@ -1830,7 +1872,7 @@ async function loginWallet(type, deps) {
|
|
|
1830
1872
|
let connectedWallet;
|
|
1831
1873
|
try {
|
|
1832
1874
|
setAuthState({ step: "connecting_wallet", walletType: type });
|
|
1833
|
-
const adapter = await deps.resolveWalletAdapter(type);
|
|
1875
|
+
const adapter = await withSignal(deps.resolveWalletAdapter(type), signal);
|
|
1834
1876
|
const available = await withSignal(adapter.isAvailable(), signal);
|
|
1835
1877
|
if (!available) {
|
|
1836
1878
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
@@ -1867,7 +1909,7 @@ async function loginWallet(type, deps) {
|
|
|
1867
1909
|
|
|
1868
1910
|
// src/client/client.ts
|
|
1869
1911
|
var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
|
|
1870
|
-
var
|
|
1912
|
+
var REFRESH_SKEW_SECONDS = 60;
|
|
1871
1913
|
function warnServerSide(method) {
|
|
1872
1914
|
console.warn(
|
|
1873
1915
|
`[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
|
|
@@ -1885,9 +1927,21 @@ var PollarClient = class {
|
|
|
1885
1927
|
this._profile = null;
|
|
1886
1928
|
/** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
|
|
1887
1929
|
this._dpopNonce = null;
|
|
1930
|
+
/**
|
|
1931
|
+
* Snapshot of each in-flight request's body, taken in `onRequest` before
|
|
1932
|
+
* `fetch()` consumes the stream. Needed because `Request.clone()` throws
|
|
1933
|
+
* once the body is disturbed, so the auto-retry path (DPoP nonce challenge
|
|
1934
|
+
* / 401 refresh) must rebuild the request from scratch instead of cloning.
|
|
1935
|
+
*/
|
|
1936
|
+
this._requestBodyCache = /* @__PURE__ */ new WeakMap();
|
|
1888
1937
|
/** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
|
|
1889
1938
|
this._refreshPromise = null;
|
|
1890
1939
|
this._storageEventHandler = null;
|
|
1940
|
+
/** Updated by the request middleware. Read by the silent-refresh scheduler
|
|
1941
|
+
* to skip proactive refreshes after `maxIdleMs` of no HTTP activity. */
|
|
1942
|
+
this._lastRequestAt = Date.now();
|
|
1943
|
+
this._refreshTimer = null;
|
|
1944
|
+
this._visibilityUnsubscribe = null;
|
|
1891
1945
|
this._transactionState = null;
|
|
1892
1946
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1893
1947
|
this._txHistoryState = { step: "idle" };
|
|
@@ -1898,15 +1952,32 @@ var PollarClient = class {
|
|
|
1898
1952
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1899
1953
|
this._networkState = { step: "idle" };
|
|
1900
1954
|
this._networkStateListeners = /* @__PURE__ */ new Set();
|
|
1955
|
+
/**
|
|
1956
|
+
* Latched once the storage adapter degrades. We dedupe (the adapter only
|
|
1957
|
+
* fires once anyway) and use it to replay state to late-subscribers — same
|
|
1958
|
+
* pattern as `onAuthStateChange` replaying `_authState` on subscribe.
|
|
1959
|
+
* Only populated when the SDK constructed the default storage adapter; if
|
|
1960
|
+
* the consumer passes `config.storage`, they own degradation notifications.
|
|
1961
|
+
*/
|
|
1962
|
+
this._storageDegraded = null;
|
|
1963
|
+
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1901
1964
|
this._walletAdapter = null;
|
|
1902
1965
|
this._loginController = null;
|
|
1903
1966
|
this.apiKey = config.apiKey;
|
|
1904
1967
|
this.id = crypto.randomUUID();
|
|
1905
1968
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
1906
|
-
this._storage = config.storage ?? defaultStorage(
|
|
1969
|
+
this._storage = config.storage ?? defaultStorage({
|
|
1970
|
+
onDegrade: (reason, error) => {
|
|
1971
|
+
config.onStorageDegrade?.(reason, error);
|
|
1972
|
+
this._dispatchStorageDegrade(reason, error);
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1907
1975
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1908
1976
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1977
|
+
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
1909
1978
|
this._deviceLabel = config.deviceLabel;
|
|
1979
|
+
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1980
|
+
this._maxIdleMs = config.maxIdleMs;
|
|
1910
1981
|
this._api = createApiClient(this.basePath);
|
|
1911
1982
|
this._wireMiddlewares();
|
|
1912
1983
|
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
@@ -1952,6 +2023,9 @@ var PollarClient = class {
|
|
|
1952
2023
|
console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
|
|
1953
2024
|
}
|
|
1954
2025
|
await this._restoreSession();
|
|
2026
|
+
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2027
|
+
if (visible) void this._maybeProactiveRefresh();
|
|
2028
|
+
});
|
|
1955
2029
|
}
|
|
1956
2030
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
1957
2031
|
destroy() {
|
|
@@ -1961,6 +2035,11 @@ var PollarClient = class {
|
|
|
1961
2035
|
}
|
|
1962
2036
|
this._loginController?.abort();
|
|
1963
2037
|
this._loginController = null;
|
|
2038
|
+
this._clearRefreshTimer();
|
|
2039
|
+
if (this._visibilityUnsubscribe) {
|
|
2040
|
+
this._visibilityUnsubscribe();
|
|
2041
|
+
this._visibilityUnsubscribe = null;
|
|
2042
|
+
}
|
|
1964
2043
|
}
|
|
1965
2044
|
// ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
|
|
1966
2045
|
_wireMiddlewares() {
|
|
@@ -1968,7 +2047,15 @@ var PollarClient = class {
|
|
|
1968
2047
|
this._api.use({
|
|
1969
2048
|
onRequest: async ({ request }) => {
|
|
1970
2049
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2050
|
+
self._lastRequestAt = Date.now();
|
|
1971
2051
|
await self._initialized;
|
|
2052
|
+
if (request.body !== null) {
|
|
2053
|
+
try {
|
|
2054
|
+
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2055
|
+
} catch (err) {
|
|
2056
|
+
console.warn("[PollarClient] Could not snapshot request body for retry", err);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1972
2059
|
const isRefresh = request.url.includes("/auth/refresh");
|
|
1973
2060
|
if (!isRefresh && self._refreshPromise) await self._refreshPromise;
|
|
1974
2061
|
if (isRefresh) {
|
|
@@ -1991,16 +2078,22 @@ var PollarClient = class {
|
|
|
1991
2078
|
const newNonce = response.headers.get("DPoP-Nonce");
|
|
1992
2079
|
if (newNonce) self._dpopNonce = newNonce;
|
|
1993
2080
|
if (response.status !== 401) return response;
|
|
1994
|
-
if (request.headers.get(RETRIED_HEADER)) return response;
|
|
1995
|
-
if (request.url.includes("/auth/refresh")) return response;
|
|
1996
2081
|
const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
|
|
1997
2082
|
const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
|
|
2083
|
+
if (request.url.includes("/auth/refresh")) {
|
|
2084
|
+
if (isNonceChallenge) return self._retryRequest(request);
|
|
2085
|
+
return response;
|
|
2086
|
+
}
|
|
1998
2087
|
if (!isNonceChallenge) {
|
|
1999
2088
|
try {
|
|
2000
2089
|
await self.refresh();
|
|
2001
2090
|
} catch {
|
|
2002
2091
|
return response;
|
|
2003
2092
|
}
|
|
2093
|
+
const method = request.method.toUpperCase();
|
|
2094
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
2095
|
+
return response;
|
|
2096
|
+
}
|
|
2004
2097
|
}
|
|
2005
2098
|
return self._retryRequest(request);
|
|
2006
2099
|
}
|
|
@@ -2024,19 +2117,37 @@ var PollarClient = class {
|
|
|
2024
2117
|
}
|
|
2025
2118
|
}
|
|
2026
2119
|
async _retryRequest(originalRequest) {
|
|
2027
|
-
const
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
if (proof)
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2120
|
+
const headers = new Headers(originalRequest.headers);
|
|
2121
|
+
const isRefresh = originalRequest.url.includes("/auth/refresh");
|
|
2122
|
+
if (isRefresh) {
|
|
2123
|
+
const proof = await this._buildProofForRequest(originalRequest, void 0);
|
|
2124
|
+
headers.delete("Authorization");
|
|
2125
|
+
if (proof) headers.set("DPoP", proof);
|
|
2126
|
+
else headers.delete("DPoP");
|
|
2127
|
+
} else {
|
|
2128
|
+
const accessToken = this._session?.token?.accessToken;
|
|
2129
|
+
if (accessToken) {
|
|
2130
|
+
const proof = await this._buildProofForRequest(originalRequest, accessToken);
|
|
2131
|
+
if (proof) {
|
|
2132
|
+
headers.set("Authorization", `DPoP ${accessToken}`);
|
|
2133
|
+
headers.set("DPoP", proof);
|
|
2134
|
+
} else {
|
|
2135
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
2136
|
+
}
|
|
2037
2137
|
}
|
|
2038
2138
|
}
|
|
2039
|
-
|
|
2139
|
+
const cachedBody = this._requestBodyCache.get(originalRequest);
|
|
2140
|
+
const retried = new Request(originalRequest.url, {
|
|
2141
|
+
method: originalRequest.method,
|
|
2142
|
+
headers,
|
|
2143
|
+
body: cachedBody ?? null,
|
|
2144
|
+
credentials: originalRequest.credentials,
|
|
2145
|
+
mode: originalRequest.mode,
|
|
2146
|
+
redirect: originalRequest.redirect,
|
|
2147
|
+
referrer: originalRequest.referrer,
|
|
2148
|
+
integrity: originalRequest.integrity
|
|
2149
|
+
});
|
|
2150
|
+
return fetch(retried);
|
|
2040
2151
|
}
|
|
2041
2152
|
// ─── Refresh (race-safe singleton) ───────────────────────────────────────
|
|
2042
2153
|
/**
|
|
@@ -2093,6 +2204,65 @@ var PollarClient = class {
|
|
|
2093
2204
|
} catch (err) {
|
|
2094
2205
|
console.error("[PollarClient] Failed to persist refreshed session", err);
|
|
2095
2206
|
}
|
|
2207
|
+
this._scheduleNextRefresh();
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
// ─── Silent refresh scheduler ────────────────────────────────────────────────
|
|
2211
|
+
/**
|
|
2212
|
+
* Arm a single setTimeout to fire shortly before the current access token
|
|
2213
|
+
* expires. Idempotent — clearing any previous timer first. Safe to call
|
|
2214
|
+
* from any session-write site (initial login, restore-from-storage, after
|
|
2215
|
+
* a successful rotation). No-op if there's no session in memory.
|
|
2216
|
+
*
|
|
2217
|
+
* Browser/RN background-tab throttling makes long-running setTimeouts
|
|
2218
|
+
* unreliable on their own; the `visibilitychange` listener compensates by
|
|
2219
|
+
* re-invoking `_maybeProactiveRefresh` whenever the app comes back to the
|
|
2220
|
+
* foreground, catching any timer that fired late or never fired at all.
|
|
2221
|
+
*/
|
|
2222
|
+
_scheduleNextRefresh() {
|
|
2223
|
+
this._clearRefreshTimer();
|
|
2224
|
+
const expiresAt = this._session?.token?.expiresAt;
|
|
2225
|
+
if (typeof expiresAt !== "number") return;
|
|
2226
|
+
const dueInMs = Math.max(0, (expiresAt - Math.floor(Date.now() / 1e3) - REFRESH_SKEW_SECONDS) * 1e3);
|
|
2227
|
+
this._refreshTimer = setTimeout(() => {
|
|
2228
|
+
void this._maybeProactiveRefresh();
|
|
2229
|
+
}, dueInMs);
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Decide whether to actually run a refresh right now. Called both from the
|
|
2233
|
+
* scheduler timer and from the visibility-change listener.
|
|
2234
|
+
*
|
|
2235
|
+
* Skip if:
|
|
2236
|
+
* - no session / no RT (nothing to refresh)
|
|
2237
|
+
* - app is hidden — wait for the visibility listener to re-trigger us
|
|
2238
|
+
* - `maxIdleMs` configured and no client request since that window — let
|
|
2239
|
+
* the next reactive 401-refresh handle it whenever the user comes back
|
|
2240
|
+
* - the AT still has more than `REFRESH_SKEW_SECONDS` of life — reschedule
|
|
2241
|
+
*
|
|
2242
|
+
* Otherwise call `refresh()`, which uses the existing in-flight singleton
|
|
2243
|
+
* so we never collide with a reactive 401-triggered refresh. On failure,
|
|
2244
|
+
* `_doRefresh` already calls `_clearSession`, so auth-state listeners see
|
|
2245
|
+
* `step:'idle'` — no extra event dispatch needed here.
|
|
2246
|
+
*/
|
|
2247
|
+
async _maybeProactiveRefresh() {
|
|
2248
|
+
if (!this._session?.token?.refreshToken) return;
|
|
2249
|
+
if (!this._visibilityProvider.isVisible()) return;
|
|
2250
|
+
if (this._maxIdleMs !== void 0 && Date.now() - this._lastRequestAt > this._maxIdleMs) return;
|
|
2251
|
+
const expiresAt = this._session.token.expiresAt;
|
|
2252
|
+
if (Math.floor(Date.now() / 1e3) < expiresAt - REFRESH_SKEW_SECONDS) {
|
|
2253
|
+
this._scheduleNextRefresh();
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
try {
|
|
2257
|
+
await this.refresh();
|
|
2258
|
+
} catch (err) {
|
|
2259
|
+
console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
_clearRefreshTimer() {
|
|
2263
|
+
if (this._refreshTimer !== null) {
|
|
2264
|
+
clearTimeout(this._refreshTimer);
|
|
2265
|
+
this._refreshTimer = null;
|
|
2096
2266
|
}
|
|
2097
2267
|
}
|
|
2098
2268
|
// ─── Auth state ──────────────────────────────────────────────────────────────
|
|
@@ -2104,6 +2274,38 @@ var PollarClient = class {
|
|
|
2104
2274
|
cb(this._authState);
|
|
2105
2275
|
return () => this._authStateListeners.delete(cb);
|
|
2106
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Subscribe to persistent-storage degradation (Safari private mode,
|
|
2279
|
+
* sandboxed iframes, quota errors, etc.). The SDK keeps running off
|
|
2280
|
+
* in-memory storage after degrade, but sessions won't survive reload — a
|
|
2281
|
+
* host UI typically wants to show "your session won't be saved" so the
|
|
2282
|
+
* user isn't blindsided after a refresh.
|
|
2283
|
+
*
|
|
2284
|
+
* Fires at most once per client lifetime (the underlying adapter dedupes).
|
|
2285
|
+
* Late subscribers receive the latched state synchronously on subscribe.
|
|
2286
|
+
*
|
|
2287
|
+
* Only fires when the SDK constructs the default storage adapter. If you
|
|
2288
|
+
* pass a custom `config.storage`, wire your own notification path through
|
|
2289
|
+
* that adapter's API — the SDK has no hook into it.
|
|
2290
|
+
*/
|
|
2291
|
+
onStorageDegrade(cb) {
|
|
2292
|
+
this._storageDegradeListeners.add(cb);
|
|
2293
|
+
if (this._storageDegraded) {
|
|
2294
|
+
cb(this._storageDegraded.reason, this._storageDegraded.error);
|
|
2295
|
+
}
|
|
2296
|
+
return () => this._storageDegradeListeners.delete(cb);
|
|
2297
|
+
}
|
|
2298
|
+
_dispatchStorageDegrade(reason, error) {
|
|
2299
|
+
if (this._storageDegraded) return;
|
|
2300
|
+
this._storageDegraded = { reason, error };
|
|
2301
|
+
for (const cb of this._storageDegradeListeners) {
|
|
2302
|
+
try {
|
|
2303
|
+
cb(reason, error);
|
|
2304
|
+
} catch (err) {
|
|
2305
|
+
console.error("[PollarClient] onStorageDegrade listener threw", err);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2107
2309
|
/** PII (email, names, avatar, providers). Held in memory only — never persisted. */
|
|
2108
2310
|
getUserProfile() {
|
|
2109
2311
|
return this._profile;
|
|
@@ -2340,10 +2542,16 @@ var PollarClient = class {
|
|
|
2340
2542
|
}
|
|
2341
2543
|
}
|
|
2342
2544
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2545
|
+
/**
|
|
2546
|
+
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
2547
|
+
* AND returns a {@link BuildOutcome} so headless callers can `await` and
|
|
2548
|
+
* inspect the result without subscribing to state changes.
|
|
2549
|
+
*/
|
|
2343
2550
|
async buildTx(operation, params, options) {
|
|
2344
2551
|
if (!this._session?.wallet?.publicKey) {
|
|
2345
|
-
|
|
2346
|
-
|
|
2552
|
+
const details = "No wallet connected";
|
|
2553
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2554
|
+
return { status: "error", details };
|
|
2347
2555
|
}
|
|
2348
2556
|
const body = {
|
|
2349
2557
|
network: this.getNetwork(),
|
|
@@ -2357,39 +2565,194 @@ var PollarClient = class {
|
|
|
2357
2565
|
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
2358
2566
|
if (!error && data?.success && data.content) {
|
|
2359
2567
|
this._setTransactionState({ step: "built", buildData: data.content });
|
|
2360
|
-
|
|
2361
|
-
const details = error?.details;
|
|
2362
|
-
this._setTransactionState({ step: "error", ...details && { details } });
|
|
2568
|
+
return { status: "built", buildData: data.content };
|
|
2363
2569
|
}
|
|
2364
|
-
|
|
2365
|
-
this._setTransactionState({ step: "error" });
|
|
2570
|
+
const details = error?.details;
|
|
2571
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
2572
|
+
return { status: "error", ...details && { details } };
|
|
2573
|
+
} catch (err) {
|
|
2574
|
+
console.error("[PollarClient] buildTx failed", err);
|
|
2575
|
+
this._setTransactionState({ step: "error", phase: "building" });
|
|
2576
|
+
return { status: "error" };
|
|
2366
2577
|
}
|
|
2367
2578
|
}
|
|
2368
2579
|
getWalletType() {
|
|
2369
2580
|
return this._walletAdapter?.type ?? null;
|
|
2370
2581
|
}
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2582
|
+
/**
|
|
2583
|
+
* Signs the given unsigned XDR and returns the signed XDR.
|
|
2584
|
+
*
|
|
2585
|
+
* - External wallets: signs locally via the wallet adapter.
|
|
2586
|
+
* - Custodial wallets: posts to `/tx/sign`. The backend signs (through
|
|
2587
|
+
* wallet-service or the app's customer-managed adapter) and returns the
|
|
2588
|
+
* signed XDR plus an `idempotencyKey` the caller should echo back to
|
|
2589
|
+
* `submitTx`.
|
|
2590
|
+
*
|
|
2591
|
+
* Drives `_setTransactionState`: emits `signing` while in flight and
|
|
2592
|
+
* `signed` on success (or `error[phase: 'signing']` on failure). `buildData`
|
|
2593
|
+
* is threaded through if the consumer previously called `buildTx`.
|
|
2594
|
+
*/
|
|
2595
|
+
async signTx(unsignedXdr) {
|
|
2596
|
+
const buildData = this._currentBuildData();
|
|
2597
|
+
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2377
2598
|
if (this._walletAdapter) {
|
|
2599
|
+
const accountToSign = this._session?.wallet?.publicKey;
|
|
2600
|
+
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2378
2601
|
try {
|
|
2379
|
-
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2380
2602
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2603
|
+
this._setTransactionState({
|
|
2604
|
+
step: "signed",
|
|
2605
|
+
signedXdr: signedTxXdr,
|
|
2606
|
+
...buildData && { buildData }
|
|
2607
|
+
});
|
|
2608
|
+
return { status: "signed", signedXdr: signedTxXdr };
|
|
2609
|
+
} catch (err) {
|
|
2610
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2611
|
+
this._setTransactionState({
|
|
2612
|
+
step: "error",
|
|
2613
|
+
phase: "signing",
|
|
2614
|
+
...buildData && { buildData },
|
|
2615
|
+
...details && { details }
|
|
2616
|
+
});
|
|
2617
|
+
return { status: "error", ...details && { details } };
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
const publicKey = this._session?.wallet?.publicKey ?? "";
|
|
2621
|
+
try {
|
|
2622
|
+
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2623
|
+
body: { network: this.getNetwork(), publicKey, unsignedXdr }
|
|
2624
|
+
});
|
|
2625
|
+
if (!error && data?.success && data.content?.signedXdr) {
|
|
2626
|
+
const { signedXdr, idempotencyKey } = data.content;
|
|
2627
|
+
this._setTransactionState({
|
|
2628
|
+
step: "signed",
|
|
2629
|
+
signedXdr,
|
|
2630
|
+
submissionToken: idempotencyKey,
|
|
2631
|
+
...buildData && { buildData }
|
|
2632
|
+
});
|
|
2633
|
+
return { status: "signed", signedXdr, submissionToken: idempotencyKey };
|
|
2634
|
+
}
|
|
2635
|
+
const details = error?.details;
|
|
2636
|
+
this._setTransactionState({
|
|
2637
|
+
step: "error",
|
|
2638
|
+
phase: "signing",
|
|
2639
|
+
...buildData && { buildData },
|
|
2640
|
+
...details && { details }
|
|
2641
|
+
});
|
|
2642
|
+
return { status: "error", ...details && { details } };
|
|
2643
|
+
} catch (err) {
|
|
2644
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2645
|
+
this._setTransactionState({
|
|
2646
|
+
step: "error",
|
|
2647
|
+
phase: "signing",
|
|
2648
|
+
...buildData && { buildData },
|
|
2649
|
+
...details && { details }
|
|
2650
|
+
});
|
|
2651
|
+
return { status: "error", ...details && { details } };
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Submits a signed XDR via `/tx/submit` regardless of wallet type
|
|
2656
|
+
* (custodial or external). Routing through sdk-api gives us:
|
|
2657
|
+
* - End-to-end tx_records persistence with full phase lifecycle so the
|
|
2658
|
+
* developer dashboard can show every tx (both custodial and external
|
|
2659
|
+
* wallet flows) at `/apps/:id/monitor/transactions`.
|
|
2660
|
+
* - Idempotency tracking via `submissionToken` (returned by `signTx`).
|
|
2661
|
+
* - A single response shape (SUCCESS / PENDING / FAILED) shared by both
|
|
2662
|
+
* flows — previously external wallets could only return SUCCESS or
|
|
2663
|
+
* error since the direct-to-Horizon path was synchronous.
|
|
2664
|
+
*
|
|
2665
|
+
* The extra hop adds ~50–150 ms vs. the legacy direct-Horizon path; the
|
|
2666
|
+
* persistence + observability win is worth it.
|
|
2667
|
+
*
|
|
2668
|
+
* Drives `_setTransactionState`: emits `submitting` while in flight,
|
|
2669
|
+
* `submitted` on Horizon ack (pending), `success` on ledger confirmation,
|
|
2670
|
+
* or `error[phase: 'submitting']` on failure.
|
|
2671
|
+
*/
|
|
2672
|
+
async submitTx(signedXdr, opts) {
|
|
2673
|
+
const buildData = this._currentBuildData();
|
|
2674
|
+
const outcomeExtra = buildData ? { buildData } : {};
|
|
2675
|
+
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2676
|
+
const publicKey = this._session?.wallet?.publicKey ?? "";
|
|
2677
|
+
try {
|
|
2678
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2679
|
+
body: {
|
|
2680
|
+
network: this.getNetwork(),
|
|
2681
|
+
publicKey,
|
|
2682
|
+
signedXdr,
|
|
2683
|
+
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2387
2684
|
}
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2685
|
+
});
|
|
2686
|
+
if (!error && data?.success && data.content) {
|
|
2687
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
2688
|
+
if (backendStatus === "SUCCESS") {
|
|
2689
|
+
this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
|
|
2690
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
2691
|
+
}
|
|
2692
|
+
if (backendStatus === "PENDING") {
|
|
2693
|
+
this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
|
|
2694
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
2695
|
+
}
|
|
2696
|
+
this._setTransactionState({
|
|
2697
|
+
step: "error",
|
|
2698
|
+
phase: "submitting",
|
|
2699
|
+
...buildData && { buildData },
|
|
2700
|
+
...resultCode && { details: resultCode }
|
|
2701
|
+
});
|
|
2702
|
+
return {
|
|
2703
|
+
status: "error",
|
|
2704
|
+
hash,
|
|
2705
|
+
...outcomeExtra,
|
|
2706
|
+
...resultCode && { details: resultCode, resultCode }
|
|
2707
|
+
};
|
|
2390
2708
|
}
|
|
2391
|
-
|
|
2709
|
+
const details = error?.details;
|
|
2710
|
+
this._setTransactionState({
|
|
2711
|
+
step: "error",
|
|
2712
|
+
phase: "submitting",
|
|
2713
|
+
...buildData && { buildData },
|
|
2714
|
+
...details && { details }
|
|
2715
|
+
});
|
|
2716
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2717
|
+
} catch (err) {
|
|
2718
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2719
|
+
this._setTransactionState({
|
|
2720
|
+
step: "error",
|
|
2721
|
+
phase: "submitting",
|
|
2722
|
+
...buildData && { buildData },
|
|
2723
|
+
...details && { details }
|
|
2724
|
+
});
|
|
2725
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2392
2726
|
}
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* Signs and submits in one logical step. Returns a {@link SubmitOutcome}.
|
|
2730
|
+
*
|
|
2731
|
+
* - **External wallets**: composes `signTx` + `submitTx` client-side. State
|
|
2732
|
+
* machine sees the full granular sequence `signing → signed → submitting
|
|
2733
|
+
* → success` because the underlying methods each emit.
|
|
2734
|
+
* - **Custodial wallets**: atomic `/tx/sign-and-send` round-trip. State
|
|
2735
|
+
* machine emits the compound `signing-submitting` step (the SDK can't
|
|
2736
|
+
* observe when one phase ends and the next begins inside that single
|
|
2737
|
+
* backend call) and then transitions to `submitted` (Horizon ack only) or
|
|
2738
|
+
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2739
|
+
*/
|
|
2740
|
+
async signAndSubmitTx(unsignedXdr) {
|
|
2741
|
+
if (this._walletAdapter) {
|
|
2742
|
+
const signed = await this.signTx(unsignedXdr);
|
|
2743
|
+
if (signed.status === "error") {
|
|
2744
|
+
const buildData2 = this._currentBuildData();
|
|
2745
|
+
return {
|
|
2746
|
+
status: "error",
|
|
2747
|
+
...buildData2 && { buildData: buildData2 },
|
|
2748
|
+
...signed.details && { details: signed.details }
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
return this.submitTx(signed.signedXdr);
|
|
2752
|
+
}
|
|
2753
|
+
const buildData = this._currentBuildData();
|
|
2754
|
+
const outcomeExtra = buildData ? { buildData } : {};
|
|
2755
|
+
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2393
2756
|
const body = {
|
|
2394
2757
|
network: this.getNetwork(),
|
|
2395
2758
|
publicKey: this._session?.wallet?.publicKey ?? "",
|
|
@@ -2398,14 +2761,128 @@ var PollarClient = class {
|
|
|
2398
2761
|
try {
|
|
2399
2762
|
const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
|
|
2400
2763
|
if (!error && data?.success && data.content?.hash) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2764
|
+
const {
|
|
2765
|
+
hash,
|
|
2766
|
+
status: backendStatus,
|
|
2767
|
+
resultCode
|
|
2768
|
+
} = data.content;
|
|
2769
|
+
if (backendStatus === "SUCCESS") {
|
|
2770
|
+
this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
|
|
2771
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
2772
|
+
}
|
|
2773
|
+
if (backendStatus === "PENDING") {
|
|
2774
|
+
this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
|
|
2775
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
2776
|
+
}
|
|
2777
|
+
this._setTransactionState({
|
|
2778
|
+
step: "error",
|
|
2779
|
+
phase: "signing-submitting",
|
|
2780
|
+
...buildData && { buildData },
|
|
2781
|
+
...resultCode && { details: resultCode }
|
|
2782
|
+
});
|
|
2783
|
+
return {
|
|
2784
|
+
status: "error",
|
|
2785
|
+
hash,
|
|
2786
|
+
...outcomeExtra,
|
|
2787
|
+
...resultCode && { details: resultCode, resultCode }
|
|
2788
|
+
};
|
|
2405
2789
|
}
|
|
2406
|
-
|
|
2407
|
-
this._setTransactionState({
|
|
2790
|
+
const details = error?.details;
|
|
2791
|
+
this._setTransactionState({
|
|
2792
|
+
step: "error",
|
|
2793
|
+
phase: "signing-submitting",
|
|
2794
|
+
...buildData && { buildData },
|
|
2795
|
+
...details && { details }
|
|
2796
|
+
});
|
|
2797
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2798
|
+
} catch (err) {
|
|
2799
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2800
|
+
this._setTransactionState({
|
|
2801
|
+
step: "error",
|
|
2802
|
+
phase: "signing-submitting",
|
|
2803
|
+
...buildData && { buildData },
|
|
2804
|
+
...details && { details }
|
|
2805
|
+
});
|
|
2806
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* One-shot: build → sign → submit, returning the final {@link SubmitOutcome}.
|
|
2811
|
+
*
|
|
2812
|
+
* - **External wallets**: composes `buildTx` + `signAndSubmitTx` client-side.
|
|
2813
|
+
* State machine sees the full granular sequence (`building → built →
|
|
2814
|
+
* signing → signed → submitting → success`) because each composed call
|
|
2815
|
+
* emits its own transitions.
|
|
2816
|
+
* - **Custodial wallets**: single round-trip to `/tx/build-sign-submit`. The
|
|
2817
|
+
* signed XDR never leaves the backend. State machine emits the compound
|
|
2818
|
+
* `building-signing-submitting` step (the SDK can't observe individual
|
|
2819
|
+
* phase boundaries inside one atomic call) and then transitions to
|
|
2820
|
+
* `submitted` / `success` / `error[phase: 'building-signing-submitting']`.
|
|
2821
|
+
*
|
|
2822
|
+
* If you need granular UI feedback for custodial flows (separate
|
|
2823
|
+
* "Building…", "Signing…", "Submitting…" indicators), call `buildTx`,
|
|
2824
|
+
* `signTx`, and `submitTx` separately instead.
|
|
2825
|
+
*/
|
|
2826
|
+
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
2827
|
+
if (this._walletAdapter) {
|
|
2828
|
+
const built = await this.buildTx(operation, params, options);
|
|
2829
|
+
if (built.status === "error") {
|
|
2830
|
+
return { status: "error", ...built.details && { details: built.details } };
|
|
2831
|
+
}
|
|
2832
|
+
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2408
2833
|
}
|
|
2834
|
+
if (!this._session?.wallet?.publicKey) {
|
|
2835
|
+
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2836
|
+
return { status: "error", details: "No wallet connected" };
|
|
2837
|
+
}
|
|
2838
|
+
this._setTransactionState({ step: "building-signing-submitting" });
|
|
2839
|
+
try {
|
|
2840
|
+
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2841
|
+
body: {
|
|
2842
|
+
network: this.getNetwork(),
|
|
2843
|
+
publicKey: this._session.wallet.publicKey,
|
|
2844
|
+
operation,
|
|
2845
|
+
params,
|
|
2846
|
+
options: options ?? {}
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
if (!error && data?.success && data.content) {
|
|
2850
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
2851
|
+
if (backendStatus === "SUCCESS") {
|
|
2852
|
+
this._setTransactionState({ step: "success", hash });
|
|
2853
|
+
return { status: "success", hash };
|
|
2854
|
+
}
|
|
2855
|
+
if (backendStatus === "PENDING") {
|
|
2856
|
+
this._setTransactionState({ step: "submitted", hash });
|
|
2857
|
+
return { status: "pending", hash };
|
|
2858
|
+
}
|
|
2859
|
+
this._setTransactionState({
|
|
2860
|
+
step: "error",
|
|
2861
|
+
phase: "building-signing-submitting",
|
|
2862
|
+
...resultCode && { details: resultCode }
|
|
2863
|
+
});
|
|
2864
|
+
return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
|
|
2865
|
+
}
|
|
2866
|
+
const details = error?.details;
|
|
2867
|
+
this._setTransactionState({
|
|
2868
|
+
step: "error",
|
|
2869
|
+
phase: "building-signing-submitting",
|
|
2870
|
+
...details && { details }
|
|
2871
|
+
});
|
|
2872
|
+
return { status: "error", ...details && { details } };
|
|
2873
|
+
} catch (err) {
|
|
2874
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2875
|
+
this._setTransactionState({
|
|
2876
|
+
step: "error",
|
|
2877
|
+
phase: "building-signing-submitting",
|
|
2878
|
+
...details && { details }
|
|
2879
|
+
});
|
|
2880
|
+
return { status: "error", ...details && { details } };
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
/** Alias for {@link buildAndSignAndSubmitTx} — shorter "just do the thing" name. */
|
|
2884
|
+
async runTx(operation, params, options) {
|
|
2885
|
+
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2409
2886
|
}
|
|
2410
2887
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2411
2888
|
async getAppConfig() {
|
|
@@ -2449,6 +2926,13 @@ var PollarClient = class {
|
|
|
2449
2926
|
pollRampTransaction(txId, opts) {
|
|
2450
2927
|
return pollRampTransaction(this._api, txId, opts);
|
|
2451
2928
|
}
|
|
2929
|
+
// ─── Distribution ─────────────────────────────────────────────────────────
|
|
2930
|
+
listDistributionRules() {
|
|
2931
|
+
return listDistributionRules(this._api);
|
|
2932
|
+
}
|
|
2933
|
+
claimDistributionRule(body) {
|
|
2934
|
+
return claimDistributionRule(this._api, body);
|
|
2935
|
+
}
|
|
2452
2936
|
_setTxHistoryState(next) {
|
|
2453
2937
|
this._txHistoryState = next;
|
|
2454
2938
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
@@ -2487,7 +2971,22 @@ var PollarClient = class {
|
|
|
2487
2971
|
*/
|
|
2488
2972
|
async _resolveWalletAdapter(id) {
|
|
2489
2973
|
if (this._walletAdapterResolver) {
|
|
2490
|
-
|
|
2974
|
+
const timeoutMs = this._walletResolverTimeoutMs;
|
|
2975
|
+
let timeoutHandle;
|
|
2976
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2977
|
+
timeoutHandle = setTimeout(() => {
|
|
2978
|
+
reject(
|
|
2979
|
+
Object.assign(new Error(`[PollarClient] Wallet adapter resolver for "${id}" timed out after ${timeoutMs}ms`), {
|
|
2980
|
+
code: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
|
|
2981
|
+
})
|
|
2982
|
+
);
|
|
2983
|
+
}, timeoutMs);
|
|
2984
|
+
});
|
|
2985
|
+
try {
|
|
2986
|
+
return await Promise.race([Promise.resolve(this._walletAdapterResolver(id)), timeoutPromise]);
|
|
2987
|
+
} finally {
|
|
2988
|
+
if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
|
|
2989
|
+
}
|
|
2491
2990
|
}
|
|
2492
2991
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2493
2992
|
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
@@ -2501,6 +3000,16 @@ var PollarClient = class {
|
|
|
2501
3000
|
this._setAuthState({ step: "idle" });
|
|
2502
3001
|
return;
|
|
2503
3002
|
}
|
|
3003
|
+
if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
|
|
3004
|
+
console.error("[PollarClient]", error.message);
|
|
3005
|
+
this._setAuthState({
|
|
3006
|
+
step: "error",
|
|
3007
|
+
previousStep: this._authState.step,
|
|
3008
|
+
message: error.message,
|
|
3009
|
+
errorCode: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
|
|
3010
|
+
});
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
2504
3013
|
console.error("[PollarClient] Unexpected error in auth flow", error);
|
|
2505
3014
|
this._setAuthState({
|
|
2506
3015
|
step: "error",
|
|
@@ -2522,6 +3031,7 @@ var PollarClient = class {
|
|
|
2522
3031
|
}
|
|
2523
3032
|
console.info("[PollarClient] Session restored from storage");
|
|
2524
3033
|
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3034
|
+
this._scheduleNextRefresh();
|
|
2525
3035
|
} else {
|
|
2526
3036
|
console.info("[PollarClient] No session in storage");
|
|
2527
3037
|
}
|
|
@@ -2548,9 +3058,11 @@ var PollarClient = class {
|
|
|
2548
3058
|
}
|
|
2549
3059
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
2550
3060
|
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3061
|
+
this._scheduleNextRefresh();
|
|
2551
3062
|
}
|
|
2552
3063
|
async _clearSession() {
|
|
2553
3064
|
console.info("[PollarClient] Session cleared");
|
|
3065
|
+
this._clearRefreshTimer();
|
|
2554
3066
|
this._session = null;
|
|
2555
3067
|
this._profile = null;
|
|
2556
3068
|
this._walletAdapter = null;
|
|
@@ -2583,6 +3095,46 @@ var PollarClient = class {
|
|
|
2583
3095
|
console.info(`[PollarClient] transaction:${next.step}`);
|
|
2584
3096
|
for (const cb of this._transactionStateListeners) cb(next);
|
|
2585
3097
|
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Threads `buildData` through state transitions. When the user has already
|
|
3100
|
+
* called `buildTx`, every subsequent state (signing, signed, submitting,
|
|
3101
|
+
* submitted, success, error) should carry the build summary so modal UIs
|
|
3102
|
+
* can keep showing "Send 5 USDC to G..." through the whole flow.
|
|
3103
|
+
*/
|
|
3104
|
+
_currentBuildData() {
|
|
3105
|
+
const s = this._transactionState;
|
|
3106
|
+
if (!s) return void 0;
|
|
3107
|
+
if ("buildData" in s && s.buildData) return s.buildData;
|
|
3108
|
+
return void 0;
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
|
|
3112
|
+
// src/stellar/StellarClient.ts
|
|
3113
|
+
var HORIZON_URLS = {
|
|
3114
|
+
mainnet: "https://horizon.stellar.org",
|
|
3115
|
+
testnet: "https://horizon-testnet.stellar.org"
|
|
3116
|
+
};
|
|
3117
|
+
var StellarClient = class {
|
|
3118
|
+
constructor(config) {
|
|
3119
|
+
this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
|
|
3120
|
+
}
|
|
3121
|
+
async submitTransaction(signedXdr) {
|
|
3122
|
+
try {
|
|
3123
|
+
const response = await fetch(`${this.horizonUrl}/transactions`, {
|
|
3124
|
+
method: "POST",
|
|
3125
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3126
|
+
body: new URLSearchParams({ tx: signedXdr })
|
|
3127
|
+
});
|
|
3128
|
+
if (!response.ok) {
|
|
3129
|
+
const body = await response.json().catch(() => ({}));
|
|
3130
|
+
return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
|
|
3131
|
+
}
|
|
3132
|
+
const data = await response.json();
|
|
3133
|
+
return { success: true, hash: data.hash };
|
|
3134
|
+
} catch {
|
|
3135
|
+
return { success: false, errorCode: "NETWORK_ERROR" };
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
2586
3138
|
};
|
|
2587
3139
|
|
|
2588
3140
|
// src/index.rn.ts
|
|
@@ -2594,6 +3146,6 @@ _setDefaultKeyManagerFactory((storage, apiKey) => {
|
|
|
2594
3146
|
return new NobleKeyManager(storage, apiKey);
|
|
2595
3147
|
});
|
|
2596
3148
|
|
|
2597
|
-
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, NobleKeyManager, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
|
|
3149
|
+
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, NobleKeyManager, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
|
|
2598
3150
|
//# sourceMappingURL=index.rn.mjs.map
|
|
2599
3151
|
//# sourceMappingURL=index.rn.mjs.map
|