mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
# Description of Changes Adds the code for the SaaS frontend as proprietary code to the OSS repo. This version of the code is adapted from 22/1/2026, which was the last SaaS version based on the 'V2' design. This will move us closer to being able to have the OSS products understand whether the user has a SaaS account, and provide the correct UI in those cases.
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react'
|
|
import { useLocation } from 'react-router-dom'
|
|
import { useAuth } from '@app/auth/UseSession'
|
|
import { signInAnonymously, supabase } from '@app/auth/supabase'
|
|
import { isAuthRoute, isHomeRoute, isToolRoute } from '@app/utils/pathUtils'
|
|
|
|
interface AutoAnonymousAuthState {
|
|
isAutoAuthenticating: boolean
|
|
autoAuthError: string | null
|
|
shouldTriggerAutoAuth: boolean
|
|
}
|
|
|
|
/**
|
|
* Automatically signs in users anonymously on direct tool access when not authenticated.
|
|
*/
|
|
export function useAutoAnonymousAuth() {
|
|
const { session, loading } = useAuth()
|
|
const location = useLocation()
|
|
const [state, setState] = useState<AutoAnonymousAuthState>({
|
|
isAutoAuthenticating: false,
|
|
autoAuthError: null,
|
|
shouldTriggerAutoAuth: false
|
|
})
|
|
|
|
const shouldAutoAuthenticate = useCallback((): boolean => {
|
|
const currentPath = location.pathname
|
|
if (isAuthRoute(currentPath)) return false
|
|
if (isHomeRoute(currentPath)) return false
|
|
return isToolRoute(currentPath)
|
|
}, [location.pathname])
|
|
|
|
const waitForToken = useCallback(async (timeoutMs = 7000) => {
|
|
// Prefer event-driven approach; fallback to polling if needed
|
|
let resolved = false
|
|
const done = (ok: boolean) => {
|
|
resolved = true
|
|
return ok
|
|
}
|
|
|
|
const hasToken = async () => {
|
|
const { data: { session } } = await supabase.auth.getSession()
|
|
return !!session?.access_token
|
|
}
|
|
|
|
if (await hasToken()) return true
|
|
|
|
const unsub = supabase.auth.onAuthStateChange(async (_evt, _session) => {
|
|
if (!resolved && (await hasToken())) {
|
|
unsub.data.subscription.unsubscribe()
|
|
done(true)
|
|
}
|
|
})
|
|
|
|
const started = Date.now()
|
|
while (!resolved && Date.now() - started < timeoutMs) {
|
|
if (await hasToken()) {
|
|
unsub.data.subscription.unsubscribe()
|
|
return done(true)
|
|
}
|
|
// gentle backoff
|
|
await new Promise(r => setTimeout(r, 120))
|
|
}
|
|
|
|
try { unsub.data.subscription.unsubscribe() } catch (err) {
|
|
// Ignore unsubscribe errors during cleanup
|
|
console.debug('[useAutoAnonymousAuth] Unsubscribe cleanup error:', err)
|
|
}
|
|
return done(false)
|
|
}, [])
|
|
|
|
const triggerAnonymousAuth = useCallback(async () => {
|
|
if (state.isAutoAuthenticating) return
|
|
|
|
setState(prev => ({ ...prev, isAutoAuthenticating: true, autoAuthError: null }))
|
|
try {
|
|
console.log('[useAutoAnonymousAuth] anonymous auth starting')
|
|
|
|
const { error } = await signInAnonymously()
|
|
if (error) throw error
|
|
|
|
// Wait for a usable token so first API calls won't 401/redirect
|
|
const ok = await waitForToken(7000)
|
|
if (!ok) {
|
|
throw new Error('Timed out waiting for anonymous session token')
|
|
}
|
|
|
|
console.log('[useAutoAnonymousAuth] anonymous auth complete')
|
|
setState(prev => ({ ...prev, isAutoAuthenticating: false, shouldTriggerAutoAuth: false }))
|
|
} catch (e) {
|
|
console.error('[useAutoAnonymousAuth] anonymous auth failed', e)
|
|
setState(prev => ({
|
|
...prev,
|
|
isAutoAuthenticating: false,
|
|
autoAuthError: e instanceof Error ? e.message : 'Anonymous authentication failed'
|
|
}))
|
|
}
|
|
}, [state.isAutoAuthenticating, waitForToken])
|
|
|
|
// Decide whether to auto-auth on mount & whenever location/auth changes
|
|
useEffect(() => {
|
|
if (loading) return
|
|
if (session) return
|
|
if (state.isAutoAuthenticating) return
|
|
|
|
const shouldAuth = shouldAutoAuthenticate()
|
|
if (state.shouldTriggerAutoAuth !== shouldAuth) {
|
|
setState(prev => ({ ...prev, shouldTriggerAutoAuth: shouldAuth }))
|
|
}
|
|
|
|
if (shouldAuth) {
|
|
console.log('[useAutoAnonymousAuth] tool route detected, auto-auth')
|
|
triggerAnonymousAuth()
|
|
}
|
|
}, [loading, session, state.isAutoAuthenticating, state.shouldTriggerAutoAuth, shouldAutoAuthenticate, triggerAnonymousAuth])
|
|
|
|
// Clear error if route is no longer a tool route, or once authenticated
|
|
useEffect(() => {
|
|
if (session || !shouldAutoAuthenticate()) {
|
|
setState(prev => ({ ...prev, autoAuthError: null, shouldTriggerAutoAuth: false }))
|
|
}
|
|
}, [session, shouldAutoAuthenticate])
|
|
|
|
return {
|
|
isAutoAuthenticating: state.isAutoAuthenticating,
|
|
autoAuthError: state.autoAuthError,
|
|
shouldTriggerAutoAuth: state.shouldTriggerAutoAuth,
|
|
triggerAnonymousAuth
|
|
}
|
|
} |