Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

chore (examples): improve synchronization between tabs#40

Merged
dbarrosop merged 3 commits into
mainfrom
sync-tabs
Jun 30, 2025
Merged

chore (examples): improve synchronization between tabs#40
dbarrosop merged 3 commits into
mainfrom
sync-tabs

Conversation

@dbarrosop

@dbarrosop dbarrosop commented Jun 27, 2025

Copy link
Copy Markdown
Member

PR Type

Enhancement, Documentation


Description

  • Introduce AuthProvider for robust client-side auth context in Next.js demo

  • Enable cross-tab and server-client session synchronization via refresh token

  • Refactor all client components to use new useAuth hook

  • Improve and document session handling in React/React-Apollo/React-Query guides


Changes walkthrough 📝

Relevant files
Enhancement
10 files
AuthProvider.tsx
Add advanced AuthProvider for client-side auth/session sync
+187/-0 
layout.tsx
Wrap app in AuthProvider for global auth context                 
+12/-9   
SecurityKeyClient.tsx
Refactor to use useAuth instead of direct client creation
+2/-2     
change-password.tsx
Refactor to use useAuth for Nhost client access                   
+2/-3     
mfa-settings.tsx
Refactor to use useAuth for Nhost client access                   
+2/-2     
client.tsx
Refactor to use useAuth for Nhost client access                   
+2/-2     
index.tsx
Remove obsolete direct Nhost client creation utility         
+0/-22   
AuthProvider.tsx
Enhance AuthProvider with session sync and documentation 
+98/-12 
AuthProvider.tsx
Enhance AuthProvider with session sync and documentation 
+99/-12 
AuthProvider.tsx
Enhance AuthProvider with session sync and documentation 
+99/-12 
Documentation
1 files
README.md
Document AuthProvider and session sync in Next.js demo     
+10/-1   

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions

    Copy link
    Copy Markdown

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Session Synchronization Logic

    The new AuthProvider implements cross-tab and server-client session synchronization using refresh tokens and event listeners. The logic for detecting session changes and refreshing the page is complex and critical for authentication consistency. The reviewer should carefully validate that session state is always accurate, that router.refresh() is only called when necessary, and that there are no race conditions or edge cases (e.g., rapid tab switching, session expiration, or sign-out in another tab) that could cause stale or inconsistent authentication state.

    export const AuthProvider = ({ children }: AuthProviderProps) => {
      const [user, setUser] = useState<Session["user"] | null>(null);
      const [session, setSession] = useState<Session | null>(null);
      const [isLoading, setIsLoading] = useState<boolean>(true);
      const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
      const lastRefreshTokenIdRef = useRef<string | null>(null);
      const router = useRouter();
    
      // Initialize Nhost client with cookie-based storage for server/client session sharing
      const nhost = useMemo(
        () =>
          createClient({
            region: process.env["NHOST_REGION"] || "local",
            subdomain: process.env["NHOST_SUBDOMAIN"] || "local",
            storage: new CookieStorage({
              secure: process.env.NODE_ENV === "production",
              sameSite: "lax",
            }),
          }),
        [],
      );
    
      /**
       * Handles session reload when refresh token changes.
       * This detects when the session has been updated by middleware or other tabs.
       *
       * @param currentRefreshTokenId - The current refresh token ID to compare against stored value
       */
      const reloadSession = (currentRefreshTokenId: string | null) => {
        if (currentRefreshTokenId !== lastRefreshTokenIdRef.current) {
          lastRefreshTokenIdRef.current = currentRefreshTokenId;
    
          // Update local authentication state to match current session
          const currentSession = nhost.getUserSession();
          setUser(currentSession?.user || null);
          setSession(currentSession);
          setIsAuthenticated(!!currentSession);
    
          // Trigger Next.js page refresh to sync server-side state with client changes
          router.refresh();
        }
      };
    
      // Initialize authentication state and set up cross-tab session synchronization
      useEffect(() => {
        setIsLoading(true);
    
        // Load initial session state from Nhost client
        const currentSession = nhost.getUserSession();
        setUser(currentSession?.user || null);
        setSession(currentSession);
        setIsAuthenticated(!!currentSession);
        lastRefreshTokenIdRef.current = currentSession?.refreshTokenId ?? null;
        setIsLoading(false);
    
        // Subscribe to session changes from other browser tabs
        // This enables real-time synchronization when user signs in/out in another tab
        const unsubscribe = nhost.sessionStorage.onChange((session) => {
          reloadSession(session?.refreshTokenId ?? null);
        });
    
        return unsubscribe;
      }, [nhost]);
    
      // Handle session changes from server-side middleware and page focus events
      useEffect(() => {
        /**
         * Checks for session changes when page becomes visible or focused.
         * This catches middleware-driven session updates that occur server-side.
         */
        const checkSessionOnFocus = () => {
          reloadSession(nhost.getUserSession()?.refreshTokenId ?? null);
        };
    
        // Monitor page visibility changes (tab switching, window minimizing)
        document.addEventListener("visibilitychange", () => {
          if (!document.hidden) {
            checkSessionOnFocus();
          }
        });
    
        // Monitor window focus events (clicking back into the browser window)
        window.addEventListener("focus", checkSessionOnFocus);
    
        // Cleanup event listeners on component unmount
        return () => {
          document.removeEventListener("visibilitychange", checkSessionOnFocus);
          window.removeEventListener("focus", checkSessionOnFocus);
        };
      }, [nhost, router]);
    Environment Variable Fallbacks

    The Nhost client initialization uses process.env variables with "local" as a fallback. The reviewer should ensure that these fallbacks are appropriate for all deployment scenarios and that sensitive configuration is not accidentally exposed or misconfigured, especially in production.

    const nhost = useMemo(
      () =>
        createClient({
          region: process.env["NHOST_REGION"] || "local",
          subdomain: process.env["NHOST_SUBDOMAIN"] || "local",
          storage: new CookieStorage({
            secure: process.env.NODE_ENV === "production",
            sameSite: "lax",
          }),
        }),
    Provider Placement

    The AuthProvider now wraps the entire application layout, including Navigation and main content. The reviewer should verify that this does not introduce hydration mismatches or unexpected context propagation issues in Next.js, particularly with server/client component boundaries.

    <AuthProvider>
      <div className="flex-col min-h-screen">
        <Navigation />
        <main className="max-w-2xl mx-auto p-6 w-full">{children}</main>
        <footer>
          <p className="text-sm" style={{ color: "var(--text-muted)" }}>
            © {new Date().getFullYear()} Nhost Demo
          </p>
        </footer>
      </div>
    </AuthProvider>

    @github-actions

    Copy link
    Copy Markdown

    PR Code Suggestions ✨

    No code suggestions found for the PR.

    @dbarrosop dbarrosop merged commit dd866fb into main Jun 30, 2025
    10 checks passed
    @dbarrosop dbarrosop deleted the sync-tabs branch June 30, 2025 11:02
    Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants