@volr/react 0.1.19 → 0.1.21

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
@@ -9330,6 +9330,21 @@ var APIClient = class {
9330
9330
  }
9331
9331
  };
9332
9332
 
9333
+ // src/config/backend.ts
9334
+ var DEFAULT_API_BASE_URL = "https://api.volr.io";
9335
+ function resolveApiBaseUrl(config) {
9336
+ const anyConfig = config;
9337
+ const devOverride = anyConfig.__devApiBaseUrl;
9338
+ if (devOverride && typeof devOverride === "string") {
9339
+ return devOverride.replace(/\/+$/, "");
9340
+ }
9341
+ const override = anyConfig.apiBaseUrl;
9342
+ if (override && typeof override === "string") {
9343
+ return override.replace(/\/+$/, "");
9344
+ }
9345
+ return DEFAULT_API_BASE_URL;
9346
+ }
9347
+
9333
9348
  // src/headless/auth-sync.ts
9334
9349
  var SessionSync = class {
9335
9350
  constructor() {
@@ -9343,11 +9358,12 @@ var SessionSync = class {
9343
9358
  }
9344
9359
  if (typeof window !== "undefined") {
9345
9360
  window.addEventListener("storage", (e) => {
9346
- if (e.key === STORAGE_KEYS.accessToken || e.key === STORAGE_KEYS.user) {
9361
+ if (e.key === STORAGE_KEYS.accessToken || e.key === STORAGE_KEYS.refreshToken || e.key === STORAGE_KEYS.user) {
9347
9362
  this.notifyListeners({
9348
9363
  type: "REFRESH",
9349
9364
  payload: {
9350
- accessToken: safeStorage.getItem(STORAGE_KEYS.accessToken) || ""
9365
+ accessToken: safeStorage.getItem(STORAGE_KEYS.accessToken) || "",
9366
+ refreshToken: safeStorage.getItem(STORAGE_KEYS.refreshToken)
9351
9367
  }
9352
9368
  });
9353
9369
  }
@@ -9401,19 +9417,63 @@ var SessionSync = class {
9401
9417
  }
9402
9418
  };
9403
9419
 
9404
- // src/config/backend.ts
9405
- var DEFAULT_API_BASE_URL = "https://api.volr.io";
9406
- function resolveApiBaseUrl(config) {
9407
- const anyConfig = config;
9408
- const devOverride = anyConfig.__devApiBaseUrl;
9409
- if (devOverride && typeof devOverride === "string") {
9410
- return devOverride.replace(/\/+$/, "");
9411
- }
9412
- const override = anyConfig.apiBaseUrl;
9413
- if (override && typeof override === "string") {
9414
- return override.replace(/\/+$/, "");
9415
- }
9416
- return DEFAULT_API_BASE_URL;
9420
+ // src/react/hooks/useSessionSync.ts
9421
+ function useSessionSync({
9422
+ client,
9423
+ config,
9424
+ setAccessTokenState,
9425
+ setRefreshTokenState,
9426
+ setUser,
9427
+ setProviderState
9428
+ }) {
9429
+ const syncRef = react.useRef(null);
9430
+ react.useEffect(() => {
9431
+ syncRef.current = new SessionSync();
9432
+ const unsubscribe = syncRef.current.subscribe((event) => {
9433
+ if (event.type === "LOGIN") {
9434
+ client.setAccessToken(event.payload.accessToken);
9435
+ setAccessTokenState(event.payload.accessToken);
9436
+ client.setRefreshToken(event.payload.refreshToken);
9437
+ setRefreshTokenState(event.payload.refreshToken);
9438
+ setUser(event.payload.user);
9439
+ safeStorage.setItem(
9440
+ STORAGE_KEYS.user,
9441
+ JSON.stringify(event.payload.user)
9442
+ );
9443
+ client.setApiKey(config.projectApiKey);
9444
+ } else if (event.type === "LOGOUT") {
9445
+ client.setAccessToken(null);
9446
+ client.setRefreshToken(null);
9447
+ setAccessTokenState(null);
9448
+ setRefreshTokenState(null);
9449
+ setUser(null);
9450
+ setProviderState(null);
9451
+ safeStorage.removeItem(STORAGE_KEYS.user);
9452
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9453
+ } else if (event.type === "REFRESH") {
9454
+ client.setAccessToken(event.payload.accessToken);
9455
+ setAccessTokenState(event.payload.accessToken);
9456
+ if (event.payload.refreshToken) {
9457
+ client.setRefreshToken(event.payload.refreshToken);
9458
+ setRefreshTokenState(event.payload.refreshToken);
9459
+ }
9460
+ } else if (event.type === "PROVIDER_SET") {
9461
+ setUser(
9462
+ (prev) => prev ? {
9463
+ ...prev,
9464
+ keyStorageType: event.payload.keyStorageType,
9465
+ address: event.payload.address
9466
+ } : null
9467
+ );
9468
+ safeStorage.setItem(STORAGE_KEYS.provider, event.payload.keyStorageType);
9469
+ }
9470
+ });
9471
+ return () => {
9472
+ unsubscribe();
9473
+ syncRef.current?.destroy();
9474
+ };
9475
+ }, [client, config.projectApiKey, setAccessTokenState, setRefreshTokenState, setUser, setProviderState]);
9476
+ return syncRef;
9417
9477
  }
9418
9478
 
9419
9479
  // src/config/webauthn.ts
@@ -9676,6 +9736,120 @@ async function restorePasskey(params) {
9676
9736
  provider
9677
9737
  };
9678
9738
  }
9739
+
9740
+ // src/react/hooks/useAutoRecover.ts
9741
+ function useAutoRecover({
9742
+ client,
9743
+ config,
9744
+ setAccessTokenState,
9745
+ setRefreshTokenState,
9746
+ setUser,
9747
+ setProviderState,
9748
+ setProvider,
9749
+ setIsLoading
9750
+ }) {
9751
+ const REQUIRE_USER_GESTURE_TO_RESTORE = true;
9752
+ const hasRecoveredRef = react.useRef(false);
9753
+ const restorationAttemptedRef = react.useRef(false);
9754
+ react.useEffect(() => {
9755
+ if (config.autoRecoverOnLogin !== false && !hasRecoveredRef.current) {
9756
+ hasRecoveredRef.current = true;
9757
+ const recover = async () => {
9758
+ try {
9759
+ setIsLoading(true);
9760
+ if (!client.getRefreshToken()) {
9761
+ console.log("[Provider] No refresh token found, skipping auto-recover");
9762
+ setIsLoading(false);
9763
+ return;
9764
+ }
9765
+ const refreshedUser = await client.refreshSession();
9766
+ console.log("[Provider] Session refreshed, user:", refreshedUser);
9767
+ setAccessTokenState(client.getAccessToken());
9768
+ setRefreshTokenState(client.getRefreshToken());
9769
+ if (refreshedUser) {
9770
+ setUser(refreshedUser);
9771
+ if (!REQUIRE_USER_GESTURE_TO_RESTORE) ; else if (refreshedUser.keyStorageType === "passkey") {
9772
+ console.log("[Provider] TTL=0 mode: Provider restoration deferred");
9773
+ if (!refreshedUser.blobUrl || !refreshedUser.prfInput) {
9774
+ console.warn("[Provider] Passkey user missing blobUrl or prfInput");
9775
+ }
9776
+ }
9777
+ } else {
9778
+ const userStr = safeStorage.getItem(STORAGE_KEYS.user);
9779
+ if (userStr) {
9780
+ try {
9781
+ setUser(JSON.parse(userStr));
9782
+ } catch {
9783
+ safeStorage.removeItem(STORAGE_KEYS.user);
9784
+ }
9785
+ }
9786
+ }
9787
+ } catch {
9788
+ client.setAccessToken(null);
9789
+ client.setRefreshToken(null);
9790
+ setAccessTokenState(null);
9791
+ setRefreshTokenState(null);
9792
+ setUser(null);
9793
+ setProviderState(null);
9794
+ safeStorage.removeItem(STORAGE_KEYS.user);
9795
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9796
+ } finally {
9797
+ setIsLoading(false);
9798
+ }
9799
+ };
9800
+ recover();
9801
+ } else {
9802
+ setIsLoading(false);
9803
+ }
9804
+ }, [client, config.autoRecoverOnLogin, setAccessTokenState, setRefreshTokenState, setUser, setProviderState, setProvider, setIsLoading]);
9805
+ }
9806
+ function useWalletEvents({
9807
+ user,
9808
+ logout,
9809
+ setUser,
9810
+ setProviderState
9811
+ }) {
9812
+ react.useEffect(() => {
9813
+ if (!user) return;
9814
+ if (typeof window === "undefined" || !window.ethereum) {
9815
+ return;
9816
+ }
9817
+ const ethereum = window.ethereum;
9818
+ const handleAccountsChanged = (accounts) => {
9819
+ console.log("[Provider] accountsChanged event:", accounts);
9820
+ if (accounts.length === 0) {
9821
+ console.warn("[Provider] Wallet disconnected, logging out...");
9822
+ setTimeout(() => logout(), 3e3);
9823
+ } else if (user.evmAddress && accounts[0].toLowerCase() !== user.evmAddress.toLowerCase()) {
9824
+ console.warn("[Provider] Account changed, logging out in 3 seconds...");
9825
+ setTimeout(() => logout(), 3e3);
9826
+ }
9827
+ };
9828
+ const handleChainChanged = (chainIdHex) => {
9829
+ const newChainId = parseInt(chainIdHex, 16);
9830
+ console.log("[Provider] chainChanged event:", newChainId);
9831
+ setUser(
9832
+ (prev) => prev ? { ...prev, lastWalletChainId: newChainId } : null
9833
+ );
9834
+ window.location.reload();
9835
+ };
9836
+ const handleDisconnect = () => {
9837
+ console.log("[Provider] disconnect event");
9838
+ setProviderState(null);
9839
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9840
+ };
9841
+ ethereum.on("accountsChanged", handleAccountsChanged);
9842
+ ethereum.on("chainChanged", handleChainChanged);
9843
+ ethereum.on("disconnect", handleDisconnect);
9844
+ return () => {
9845
+ ethereum.removeListener("accountsChanged", handleAccountsChanged);
9846
+ ethereum.removeListener("chainChanged", handleChainChanged);
9847
+ ethereum.removeListener("disconnect", handleDisconnect);
9848
+ };
9849
+ }, [user, logout, setUser, setProviderState]);
9850
+ }
9851
+
9852
+ // src/utils/json.ts
9679
9853
  function serializeBigIntDeep(obj) {
9680
9854
  if (obj === null || obj === void 0) {
9681
9855
  return obj;
@@ -9697,8 +9871,62 @@ function serializeBigIntDeep(obj) {
9697
9871
  }
9698
9872
  return obj;
9699
9873
  }
9874
+
9875
+ // src/react/hooks/useVolrActions.ts
9876
+ function useVolrActions({
9877
+ client,
9878
+ setError
9879
+ }) {
9880
+ const precheck = react.useCallback(
9881
+ async (input) => {
9882
+ try {
9883
+ setError(null);
9884
+ await client.ensureAccessToken();
9885
+ const response = await client.post(
9886
+ "/wallet/precheck",
9887
+ input
9888
+ );
9889
+ return response.quote;
9890
+ } catch (err) {
9891
+ const error = err instanceof Error ? err : new Error("Precheck failed");
9892
+ const diag = err?.response?.data?.error?.developerDiagnostics;
9893
+ if (diag) {
9894
+ console.error("[volr][precheck] developerDiagnostics:", diag);
9895
+ }
9896
+ setError(error);
9897
+ throw error;
9898
+ }
9899
+ },
9900
+ [client, setError]
9901
+ );
9902
+ const relay = react.useCallback(
9903
+ async (input, opts) => {
9904
+ try {
9905
+ setError(null);
9906
+ await client.ensureAccessToken();
9907
+ const idempotencyKey = opts?.idempotencyKey ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
9908
+ const serializedInput = serializeBigIntDeep(input);
9909
+ const response = await client.post(
9910
+ "/wallet/relay",
9911
+ serializedInput,
9912
+ idempotencyKey
9913
+ );
9914
+ return response;
9915
+ } catch (err) {
9916
+ const error = err instanceof Error ? err : new Error("Relay failed");
9917
+ const diag = err?.response?.data?.error?.developerDiagnostics;
9918
+ if (diag) {
9919
+ console.error("[volr][relay] developerDiagnostics:", diag);
9920
+ }
9921
+ setError(error);
9922
+ throw error;
9923
+ }
9924
+ },
9925
+ [client, setError]
9926
+ );
9927
+ return { precheck, relay };
9928
+ }
9700
9929
  function VolrProvider({ config, children }) {
9701
- const REQUIRE_USER_GESTURE_TO_RESTORE = true;
9702
9930
  const providerCountRef = react.useRef(0);
9703
9931
  react.useEffect(() => {
9704
9932
  providerCountRef.current++;
@@ -9747,45 +9975,14 @@ function VolrProvider({ config, children }) {
9747
9975
  const [refreshToken, setRefreshTokenState] = react.useState(() => {
9748
9976
  return client.getRefreshToken();
9749
9977
  });
9750
- const syncRef = react.useRef(null);
9751
- react.useEffect(() => {
9752
- syncRef.current = new SessionSync();
9753
- const unsubscribe = syncRef.current.subscribe((event) => {
9754
- if (event.type === "LOGIN") {
9755
- client.setAccessToken(event.payload.accessToken);
9756
- setAccessTokenState(event.payload.accessToken);
9757
- setUser(event.payload.user);
9758
- safeStorage.setItem(
9759
- STORAGE_KEYS.user,
9760
- JSON.stringify(event.payload.user)
9761
- );
9762
- client.setApiKey(config.projectApiKey);
9763
- } else if (event.type === "LOGOUT") {
9764
- client.setAccessToken(null);
9765
- client.setRefreshToken(null);
9766
- setAccessTokenState(null);
9767
- setRefreshTokenState(null);
9768
- setUser(null);
9769
- setProviderState(null);
9770
- safeStorage.removeItem(STORAGE_KEYS.user);
9771
- safeStorage.removeItem(STORAGE_KEYS.provider);
9772
- } else if (event.type === "REFRESH") {
9773
- client.setAccessToken(event.payload.accessToken);
9774
- setAccessTokenState(event.payload.accessToken);
9775
- } else if (event.type === "PROVIDER_SET") {
9776
- setUser((prev) => ({
9777
- ...prev,
9778
- keyStorageType: event.payload.keyStorageType,
9779
- address: event.payload.address
9780
- }));
9781
- safeStorage.setItem(STORAGE_KEYS.provider, event.payload.keyStorageType);
9782
- }
9783
- });
9784
- return () => {
9785
- unsubscribe();
9786
- syncRef.current?.destroy();
9787
- };
9788
- }, [client, config.projectApiKey]);
9978
+ const syncRef = useSessionSync({
9979
+ client,
9980
+ config,
9981
+ setAccessTokenState,
9982
+ setRefreshTokenState,
9983
+ setUser,
9984
+ setProviderState
9985
+ });
9789
9986
  const setProvider = react.useCallback(
9790
9987
  async (newProvider) => {
9791
9988
  try {
@@ -9817,108 +10014,22 @@ function VolrProvider({ config, children }) {
9817
10014
  throw error2;
9818
10015
  }
9819
10016
  },
9820
- [client, user]
9821
- );
9822
- const hasRecoveredRef = react.useRef(false);
9823
- const restorationAttemptedRef = react.useRef(false);
9824
- react.useEffect(() => {
9825
- if (config.autoRecoverOnLogin !== false && !hasRecoveredRef.current) {
9826
- hasRecoveredRef.current = true;
9827
- const recover = async () => {
9828
- try {
9829
- setIsLoading(true);
9830
- if (!client.getRefreshToken()) {
9831
- console.log("[Provider] No refresh token found, skipping auto-recover");
9832
- setIsLoading(false);
9833
- return;
9834
- }
9835
- const refreshedUser = await client.refreshSession();
9836
- console.log("[Provider] Session refreshed, user:", refreshedUser);
9837
- setAccessTokenState(client.getAccessToken());
9838
- setRefreshTokenState(client.getRefreshToken());
9839
- if (refreshedUser) {
9840
- setUser(refreshedUser);
9841
- if (!REQUIRE_USER_GESTURE_TO_RESTORE) ; else if (refreshedUser.keyStorageType === "passkey") {
9842
- console.log("[Provider] TTL=0 mode: Provider restoration deferred");
9843
- if (!refreshedUser.blobUrl || !refreshedUser.prfInput) {
9844
- console.warn("[Provider] Passkey user missing blobUrl or prfInput");
9845
- }
9846
- }
9847
- } else {
9848
- const userStr = safeStorage.getItem(STORAGE_KEYS.user);
9849
- if (userStr) {
9850
- try {
9851
- setUser(JSON.parse(userStr));
9852
- } catch {
9853
- safeStorage.removeItem(STORAGE_KEYS.user);
9854
- }
9855
- }
9856
- }
9857
- } catch {
9858
- client.setAccessToken(null);
9859
- client.setRefreshToken(null);
9860
- setAccessTokenState(null);
9861
- setRefreshTokenState(null);
9862
- setUser(null);
9863
- setProviderState(null);
9864
- safeStorage.removeItem(STORAGE_KEYS.user);
9865
- safeStorage.removeItem(STORAGE_KEYS.provider);
9866
- } finally {
9867
- setIsLoading(false);
9868
- }
9869
- };
9870
- recover();
9871
- } else {
9872
- setIsLoading(false);
9873
- }
9874
- }, [client, config.autoRecoverOnLogin]);
9875
- const precheck = react.useCallback(
9876
- async (input) => {
9877
- try {
9878
- setError(null);
9879
- await client.ensureAccessToken();
9880
- const response = await client.post(
9881
- "/wallet/precheck",
9882
- input
9883
- );
9884
- return response.quote;
9885
- } catch (err) {
9886
- const error2 = err instanceof Error ? err : new Error("Precheck failed");
9887
- const diag = err?.response?.data?.error?.developerDiagnostics;
9888
- if (diag) {
9889
- console.error("[volr][precheck] developerDiagnostics:", diag);
9890
- }
9891
- setError(error2);
9892
- throw error2;
9893
- }
9894
- },
9895
- [client]
9896
- );
9897
- const relay = react.useCallback(
9898
- async (input, opts) => {
9899
- try {
9900
- setError(null);
9901
- await client.ensureAccessToken();
9902
- const idempotencyKey = opts?.idempotencyKey ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
9903
- const serializedInput = serializeBigIntDeep(input);
9904
- const response = await client.post(
9905
- "/wallet/relay",
9906
- serializedInput,
9907
- idempotencyKey
9908
- );
9909
- return response;
9910
- } catch (err) {
9911
- const error2 = err instanceof Error ? err : new Error("Relay failed");
9912
- const diag = err?.response?.data?.error?.developerDiagnostics;
9913
- if (diag) {
9914
- console.error("[volr][relay] developerDiagnostics:", diag);
9915
- }
9916
- setError(error2);
9917
- throw error2;
9918
- }
9919
- },
9920
- [client]
10017
+ [client, user, syncRef]
9921
10018
  );
10019
+ useAutoRecover({
10020
+ client,
10021
+ config,
10022
+ setAccessTokenState,
10023
+ setRefreshTokenState,
10024
+ setUser,
10025
+ setProviderState,
10026
+ setProvider,
10027
+ setIsLoading
10028
+ });
10029
+ const { precheck, relay } = useVolrActions({
10030
+ client,
10031
+ setError
10032
+ });
9922
10033
  const logout = react.useCallback(async () => {
9923
10034
  try {
9924
10035
  setError(null);
@@ -9938,45 +10049,13 @@ function VolrProvider({ config, children }) {
9938
10049
  setError(error2);
9939
10050
  throw error2;
9940
10051
  }
9941
- }, [client]);
9942
- react.useEffect(() => {
9943
- if (!user) return;
9944
- if (typeof window === "undefined" || !window.ethereum) {
9945
- return;
9946
- }
9947
- const ethereum = window.ethereum;
9948
- const handleAccountsChanged = (accounts) => {
9949
- console.log("[Provider] accountsChanged event:", accounts);
9950
- if (accounts.length === 0) {
9951
- console.warn("[Provider] Wallet disconnected, logging out...");
9952
- setTimeout(() => logout(), 3e3);
9953
- } else if (user.evmAddress && accounts[0].toLowerCase() !== user.evmAddress.toLowerCase()) {
9954
- console.warn("[Provider] Account changed, logging out in 3 seconds...");
9955
- setTimeout(() => logout(), 3e3);
9956
- }
9957
- };
9958
- const handleChainChanged = (chainIdHex) => {
9959
- const newChainId = parseInt(chainIdHex, 16);
9960
- console.log("[Provider] chainChanged event:", newChainId);
9961
- setUser(
9962
- (prev) => prev ? { ...prev, lastWalletChainId: newChainId } : null
9963
- );
9964
- window.location.reload();
9965
- };
9966
- const handleDisconnect = () => {
9967
- console.log("[Provider] disconnect event");
9968
- setProviderState(null);
9969
- safeStorage.removeItem(STORAGE_KEYS.provider);
9970
- };
9971
- ethereum.on("accountsChanged", handleAccountsChanged);
9972
- ethereum.on("chainChanged", handleChainChanged);
9973
- ethereum.on("disconnect", handleDisconnect);
9974
- return () => {
9975
- ethereum.removeListener("accountsChanged", handleAccountsChanged);
9976
- ethereum.removeListener("chainChanged", handleChainChanged);
9977
- ethereum.removeListener("disconnect", handleDisconnect);
9978
- };
9979
- }, [user, logout, setUser]);
10052
+ }, [client, syncRef]);
10053
+ useWalletEvents({
10054
+ user,
10055
+ logout,
10056
+ setUser,
10057
+ setProviderState
10058
+ });
9980
10059
  const publicValue = react.useMemo(
9981
10060
  () => ({
9982
10061
  config,
@@ -18562,6 +18641,10 @@ function useVolrLogin() {
18562
18641
  setAccessToken(accessToken);
18563
18642
  if (refreshToken) {
18564
18643
  setRefreshToken(refreshToken);
18644
+ } else {
18645
+ console.warn(
18646
+ "[useVolrLogin] Access token received but refresh token is missing. Check if backend is updated to return refresh token in body."
18647
+ );
18565
18648
  }
18566
18649
  if (userFromServer) {
18567
18650
  setUser(toVolrUser(userFromServer));