@oasisprotocol/privana-sdk 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,13 +3,13 @@
3
3
 
4
4
  var react = require('react');
5
5
  var viem = require('viem');
6
+ var siwe = require('viem/siwe');
7
+ var wagmi = require('wagmi');
8
+ var actions = require('wagmi/actions');
6
9
  var jsxRuntime = require('react/jsx-runtime');
7
10
  var reactQuery = require('@tanstack/react-query');
8
11
  var clsx = require('clsx');
9
12
  var tailwindMerge = require('tailwind-merge');
10
- var siwe = require('viem/siwe');
11
- var wagmi = require('wagmi');
12
- var actions = require('wagmi/actions');
13
13
  var actions$1 = require('viem/actions');
14
14
  var reactSlot = require('@radix-ui/react-slot');
15
15
  var classVarianceAuthority = require('class-variance-authority');
@@ -791,6 +791,235 @@ async function signWithdrawFromLockMessage({
791
791
  });
792
792
  return signature;
793
793
  }
794
+ var defaultResult = {
795
+ address: void 0,
796
+ isConnected: false,
797
+ status: "disconnected"
798
+ };
799
+ function useSafeAccount() {
800
+ const context = react.useContext(wagmi.WagmiContext);
801
+ const cacheRef = react.useRef(defaultResult);
802
+ const subscribe = react.useCallback(
803
+ (onChange) => {
804
+ if (!context) return () => {
805
+ };
806
+ return actions.watchAccount(context, { onChange });
807
+ },
808
+ [context]
809
+ );
810
+ const getSnapshot = react.useCallback(() => {
811
+ if (!context) return defaultResult;
812
+ const account = actions.getAccount(context);
813
+ if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected || cacheRef.current.status !== account.status) {
814
+ cacheRef.current = {
815
+ address: account.address,
816
+ isConnected: account.isConnected,
817
+ status: account.status
818
+ };
819
+ }
820
+ return cacheRef.current;
821
+ }, [context]);
822
+ const getServerSnapshot = react.useCallback(() => defaultResult, []);
823
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
824
+ }
825
+
826
+ // src/sdk/hooks/private-read-token-store.ts
827
+ var AUTH_CLOCK_SKEW_MS = 3e4;
828
+ var cache = /* @__PURE__ */ new Map();
829
+ function createScopeKey(apiUrl, chainId, address) {
830
+ return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
831
+ }
832
+ function getCachedPrivateReadToken(scopeKey) {
833
+ const cached = cache.get(scopeKey);
834
+ if (!cached) return null;
835
+ if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
836
+ cache.delete(scopeKey);
837
+ return null;
838
+ }
839
+ return cached.token;
840
+ }
841
+ function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
842
+ cache.set(scopeKey, { token, expiresAt });
843
+ }
844
+ function deleteCachedPrivateReadToken(scopeKey) {
845
+ cache.delete(scopeKey);
846
+ }
847
+ var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
848
+ var DEFAULT_STATEMENT = "Sign in to access your private account data.";
849
+ var AUTH_REFRESH_SKEW_MS = 3e4;
850
+ var SiweAuthContext = react.createContext(null);
851
+ function SiweAuthProvider({
852
+ children,
853
+ client,
854
+ networkConfig,
855
+ autoLogin = true,
856
+ statement
857
+ }) {
858
+ const wagmiContext = react.useContext(wagmi.WagmiContext);
859
+ const { address, isConnected, status } = useSafeAccount();
860
+ const [session, setSession] = react.useState(null);
861
+ const [tokens, setTokens] = react.useState(null);
862
+ const [isLoading, setIsLoading] = react.useState(false);
863
+ const [error, setError] = react.useState(null);
864
+ const [accessTokenExpiresAt, setAccessTokenExpiresAt] = react.useState(null);
865
+ const loginInFlight = react.useRef(false);
866
+ const autoAttemptedAddress = react.useRef(null);
867
+ const refreshInFlight = react.useRef(false);
868
+ const refreshDataRef = react.useRef(null);
869
+ const clearSession = react.useCallback(() => {
870
+ refreshDataRef.current = null;
871
+ setAccessTokenExpiresAt(null);
872
+ client.clearPrivateReadToken();
873
+ client.clearBearerToken();
874
+ setSession(null);
875
+ setTokens(null);
876
+ setError(null);
877
+ autoAttemptedAddress.current = null;
878
+ }, [client]);
879
+ const logout = react.useCallback(async () => {
880
+ setError(null);
881
+ const refreshToken = refreshDataRef.current?.refreshToken;
882
+ try {
883
+ if (refreshToken) {
884
+ await client.logoutJwtSession({ refresh_token: refreshToken });
885
+ }
886
+ } finally {
887
+ clearSession();
888
+ autoAttemptedAddress.current = address ?? null;
889
+ }
890
+ }, [clearSession, client, address]);
891
+ const login = react.useCallback(async () => {
892
+ if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
893
+ if (!address) throw new Error("No wallet connected");
894
+ if (loginInFlight.current) return;
895
+ loginInFlight.current = true;
896
+ setIsLoading(true);
897
+ setError(null);
898
+ try {
899
+ const walletClient = await actions.getWalletClient(wagmiContext);
900
+ if (!walletClient) throw new Error("No wallet client available");
901
+ const [{ domain }, nonceRes] = await Promise.all([
902
+ client.getSiweDomain(),
903
+ client.getSiweNonce(address)
904
+ ]);
905
+ const issuedAt = /* @__PURE__ */ new Date();
906
+ const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
907
+ const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
908
+ const message = siwe.createSiweMessage({
909
+ address,
910
+ chainId: networkConfig.chainId,
911
+ domain,
912
+ uri,
913
+ version: "1",
914
+ nonce: nonceRes.nonce,
915
+ statement: statement ?? DEFAULT_STATEMENT,
916
+ issuedAt,
917
+ expirationTime
918
+ });
919
+ const signature = await walletClient.signMessage({
920
+ account: walletClient.account ?? address,
921
+ message
922
+ });
923
+ const res = await client.loginWithSiwe({ siwe_message: message, signature });
924
+ const loggedInAt = Date.now();
925
+ client.setPrivateReadToken(res.siwe_token);
926
+ client.setBearerToken(res.jwt_access_token);
927
+ refreshDataRef.current = {
928
+ refreshToken: res.jwt_refresh_token,
929
+ refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
930
+ };
931
+ setCachedPrivateReadToken(
932
+ createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
933
+ res.siwe_token,
934
+ expirationTime.getTime()
935
+ );
936
+ setSession({ address: res.address });
937
+ setTokens({
938
+ siwe_token: res.siwe_token,
939
+ jwt_access_token: res.jwt_access_token,
940
+ jwt_refresh_token: res.jwt_refresh_token,
941
+ address: res.address
942
+ });
943
+ setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
944
+ } catch (err) {
945
+ setError(err instanceof Error ? err : new Error("Sign-in failed"));
946
+ throw err;
947
+ } finally {
948
+ setIsLoading(false);
949
+ loginInFlight.current = false;
950
+ }
951
+ }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl, statement]);
952
+ const refreshAccessToken = react.useCallback(async () => {
953
+ const data = refreshDataRef.current;
954
+ if (!data || refreshInFlight.current) return;
955
+ if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
956
+ clearSession();
957
+ return;
958
+ }
959
+ refreshInFlight.current = true;
960
+ try {
961
+ const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
962
+ const refreshedAt = Date.now();
963
+ client.setBearerToken(res.token);
964
+ refreshDataRef.current = {
965
+ refreshToken: res.refresh_token,
966
+ refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
967
+ };
968
+ setTokens(
969
+ (prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
970
+ );
971
+ setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
972
+ } catch {
973
+ clearSession();
974
+ } finally {
975
+ refreshInFlight.current = false;
976
+ }
977
+ }, [client, clearSession]);
978
+ react.useEffect(() => {
979
+ if (accessTokenExpiresAt == null) return;
980
+ const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
981
+ const timer = setTimeout(() => {
982
+ void refreshAccessToken();
983
+ }, delay);
984
+ return () => clearTimeout(timer);
985
+ }, [accessTokenExpiresAt, refreshAccessToken]);
986
+ react.useEffect(() => {
987
+ if (!autoLogin) return;
988
+ if (status === "connecting" || status === "reconnecting") return;
989
+ if (!isConnected && session) {
990
+ clearSession();
991
+ return;
992
+ }
993
+ if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
994
+ clearSession();
995
+ return;
996
+ }
997
+ if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
998
+ autoAttemptedAddress.current = address;
999
+ void login().catch(() => {
1000
+ });
1001
+ }
1002
+ }, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
1003
+ const value = react.useMemo(
1004
+ () => ({
1005
+ isAuthenticated: !!session,
1006
+ isLoading,
1007
+ error,
1008
+ session,
1009
+ accessToken: tokens?.jwt_access_token,
1010
+ tokens,
1011
+ login,
1012
+ logout
1013
+ }),
1014
+ [session, isLoading, error, tokens, login, logout]
1015
+ );
1016
+ return /* @__PURE__ */ jsxRuntime.jsx(SiweAuthContext.Provider, { value, children });
1017
+ }
1018
+ function useSiweAuth() {
1019
+ const ctx = react.useContext(SiweAuthContext);
1020
+ if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
1021
+ return ctx;
1022
+ }
794
1023
  var PrivanaContext = react.createContext(null);
