@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.mjs
CHANGED
|
@@ -26,10 +26,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
|
|
27
27
|
// ../../node_modules/@stellar/freighter-api/build/index.min.js
|
|
28
28
|
var require_index_min = __commonJS({
|
|
29
|
-
"../../node_modules/@stellar/freighter-api/build/index.min.js"(exports
|
|
29
|
+
"../../node_modules/@stellar/freighter-api/build/index.min.js"(exports, module) {
|
|
30
30
|
!(function(e, r) {
|
|
31
|
-
"object" == typeof exports
|
|
32
|
-
})(exports
|
|
31
|
+
"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();
|
|
32
|
+
})(exports, (() => (() => {
|
|
33
33
|
var e, r, E = { d: (e2, r2) => {
|
|
34
34
|
for (var o2 in r2) E.o(r2, o2) && !E.o(e2, o2) && Object.defineProperty(e2, o2, { enumerable: true, get: r2[o2] });
|
|
35
35
|
}, o: (e2, r2) => Object.prototype.hasOwnProperty.call(e2, r2), r: (e2) => {
|
|
@@ -323,9 +323,7 @@ var WebCryptoKeyManager = class {
|
|
|
323
323
|
*/
|
|
324
324
|
this._initPromise = null;
|
|
325
325
|
if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
|
|
326
|
-
throw new Error(
|
|
327
|
-
"[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
|
|
328
|
-
);
|
|
326
|
+
throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
|
|
329
327
|
}
|
|
330
328
|
this.apiKey = apiKey;
|
|
331
329
|
}
|
|
@@ -440,7 +438,7 @@ function createClient(clientOptions) {
|
|
|
440
438
|
const {
|
|
441
439
|
baseUrl: localBaseUrl,
|
|
442
440
|
fetch: fetch2 = baseFetch,
|
|
443
|
-
Request = CustomRequest,
|
|
441
|
+
Request: Request2 = CustomRequest,
|
|
444
442
|
headers,
|
|
445
443
|
params = {},
|
|
446
444
|
parseAs = "json",
|
|
@@ -492,7 +490,7 @@ function createClient(clientOptions) {
|
|
|
492
490
|
};
|
|
493
491
|
let id;
|
|
494
492
|
let options;
|
|
495
|
-
let request = new
|
|
493
|
+
let request = new Request2(
|
|
496
494
|
createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
|
|
497
495
|
requestInit
|
|
498
496
|
);
|
|
@@ -522,7 +520,7 @@ function createClient(clientOptions) {
|
|
|
522
520
|
id
|
|
523
521
|
});
|
|
524
522
|
if (result) {
|
|
525
|
-
if (result instanceof
|
|
523
|
+
if (result instanceof Request2) {
|
|
526
524
|
request = result;
|
|
527
525
|
} else if (result instanceof Response) {
|
|
528
526
|
response = result;
|
|
@@ -894,24 +892,44 @@ function createApiClient(baseUrl) {
|
|
|
894
892
|
return createClient({ baseUrl });
|
|
895
893
|
}
|
|
896
894
|
|
|
895
|
+
// src/api/endpoints/distribution.ts
|
|
896
|
+
async function listDistributionRules(api) {
|
|
897
|
+
const { data, error } = await api.GET("/distribution/rules");
|
|
898
|
+
if (!data?.content || error) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
error?.code ?? error?.error ?? "Failed to list distribution rules"
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
return data.content.rules;
|
|
904
|
+
}
|
|
905
|
+
async function claimDistributionRule(api, body) {
|
|
906
|
+
const { data, error } = await api.POST("/distribution/claim", { body });
|
|
907
|
+
if (!data?.content || error) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
error?.code ?? error?.error ?? "Failed to claim distribution rule"
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
return data.content;
|
|
913
|
+
}
|
|
914
|
+
|
|
897
915
|
// src/api/endpoints/kyc.ts
|
|
898
916
|
async function getKycStatus(api, providerId) {
|
|
899
917
|
const { data, error } = await api.GET("/kyc/status", {
|
|
900
918
|
params: { query: providerId ? { providerId } : {} }
|
|
901
919
|
});
|
|
902
920
|
if (!data?.content || error) {
|
|
903
|
-
throw new Error(error?.error ?? "Failed to get KYC status");
|
|
921
|
+
throw new Error(error?.code ?? error?.error ?? "Failed to get KYC status");
|
|
904
922
|
}
|
|
905
923
|
return data.content;
|
|
906
924
|
}
|
|
907
925
|
async function getKycProviders(api, country) {
|
|
908
926
|
const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
|
|
909
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
|
|
927
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get KYC providers");
|
|
910
928
|
return data.content;
|
|
911
929
|
}
|
|
912
930
|
async function startKyc(api, body) {
|
|
913
931
|
const { data, error } = await api.POST("/kyc/start", { body });
|
|
914
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
|
|
932
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to start KYC");
|
|
915
933
|
return data.content;
|
|
916
934
|
}
|
|
917
935
|
async function resolveKyc(api, providerId, level = "basic") {
|
|
@@ -933,22 +951,22 @@ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e
|
|
|
933
951
|
// src/api/endpoints/ramps.ts
|
|
934
952
|
async function getRampsQuote(api, query) {
|
|
935
953
|
const { data, error } = await api.GET("/ramps/quote", { params: { query } });
|
|
936
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
|
|
954
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get ramp quotes");
|
|
937
955
|
return data.content;
|
|
938
956
|
}
|
|
939
957
|
async function createOnRamp(api, body) {
|
|
940
958
|
const { data, error } = await api.POST("/ramps/onramp", { body });
|
|
941
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
|
|
959
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create onramp");
|
|
942
960
|
return data.content;
|
|
943
961
|
}
|
|
944
962
|
async function createOffRamp(api, body) {
|
|
945
963
|
const { data, error } = await api.POST("/ramps/offramp", { body });
|
|
946
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
|
|
964
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to create offramp");
|
|
947
965
|
return data.content;
|
|
948
966
|
}
|
|
949
967
|
async function getRampTransaction(api, txId) {
|
|
950
968
|
const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
|
|
951
|
-
if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
|
|
969
|
+
if (!data?.content || error) throw new Error(error?.code ?? error?.error ?? "Failed to get transaction");
|
|
952
970
|
return data.content;
|
|
953
971
|
}
|
|
954
972
|
async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
|
|
@@ -1023,34 +1041,6 @@ function generateJti() {
|
|
|
1023
1041
|
);
|
|
1024
1042
|
}
|
|
1025
1043
|
|
|
1026
|
-
// src/stellar/StellarClient.ts
|
|
1027
|
-
var HORIZON_URLS = {
|
|
1028
|
-
mainnet: "https://horizon.stellar.org",
|
|
1029
|
-
testnet: "https://horizon-testnet.stellar.org"
|
|
1030
|
-
};
|
|
1031
|
-
var StellarClient = class {
|
|
1032
|
-
constructor(config) {
|
|
1033
|
-
this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
|
|
1034
|
-
}
|
|
1035
|
-
async submitTransaction(signedXdr) {
|
|
1036
|
-
try {
|
|
1037
|
-
const response = await fetch(`${this.horizonUrl}/transactions`, {
|
|
1038
|
-
method: "POST",
|
|
1039
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1040
|
-
body: new URLSearchParams({ tx: signedXdr })
|
|
1041
|
-
});
|
|
1042
|
-
if (!response.ok) {
|
|
1043
|
-
const body = await response.json().catch(() => ({}));
|
|
1044
|
-
return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
|
|
1045
|
-
}
|
|
1046
|
-
const data = await response.json();
|
|
1047
|
-
return { success: true, hash: data.hash };
|
|
1048
|
-
} catch {
|
|
1049
|
-
return { success: false, errorCode: "NETWORK_ERROR" };
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
1044
|
// src/storage/web.ts
|
|
1055
1045
|
var LOG_PREFIX = "[PollarClient:storage]";
|
|
1056
1046
|
function createMemoryAdapter() {
|
|
@@ -1138,6 +1128,57 @@ function defaultStorage(options = {}) {
|
|
|
1138
1128
|
return createLocalStorageAdapter(options);
|
|
1139
1129
|
}
|
|
1140
1130
|
|
|
1131
|
+
// src/visibility/noop.ts
|
|
1132
|
+
function createNoopVisibilityProvider() {
|
|
1133
|
+
return {
|
|
1134
|
+
isVisible: () => true,
|
|
1135
|
+
onChange: () => () => {
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/visibility/web.ts
|
|
1141
|
+
function createWebVisibilityProvider() {
|
|
1142
|
+
const isVisibleNow = () => typeof document === "undefined" || document.visibilityState === "visible";
|
|
1143
|
+
return {
|
|
1144
|
+
isVisible: isVisibleNow,
|
|
1145
|
+
onChange: (cb) => {
|
|
1146
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1147
|
+
return () => {
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
let last = isVisibleNow();
|
|
1151
|
+
const handler = () => {
|
|
1152
|
+
const next = isVisibleNow();
|
|
1153
|
+
if (next !== last) {
|
|
1154
|
+
last = next;
|
|
1155
|
+
cb(next);
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
document.addEventListener("visibilitychange", handler);
|
|
1159
|
+
window.addEventListener("pageshow", handler);
|
|
1160
|
+
window.addEventListener("pagehide", handler);
|
|
1161
|
+
window.addEventListener("focus", handler);
|
|
1162
|
+
window.addEventListener("blur", handler);
|
|
1163
|
+
return () => {
|
|
1164
|
+
document.removeEventListener("visibilitychange", handler);
|
|
1165
|
+
window.removeEventListener("pageshow", handler);
|
|
1166
|
+
window.removeEventListener("pagehide", handler);
|
|
1167
|
+
window.removeEventListener("focus", handler);
|
|
1168
|
+
window.removeEventListener("blur", handler);
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/visibility/autodetect.ts
|
|
1175
|
+
function defaultVisibilityProvider() {
|
|
1176
|
+
if (typeof document !== "undefined" && typeof window !== "undefined") {
|
|
1177
|
+
return createWebVisibilityProvider();
|
|
1178
|
+
}
|
|
1179
|
+
return createNoopVisibilityProvider();
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1141
1182
|
// src/types.ts
|
|
1142
1183
|
var AUTH_ERROR_CODES = {
|
|
1143
1184
|
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
@@ -1148,6 +1189,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1148
1189
|
AUTH_FAILED: "AUTH_FAILED",
|
|
1149
1190
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1150
1191
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1192
|
+
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1151
1193
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1152
1194
|
};
|
|
1153
1195
|
var PollarFlowError = class extends Error {
|
|
@@ -1538,7 +1580,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
|
1538
1580
|
setAuthState({ step: "authenticating" });
|
|
1539
1581
|
await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
|
|
1540
1582
|
const dpopJwk = await deps.getPublicJwk();
|
|
1541
|
-
const { data
|
|
1583
|
+
const { data } = await api.POST("/auth/login", {
|
|
1542
1584
|
body: {
|
|
1543
1585
|
clientSessionId,
|
|
1544
1586
|
dpopJwk,
|
|
@@ -1703,7 +1745,7 @@ async function loginWallet(type, deps) {
|
|
|
1703
1745
|
let connectedWallet;
|
|
1704
1746
|
try {
|
|
1705
1747
|
setAuthState({ step: "connecting_wallet", walletType: type });
|
|
1706
|
-
const adapter = await deps.resolveWalletAdapter(type);
|
|
1748
|
+
const adapter = await withSignal(deps.resolveWalletAdapter(type), signal);
|
|
1707
1749
|
const available = await withSignal(adapter.isAvailable(), signal);
|
|
1708
1750
|
if (!available) {
|
|
1709
1751
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
@@ -1740,7 +1782,7 @@ async function loginWallet(type, deps) {
|
|
|
1740
1782
|
|
|
1741
1783
|
// src/client/client.ts
|
|
1742
1784
|
var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
|
|
1743
|
-
var
|
|
1785
|
+
var REFRESH_SKEW_SECONDS = 60;
|
|
1744
1786
|
function warnServerSide(method) {
|
|
1745
1787
|
console.warn(
|
|
1746
1788
|
`[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
|
|
@@ -1758,9 +1800,21 @@ var PollarClient = class {
|
|
|
1758
1800
|
this._profile = null;
|
|
1759
1801
|
/** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
|
|
1760
1802
|
this._dpopNonce = null;
|
|
1803
|
+
/**
|
|
1804
|
+
* Snapshot of each in-flight request's body, taken in `onRequest` before
|
|
1805
|
+
* `fetch()` consumes the stream. Needed because `Request.clone()` throws
|
|
1806
|
+
* once the body is disturbed, so the auto-retry path (DPoP nonce challenge
|
|
1807
|
+
* / 401 refresh) must rebuild the request from scratch instead of cloning.
|
|
1808
|
+
*/
|
|
1809
|
+
this._requestBodyCache = /* @__PURE__ */ new WeakMap();
|
|
1761
1810
|
/** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
|
|
1762
1811
|
this._refreshPromise = null;
|
|
1763
1812
|
this._storageEventHandler = null;
|
|
1813
|
+
/** Updated by the request middleware. Read by the silent-refresh scheduler
|
|
1814
|
+
* to skip proactive refreshes after `maxIdleMs` of no HTTP activity. */
|
|
1815
|
+
this._lastRequestAt = Date.now();
|
|
1816
|
+
this._refreshTimer = null;
|
|
1817
|
+
this._visibilityUnsubscribe = null;
|
|
1764
1818
|
this._transactionState = null;
|
|
1765
1819
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1766
1820
|
this._txHistoryState = { step: "idle" };
|
|
@@ -1771,15 +1825,32 @@ var PollarClient = class {
|
|
|
1771
1825
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1772
1826
|
this._networkState = { step: "idle" };
|
|
1773
1827
|
this._networkStateListeners = /* @__PURE__ */ new Set();
|
|
1828
|
+
/**
|
|
1829
|
+
* Latched once the storage adapter degrades. We dedupe (the adapter only
|
|
1830
|
+
* fires once anyway) and use it to replay state to late-subscribers — same
|
|
1831
|
+
* pattern as `onAuthStateChange` replaying `_authState` on subscribe.
|
|
1832
|
+
* Only populated when the SDK constructed the default storage adapter; if
|
|
1833
|
+
* the consumer passes `config.storage`, they own degradation notifications.
|
|
1834
|
+
*/
|
|
1835
|
+
this._storageDegraded = null;
|
|
1836
|
+
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1774
1837
|
this._walletAdapter = null;
|
|
1775
1838
|
this._loginController = null;
|
|
1776
1839
|
this.apiKey = config.apiKey;
|
|
1777
1840
|
this.id = crypto.randomUUID();
|
|
1778
1841
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
1779
|
-
this._storage = config.storage ?? defaultStorage(
|
|
1842
|
+
this._storage = config.storage ?? defaultStorage({
|
|
1843
|
+
onDegrade: (reason, error) => {
|
|
1844
|
+
config.onStorageDegrade?.(reason, error);
|
|
1845
|
+
this._dispatchStorageDegrade(reason, error);
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1780
1848
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1781
1849
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1850
|
+
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
1782
1851
|
this._deviceLabel = config.deviceLabel;
|
|
1852
|
+
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1853
|
+
this._maxIdleMs = config.maxIdleMs;
|
|
1783
1854
|
this._api = createApiClient(this.basePath);
|
|
1784
1855
|
this._wireMiddlewares();
|
|
1785
1856
|
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
@@ -1825,6 +1896,9 @@ var PollarClient = class {
|
|
|
1825
1896
|
console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
|
|
1826
1897
|
}
|
|
1827
1898
|
await this._restoreSession();
|
|
1899
|
+
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
1900
|
+
if (visible) void this._maybeProactiveRefresh();
|
|
1901
|
+
});
|
|
1828
1902
|
}
|
|
1829
1903
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
1830
1904
|
destroy() {
|
|
@@ -1834,6 +1908,11 @@ var PollarClient = class {
|
|
|
1834
1908
|
}
|
|
1835
1909
|
this._loginController?.abort();
|
|
1836
1910
|
this._loginController = null;
|
|
1911
|
+
this._clearRefreshTimer();
|
|
1912
|
+
if (this._visibilityUnsubscribe) {
|
|
1913
|
+
this._visibilityUnsubscribe();
|
|
1914
|
+
this._visibilityUnsubscribe = null;
|
|
1915
|
+
}
|
|
1837
1916
|
}
|
|
1838
1917
|
// ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
|
|
1839
1918
|
_wireMiddlewares() {
|
|
@@ -1841,7 +1920,15 @@ var PollarClient = class {
|
|
|
1841
1920
|
this._api.use({
|
|
1842
1921
|
onRequest: async ({ request }) => {
|
|
1843
1922
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
1923
|
+
self._lastRequestAt = Date.now();
|
|
1844
1924
|
await self._initialized;
|
|
1925
|
+
if (request.body !== null) {
|
|
1926
|
+
try {
|
|
1927
|
+
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
1928
|
+
} catch (err) {
|
|
1929
|
+
console.warn("[PollarClient] Could not snapshot request body for retry", err);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1845
1932
|
const isRefresh = request.url.includes("/auth/refresh");
|
|
1846
1933
|
if (!isRefresh && self._refreshPromise) await self._refreshPromise;
|
|
1847
1934
|
if (isRefresh) {
|
|
@@ -1864,16 +1951,22 @@ var PollarClient = class {
|
|
|
1864
1951
|
const newNonce = response.headers.get("DPoP-Nonce");
|
|
1865
1952
|
if (newNonce) self._dpopNonce = newNonce;
|
|
1866
1953
|
if (response.status !== 401) return response;
|
|
1867
|
-
if (request.headers.get(RETRIED_HEADER)) return response;
|
|
1868
|
-
if (request.url.includes("/auth/refresh")) return response;
|
|
1869
1954
|
const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
|
|
1870
1955
|
const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
|
|
1956
|
+
if (request.url.includes("/auth/refresh")) {
|
|
1957
|
+
if (isNonceChallenge) return self._retryRequest(request);
|
|
1958
|
+
return response;
|
|
1959
|
+
}
|
|
1871
1960
|
if (!isNonceChallenge) {
|
|
1872
1961
|
try {
|
|
1873
1962
|
await self.refresh();
|
|
1874
1963
|
} catch {
|
|
1875
1964
|
return response;
|
|
1876
1965
|
}
|
|
1966
|
+
const method = request.method.toUpperCase();
|
|
1967
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1968
|
+
return response;
|
|
1969
|
+
}
|
|
1877
1970
|
}
|
|
1878
1971
|
return self._retryRequest(request);
|
|
1879
1972
|
}
|
|
@@ -1897,19 +1990,37 @@ var PollarClient = class {
|
|
|
1897
1990
|
}
|
|
1898
1991
|
}
|
|
1899
1992
|
async _retryRequest(originalRequest) {
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
if (proof)
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1993
|
+
const headers = new Headers(originalRequest.headers);
|
|
1994
|
+
const isRefresh = originalRequest.url.includes("/auth/refresh");
|
|
1995
|
+
if (isRefresh) {
|
|
1996
|
+
const proof = await this._buildProofForRequest(originalRequest, void 0);
|
|
1997
|
+
headers.delete("Authorization");
|
|
1998
|
+
if (proof) headers.set("DPoP", proof);
|
|
1999
|
+
else headers.delete("DPoP");
|
|
2000
|
+
} else {
|
|
2001
|
+
const accessToken = this._session?.token?.accessToken;
|
|
2002
|
+
if (accessToken) {
|
|
2003
|
+
const proof = await this._buildProofForRequest(originalRequest, accessToken);
|
|
2004
|
+
if (proof) {
|
|
2005
|
+
headers.set("Authorization", `DPoP ${accessToken}`);
|
|
2006
|
+
headers.set("DPoP", proof);
|
|
2007
|
+
} else {
|
|
2008
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
2009
|
+
}
|
|
1910
2010
|
}
|
|
1911
2011
|
}
|
|
1912
|
-
|
|
2012
|
+
const cachedBody = this._requestBodyCache.get(originalRequest);
|
|
2013
|
+
const retried = new Request(originalRequest.url, {
|
|
2014
|
+
method: originalRequest.method,
|
|
2015
|
+
headers,
|
|
2016
|
+
body: cachedBody ?? null,
|
|
2017
|
+
credentials: originalRequest.credentials,
|
|
2018
|
+
mode: originalRequest.mode,
|
|
2019
|
+
redirect: originalRequest.redirect,
|
|
2020
|
+
referrer: originalRequest.referrer,
|
|
2021
|
+
integrity: originalRequest.integrity
|
|
2022
|
+
});
|
|
2023
|
+
return fetch(retried);
|
|
1913
2024
|
}
|
|
1914
2025
|
// ─── Refresh (race-safe singleton) ───────────────────────────────────────
|
|
1915
2026
|
/**
|
|
@@ -1966,6 +2077,65 @@ var PollarClient = class {
|
|
|
1966
2077
|
} catch (err) {
|
|
1967
2078
|
console.error("[PollarClient] Failed to persist refreshed session", err);
|
|
1968
2079
|
}
|
|
2080
|
+
this._scheduleNextRefresh();
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
// ─── Silent refresh scheduler ────────────────────────────────────────────────
|
|
2084
|
+
/**
|
|
2085
|
+
* Arm a single setTimeout to fire shortly before the current access token
|
|
2086
|
+
* expires. Idempotent — clearing any previous timer first. Safe to call
|
|
2087
|
+
* from any session-write site (initial login, restore-from-storage, after
|
|
2088
|
+
* a successful rotation). No-op if there's no session in memory.
|
|
2089
|
+
*
|
|
2090
|
+
* Browser/RN background-tab throttling makes long-running setTimeouts
|
|
2091
|
+
* unreliable on their own; the `visibilitychange` listener compensates by
|
|
2092
|
+
* re-invoking `_maybeProactiveRefresh` whenever the app comes back to the
|
|
2093
|
+
* foreground, catching any timer that fired late or never fired at all.
|
|
2094
|
+
*/
|
|
2095
|
+
_scheduleNextRefresh() {
|
|
2096
|
+
this._clearRefreshTimer();
|
|
2097
|
+
const expiresAt = this._session?.token?.expiresAt;
|
|
2098
|
+
if (typeof expiresAt !== "number") return;
|
|
2099
|
+
const dueInMs = Math.max(0, (expiresAt - Math.floor(Date.now() / 1e3) - REFRESH_SKEW_SECONDS) * 1e3);
|
|
2100
|
+
this._refreshTimer = setTimeout(() => {
|
|
2101
|
+
void this._maybeProactiveRefresh();
|
|
2102
|
+
}, dueInMs);
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Decide whether to actually run a refresh right now. Called both from the
|
|
2106
|
+
* scheduler timer and from the visibility-change listener.
|
|
2107
|
+
*
|
|
2108
|
+
* Skip if:
|
|
2109
|
+
* - no session / no RT (nothing to refresh)
|
|
2110
|
+
* - app is hidden — wait for the visibility listener to re-trigger us
|
|
2111
|
+
* - `maxIdleMs` configured and no client request since that window — let
|
|
2112
|
+
* the next reactive 401-refresh handle it whenever the user comes back
|
|
2113
|
+
* - the AT still has more than `REFRESH_SKEW_SECONDS` of life — reschedule
|
|
2114
|
+
*
|
|
2115
|
+
* Otherwise call `refresh()`, which uses the existing in-flight singleton
|
|
2116
|
+
* so we never collide with a reactive 401-triggered refresh. On failure,
|
|
2117
|
+
* `_doRefresh` already calls `_clearSession`, so auth-state listeners see
|
|
2118
|
+
* `step:'idle'` — no extra event dispatch needed here.
|
|
2119
|
+
*/
|
|
2120
|
+
async _maybeProactiveRefresh() {
|
|
2121
|
+
if (!this._session?.token?.refreshToken) return;
|
|
2122
|
+
if (!this._visibilityProvider.isVisible()) return;
|
|
2123
|
+
if (this._maxIdleMs !== void 0 && Date.now() - this._lastRequestAt > this._maxIdleMs) return;
|
|
2124
|
+
const expiresAt = this._session.token.expiresAt;
|
|
2125
|
+
if (Math.floor(Date.now() / 1e3) < expiresAt - REFRESH_SKEW_SECONDS) {
|
|
2126
|
+
this._scheduleNextRefresh();
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
try {
|
|
2130
|
+
await this.refresh();
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
_clearRefreshTimer() {
|
|
2136
|
+
if (this._refreshTimer !== null) {
|
|
2137
|
+
clearTimeout(this._refreshTimer);
|
|
2138
|
+
this._refreshTimer = null;
|
|
1969
2139
|
}
|
|
1970
2140
|
}
|
|
1971
2141
|
// ─── Auth state ──────────────────────────────────────────────────────────────
|
|
@@ -1977,6 +2147,38 @@ var PollarClient = class {
|
|
|
1977
2147
|
cb(this._authState);
|
|
1978
2148
|
return () => this._authStateListeners.delete(cb);
|
|
1979
2149
|
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Subscribe to persistent-storage degradation (Safari private mode,
|
|
2152
|
+
* sandboxed iframes, quota errors, etc.). The SDK keeps running off
|
|
2153
|
+
* in-memory storage after degrade, but sessions won't survive reload — a
|
|
2154
|
+
* host UI typically wants to show "your session won't be saved" so the
|
|
2155
|
+
* user isn't blindsided after a refresh.
|
|
2156
|
+
*
|
|
2157
|
+
* Fires at most once per client lifetime (the underlying adapter dedupes).
|
|
2158
|
+
* Late subscribers receive the latched state synchronously on subscribe.
|
|
2159
|
+
*
|
|
2160
|
+
* Only fires when the SDK constructs the default storage adapter. If you
|
|
2161
|
+
* pass a custom `config.storage`, wire your own notification path through
|
|
2162
|
+
* that adapter's API — the SDK has no hook into it.
|
|
2163
|
+
*/
|
|
2164
|
+
onStorageDegrade(cb) {
|
|
2165
|
+
this._storageDegradeListeners.add(cb);
|
|
2166
|
+
if (this._storageDegraded) {
|
|
2167
|
+
cb(this._storageDegraded.reason, this._storageDegraded.error);
|
|
2168
|
+
}
|
|
2169
|
+
return () => this._storageDegradeListeners.delete(cb);
|
|
2170
|
+
}
|
|
2171
|
+
_dispatchStorageDegrade(reason, error) {
|
|
2172
|
+
if (this._storageDegraded) return;
|
|
2173
|
+
this._storageDegraded = { reason, error };
|
|
2174
|
+
for (const cb of this._storageDegradeListeners) {
|
|
2175
|
+
try {
|
|
2176
|
+
cb(reason, error);
|
|
2177
|
+
} catch (err) {
|
|
2178
|
+
console.error("[PollarClient] onStorageDegrade listener threw", err);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
1980
2182
|
/** PII (email, names, avatar, providers). Held in memory only — never persisted. */
|
|
1981
2183
|
getUserProfile() {
|
|
1982
2184
|
return this._profile;
|
|
@@ -2213,10 +2415,16 @@ var PollarClient = class {
|
|
|
2213
2415
|
}
|
|
2214
2416
|
}
|
|
2215
2417
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2418
|
+
/**
|
|
2419
|
+
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
2420
|
+
* AND returns a {@link BuildOutcome} so headless callers can `await` and
|
|
2421
|
+
* inspect the result without subscribing to state changes.
|
|
2422
|
+
*/
|
|
2216
2423
|
async buildTx(operation, params, options) {
|
|
2217
2424
|
if (!this._session?.wallet?.publicKey) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2425
|
+
const details = "No wallet connected";
|
|
2426
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2427
|
+
return { status: "error", details };
|
|
2220
2428
|
}
|
|
2221
2429
|
const body = {
|
|
2222
2430
|
network: this.getNetwork(),
|
|
@@ -2230,39 +2438,194 @@ var PollarClient = class {
|
|
|
2230
2438
|
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
2231
2439
|
if (!error && data?.success && data.content) {
|
|
2232
2440
|
this._setTransactionState({ step: "built", buildData: data.content });
|
|
2233
|
-
|
|
2234
|
-
const details = error?.details;
|
|
2235
|
-
this._setTransactionState({ step: "error", ...details && { details } });
|
|
2441
|
+
return { status: "built", buildData: data.content };
|
|
2236
2442
|
}
|
|
2237
|
-
|
|
2238
|
-
this._setTransactionState({ step: "error" });
|
|
2443
|
+
const details = error?.details;
|
|
2444
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
2445
|
+
return { status: "error", ...details && { details } };
|
|
2446
|
+
} catch (err) {
|
|
2447
|
+
console.error("[PollarClient] buildTx failed", err);
|
|
2448
|
+
this._setTransactionState({ step: "error", phase: "building" });
|
|
2449
|
+
return { status: "error" };
|
|
2239
2450
|
}
|
|
2240
2451
|
}
|
|
2241
2452
|
getWalletType() {
|
|
2242
2453
|
return this._walletAdapter?.type ?? null;
|
|
2243
2454
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2455
|
+
/**
|
|
2456
|
+
* Signs the given unsigned XDR and returns the signed XDR.
|
|
2457
|
+
*
|
|
2458
|
+
* - External wallets: signs locally via the wallet adapter.
|
|
2459
|
+
* - Custodial wallets: posts to `/tx/sign`. The backend signs (through
|
|
2460
|
+
* wallet-service or the app's customer-managed adapter) and returns the
|
|
2461
|
+
* signed XDR plus an `idempotencyKey` the caller should echo back to
|
|
2462
|
+
* `submitTx`.
|
|
2463
|
+
*
|
|
2464
|
+
* Drives `_setTransactionState`: emits `signing` while in flight and
|
|
2465
|
+
* `signed` on success (or `error[phase: 'signing']` on failure). `buildData`
|
|
2466
|
+
* is threaded through if the consumer previously called `buildTx`.
|
|
2467
|
+
*/
|
|
2468
|
+
async signTx(unsignedXdr) {
|
|
2469
|
+
const buildData = this._currentBuildData();
|
|
2470
|
+
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2250
2471
|
if (this._walletAdapter) {
|
|
2472
|
+
const accountToSign = this._session?.wallet?.publicKey;
|
|
2473
|
+
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2251
2474
|
try {
|
|
2252
|
-
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2253
2475
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2476
|
+
this._setTransactionState({
|
|
2477
|
+
step: "signed",
|
|
2478
|
+
signedXdr: signedTxXdr,
|
|
2479
|
+
...buildData && { buildData }
|
|
2480
|
+
});
|
|
2481
|
+
return { status: "signed", signedXdr: signedTxXdr };
|
|
2482
|
+
} catch (err) {
|
|
2483
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2484
|
+
this._setTransactionState({
|
|
2485
|
+
step: "error",
|
|
2486
|
+
phase: "signing",
|
|
2487
|
+
...buildData && { buildData },
|
|
2488
|
+
...details && { details }
|
|
2489
|
+
});
|
|
2490
|
+
return { status: "error", ...details && { details } };
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
const publicKey = this._session?.wallet?.publicKey ?? "";
|
|
2494
|
+
try {
|
|
2495
|
+
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2496
|
+
body: { network: this.getNetwork(), publicKey, unsignedXdr }
|
|
2497
|
+
});
|
|
2498
|
+
if (!error && data?.success && data.content?.signedXdr) {
|
|
2499
|
+
const { signedXdr, idempotencyKey } = data.content;
|
|
2500
|
+
this._setTransactionState({
|
|
2501
|
+
step: "signed",
|
|
2502
|
+
signedXdr,
|
|
2503
|
+
submissionToken: idempotencyKey,
|
|
2504
|
+
...buildData && { buildData }
|
|
2505
|
+
});
|
|
2506
|
+
return { status: "signed", signedXdr, submissionToken: idempotencyKey };
|
|
2507
|
+
}
|
|
2508
|
+
const details = error?.details;
|
|
2509
|
+
this._setTransactionState({
|
|
2510
|
+
step: "error",
|
|
2511
|
+
phase: "signing",
|
|
2512
|
+
...buildData && { buildData },
|
|
2513
|
+
...details && { details }
|
|
2514
|
+
});
|
|
2515
|
+
return { status: "error", ...details && { details } };
|
|
2516
|
+
} catch (err) {
|
|
2517
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2518
|
+
this._setTransactionState({
|
|
2519
|
+
step: "error",
|
|
2520
|
+
phase: "signing",
|
|
2521
|
+
...buildData && { buildData },
|
|
2522
|
+
...details && { details }
|
|
2523
|
+
});
|
|
2524
|
+
return { status: "error", ...details && { details } };
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Submits a signed XDR via `/tx/submit` regardless of wallet type
|
|
2529
|
+
* (custodial or external). Routing through sdk-api gives us:
|
|
2530
|
+
* - End-to-end tx_records persistence with full phase lifecycle so the
|
|
2531
|
+
* developer dashboard can show every tx (both custodial and external
|
|
2532
|
+
* wallet flows) at `/apps/:id/monitor/transactions`.
|
|
2533
|
+
* - Idempotency tracking via `submissionToken` (returned by `signTx`).
|
|
2534
|
+
* - A single response shape (SUCCESS / PENDING / FAILED) shared by both
|
|
2535
|
+
* flows — previously external wallets could only return SUCCESS or
|
|
2536
|
+
* error since the direct-to-Horizon path was synchronous.
|
|
2537
|
+
*
|
|
2538
|
+
* The extra hop adds ~50–150 ms vs. the legacy direct-Horizon path; the
|
|
2539
|
+
* persistence + observability win is worth it.
|
|
2540
|
+
*
|
|
2541
|
+
* Drives `_setTransactionState`: emits `submitting` while in flight,
|
|
2542
|
+
* `submitted` on Horizon ack (pending), `success` on ledger confirmation,
|
|
2543
|
+
* or `error[phase: 'submitting']` on failure.
|
|
2544
|
+
*/
|
|
2545
|
+
async submitTx(signedXdr, opts) {
|
|
2546
|
+
const buildData = this._currentBuildData();
|
|
2547
|
+
const outcomeExtra = buildData ? { buildData } : {};
|
|
2548
|
+
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2549
|
+
const publicKey = this._session?.wallet?.publicKey ?? "";
|
|
2550
|
+
try {
|
|
2551
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2552
|
+
body: {
|
|
2553
|
+
network: this.getNetwork(),
|
|
2554
|
+
publicKey,
|
|
2555
|
+
signedXdr,
|
|
2556
|
+
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2260
2557
|
}
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2558
|
+
});
|
|
2559
|
+
if (!error && data?.success && data.content) {
|
|
2560
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
2561
|
+
if (backendStatus === "SUCCESS") {
|
|
2562
|
+
this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
|
|
2563
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
2564
|
+
}
|
|
2565
|
+
if (backendStatus === "PENDING") {
|
|
2566
|
+
this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
|
|
2567
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
2568
|
+
}
|
|
2569
|
+
this._setTransactionState({
|
|
2570
|
+
step: "error",
|
|
2571
|
+
phase: "submitting",
|
|
2572
|
+
...buildData && { buildData },
|
|
2573
|
+
...resultCode && { details: resultCode }
|
|
2574
|
+
});
|
|
2575
|
+
return {
|
|
2576
|
+
status: "error",
|
|
2577
|
+
hash,
|
|
2578
|
+
...outcomeExtra,
|
|
2579
|
+
...resultCode && { details: resultCode, resultCode }
|
|
2580
|
+
};
|
|
2263
2581
|
}
|
|
2264
|
-
|
|
2582
|
+
const details = error?.details;
|
|
2583
|
+
this._setTransactionState({
|
|
2584
|
+
step: "error",
|
|
2585
|
+
phase: "submitting",
|
|
2586
|
+
...buildData && { buildData },
|
|
2587
|
+
...details && { details }
|
|
2588
|
+
});
|
|
2589
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2590
|
+
} catch (err) {
|
|
2591
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2592
|
+
this._setTransactionState({
|
|
2593
|
+
step: "error",
|
|
2594
|
+
phase: "submitting",
|
|
2595
|
+
...buildData && { buildData },
|
|
2596
|
+
...details && { details }
|
|
2597
|
+
});
|
|
2598
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2265
2599
|
}
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Signs and submits in one logical step. Returns a {@link SubmitOutcome}.
|
|
2603
|
+
*
|
|
2604
|
+
* - **External wallets**: composes `signTx` + `submitTx` client-side. State
|
|
2605
|
+
* machine sees the full granular sequence `signing → signed → submitting
|
|
2606
|
+
* → success` because the underlying methods each emit.
|
|
2607
|
+
* - **Custodial wallets**: atomic `/tx/sign-and-send` round-trip. State
|
|
2608
|
+
* machine emits the compound `signing-submitting` step (the SDK can't
|
|
2609
|
+
* observe when one phase ends and the next begins inside that single
|
|
2610
|
+
* backend call) and then transitions to `submitted` (Horizon ack only) or
|
|
2611
|
+
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2612
|
+
*/
|
|
2613
|
+
async signAndSubmitTx(unsignedXdr) {
|
|
2614
|
+
if (this._walletAdapter) {
|
|
2615
|
+
const signed = await this.signTx(unsignedXdr);
|
|
2616
|
+
if (signed.status === "error") {
|
|
2617
|
+
const buildData2 = this._currentBuildData();
|
|
2618
|
+
return {
|
|
2619
|
+
status: "error",
|
|
2620
|
+
...buildData2 && { buildData: buildData2 },
|
|
2621
|
+
...signed.details && { details: signed.details }
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
return this.submitTx(signed.signedXdr);
|
|
2625
|
+
}
|
|
2626
|
+
const buildData = this._currentBuildData();
|
|
2627
|
+
const outcomeExtra = buildData ? { buildData } : {};
|
|
2628
|
+
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2266
2629
|
const body = {
|
|
2267
2630
|
network: this.getNetwork(),
|
|
2268
2631
|
publicKey: this._session?.wallet?.publicKey ?? "",
|
|
@@ -2271,14 +2634,128 @@ var PollarClient = class {
|
|
|
2271
2634
|
try {
|
|
2272
2635
|
const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
|
|
2273
2636
|
if (!error && data?.success && data.content?.hash) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2637
|
+
const {
|
|
2638
|
+
hash,
|
|
2639
|
+
status: backendStatus,
|
|
2640
|
+
resultCode
|
|
2641
|
+
} = data.content;
|
|
2642
|
+
if (backendStatus === "SUCCESS") {
|
|
2643
|
+
this._setTransactionState({ step: "success", hash, ...buildData && { buildData } });
|
|
2644
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
2645
|
+
}
|
|
2646
|
+
if (backendStatus === "PENDING") {
|
|
2647
|
+
this._setTransactionState({ step: "submitted", hash, ...buildData && { buildData } });
|
|
2648
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
2649
|
+
}
|
|
2650
|
+
this._setTransactionState({
|
|
2651
|
+
step: "error",
|
|
2652
|
+
phase: "signing-submitting",
|
|
2653
|
+
...buildData && { buildData },
|
|
2654
|
+
...resultCode && { details: resultCode }
|
|
2655
|
+
});
|
|
2656
|
+
return {
|
|
2657
|
+
status: "error",
|
|
2658
|
+
hash,
|
|
2659
|
+
...outcomeExtra,
|
|
2660
|
+
...resultCode && { details: resultCode, resultCode }
|
|
2661
|
+
};
|
|
2278
2662
|
}
|
|
2279
|
-
|
|
2280
|
-
this._setTransactionState({
|
|
2663
|
+
const details = error?.details;
|
|
2664
|
+
this._setTransactionState({
|
|
2665
|
+
step: "error",
|
|
2666
|
+
phase: "signing-submitting",
|
|
2667
|
+
...buildData && { buildData },
|
|
2668
|
+
...details && { details }
|
|
2669
|
+
});
|
|
2670
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2671
|
+
} catch (err) {
|
|
2672
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2673
|
+
this._setTransactionState({
|
|
2674
|
+
step: "error",
|
|
2675
|
+
phase: "signing-submitting",
|
|
2676
|
+
...buildData && { buildData },
|
|
2677
|
+
...details && { details }
|
|
2678
|
+
});
|
|
2679
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* One-shot: build → sign → submit, returning the final {@link SubmitOutcome}.
|
|
2684
|
+
*
|
|
2685
|
+
* - **External wallets**: composes `buildTx` + `signAndSubmitTx` client-side.
|
|
2686
|
+
* State machine sees the full granular sequence (`building → built →
|
|
2687
|
+
* signing → signed → submitting → success`) because each composed call
|
|
2688
|
+
* emits its own transitions.
|
|
2689
|
+
* - **Custodial wallets**: single round-trip to `/tx/build-sign-submit`. The
|
|
2690
|
+
* signed XDR never leaves the backend. State machine emits the compound
|
|
2691
|
+
* `building-signing-submitting` step (the SDK can't observe individual
|
|
2692
|
+
* phase boundaries inside one atomic call) and then transitions to
|
|
2693
|
+
* `submitted` / `success` / `error[phase: 'building-signing-submitting']`.
|
|
2694
|
+
*
|
|
2695
|
+
* If you need granular UI feedback for custodial flows (separate
|
|
2696
|
+
* "Building…", "Signing…", "Submitting…" indicators), call `buildTx`,
|
|
2697
|
+
* `signTx`, and `submitTx` separately instead.
|
|
2698
|
+
*/
|
|
2699
|
+
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
2700
|
+
if (this._walletAdapter) {
|
|
2701
|
+
const built = await this.buildTx(operation, params, options);
|
|
2702
|
+
if (built.status === "error") {
|
|
2703
|
+
return { status: "error", ...built.details && { details: built.details } };
|
|
2704
|
+
}
|
|
2705
|
+
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2281
2706
|
}
|
|
2707
|
+
if (!this._session?.wallet?.publicKey) {
|
|
2708
|
+
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2709
|
+
return { status: "error", details: "No wallet connected" };
|
|
2710
|
+
}
|
|
2711
|
+
this._setTransactionState({ step: "building-signing-submitting" });
|
|
2712
|
+
try {
|
|
2713
|
+
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2714
|
+
body: {
|
|
2715
|
+
network: this.getNetwork(),
|
|
2716
|
+
publicKey: this._session.wallet.publicKey,
|
|
2717
|
+
operation,
|
|
2718
|
+
params,
|
|
2719
|
+
options: options ?? {}
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
if (!error && data?.success && data.content) {
|
|
2723
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
2724
|
+
if (backendStatus === "SUCCESS") {
|
|
2725
|
+
this._setTransactionState({ step: "success", hash });
|
|
2726
|
+
return { status: "success", hash };
|
|
2727
|
+
}
|
|
2728
|
+
if (backendStatus === "PENDING") {
|
|
2729
|
+
this._setTransactionState({ step: "submitted", hash });
|
|
2730
|
+
return { status: "pending", hash };
|
|
2731
|
+
}
|
|
2732
|
+
this._setTransactionState({
|
|
2733
|
+
step: "error",
|
|
2734
|
+
phase: "building-signing-submitting",
|
|
2735
|
+
...resultCode && { details: resultCode }
|
|
2736
|
+
});
|
|
2737
|
+
return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
|
|
2738
|
+
}
|
|
2739
|
+
const details = error?.details;
|
|
2740
|
+
this._setTransactionState({
|
|
2741
|
+
step: "error",
|
|
2742
|
+
phase: "building-signing-submitting",
|
|
2743
|
+
...details && { details }
|
|
2744
|
+
});
|
|
2745
|
+
return { status: "error", ...details && { details } };
|
|
2746
|
+
} catch (err) {
|
|
2747
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
2748
|
+
this._setTransactionState({
|
|
2749
|
+
step: "error",
|
|
2750
|
+
phase: "building-signing-submitting",
|
|
2751
|
+
...details && { details }
|
|
2752
|
+
});
|
|
2753
|
+
return { status: "error", ...details && { details } };
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
/** Alias for {@link buildAndSignAndSubmitTx} — shorter "just do the thing" name. */
|
|
2757
|
+
async runTx(operation, params, options) {
|
|
2758
|
+
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2282
2759
|
}
|
|
2283
2760
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2284
2761
|
async getAppConfig() {
|
|
@@ -2322,6 +2799,13 @@ var PollarClient = class {
|
|
|
2322
2799
|
pollRampTransaction(txId, opts) {
|
|
2323
2800
|
return pollRampTransaction(this._api, txId, opts);
|
|
2324
2801
|
}
|
|
2802
|
+
// ─── Distribution ─────────────────────────────────────────────────────────
|
|
2803
|
+
listDistributionRules() {
|
|
2804
|
+
return listDistributionRules(this._api);
|
|
2805
|
+
}
|
|
2806
|
+
claimDistributionRule(body) {
|
|
2807
|
+
return claimDistributionRule(this._api, body);
|
|
2808
|
+
}
|
|
2325
2809
|
_setTxHistoryState(next) {
|
|
2326
2810
|
this._txHistoryState = next;
|
|
2327
2811
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
@@ -2360,7 +2844,22 @@ var PollarClient = class {
|
|
|
2360
2844
|
*/
|
|
2361
2845
|
async _resolveWalletAdapter(id) {
|
|
2362
2846
|
if (this._walletAdapterResolver) {
|
|
2363
|
-
|
|
2847
|
+
const timeoutMs = this._walletResolverTimeoutMs;
|
|
2848
|
+
let timeoutHandle;
|
|
2849
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2850
|
+
timeoutHandle = setTimeout(() => {
|
|
2851
|
+
reject(
|
|
2852
|
+
Object.assign(new Error(`[PollarClient] Wallet adapter resolver for "${id}" timed out after ${timeoutMs}ms`), {
|
|
2853
|
+
code: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
|
|
2854
|
+
})
|
|
2855
|
+
);
|
|
2856
|
+
}, timeoutMs);
|
|
2857
|
+
});
|
|
2858
|
+
try {
|
|
2859
|
+
return await Promise.race([Promise.resolve(this._walletAdapterResolver(id)), timeoutPromise]);
|
|
2860
|
+
} finally {
|
|
2861
|
+
if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
|
|
2862
|
+
}
|
|
2364
2863
|
}
|
|
2365
2864
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2366
2865
|
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
@@ -2374,6 +2873,16 @@ var PollarClient = class {
|
|
|
2374
2873
|
this._setAuthState({ step: "idle" });
|
|
2375
2874
|
return;
|
|
2376
2875
|
}
|
|
2876
|
+
if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
|
|
2877
|
+
console.error("[PollarClient]", error.message);
|
|
2878
|
+
this._setAuthState({
|
|
2879
|
+
step: "error",
|
|
2880
|
+
previousStep: this._authState.step,
|
|
2881
|
+
message: error.message,
|
|
2882
|
+
errorCode: AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT
|
|
2883
|
+
});
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2377
2886
|
console.error("[PollarClient] Unexpected error in auth flow", error);
|
|
2378
2887
|
this._setAuthState({
|
|
2379
2888
|
step: "error",
|
|
@@ -2395,6 +2904,7 @@ var PollarClient = class {
|
|
|
2395
2904
|
}
|
|
2396
2905
|
console.info("[PollarClient] Session restored from storage");
|
|
2397
2906
|
this._setAuthState({ step: "authenticated", session: this._session });
|
|
2907
|
+
this._scheduleNextRefresh();
|
|
2398
2908
|
} else {
|
|
2399
2909
|
console.info("[PollarClient] No session in storage");
|
|
2400
2910
|
}
|
|
@@ -2421,9 +2931,11 @@ var PollarClient = class {
|
|
|
2421
2931
|
}
|
|
2422
2932
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
2423
2933
|
this._setAuthState({ step: "authenticated", session: persisted });
|
|
2934
|
+
this._scheduleNextRefresh();
|
|
2424
2935
|
}
|
|
2425
2936
|
async _clearSession() {
|
|
2426
2937
|
console.info("[PollarClient] Session cleared");
|
|
2938
|
+
this._clearRefreshTimer();
|
|
2427
2939
|
this._session = null;
|
|
2428
2940
|
this._profile = null;
|
|
2429
2941
|
this._walletAdapter = null;
|
|
@@ -2456,11 +2968,51 @@ var PollarClient = class {
|
|
|
2456
2968
|
console.info(`[PollarClient] transaction:${next.step}`);
|
|
2457
2969
|
for (const cb of this._transactionStateListeners) cb(next);
|
|
2458
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Threads `buildData` through state transitions. When the user has already
|
|
2973
|
+
* called `buildTx`, every subsequent state (signing, signed, submitting,
|
|
2974
|
+
* submitted, success, error) should carry the build summary so modal UIs
|
|
2975
|
+
* can keep showing "Send 5 USDC to G..." through the whole flow.
|
|
2976
|
+
*/
|
|
2977
|
+
_currentBuildData() {
|
|
2978
|
+
const s = this._transactionState;
|
|
2979
|
+
if (!s) return void 0;
|
|
2980
|
+
if ("buildData" in s && s.buildData) return s.buildData;
|
|
2981
|
+
return void 0;
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
// src/stellar/StellarClient.ts
|
|
2986
|
+
var HORIZON_URLS = {
|
|
2987
|
+
mainnet: "https://horizon.stellar.org",
|
|
2988
|
+
testnet: "https://horizon-testnet.stellar.org"
|
|
2989
|
+
};
|
|
2990
|
+
var StellarClient = class {
|
|
2991
|
+
constructor(config) {
|
|
2992
|
+
this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
|
|
2993
|
+
}
|
|
2994
|
+
async submitTransaction(signedXdr) {
|
|
2995
|
+
try {
|
|
2996
|
+
const response = await fetch(`${this.horizonUrl}/transactions`, {
|
|
2997
|
+
method: "POST",
|
|
2998
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2999
|
+
body: new URLSearchParams({ tx: signedXdr })
|
|
3000
|
+
});
|
|
3001
|
+
if (!response.ok) {
|
|
3002
|
+
const body = await response.json().catch(() => ({}));
|
|
3003
|
+
return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
|
|
3004
|
+
}
|
|
3005
|
+
const data = await response.json();
|
|
3006
|
+
return { success: true, hash: data.hash };
|
|
3007
|
+
} catch {
|
|
3008
|
+
return { success: false, errorCode: "NETWORK_ERROR" };
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
2459
3011
|
};
|
|
2460
3012
|
|
|
2461
3013
|
// src/index.ts
|
|
2462
3014
|
_setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
|
|
2463
3015
|
|
|
2464
|
-
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
|
|
3016
|
+
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, 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 };
|
|
2465
3017
|
//# sourceMappingURL=index.mjs.map
|
|
2466
3018
|
//# sourceMappingURL=index.mjs.map
|