(ns outliner.boundaries.auth (:require [outliner.model.core.logic :as logic] [outliner.model.state :as state] [outliner.boundaries.firebase :as fb] [outliner.boundaries.storage :as storage] [outliner.control.system :as system])) (defn is-mobile? [] (let [ua (.. js/window -navigator -userAgent) ;; Check for common mobile strings in UA is-mobile-ua? (boolean (re-find #"(?i)iPhone|iPad|iPod|Android|Mobi|Tablet" ua)) ;; Check for touch support (catches iPad in desktop mode) has-touch? (and (.-maxTouchPoints js/navigator) (> (.-maxTouchPoints js/navigator) 0))] (or is-mobile-ua? has-touch?))) (defn on-auth-error [err] (let [code (.-code err) message (.-message err) hostname js/location.hostname] (if (= code "auth/unauthorized-domain") (js/alert (str "Login failed: This domain (" hostname ") is not authorized in Firebase.\n\n" "RECIPE TO FIX:\n" "1. Open the Firebase Console.\n" "2. Go to 'Authentication' -> 'Settings' tab.\n" "3. Click on 'Authorized domains' in the left menu.\n" "4. Click 'Add domain' and enter '" hostname "'.\n" "5. Wait a minute and try logging in again.")) (js/alert (str "Login failed: " message))))) (defn sign-in-with-google [] (if-not (and js/fb (.-auth js/fb) js/firebaseAuth) (do (fb/init-firebase!) (js/alert "Firebase is still initializing. Please wait a second and try again.")) (try (let [provider (new (-> js/fb .-auth .-GoogleAuthProvider))] ;; Always attempt popup first, even on mobile. ;; Redirect is more reliable on mobile Safari due to ITP, but popups are better when they work. (js/console.log "Attempting signInWithPopup") (-> ((-> js/fb .-auth .-signInWithPopup) js/firebaseAuth provider) (.catch (fn [err] (js/console.error "Popup error:" err) (if (or (= (.-code err) "auth/popup-blocked") (= (.-code err) "auth/popup-closed-by-user") (is-mobile?)) (do (js/console.log "Falling back to signInWithRedirect") (.setItem js/sessionStorage "pending_redirect" "true") ((-> js/fb .-auth .-signInWithRedirect) js/firebaseAuth provider)) (on-auth-error err)))))) (catch :default e (js/console.error "Login exception:" e) (js/alert (str "An unexpected error occurred: " (.-message e))))))) (defn get-redirect-result [] (when (and js/fb (.-auth js/fb) js/firebaseAuth) ((-> js/fb .-auth .-getRedirectResult) js/firebaseAuth))) (defn sign-out [] (when (and js/fb (.-auth js/fb) js/firebaseAuth) ((-> js/fb .-auth .-signOut) js/firebaseAuth))) (defn on-auth-state-change [callback] (when (and js/fb (.-auth js/fb) js/firebaseAuth) ((-> js/fb .-auth .-onAuthStateChanged) js/firebaseAuth callback))) (defn- process-redirect-result! [] (let [redirect-promise (get-redirect-result)] (when redirect-promise (-> redirect-promise (.then (fn [result] (.removeItem js/sessionStorage "pending_redirect") (when result (js/console.log "Auth redirect successful")))) (.catch (fn [error] (.removeItem js/sessionStorage "pending_redirect") (js/console.error "Auth redirect error:" error) (on-auth-error error))))))) (defn- apply-doc! [doc user-id] (let [doc-with-system (system/inject-system-nodes doc user-id) zoom-id (:zoom-id @state/view-state)] (reset! state/doc-state doc-with-system) (when zoom-id (system/handle-node-expanded doc-with-system @state/sys-state zoom-id)))) (defn- on-user-login! [user] (js/console.log "Auth state changed: User logged in" (fb/get-user-display-name user)) (storage/cleanup-sync state/sys-state) (swap! state/sys-state assoc :user user :sync-status :loading) (storage/load-state user (fn [loaded-doc] (let [user-id (fb/get-user-uid user) local-doc (storage/load-local nil)] (swap! state/sys-state assoc :sync-status :synced) (cond (system/has-user-nodes? loaded-doc) (apply-doc! loaded-doc user-id) (system/has-user-nodes? local-doc) (do (js/console.log "Cloud state empty, falling back to local.") (apply-doc! local-doc user-id)) :else (state/reset-to-initial-state!))) (storage/setup-sync user state/doc-state state/sys-state)))) (defn- on-user-logout! [] (js/console.log "Auth state changed: User logged out") (storage/cleanup-sync state/sys-state) (swap! state/sys-state assoc :user nil :sync-status :disconnected) (let [local-doc (storage/load-local nil)] (if (system/has-user-nodes? local-doc) (apply-doc! local-doc nil) (state/reset-to-initial-state!)))) (defn init-auth-state [] (if-not (and js/fb (.-auth js/fb) js/firebaseAuth) (do (js/console.log "init-auth-state: Firebase not ready, waiting...") (js/setTimeout init-auth-state 500)) (try (let [pending-redirect? (.getItem js/sessionStorage "pending_redirect")] (process-redirect-result!) (if-let [unsub-fn (on-auth-state-change (fn [user] (cond user (on-user-login! user) pending-redirect? (js/console.log "Auth state changed: Still waiting for redirect result") :else (on-user-logout!))))] (do (js/console.log "onAuthStateChanged listener registered") unsub-fn) (do (js/console.warn "Firebase Auth not available, skipping listener registration") (let [local-doc (storage/load-local nil)] (if (system/has-user-nodes? local-doc) (apply-doc! local-doc nil) (state/reset-to-initial-state!)))))) (catch :default e (js/console.error "Error in init-auth-state:" e)))))