795
1024
  function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
796
1025
  const raw = storage.getItem(hostedAuthStorageKey);
@@ -831,8 +1060,14 @@ function PrivanaProvider({
831
1060
  chains,
832
1061
  pollingInterval = 1e4,
833
1062
  serviceAddress,
834
- hostedAuth
1063
+ hostedAuth,
1064
+ siweAuth
835
1065
  }) {
1066
+ if (hostedAuth && siweAuth) {
1067
+ throw new Error(
1068
+ "PrivanaProvider: `hostedAuth` and `siweAuth` are mutually exclusive - provide only one. When both are set, private reads use hosted auth and the in-app SIWE login is ignored."
1069
+ );
1070
+ }
836
1071
  const networkConfig = react.useMemo(() => {
837
1072
  const config = {
838
1073
  ...DEFAULT_NETWORK_CONFIG,
@@ -1067,7 +1302,16 @@ function PrivanaProvider({
1067
1302
  refreshHostedAuthSession
1068
1303
  ]
1069
1304
  );
1070
- return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children });
1305
+ return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children: siweAuth ? /* @__PURE__ */ jsxRuntime.jsx(
1306
+ SiweAuthProvider,
1307
+ {
1308
+ client,
1309
+ networkConfig,
1310
+ autoLogin: siweAuth.autoLogin,
1311
+ statement: siweAuth.statement,
1312
+ children
1313
+ }
1314
+ ) : children });
1071
1315
  }
