@pollar/core 0.8.0 → 0.8.2

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.
@@ -1,7 +1,8 @@
1
1
  import { KeyManager, PublicEcJwk } from './index.mjs';
2
- export { AUTH_ERROR_CODES, AdapterFn, AlbedoAdapter, AuthErrorCode, AuthState, BuildOutcome, BuildProofArgs, ConnectWalletResponse, DistributionClaimBody, DistributionClaimContent, DistributionRule, DistributionRulesState, FreighterAdapter, KycFlow, KycLevel, KycProvider, KycStartBody, KycStartResponse, KycStatus, LocalStorageAdapterOptions, NetworkState, PaymentInstructions, PollarAdapter, PollarAdapters, PollarApiClient, PollarApplicationConfigContent, PollarApplicationConfigResponse, PollarClient, PollarClientConfig, PollarFlowError, PollarLoginOptions, PollarPersistedSession, PollarUserProfile, RampDirection, RampQuote, RampTxStatus, RampsOfframpBody, RampsOfframpResponse, RampsOnrampBody, RampsOnrampResponse, RampsQuoteQuery, RampsQuoteResponse, RampsTransactionResponse, RulePeriod, SessionInfo, SignAuthEntryOptions, SignAuthEntryResponse, SignOutcome, SignTransactionOptions, SignTransactionResponse, StellarBalance, StellarClient, StellarClientConfig, StellarNetwork, SubmitOutcome, TransactionState, TxBuildBody, TxBuildContent, TxBuildResponse, TxBuildSignSubmitBody, TxBuildSignSubmitContent, TxBuildSignSubmitResponse, TxErrorPhase, TxHistoryContent, TxHistoryParams, TxHistoryRecord, TxHistoryState, TxSignAndSendBody, TxSignBody, TxSignContent, TxSignResponse, TxSignSendResponse, TxSubmitSignedBody, WalletAdapter, WalletAdapterResolver, WalletBalanceContent, WalletBalanceRecord, WalletBalanceState, WalletId, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, pollarPaths, resolveKyc, startKyc } from './index.mjs';
2
+ export { AUTH_ERROR_CODES, AdapterFn, AlbedoAdapter, AuthErrorCode, AuthOpenContext, AuthState, AuthUrlOpener, BuildOutcome, BuildProofArgs, ConnectWalletResponse, DistributionClaimBody, DistributionClaimContent, DistributionRule, DistributionRulesState, FreighterAdapter, KycFlow, KycLevel, KycProvider, KycStartBody, KycStartResponse, KycStatus, LocalStorageAdapterOptions, NetworkState, PaymentInstructions, PollarAdapter, PollarAdapters, PollarApiClient, PollarApplicationConfigContent, PollarApplicationConfigResponse, PollarClient, PollarClientConfig, PollarFlowError, PollarLoginOptions, PollarPersistedSession, PollarUserProfile, RampDirection, RampQuote, RampTxStatus, RampsOfframpBody, RampsOfframpResponse, RampsOnrampBody, RampsOnrampResponse, RampsQuoteQuery, RampsQuoteResponse, RampsTransactionResponse, RulePeriod, SessionInfo, SignAuthEntryOptions, SignAuthEntryResponse, SignOutcome, SignTransactionOptions, SignTransactionResponse, StellarBalance, StellarClient, StellarClientConfig, StellarNetwork, SubmitOutcome, TransactionState, TxBuildBody, TxBuildContent, TxBuildResponse, TxBuildSignSubmitBody, TxBuildSignSubmitContent, TxBuildSignSubmitResponse, TxErrorPhase, TxHistoryContent, TxHistoryParams, TxHistoryRecord, TxHistoryState, TxSignAndSendBody, TxSignBody, TxSignContent, TxSignResponse, TxSignSendResponse, TxSubmitSignedBody, WalletAdapter, WalletAdapterResolver, WalletBalanceContent, WalletBalanceRecord, WalletBalanceState, WalletId, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, pollarPaths, resolveKyc, startKyc } from './index.mjs';
3
3
  import { S as Storage } from './types-DqgJIJBl.mjs';
4
4
  export { O as OnStorageDegrade, a as StorageDegradeReason } from './types-DqgJIJBl.mjs';
5
+ import './types-84G_htcn.mjs';
5
6
  import 'openapi-fetch';
6
7
 
