@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 +253 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +253 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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;
|