Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: web3auth session expiration, and refactor account-popover #476

Merged
merged 3 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/auth/context/web3Auth/config/web3AuthSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export function web3AuthFactory(): Web3Auth {
privateKeyProvider,
accountAbstractionProvider,
chainConfig: chain.polygonAmoy,
storageKey: 'local',
clientId: GLOBAL_CONSTANTS.WEB3_CLIENT_ID,
uiConfig: {
appName: 'Watchit',
Expand Down
3 changes: 1 addition & 2 deletions src/components/activate-subscription-profile-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ export const ActivateSubscriptionProfileModal = ({

onClose?.();
} catch (err) {
console.error('err');
console.error(err);
notifyError(ERRORS.ACTIVATE_SUBSCRIPTION_FAILED_ERROR);
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/custom-popover/use-popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { useCallback, useState } from 'react';

// ----------------------------------------------------------------------

type ReturnType = {
export type UsePopoverReturnType = {
onClose: VoidFunction;
open: HTMLElement | null;
onOpen: (event: React.MouseEvent<HTMLElement>) => void;
setOpen: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
};

export default function usePopover(): ReturnType {
export default function usePopover(): UsePopoverReturnType {
const [open, setOpen] = useState<HTMLElement | null>(null);

const onOpen = useCallback((event: React.MouseEvent<HTMLElement>) => {
Expand Down
188 changes: 188 additions & 0 deletions src/hooks/use-account-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// REACT IMPORTS
import { useEffect, useCallback } from 'react';

// REDUX IMPORTS
import { useDispatch, useSelector } from 'react-redux';
import { setAuthLoading, setSession, setBalance } from '@redux/auth';

// LENS IMPORTS
import { useSession, useLogout } from '@lens-protocol/react-web';

// NOTIFICATIONS IMPORTS
import { ERRORS } from '@notifications/errors';
import { notifyError } from '@notifications/internal-notifications';

// WEB3AUTH IMPORTS
import { useWeb3Auth } from '@src/hooks/use-web3-auth';
import { useWeb3Session } from '@src/hooks/use-web3-session';

// ----------------------------------------------------------------------

interface UseAccountSessionProps {
/**
* Whether to automatically run the expiration checks
* on mount + every interval (default: true).
*/
autoCheck?: boolean;
}

interface UseAccountSessionHook {
/**
* Combined logout for Lens + Web3Auth
*/
logout: () => Promise<void>;

/**
* Check session validity on demand:
* - Verifies Web3Auth is fully connected (or still connecting)
* - Verifies localStorage expiration
* - Logs out + notifies error if invalid
*/
checkSessionValidity: () => void;
}

// ----------------------------------------------------------------------

/**
* This hook consolidates:
* 1. Lens session fetching & Redux updates.
* 2. Web3Auth session validation (connected, bundler, smartAccount).
* 3. LocalStorage expiration checks + auto-logout (optionally).
*
* If `autoCheck` is false, the hook won't run the checks automatically,
* but you can still call `checkSessionValidity()` manually.
*/
export const useAccountSession = (
{ autoCheck = true }: UseAccountSessionProps = {}
): UseAccountSessionHook => {
const dispatch = useDispatch();
const { execute: lensLogout } = useLogout();
const { web3Auth } = useWeb3Auth();
const { data, loading } = useSession();
const isUpdatingMetadata: boolean = useSelector(
(state: any) => state.auth.isUpdatingMetadata
);
const { bundlerClient, smartAccount } = useWeb3Session();

const parsedSessionConnected = JSON.stringify(web3Auth.connected);
const parsedSessionData = JSON.stringify(data);

// Keep Redux in sync with Lens loading state
useEffect(() => {
// If autoCheck is disabled, skip
if (!autoCheck) return;
dispatch(setAuthLoading({ isSessionLoading: loading }));
}, [loading, autoCheck]);

// Keep Redux in sync with actual Lens session data
useEffect(() => {
// If autoCheck is disabled, skip
if (!autoCheck) return;
if (!isUpdatingMetadata) {
dispatch(setSession({ session: data }));
}
}, [parsedSessionData, isUpdatingMetadata, autoCheck]);

// LOGOUT (Lens + Web3Auth)
const logout = useCallback(async () => {
try {
// 1) Logout from Lens
await lensLogout();
// 2) Logout from Web3Auth
await web3Auth?.logout();
// 3) Clear Redux state & localStorage
dispatch(setBalance({ balance: 0 }));
localStorage.removeItem('sessionExpiration');
} catch (err) {
console.error('Error during logout:', err);
localStorage.removeItem('sessionExpiration');
}
}, [lensLogout, web3Auth, dispatch]);

// Decide if Web3Auth is in a valid/connecting state
const isValidWeb3AuthSession = useCallback((): boolean => {
const isConnecting =
web3Auth.status === 'connecting' ||
web3Auth.status === 'not_ready';

const isFullyValid =
web3Auth.connected &&
web3Auth.status === 'connected' &&
!!bundlerClient &&
!!smartAccount;

console.log('isValidWeb3AuthSession')
console.log(isConnecting)
console.log(isFullyValid)
console.log(isConnecting || isFullyValid)

// Return true if either "still connecting" or "fully valid"
return isConnecting || isFullyValid;
}, [web3Auth.connected, web3Auth.status, bundlerClient, smartAccount]);

// If session is invalid or expired, do logout + show error
const handleSessionExpired = useCallback(() => {
logout();
notifyError(ERRORS.BUNDLER_UNAVAILABLE);
}, [logout]);

const checkSessionValidity = useCallback(() => {
console.log('checkSessionValidity')
// 1) If Web3Auth isn't valid (and not just connecting), expire
if (!isValidWeb3AuthSession()) {
handleSessionExpired();
return;
}

// 2) Otherwise, check localStorage for expiration
const expirationStr = localStorage.getItem('sessionExpiration');
if (!expirationStr) return;

const expirationTime = parseInt(expirationStr, 10);
if (Date.now() >= expirationTime) {
handleSessionExpired();
}
}, [isValidWeb3AuthSession, handleSessionExpired]);

// Automatic checks on mount + interval
useEffect(() => {
// If autoCheck is disabled, skip
if (!autoCheck) return;

// If Lens or Web3Auth is still loading, skip checks until loaded
if (loading || web3Auth.status === 'connecting' || web3Auth.status === 'not_ready') return;

// If user is not authenticated in Lens, skip
if (!data?.authenticated) {
return;
}

// If Web3Auth is in "ready" state but not connected, log out
// (meaning: it's done with any connecting state, but the user is not actually connected)
if (!web3Auth.connected && web3Auth.status === 'ready') {
logout();
return;
}

// Check once immediately
checkSessionValidity();

// Then check every 60 seconds
const intervalId = setInterval(() => {
checkSessionValidity();
}, 60 * 1000);

// Cleanup
return () => clearInterval(intervalId);
}, [
autoCheck,
loading,
parsedSessionConnected,
parsedSessionData
]);

return {
logout,
checkSessionValidity,
};
};
29 changes: 15 additions & 14 deletions src/hooks/use-authorize-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { GLOBAL_CONSTANTS } from '@src/config-global.ts';
import { useSelector } from 'react-redux';
import { useWeb3Session } from '@src/hooks/use-web3-session.ts';
import { ERRORS } from '@notifications/errors.ts';
import { useAccountSession } from '@src/hooks/use-account-session.ts';

// ----------------------------------------------------------------------
// Define the return type of the useAuthorizePolicy hook
Expand All @@ -34,6 +35,7 @@ export const useAuthorizePolicy = (): useAuthorizePolicyHook => {
const [error, setError] = useState<keyof typeof ERRORS | null>(null);
const sessionData = useSelector((state: any) => state.auth.session);
const { bundlerClient, smartAccount } = useWeb3Session();
const { checkSessionValidity } = useAccountSession({ autoCheck: false });

/**
* Creates the flash policy agreement data.
Expand All @@ -57,19 +59,19 @@ export const useAuthorizePolicy = (): useAuthorizePolicyHook => {
setLoading(true);
setError(null);

try {
if (!sessionData?.authenticated) {
setError(ERRORS.AUTHORIZATION_POLICY_ERROR);
setLoading(false);
return;
}

if (!bundlerClient) {
setError(ERRORS.BUNDLER_UNAVAILABLE);
setLoading(false);
return;
}
if (!sessionData?.authenticated) {
setError(ERRORS.AUTHORIZATION_POLICY_ERROR);
setLoading(false);
return;
}

if (!bundlerClient) {
checkSessionValidity();
setLoading(false);
throw new Error('Invalid Web3Auth session');
}

try {
// Prepare the authorize policy data
const rightPolicyAuthorizerData = initializeAuthorizePolicy({
policyAddress,
Expand Down Expand Up @@ -100,8 +102,7 @@ export const useAuthorizePolicy = (): useAuthorizePolicyHook => {
setData(receipt);
setLoading(false);
} catch (err: any) {
console.log('err')
console.log(err)
console.error('USE AUTHORIZE POLICY ERR:', err);
setError(ERRORS.UNKNOWN_ERROR);
setLoading(false);
}
Expand Down
29 changes: 14 additions & 15 deletions src/hooks/use-deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MMCAbi from '@src/config/abi/MMC.json';
import { GLOBAL_CONSTANTS } from '@src/config-global';
import { useWeb3Session } from '@src/hooks/use-web3-session.ts';
import { ERRORS } from '@notifications/errors.ts';
import { useAccountSession } from '@src/hooks/use-account-session.ts';

interface DepositParams {
recipient: string; // address
Expand All @@ -25,6 +26,7 @@ export const useDeposit = (): UseDepositHook => {
const [error, setError] = useState<keyof typeof ERRORS | null>(null);
const { bundlerClient, smartAccount } = useWeb3Session();
const sessionData = useSelector((state: any) => state.auth.session);
const { checkSessionValidity } = useAccountSession({ autoCheck: false });

const approveMMC = (amount: number): string => {
// Convert to Wei (assuming 18 decimals)
Expand Down Expand Up @@ -58,19 +60,19 @@ export const useDeposit = (): UseDepositHook => {
setLoading(true);
setError(null);

try {
if (!sessionData?.authenticated) {
setError(ERRORS.DEPOSIT_FAILED_ERROR);
setLoading(false);
return;
}
if (!sessionData?.authenticated) {
setError(ERRORS.DEPOSIT_FAILED_ERROR);
setLoading(false);
return;
}

if (!bundlerClient) {
setError(ERRORS.BUNDLER_UNAVAILABLE);
setLoading(false);
return;
}
if (!bundlerClient) {
checkSessionValidity();
setLoading(false);
throw new Error('Invalid Web3Auth session');
}

try {
const approveData = approveMMC(amount);
const depositData = initializeDeposit({ recipient, amount });

Expand Down Expand Up @@ -102,11 +104,8 @@ export const useDeposit = (): UseDepositHook => {
setData(receipt);
setLoading(false);
} catch (err: any) {

console.error('USE DEPOSIT ERR:', err);
setError(ERRORS.UNKNOWN_ERROR);

console.error('USE DEPOSIT:', err);

setLoading(false);
}
};
Expand Down
29 changes: 15 additions & 14 deletions src/hooks/use-subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { GLOBAL_CONSTANTS } from '@src/config-global.ts';
import { useSelector } from 'react-redux';
import { useWeb3Session } from '@src/hooks/use-web3-session.ts';
import { ERRORS } from '@notifications/errors.ts';
import { useAccountSession } from '@src/hooks/use-account-session.ts';

// ----------------------------------------------------------------------

Expand Down Expand Up @@ -44,6 +45,7 @@ export const useSubscribe = (): UseSubscribeHook => {
const [error, setError] = useState<keyof typeof ERRORS | null>(null);
const sessionData = useSelector((state: any) => state.auth.session);
const { bundlerClient, smartAccount } = useWeb3Session();
const { checkSessionValidity } = useAccountSession({ autoCheck: false });

const transferToAccessAgreement = (approvalAmount: bigint): string => {
return encodeFunctionData({
Expand Down Expand Up @@ -84,19 +86,19 @@ export const useSubscribe = (): UseSubscribeHook => {
setLoading(true);
setError(null);

try {
if (!sessionData?.authenticated) {
setError(ERRORS.SUBSCRIBE_LOGIN_ERROR);
setLoading(false);
return;
}

if (!bundlerClient) {
setError(ERRORS.BUNDLER_UNAVAILABLE);
setLoading(false);
return;
}
if (!sessionData?.authenticated) {
setError(ERRORS.SUBSCRIBE_LOGIN_ERROR);
setLoading(false);
return;
}

if (!bundlerClient) {
checkSessionValidity();
setLoading(false);
throw new Error('Invalid Web3Auth session');
}

try {
const approvalAmountInWei = ethers.parseUnits(amount, 18); // Convert amount to BigInt (in Wei)
const parties = [sessionData?.profile?.ownedBy.address]; // The parties involved in the agreement (e.g., the user's address)
const payload = '0x'; // Additional payload data if needed
Expand Down Expand Up @@ -141,8 +143,7 @@ export const useSubscribe = (): UseSubscribeHook => {
setData(receipt);
setLoading(false);
} catch (err: any) {
console.log('err:');
console.log(err);
console.error('USE SUBSCRIBE ERR:', err);
setError(ERRORS.UNKNOWN_ERROR);
setLoading(false);
}
Expand Down
Loading
Loading