7
8
  declare class NobleKeyManager implements KeyManager {
@@ -1,7 +1,8 @@
1
1
  import { KeyManager, PublicEcJwk } from './index.js';
2
- export { AUTH_ERROR_CODES, AdapterFn, AlbedoAdapter, AuthErrorCode, AuthState, BuildOutcome, BuildProofArgs, ConnectWalletResponse, DistributionClaimBody, DistributionClaimContent, DistributionRule, DistributionRulesState, FreighterAdapter, KycFlow, KycLevel, KycProvider, KycStartBody, KycStartResponse, KycStatus, LocalStorageAdapterOptions, NetworkState, PaymentInstructions, PollarAdapter, PollarAdapters, PollarApiClient, PollarApplicationConfigContent, PollarApplicationConfigResponse, PollarClient, PollarClientConfig, PollarFlowError, PollarLoginOptions, PollarPersistedSession, PollarUserProfile, RampDirection, RampQuote, RampTxStatus, RampsOfframpBody, RampsOfframpResponse, RampsOnrampBody, RampsOnrampResponse, RampsQuoteQuery, RampsQuoteResponse, RampsTransactionResponse, RulePeriod, SessionInfo, SignAuthEntryOptions, SignAuthEntryResponse, SignOutcome, SignTransactionOptions, SignTransactionResponse, StellarBalance, StellarClient, StellarClientConfig, StellarNetwork, SubmitOutcome, TransactionState, TxBuildBody, TxBuildContent, TxBuildResponse, TxBuildSignSubmitBody, TxBuildSignSubmitContent, TxBuildSignSubmitResponse, TxErrorPhase, TxHistoryContent, TxHistoryParams, TxHistoryRecord, TxHistoryState, TxSignAndSendBody, TxSignBody, TxSignContent, TxSignResponse, TxSignSendResponse, TxSubmitSignedBody, WalletAdapter, WalletAdapterResolver, WalletBalanceContent, WalletBalanceRecord, WalletBalanceState, WalletId, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, pollarPaths, resolveKyc, startKyc } from './index.js';
2
+ export { AUTH_ERROR_CODES, AdapterFn, AlbedoAdapter, AuthErrorCode, AuthOpenContext, AuthState, AuthUrlOpener, BuildOutcome, BuildProofArgs, ConnectWalletResponse, DistributionClaimBody, DistributionClaimContent, DistributionRule, DistributionRulesState, FreighterAdapter, KycFlow, KycLevel, KycProvider, KycStartBody, KycStartResponse, KycStatus, LocalStorageAdapterOptions, NetworkState, PaymentInstructions, PollarAdapter, PollarAdapters, PollarApiClient, PollarApplicationConfigContent, PollarApplicationConfigResponse, PollarClient, PollarClientConfig, PollarFlowError, PollarLoginOptions, PollarPersistedSession, PollarUserProfile, RampDirection, RampQuote, RampTxStatus, RampsOfframpBody, RampsOfframpResponse, RampsOnrampBody, RampsOnrampResponse, RampsQuoteQuery, RampsQuoteResponse, RampsTransactionResponse, RulePeriod, SessionInfo, SignAuthEntryOptions, SignAuthEntryResponse, SignOutcome, SignTransactionOptions, SignTransactionResponse, StellarBalance, StellarClient, StellarClientConfig, StellarNetwork, SubmitOutcome, TransactionState, TxBuildBody, TxBuildContent, TxBuildResponse, TxBuildSignSubmitBody, TxBuildSignSubmitContent, TxBuildSignSubmitResponse, TxErrorPhase, TxHistoryContent, TxHistoryParams, TxHistoryRecord, TxHistoryState, TxSignAndSendBody, TxSignBody, TxSignContent, TxSignResponse, TxSignSendResponse, TxSubmitSignedBody, WalletAdapter, WalletAdapterResolver, WalletBalanceContent, WalletBalanceRecord, WalletBalanceState, WalletId, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, pollarPaths, resolveKyc, startKyc } from './index.js';
3
3
  import { S as Storage } from './types-DqgJIJBl.js';
4
4
  export { O as OnStorageDegrade, a as StorageDegradeReason } from './types-DqgJIJBl.js';
5
+ import './types-84G_htcn.js';
5
6
  import 'openapi-fetch';
6
7
 
7
8
  declare class NobleKeyManager implements KeyManager {
package/dist/index.rn.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var p256 = require('@noble/curves/p256');
3
+ var nist = require('@noble/curves/nist');
4
+ var sha2 = require('@noble/hashes/sha2');
4
5
 
5
6
  var __create = Object.create;
6
7
  var __defProp = Object.defineProperty;
@@ -186,11 +187,8 @@ function defaultKeyManager(storage, apiKey) {
186
187
  }
187
188
  return _factory(storage, apiKey);
188
189
  }
189
-
190
- // src/lib/sha256.ts
191
190
  async function sha256(data) {
192
- const buf = await crypto.subtle.digest("SHA-256", data);
193
- return new Uint8Array(buf);
191
+ return sha2.sha256(data);
194
192
  }
195
193
 
196
194
  // src/lib/api-key-hash.ts
@@ -325,14 +323,14 @@ var NobleKeyManager = class {
325
323
  priv = null;
326
324
  }
327
325
  if (!priv) {
328
- priv = p256.p256.utils.randomPrivateKey();
326
+ priv = nist.p256.utils.randomPrivateKey();
329
327
  try {
330
328
  await this.storage.set(this.storageKey, base64urlEncode(priv));
331
329
  } catch {
332
330
  }
333
331
  }
334
332
  this.privateKey = priv;
335
- const pub = p256.p256.getPublicKey(priv, false);
333
+ const pub = nist.p256.getPublicKey(priv, false);
336
334
  if (pub.length !== 65 || pub[0] !== 4) {
337
335
  throw new Error("[PollarClient:keys] Unexpected public key format from @noble/curves");
338
336
  }
@@ -374,7 +372,7 @@ var NobleKeyManager = class {
374
372
  throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
375
373
  }
376
374
  const digest = await sha256(payload);
377
- const signature = p256.p256.sign(digest, this.privateKey, { prehash: false });
375
+ const signature = nist.p256.sign(digest, this.privateKey, { prehash: false });
378
376
  return signature.toCompactRawBytes();
379
377
  }
