Jake Vanderwerf
112 mins ago 3baf3d2545ba6ece6b74a64c0def59bd0774cf54
assets/js/concise/AuthManager.js
@@ -17,8 +17,6 @@
      this.nonces = {};
      this.subscribers = new Set();
      this.storageKey = 'jvb_auth_state';
      this.cacheMetaKey = 'jvb_auth_meta';
      this.cacheExpiry = 5 * 60 * 1000; // 5 minutes
      this.init();
@@ -28,36 +26,23 @@
    * Initialize authentication
    */
   async init() {
      if (this.isAuthenticating) {
         // Wait for existing auth to complete
         return new Promise(resolve => {
            const checkAuth = setInterval(() => {
               if (this.initialized) {
                  clearInterval(checkAuth);
                  resolve();
               }
            }, 50);
         });
      }
      if (this.isAuthenticating) return;
      this.isAuthenticating = true;
      try {
         // Check if we have cached auth and cookie hasn't changed
         const cached = this.getCachedAuth();
         if (cached) {
            this.setAuthData(cached);
         // Inlined by wp_localize_script — zero cost
         if (typeof jvbAuth !== 'undefined') {
            this.setAuthData(jvbAuth);
            this.initialized = true;
            this.isAuthenticating = false;
            this.notify('auth-loaded', { fromCache: true });
            this.notify('auth-loaded', { fromCache: false });
            return;
         }
         // Fetch fresh auth data
         // Fallback: REST fetch (Redis-backed, fast server-side)
         await this.fetchAuth();
      } catch (error) {
         console.error('Failed to initialize auth:', error);
         this.clearAuthData();
         this.initialized = true;
         this.isAuthenticating = false;
@@ -66,37 +51,70 @@
   }
   /**
    * Refresh nonce if authentication fails
    */
   async refreshNonce(action = 'wp_rest') {
      try {
         await this.fetchAuth();
         return this.getNonce(action);
      } catch (error) {
         console.error('Failed to refresh nonce:', error);
         return null;
      }
   }
   /**
    * Fetch with automatic nonce refresh on auth failure
    * Use this for all authenticated API requests
    */
   async fetch(url, options = {}) {
      const attempt = async (retryCount = 0) => {
         const isFormData = options.body instanceof FormData;
         const headers = {
            ...(!isFormData && { 'Content-Type': 'application/json' }),
            ...options.headers,
            'X-WP-Nonce': this.getNonce()
         };
         const response = await fetch(url, {
            ...options,
            credentials: 'same-origin',
            headers
         });
         if ((response.status === 403 || response.status === 401) && retryCount === 0) {
            const result = await response.clone().json();
            if (result.code === 'rest_cookie_invalid_nonce' || result.message?.includes('Cookie check')) {
               console.log('Nonce invalid, refreshing auth...');
               await this.refresh();
               return attempt(retryCount + 1);
            }
         }
         return response;
      };
      return attempt();
   }
   /**
    * Fetch authentication status from API
    */
   async fetchAuth() {
      const response = await fetch(`${jvbSettings.api}auth/status`, {
         method: 'GET',
         credentials: 'same-origin',
         headers: {
            'Content-Type': 'application/json'
         }
         headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) {
         throw new Error('Auth check failed');
      }
      if (!response.ok) throw new Error('Auth check failed');
      const authData = await response.json();
      // Check if session changed (e.g., logout in another tab)
      const cachedMeta = sessionStorage.getItem(this.cacheMetaKey);
      if (cachedMeta) {
         const meta = JSON.parse(cachedMeta);
         if (meta.session_id && meta.session_id !== authData.session_id) {
            this.clearCachedAuth();
            this.notify('session-changed', {});
         }
      }
      this.cacheAuth(authData);
      this.setAuthData(authData);
      this.initialized = true;
      this.isAuthenticating = false;
      this.notify('auth-loaded', { fromCache: false });
   }
@@ -104,9 +122,17 @@
    * Set authentication data
    */
   setAuthData(authData) {
      const wasAuthenticated = this.initialized && this.authenticated;
      this.authenticated = authData.authenticated || false;
      this.user = authData.user || false;
      this.nonces = authData.nonces || {};
      console.log(this.nonces);
      // Session expired — was logged in, now isn't
      if (wasAuthenticated && !this.authenticated) {
         window.location.href = `/login?redirect_to=${encodeURIComponent(window.location.href)}`;
      }
   }
   /**
@@ -116,62 +142,8 @@
      this.authenticated = false;
      this.user = null;
      this.nonces = {};
      sessionStorage.removeItem(this.storageKey);
      sessionStorage.removeItem(this.cacheMetaKey );
   }
   /**
    * Get cached auth data (only if cookie matches)
    */
   getCachedAuth() {
      try {
         const cachedAuth = sessionStorage.getItem(this.storageKey);
         const cacheMeta = sessionStorage.getItem(this.cacheMetaKey);
         if (!cachedAuth || !cacheMeta) {
            return null;
         }
         const meta = JSON.parse(cacheMeta);
         const authData = JSON.parse(cachedAuth);
         // Time-based expiry (nonce freshness)
         if (Date.now() - meta.timestamp > this.cacheExpiry) {
            this.clearCachedAuth();
            return null;
         }
         // Session changed (login/logout in another tab/window)
         // We'll verify this on next fetch and clear if mismatched
         return authData;
      } catch (error) {
         console.error('Error reading cached auth:', error);
         return null;
      }
   }
   /**
    * Cache auth data in sessionStorage
    */
   cacheAuth(authData) {
      try {
         sessionStorage.setItem(this.storageKey, JSON.stringify(authData));
         sessionStorage.setItem(this.cacheMetaKey, JSON.stringify({
            session_id: authData.session_id || null,
            timestamp: Date.now()
         }));
      } catch (error) {
         console.error('Error caching auth:', error);
      }
   }
   clearCachedAuth() {
      sessionStorage.removeItem(this.storageKey);
      sessionStorage.removeItem(this.cacheMetaKey);
   }
   /**
    * Refresh authentication (force new fetch)
@@ -211,21 +183,13 @@
    * Handle successful login (call after login completes)
    */
   async handleLogin(authData = null) {
      // Clear old cache
      sessionStorage.removeItem(this.storageKey);
      sessionStorage.removeItem(this.cacheMetaKey);
      // If auth data provided, use it directly
      if (authData) {
         this.cacheAuth(authData);
         this.setAuthData(authData);
         this.initialized = true;
         this.isAuthenticating = false;
         this.notify('auth-loaded', { fromCache: false, fromLogin: true });
         return;
      }
      // Otherwise fetch fresh (for backward compatibility)
      await this.refresh();
   }
@@ -267,23 +231,6 @@
      });
   }
   /**
    * Wait for auth to be ready
    */
   ready() {
      if (this.initialized) {
         return Promise.resolve();
      }
      return new Promise(resolve => {
         const unsubscribe = this.subscribe((event) => {
            if (event === 'auth-loaded' || event === 'auth-error') {
               unsubscribe();
               resolve();
            }
         });
      });
   }
}
// Initialize global instance