hololake-system-architecture/product-source/hololake-platform/src/hooks/useHoloLakeAccount.ts

140 lines
4.2 KiB
TypeScript
Raw Normal View History

import { invoke } from '@tauri-apps/api/core'
import { useCallback, useEffect, useState } from 'react'
import { trackEvent } from '../lib/telemetry'
import { isTauri } from '../mock-tauri'
type AccountPhase = 'checking' | 'signed-out' | 'code-sent' | 'authenticated' | 'busy' | 'error'
type AccountState = {
error: string | null
expiresAt: number | null
lastCommit: string | null
phase: AccountPhase
requestId: string | null
}
type NativeAccountStatus = {
expires_at?: number
state?: 'authenticated' | 'signed_out'
}
type NativeCodeRequest = {
request_id?: string
}
type NativeKnowledgeReceipt = {
commit?: string
}
const initialState: AccountState = {
error: null,
expiresAt: null,
lastCommit: null,
phase: 'checking',
requestId: null,
}
function errorText(error: unknown): string {
if (error instanceof Error) return error.message
return String(error)
}
export function useHoloLakeAccount() {
const [state, setState] = useState<AccountState>(initialState)
const refresh = useCallback(async () => {
if (!isTauri()) {
setState((current) => ({ ...current, phase: 'signed-out' }))
return
}
try {
const status = await invoke<NativeAccountStatus>('hololake_account_status')
setState((current) => ({
...current,
error: null,
expiresAt: status.expires_at ?? null,
phase: status.state === 'authenticated' ? 'authenticated' : 'signed-out',
}))
} catch (error) {
setState((current) => ({ ...current, error: errorText(error), phase: 'signed-out' }))
}
}, [])
useEffect(() => {
void refresh()
}, [refresh])
const requestCode = useCallback(async (email: string) => {
setState((current) => ({ ...current, error: null, phase: 'busy' }))
trackEvent('hololake_account_code_requested', { surface: 'account-panel' })
try {
const result = await invoke<NativeCodeRequest>('hololake_account_request_email_code', { email })
if (!result.request_id) throw new Error('hololake_login_request_id_missing')
setState((current) => ({
...current,
phase: 'code-sent',
requestId: result.request_id ?? null,
}))
} catch (error) {
setState((current) => ({ ...current, error: errorText(error), phase: 'error' }))
}
}, [])
const verifyCode = useCallback(async (code: string) => {
if (!state.requestId) return
setState((current) => ({ ...current, error: null, phase: 'busy' }))
try {
const result = await invoke<NativeAccountStatus>('hololake_account_verify_email_code', {
requestId: state.requestId,
code,
})
trackEvent('hololake_account_login_completed', { result: 'authenticated' })
setState((current) => ({
...current,
expiresAt: result.expires_at ?? null,
phase: 'authenticated',
requestId: null,
}))
} catch (error) {
trackEvent('hololake_account_login_completed', { result: 'error' })
setState((current) => ({ ...current, error: errorText(error), phase: 'code-sent' }))
}
}, [state.requestId])
const syncKnowledge = useCallback(async () => {
setState((current) => ({ ...current, error: null, phase: 'busy' }))
trackEvent('hololake_knowledge_sync_requested', { repository: 'hololake-knowledge-base' })
try {
const result = await invoke<NativeKnowledgeReceipt>('hololake_sync_knowledge')
trackEvent('hololake_knowledge_sync_completed', { result: 'success' })
setState((current) => ({
...current,
lastCommit: result.commit ?? null,
phase: 'authenticated',
}))
} catch (error) {
trackEvent('hololake_knowledge_sync_completed', { result: 'error' })
setState((current) => ({ ...current, error: errorText(error), phase: 'authenticated' }))
}
}, [])
const logout = useCallback(async () => {
setState((current) => ({ ...current, error: null, phase: 'busy' }))
try {
await invoke('hololake_account_logout')
trackEvent('hololake_account_logout_completed')
setState(initialState)
await refresh()
} catch (error) {
setState((current) => ({ ...current, error: errorText(error), phase: 'authenticated' }))
}
}, [refresh])
return {
logout,
requestCode,
state,
syncKnowledge,
verifyCode,
}
}