380
378
  };
@@ -1108,6 +1106,26 @@ async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e
1108
1106
  throw new Error("Ramp transaction polling timed out");
1109
1107
  }
1110
1108
 
1109
+ // src/lib/random-uuid.ts
1110
+ function randomUUID() {
1111
+ const c = globalThis.crypto;
1112
+ if (c && typeof c.randomUUID === "function") {
1113
+ return c.randomUUID();
1114
+ }
1115
+ if (c && typeof c.getRandomValues === "function") {
1116
+ const bytes = new Uint8Array(16);
1117
+ c.getRandomValues(bytes);
1118
+ bytes[6] = bytes[6] & 15 | 64;
1119
+ bytes[8] = bytes[8] & 63 | 128;
1120
+ const hex = [];
1121
+ for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
1122
+ return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
1123
+ }
1124
+ throw new Error(
1125
+ "[PollarClient] No secure random source available (crypto.randomUUID / crypto.getRandomValues). DPoP requires a secure context (HTTPS) or, in React Native, the `react-native-get-random-values` polyfill."
1126
+ );
1127
+ }
1128
+
1111
1129
  // src/dpop.ts
1112
1130
  async function buildProof(args, keyManager) {
1113
1131
  const jwk = await keyManager.getPublicJwk();
@@ -1117,7 +1135,7 @@ async function buildProof(args, keyManager) {
1117
1135
  jwk
1118
1136
  };
1119
1137
  const payload = {
1120
- jti: generateJti(),
1138
+ jti: randomUUID(),
1121
1139
  htm: args.htm.toUpperCase(),
1122
1140
  htu: normalizeHtu(args.htu),
1123
1141
  iat: Math.floor(Date.now() / 1e3)
@@ -1151,24 +1169,6 @@ function normalizeHtu(rawUrl) {
1151
1169
  const portPart = port ? `:${port}` : "";
1152
1170
  return `${scheme}//${host}${portPart}${url.pathname}`;
1153
1171
  }
1154
- function generateJti() {
1155
- const c = globalThis.crypto;
1156
- if (c && typeof c.randomUUID === "function") {
1157
- return c.randomUUID();
1158
- }
1159
- if (c && typeof c.getRandomValues === "function") {
1160
- const bytes = new Uint8Array(16);
1161
- c.getRandomValues(bytes);
1162
- bytes[6] = bytes[6] & 15 | 64;
1163
- bytes[8] = bytes[8] & 63 | 128;
1164
- const hex = [];
1165
- for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
1166
- return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
1167
- }
1168
- throw new Error(
1169
- "[PollarClient:dpop] No secure random source available (crypto.randomUUID / crypto.getRandomValues). DPoP requires a secure context (HTTPS) or, in React Native, the `react-native-get-random-values` polyfill."
1170
- );
1171
- }
1172
1172
 
1173
1173
  // src/storage/web.ts
1174
1174
  var LOG_PREFIX = "[PollarClient:storage]";
@@ -1311,6 +1311,8 @@ function defaultVisibilityProvider() {
1311
1311
  // src/types.ts
1312
1312
  var AUTH_ERROR_CODES = {
1313
1313
  SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
1314
+ SESSION_EXPIRED: "SESSION_EXPIRED",
1315
+ SESSION_INVALID: "SESSION_INVALID",
1314
1316
  EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
1315
1317
  EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
1316
1318
  EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
@@ -1624,7 +1626,32 @@ async function readWalletType(storage, apiKeyHash) {
1624
1626
  return storage.get(walletTypeStorageKey(apiKeyHash));
1625
1627
  }
1626
1628
 
1629
+ // src/lib/abort.ts
1630
+ function abortError() {
1631
+ if (typeof DOMException !== "undefined") {
1632
+ return new DOMException("Aborted", "AbortError");
1633
+ }
1634
+ const err = new Error("Aborted");
1635
+ err.name = "AbortError";
1636
+ return err;
1637
+ }
1638
+ function throwIfAborted(signal) {
1639
+ if (signal?.aborted) throw abortError();
1640
+ }
1641
+
1627
1642
  // src/client/stream.ts
1643
+ var SessionStatusError = class extends Error {
1644
+ constructor(code) {
1645
+ super(`[PollarClient] Session status terminal: ${code}`);
1646
+ this.code = code;
1647
+ this.name = "SessionStatusError";
1648
+ }
1649
+ };
1650
+ function terminalStatusCode(parsed) {
1651
+ const err = parsed?.error;
1652
+ if (err === "INVALID_CLIENT_SESSION_ID" || err === "EXPIRED_CLIENT_ID") return err;
1653
+ return null;
1654
+ }
1628
1655
  function abortableDelay(ms, signal) {
1629
1656
  return new Promise((resolve, reject) => {
1630
1657
  const t = setTimeout(resolve, ms);
@@ -1632,7 +1659,7 @@ function abortableDelay(ms, signal) {
1632
1659
  "abort",
1633
1660
  () => {
1634
1661
  clearTimeout(t);
1635
- reject(new DOMException("Aborted", "AbortError"));
1662
+ reject(abortError());
1636
1663
  },
1637
1664
  { once: true }
1638
1665
  );
@@ -1647,7 +1674,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1647
1674
  else await new Promise((r) => setTimeout(r, ms));
1648
1675
  };
1649
1676
  while (true) {
1650
- signal?.throwIfAborted();
1677
+ throwIfAborted(signal);
1651
1678
  let data, error;
1652
1679
  try {
1653
1680
  ({ data, error } = await api.GET("/auth/session/status/{clientSessionId}", {
@@ -1670,7 +1697,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1670
1697
  let sawAnyChunk = false;
1671
1698
  try {
1672
1699
  while (true) {
1673
- signal?.throwIfAborted();
1700
+ throwIfAborted(signal);
1674
1701
  const { done, value } = await reader.read();
1675
1702
  if (done) {
1676
1703
  streamDone = true;
@@ -1681,17 +1708,22 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1681
1708
  for (const message of chunk.split("\n\n").filter(Boolean)) {
1682
1709
  const dataLine = message.split("\n").find((l) => l.startsWith("data:"));
1683
1710
  if (!dataLine) continue;
1711
+ let parsed;
1684
1712
  try {
1685
- const parsed = JSON.parse(dataLine.slice("data:".length).trim());
1686
- if (check(parsed)) {
1687
- return parsed;
1688
- }
1713
+ parsed = JSON.parse(dataLine.slice("data:".length).trim());
1689
1714
  } catch {
1715
+ continue;
1716
+ }
1717
+ const terminal = terminalStatusCode(parsed);
1718
+ if (terminal) throw new SessionStatusError(terminal);
1719
+ if (check(parsed)) {
1720
+ return parsed;
1690
1721
  }
1691
1722
  }
1692
1723
  }
1693
1724
  } catch (e) {
1694
1725
  if (e instanceof Error && e.name === "AbortError") throw e;
1726
+ if (e instanceof SessionStatusError) throw e;
1695
1727
  console.warn(e);
1696
1728
  } finally {
1697
1729
  reader.releaseLock();
@@ -1702,12 +1734,72 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1702
1734
  if (delay) await sleep(delay);
1703
1735
  }
1704
1736
  }
1737
+ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal) {
1738
+ const url = `${baseUrl}/auth/session/status/${encodeURIComponent(clientSessionId)}/poll`;
1739
+ let backoff = intervalMs;
1740
+ const sleep = async (ms) => {
1741
+ if (ms <= 0) return;
1742
+ if (signal) await abortableDelay(ms, signal);
1743
+ else await new Promise((r) => setTimeout(r, ms));
1744
+ };
1745
+ while (true) {
1746
+ throwIfAborted(signal);
1747
+ let envelope = null;
1748
+ let httpStatus = 0;
1749
+ try {
1750
+ const response = await fetch(url, { headers: { accept: "application/json" }, signal: signal ?? null });
1751
+ httpStatus = response.status;
1752
+ envelope = await response.json().catch(() => null);
1753
+ } catch (e) {
1754
+ if (e instanceof Error && e.name === "AbortError") throw e;
1755
+ console.warn(e);
1756
+ }
1757
+ if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
1758
+ throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
1759
+ }
1760
+ if (httpStatus === 410 || envelope?.code === "EXPIRED_CLIENT_ID") {
1761
+ throw new SessionStatusError("EXPIRED_CLIENT_ID");
1762
+ }
1763
+ if (envelope?.success && envelope.content && check(envelope.content)) {
1764
+ return envelope.content;
1765
+ }
1766
+ if (envelope) backoff = intervalMs;
1767
+ else backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
1768
+ await sleep(backoff);
1769
+ }
1770
+ }
1771
+ function waitForSessionReady(args) {
1772
+ const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal } = args;
1773
+ return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal);
1774
+ }
1705
1775
 
1706
1776
  // src/client/auth/authenticate.ts
1707
1777
  async function authenticate(clientSessionId, deps, expectedWallet) {
1708
- const { api, signal, setAuthState, storeSession, clearSession } = deps;
1778
+ const { api, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
1709
1779
  setAuthState({ step: "authenticating" });
1710
- await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
1780
+ try {
1781
+ await waitForSessionReady({
1782
+ api,
1783
+ baseUrl: basePath,
1784
+ clientSessionId,
1785
+ check: (data2) => data2?.status === "READY",
1786
+ useStreaming,
1787
+ signal
1788
+ });
1789
+ } catch (err) {
1790
+ if (err instanceof SessionStatusError) {
1791
+ const expired = err.code === "EXPIRED_CLIENT_ID";
1792
+ setAuthState({
1793
+ step: "error",
1794
+ previousStep: "authenticating",
1795
+ message: expired ? "Login session expired \u2014 please try again" : "Login session is no longer valid \u2014 please try again",
1796
+ errorCode: expired ? AUTH_ERROR_CODES.SESSION_EXPIRED : AUTH_ERROR_CODES.SESSION_INVALID
1797
+ });
1798
+ await clearSession();
1799
+ return;
1800
+ }
1801
+ throw err;
1802
+ }
1711
1803
  const dpopJwk = await deps.getPublicJwk();
1712
1804
  const { data } = await api.POST("/auth/login", {
1713
1805
  body: {
@@ -1831,26 +1923,36 @@ function severOpener(popup) {
1831
1923
  } catch {
1832
1924
  }
1833
1925
  }
1834
- async function loginOAuth(provider, deps) {
1835
- const { setAuthState, basePath, apiKey } = deps;
1836
- const popup = window.open("about:blank", "_blank");
1926
+ var defaultWebOAuthOpener = async ({ getUrl }) => {
1927
+ const popup = typeof window !== "undefined" ? window.open("about:blank", "_blank") : null;
1837
1928
  severOpener(popup);
1838
- const clientSessionId = await createAuthSession(deps);
1839
- if (!clientSessionId) {
1929
+ const url = await getUrl();
1930
+ if (!url) {
1840
1931
  popup?.close();
1841
1932
  return;
1842
1933
  }
1843
- setAuthState({ step: "opening_oauth", provider });
1844
- const url = new URL(`${basePath}/auth/${provider}`);
1845
- url.searchParams.set("api_key", apiKey);
1846
- url.searchParams.set("client_session_id", clientSessionId);
1847
- url.searchParams.set("redirect_uri", window.location.origin);
1848
1934
  if (popup) {
1849
- popup.location.href = url.toString();
1935
+ popup.location.href = url;
1850
1936
  severOpener(popup);
1851
- } else {
1852
- window.open(url.toString(), "_blank", "noopener,noreferrer");
1937
+ } else if (typeof window !== "undefined") {
1938
+ window.open(url, "_blank", "noopener,noreferrer");
1853
1939
  }
1940
+ };
1941
+ async function loginOAuth(provider, deps) {
1942
+ const { setAuthState, basePath, apiKey, openAuthUrl, redirectUri, signal } = deps;
1943
+ let clientSessionId = null;
1944
+ const getUrl = async () => {
1945
+ clientSessionId = await createAuthSession(deps);
1946
+ if (!clientSessionId) return null;
1947
+ setAuthState({ step: "opening_oauth", provider });
1948
+ const url = new URL(`${basePath}/auth/${provider}`);
1949
+ url.searchParams.set("api_key", apiKey);
1950
+ url.searchParams.set("client_session_id", clientSessionId);
1951
+ url.searchParams.set("redirect_uri", redirectUri);
1952
+ return url.toString();
1953
+ };
1954
+ await openAuthUrl({ provider, getUrl, redirectUri, signal });
1955
+ if (!clientSessionId) return;
1854
1956
  await authenticate(clientSessionId, deps);
1855
1957
  }
1856
1958
 
@@ -1860,10 +1962,10 @@ function withSignal(promise, signal) {
1860
1962
  promise,
1861
1963
  new Promise((_, reject) => {
1862
1964
  if (signal.aborted) {
1863
- reject(new DOMException("Aborted", "AbortError"));
1965
+ reject(abortError());
1864
1966
  return;
1865
1967
  }
1866
- signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1968
+ signal.addEventListener("abort", () => reject(abortError()), { once: true });
1867
1969
  })
1868
1970
  ]);
1869
1971
  }
@@ -1911,6 +2013,8 @@ async function loginWallet(type, deps) {
1911
2013
 
1912
2014
  // src/client/client.ts
1913
2015
  var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
2016
+ var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
2017
+ var isClientRuntime = isBrowser || isReactNative;
1914
2018
  var REFRESH_SKEW_SECONDS = 60;
1915
2019
  function warnServerSide(method) {
1916
2020
  console.warn(
@@ -1966,7 +2070,7 @@ var PollarClient = class {
1966
2070
  this._walletAdapter = null;
1967
2071
  this._loginController = null;
1968
2072
  this.apiKey = config.apiKey;
1969
- this.id = crypto.randomUUID();
2073
+ this.id = randomUUID();
1970
2074
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1971
2075
  this._storage = config.storage ?? defaultStorage({
1972
2076
  onDegrade: (reason, error) => {
@@ -1980,10 +2084,12 @@ var PollarClient = class {
1980
2084
  this._deviceLabel = config.deviceLabel;
1981
2085
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
1982
2086
  this._maxIdleMs = config.maxIdleMs;
2087
+ this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
2088
+ this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location.origin : "");
1983
2089
  this._api = createApiClient(this.basePath);
1984
2090
  this._wireMiddlewares();
1985
2091
  this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
1986
- if (!isBrowser) {
2092
+ if (!isClientRuntime) {
1987
2093
  warnServerSide("constructor");
1988
2094
  this._initialized = Promise.resolve();
1989
2095
  return;
@@ -2009,7 +2115,7 @@ var PollarClient = class {
2009
2115
  // ─── Lifecycle ────────────────────────────────────────────────────────────
2010
2116
  async _initialize() {
2011
2117
  this._apiKeyHash = await hashApiKey(this.apiKey);
2012
- if (typeof window !== "undefined") {
2118
+ if (isBrowser) {
2013
2119
  const sessionKey = sessionStorageKey(this._apiKeyHash);
2014
2120
  const handler = (e) => {
2015
2121
  if (e.key === sessionKey) {
@@ -2031,7 +2137,7 @@ var PollarClient = class {
2031
2137
  }
2032
2138
  /** Detach the cross-tab storage listener and abort any in-flight login. */
2033
2139
  destroy() {
2034
- if (this._storageEventHandler && typeof window !== "undefined") {
2140
+ if (this._storageEventHandler && isBrowser) {
2035
2141
  window.removeEventListener("storage", this._storageEventHandler);
2036
2142
  this._storageEventHandler = null;
2037
2143
  }
@@ -2314,7 +2420,7 @@ var PollarClient = class {
2314
2420
  }
2315
2421
  // ─── Login (unified entry point) ─────────────────────────────────────────
2316
2422
  login(options) {
2317
- if (!isBrowser) {
2423
+ if (!isClientRuntime) {
2318
2424
  warnServerSide("login");
2319
2425
  return;
2320
2426
  }
@@ -2325,7 +2431,9 @@ var PollarClient = class {
2325
2431
  loginOAuth(options.provider, {
2326
2432
  ...deps,
2327
2433
  basePath: this.basePath,
2328
- apiKey: this.apiKey
2434
+ apiKey: this.apiKey,
2435
+ openAuthUrl: this._openAuthUrl,
2436
+ redirectUri: this._oauthRedirectUri
2329
2437
  }).catch((err) => this._handleFlowError(err));
2330
2438
  } else if (options.provider === "email") {
2331
2439
  const { email } = options;
@@ -2341,7 +2449,7 @@ var PollarClient = class {
2341
2449
  }
2342
2450
  // ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
2343
2451
  beginEmailLogin() {
2344
- if (!isBrowser) {
2452
+ if (!isClientRuntime) {
2345
2453
  warnServerSide("beginEmailLogin");
2346
2454
  return;
2347
2455
  }
@@ -2349,7 +2457,7 @@ var PollarClient = class {
2349
2457
  initEmailSession(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2350
2458
  }
2351
2459
  sendEmailCode(email) {
2352
- if (!isBrowser) {
2460
+ if (!isClientRuntime) {
2353
2461
  warnServerSide("sendEmailCode");
2354
2462
  return;
2355
2463
  }
@@ -2361,7 +2469,7 @@ var PollarClient = class {
2361
2469
  sendEmailCode(email, clientSessionId, this._flowDeps(signal)).catch((err) => this._handleFlowError(err));
2362
2470
  }
2363
2471
  verifyEmailCode(code) {
2364
- if (!isBrowser) {
2472
+ if (!isClientRuntime) {
2365
2473
  warnServerSide("verifyEmailCode");
2366
2474
  return;
2367
2475
  }
@@ -2379,7 +2487,7 @@ var PollarClient = class {
2379
2487
  }
2380
2488
  // ─── Wallet flow (single call) ────────────────────────────────────────────
2381
2489
  loginWallet(type) {
2382
- if (!isBrowser) {
2490
+ if (!isClientRuntime) {
2383
2491
  warnServerSide("loginWallet");
2384
2492
  return;
2385
2493
  }
@@ -2405,7 +2513,7 @@ var PollarClient = class {
2405
2513
  * across all devices.
2406
2514
  */
2407
2515
  async logout(options = {}) {
2408
- if (!isBrowser) {
2516
+ if (!isClientRuntime) {
2409
2517
  warnServerSide("logout");
2410
2518
  return;
2411
2519
  }
@@ -2435,7 +2543,7 @@ var PollarClient = class {
2435
2543
  * `current` flag identifies which entry corresponds to this client.
2436
2544
  */
2437
2545
  async listSessions() {
2438
- if (!isBrowser) {
2546
+ if (!isClientRuntime) {
2439
2547
  warnServerSide("listSessions");
2440
2548
  return [];
2441
2549
  }
@@ -2454,7 +2562,7 @@ var PollarClient = class {
2454
2562
  * does NOT clear local state — call `logout()` for that case.
2455
2563
  */
2456
2564
  async revokeSession(familyId) {
2457
- if (!isBrowser) {
2565
+ if (!isClientRuntime) {
2458
2566
  warnServerSide("revokeSession");
2459
2567
  return;
2460
2568
  }
@@ -2952,6 +3060,11 @@ var PollarClient = class {
2952
3060
  _flowDeps(signal) {
2953
3061
  return {
2954
3062
  api: this._api,
3063
+ basePath: this.basePath,
3064
+ // SSE status streaming works on web; React Native's `fetch` has no
3065
+ // readable `response.body`, so those clients poll the non-streaming
3066
+ // status endpoint instead. `isBrowser` is false in RN and SSR alike.
3067
+ useStreaming: isBrowser,
2955
3068
  signal,
2956
3069
  setAuthState: this._setAuthState.bind(this),
2957
3070
  storeSession: this._storeSession.bind(this),