1072
1316
  function usePrivanaContext() {
1073
1317
  const context = react.useContext(PrivanaContext);
@@ -1327,40 +1571,10 @@ function formatTimeRemaining(expiryTimestamp) {
1327
1571
  }
1328
1572
  return `${minutes}m left`;
1329
1573
  }
1330
- var defaultResult = {
1331
- address: void 0,
1332
- isConnected: false
1333
- };
1334
- function useSafeAccount() {
1335
- const context = react.useContext(wagmi.WagmiContext);
1336
- const cacheRef = react.useRef(defaultResult);
1337
- const subscribe = react.useCallback(
1338
- (onChange) => {
1339
- if (!context) return () => {
1340
- };
1341
- return actions.watchAccount(context, { onChange });
1342
- },
1343
- [context]
1344
- );
1345
- const getSnapshot = react.useCallback(() => {
1346
- if (!context) return defaultResult;
1347
- const account = actions.getAccount(context);
1348
- if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected) {
1349
- cacheRef.current = { address: account.address, isConnected: account.isConnected };
1350
- }
1351
- return cacheRef.current;
1352
- }, [context]);
1353
- const getServerSnapshot = react.useCallback(() => defaultResult, []);
1354
- return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1355
- }
1356
-
1357
- // src/sdk/hooks/use-private-read-request.ts
1358
- var AUTH_CLOCK_SKEW_MS = 3e4;
1359
1574
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1360
1575
  var MAX_AUTH_BACKOFF_MS = 6e4;
1361
1576
  var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1362
1577
  var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
1363
- var privateReadTokenCache = /* @__PURE__ */ new Map();
1364
1578
  var privateReadFailureCache = /* @__PURE__ */ new Map();
1365
1579
  var privateReadInflight = /* @__PURE__ */ new Map();
1366
1580
  async function executeHostedAuthPrivateReadRequest({
@@ -1394,20 +1608,8 @@ async function executeHostedAuthPrivateReadRequest({
1394
1608
  return request();
1395
1609
  }
1396
1610
  }
1397
- function createScopeKey(apiUrl, deploymentChainId, address) {
1398
- return `${apiUrl.replace(/\/$/, "")}:${deploymentChainId}:${address.toLowerCase()}`;
1399
- }
1400
- function getCachedPrivateReadToken(scopeKey) {
1401
- const cached = privateReadTokenCache.get(scopeKey);
1402
- if (!cached) return null;
1403
- if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
1404
- privateReadTokenCache.delete(scopeKey);
1405
- return null;
1406
- }
1407
- return cached.token;
1408
- }
1409
1611
  function clearPrivateReadScope(scopeKey, client) {
1410
- privateReadTokenCache.delete(scopeKey);
1612
+ deleteCachedPrivateReadToken(scopeKey);
1411
1613
  privateReadFailureCache.delete(scopeKey);
1412
1614
  client.clearPrivateReadToken();
1413
1615
  }
@@ -1506,10 +1708,7 @@ function usePrivateReadRequest() {
1506
1708
  siwe_message: message,
1507
1709
  signature
1508
1710
  });
1509
- privateReadTokenCache.set(scopeKey, {
1510
- token: login.siwe_token,
1511
- expiresAt: expirationTime.getTime()
1512
- });
1711
+ setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
1513
1712
  privateReadFailureCache.delete(scopeKey);
1514
1713
  client.setPrivateReadToken(login.siwe_token);
1515
1714
  return login.siwe_token;
@@ -4476,6 +4675,7 @@ exports.PrivanaInlineModal = PrivanaInlineModal;
4476
4675
  exports.PrivanaModal = PrivanaModal;
4477
4676
  exports.PrivanaProvider = PrivanaProvider;
4478
4677
  exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
4678
+ exports.SiweAuthProvider = SiweAuthProvider;
4479
4679
  exports.Skeleton = Skeleton;
4480
4680
  exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
4481
4681
  exports.TRANSFER_TYPES = TRANSFER_TYPES;
@@ -4530,6 +4730,7 @@ exports.usePrivanaClient = usePrivanaClient;
4530
4730
  exports.usePrivanaContext = usePrivanaContext;
4531
4731
  exports.useSafeAccount = useSafeAccount;
4532
4732
  exports.useSafePrivanaContext = useSafePrivanaContext;
4733
+ exports.useSiweAuth = useSiweAuth;
4533
4734
  exports.useTokenInfo = useTokenInfo;
4534
4735
  exports.useTokenList = useTokenList;
4535
4736
  exports.useTotalLockedBalance = useTotalLockedBalance;