Download public/js/auth.js from sharktide/chat: direct link, hf CLI and curl.
- Browser
- Download file 8.67 kB
-
https://huggingface.co/spaces/sharktide/chat/resolve/d70ec8d3f4aca138791e55700f892f79c7339f75/public/js/auth.js
- Command line
-
hf download hf://spaces/sharktide/chat@d70ec8d3f4aca138791e55700f892f79c7339f75/public/js/auth.js
-
curl -L -o auth.js https://huggingface.co/spaces/sharktide/chat/resolve/d70ec8d3f4aca138791e55700f892f79c7339f75/public/js/auth.js
8.67 kB
| // auth.js - Authentication state and Supabase integration | |
| import { send, on } from './ws.js'; | |
| const SUPABASE_URL = 'https://dpixehhdbtzsbckfektd.supabase.co'; | |
| const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRwaXhlaGhkYnR6c2Jja2Zla3RkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjExNDI0MjcsImV4cCI6MjA3NjcxODQyN30.nR1KCSRQj1E_evQWnE2VaZzg7PgLp2kqt4eDKP2PkpE'; | |
| const AUTH_KEY = 'ipai_auth_v1'; | |
| const TEMP_ID_KEY = 'ipai_temp_id'; | |
| const CLIENT_ID_KEY= 'ipai_client_id'; | |
| export let currentUser = null; | |
| export let userProfile = null; | |
| export let userSettings = null; | |
| export let subscriptionInfo = null; | |
| const authListeners = new Set(); | |
| export function onAuthChange(fn) { authListeners.add(fn); return () => authListeners.delete(fn); } | |
| export function isAuthenticated() { return !!currentUser; } | |
| function notifyListeners() { | |
| authListeners.forEach(fn => fn({ currentUser, userProfile, userSettings })); | |
| } | |
| export function getClientId() { | |
| let id = localStorage.getItem(CLIENT_ID_KEY); | |
| if (!id) { id = `web-${crypto.randomUUID()}`; localStorage.setItem(CLIENT_ID_KEY, id); } | |
| return id; | |
| } | |
| export function getTempId() { | |
| let id = localStorage.getItem(TEMP_ID_KEY); | |
| if (!id) { id = crypto.randomUUID(); localStorage.setItem(TEMP_ID_KEY, id); } | |
| return id; | |
| } | |
| export function saveAuth(data) { localStorage.setItem(AUTH_KEY, JSON.stringify(data)); } | |
| export function loadAuth() { try { return JSON.parse(localStorage.getItem(AUTH_KEY) || 'null'); } catch { return null; } } | |
| export function clearAuth() { localStorage.removeItem(AUTH_KEY); } | |
| // ββ Supabase REST helpers βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function supabaseFetch(path, options = {}) { | |
| const token = options._useToken || SUPABASE_ANON_KEY; | |
| delete options._useToken; | |
| const res = await fetch(`${SUPABASE_URL}${path}`, { | |
| ...options, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| apikey: SUPABASE_ANON_KEY, | |
| Authorization: `Bearer ${token}`, | |
| ...(options.headers || {}), | |
| }, | |
| }); | |
| return res.json(); | |
| } | |
| export async function loginWithEmail(email, password) { | |
| const data = await supabaseFetch('/auth/v1/token?grant_type=password', { | |
| method: 'POST', body: JSON.stringify({ email, password }), | |
| }); | |
| if (data.error) throw new Error(data.error_description || data.error); | |
| await handleSupabaseSession(data); | |
| return data; | |
| } | |
| export async function signUpWithEmail(email, password) { | |
| const data = await supabaseFetch('/auth/v1/signup', { | |
| method: 'POST', body: JSON.stringify({ email, password }), | |
| }); | |
| if (data.error) throw new Error(data.error_description || data.error); | |
| if (data.access_token) await handleSupabaseSession(data); | |
| return data; | |
| } | |
| export async function loginWithOAuth(provider) { | |
| const redirectTo = encodeURIComponent(`${location.origin}/oauth-callback`); | |
| const url = `${SUPABASE_URL}/auth/v1/authorize?provider=${provider}&redirect_to=${redirectTo}`; | |
| window.open(url, '_blank', 'width=520,height=640,noopener'); | |
| } | |
| export async function logout() { | |
| const auth = loadAuth(); | |
| if (auth?.access_token) { | |
| try { | |
| await supabaseFetch('/auth/v1/logout', { | |
| method: 'POST', _useToken: auth.access_token, | |
| }); | |
| } catch {} | |
| } | |
| send({ type: 'auth:logout' }); | |
| clearAuth(); | |
| currentUser = null; userProfile = null; userSettings = null; subscriptionInfo = null; | |
| notifyListeners(); | |
| updateSidebarProfile(); | |
| initAsGuest(); | |
| } | |
| // ββ Session handling ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function handleSupabaseSession(data) { | |
| if (!data.access_token) throw new Error('No access token'); | |
| saveAuth({ access_token: data.access_token, refresh_token: data.refresh_token, user: data.user }); | |
| return new Promise((resolve, reject) => { | |
| const tempId = getTempId(); | |
| const clientId = getClientId(); | |
| send({ type: 'auth:login', accessToken: data.access_token, refreshToken: data.refresh_token, | |
| tempId, clientId }); | |
| const unsubOk = on('auth:ok', (msg) => { unsubOk(); unsubErr(); applyAuthOk(msg); resolve(msg); }); | |
| const unsubErr = on('auth:error', (msg) => { unsubOk(); unsubErr(); reject(new Error(msg.message)); }); | |
| setTimeout(() => { unsubOk(); unsubErr(); reject(new Error('Auth timeout')); }, 12000); | |
| }); | |
| } | |
| function applyAuthOk(msg) { | |
| currentUser = { id: msg.userId, email: msg.email }; | |
| userProfile = msg.profile; | |
| userSettings = msg.settings; | |
| const auth = loadAuth() || {}; | |
| saveAuth({ ...auth, userId: msg.userId, deviceToken: msg.deviceToken }); | |
| notifyListeners(); | |
| updateSidebarProfile(); | |
| // Apply saved theme | |
| if (msg.settings?.theme) { | |
| import('./settings.js').then(m => m.applyTheme(msg.settings.theme)); | |
| } | |
| } | |
| function initAsGuest() { | |
| send({ type: 'auth:guest', tempId: getTempId() }); | |
| } | |
| // ββ WS events βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| on('auth:newLogin', (msg) => { | |
| import('./ui.js').then(({ showNotification }) => { | |
| showNotification({ | |
| type: 'warning', | |
| message: `New login detected from ${msg.ip || 'unknown location'}`, | |
| action: { label: 'View', onClick: () => import('./settings.js').then(m => m.openSettings('account')) }, | |
| duration: 8000, | |
| }); | |
| }); | |
| }); | |
| on('auth:forcedLogout', (msg) => { | |
| import('./ui.js').then(({ showNotification }) => { | |
| showNotification({ type: 'error', message: msg.reason || 'Session revoked', duration: 5000 }); | |
| }); | |
| setTimeout(() => logout(), 1500); | |
| }); | |
| on('settings:updated', (msg) => { | |
| if (msg.settings) { | |
| userSettings = msg.settings; | |
| import('./settings.js').then(m => m.applyTheme(msg.settings.theme)); | |
| } | |
| }); | |
| // ββ Reconnect on WS connect ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| on('ws:connected', async () => { | |
| const auth = loadAuth(); | |
| if (auth?.access_token) { | |
| try { await handleSupabaseSession(auth); } | |
| catch { clearAuth(); initAsGuest(); } | |
| } else { | |
| initAsGuest(); | |
| } | |
| }); | |
| // ββ OAuth popup message βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.addEventListener('message', async (e) => { | |
| if (e.origin !== location.origin) return; | |
| if (e.data?.type !== 'oauth:callback') return; | |
| const { access_token, refresh_token } = e.data; | |
| if (!access_token) return; | |
| try { await handleSupabaseSession({ access_token, refresh_token }); } | |
| catch (err) { | |
| import('./ui.js').then(({ showNotification }) => | |
| showNotification({ type: 'error', message: `Sign-in failed: ${err.message}`, duration: 4000 })); | |
| } | |
| }); | |
| // Also handle if page was opened after OAuth redirect (no popup) | |
| (function checkOAuthRedirect() { | |
| const params = new URLSearchParams(location.search); | |
| const t = params.get('t'), r = params.get('r'); | |
| if (params.get('oauth') === '1' && t) { | |
| history.replaceState({}, '', '/'); | |
| handleSupabaseSession({ access_token: t, refresh_token: r || '' }).catch(() => {}); | |
| } | |
| })(); | |
| // ββ Sidebar profile βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export function updateSidebarProfile() { | |
| const guestEl = document.getElementById('guest-section'); | |
| const userEl = document.getElementById('user-section'); | |
| const nameEl = document.getElementById('user-name-display'); | |
| const planEl = document.getElementById('user-plan-display'); | |
| const avatarEl= document.getElementById('user-avatar'); | |
| if (!currentUser) { | |
| guestEl?.classList.remove('hidden'); | |
| userEl?.classList.add('hidden'); | |
| return; | |
| } | |
| guestEl?.classList.add('hidden'); | |
| userEl?.classList.remove('hidden'); | |
| const username = userProfile?.username || currentUser.email?.split('@')[0] || '?'; | |
| if (nameEl) nameEl.textContent = username; | |
| if (avatarEl) avatarEl.textContent = username[0].toUpperCase(); | |
| const plan = subscriptionInfo?.planKey || 'free'; | |
| const planName = subscriptionInfo?.planName || 'Free'; | |
| if (planEl) { planEl.textContent = planName; planEl.setAttribute('data-plan', plan); } | |
| } | |
| on('auth:ok', updateSidebarProfile); | |
| on('auth:loggedOut', updateSidebarProfile); | |