feat: 更新应用版本号,优化用户数据获取逻辑,简化认证流程
This commit is contained in:
parent
43857594d2
commit
6bb78c11cd
|
|
@ -10,7 +10,7 @@ export const IOS_UNIVERSAL_LINK = 'duooomi.bowong.cn'
|
||||||
// 原生版本,原生代码变更时需要更新此版本号
|
// 原生版本,原生代码变更时需要更新此版本号
|
||||||
export const VERSION = '1.5.0'
|
export const VERSION = '1.5.0'
|
||||||
// JavaScript版本,JS代码变更时需要更新此版本号
|
// JavaScript版本,JS代码变更时需要更新此版本号
|
||||||
export const APP_VERSION = 'dev202602041525'
|
export const APP_VERSION = 'dev202602041658'
|
||||||
|
|
||||||
const ALIPAY_SCHEMA = 'alipay2021006119657394'
|
const ALIPAY_SCHEMA = 'alipay2021006119657394'
|
||||||
const ALIPAY_SCHEMA_SANDBOX = 'alipay9021000158673972'
|
const ALIPAY_SCHEMA_SANDBOX = 'alipay9021000158673972'
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,6 @@ const CustomTabBar = observer(function CustomTabBar(props: any) {
|
||||||
const activeRouteName = routes[activeIndex]?.name || 'index'
|
const activeRouteName = routes[activeIndex]?.name || 'index'
|
||||||
|
|
||||||
const handleChange = (route: string) => {
|
const handleChange = (route: string) => {
|
||||||
// if (!isLogin && route !== 'index') {
|
|
||||||
// props.navigation.push('auth')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
props.navigation.navigate(route)
|
props.navigation.navigate(route)
|
||||||
}
|
}
|
||||||
return <BottomNav currentRoute={activeRouteName} onRouteChange={handleChange} />
|
return <BottomNav currentRoute={activeRouteName} onRouteChange={handleChange} />
|
||||||
|
|
@ -74,23 +70,11 @@ export default function Layout() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
router.replace('(tabs)/sync')
|
router.replace('/(tabs)/sync')
|
||||||
}, 100) // 小延迟确保导航准备好
|
}, 100) // 小延迟确保导航准备好
|
||||||
|
|
||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
}, [router])
|
}, [router])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 配置WebBrowser
|
|
||||||
// const setupWebBrowser = async () => {
|
|
||||||
// try {
|
|
||||||
// await configureWebBrowser()
|
|
||||||
// } catch (error) {
|
|
||||||
// console.warn('Failed to configure WebBrowser:', error)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// setupWebBrowser()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <Tabs screenOptions={{ headerShown: false }} tabBar={(props: any) => <CustomTabBar {...props} />}></Tabs>
|
return <Tabs screenOptions={{ headerShown: false }} tabBar={(props: any) => <CustomTabBar {...props} />}></Tabs>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ const Index = observer(function Index() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('expo env------------', process.env.EXPO_PUBLIC_ENV)
|
console.log('expo env------------', process.env.EXPO_PUBLIC_ENV)
|
||||||
|
userStore.getUserData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** ================= refs(核心) ================= */
|
/** ================= refs(核心) ================= */
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import '../global.css'
|
import '../global.css'
|
||||||
import 'react-native-reanimated'
|
import 'react-native-reanimated'
|
||||||
|
|
||||||
import { DarkTheme, DefaultTheme, ThemeProvider, useIsFocused } from '@react-navigation/native'
|
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
|
||||||
import * as Sentry from '@sentry/react-native'
|
import * as Sentry from '@sentry/react-native'
|
||||||
import { useKeepAwake } from 'expo-keep-awake'
|
import { useKeepAwake } from 'expo-keep-awake'
|
||||||
import { router, Stack, useNavigationContainerRef, usePathname } from 'expo-router'
|
import { Stack, useNavigationContainerRef, usePathname } from 'expo-router'
|
||||||
import { ShareIntentProvider } from 'expo-share-intent'
|
import { ShareIntentProvider } from 'expo-share-intent'
|
||||||
import { StatusBar } from 'expo-status-bar'
|
import { StatusBar } from 'expo-status-bar'
|
||||||
import { useCallback, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||||
import { KeyboardProvider } from 'react-native-keyboard-controller'
|
import { KeyboardProvider } from 'react-native-keyboard-controller'
|
||||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'
|
||||||
|
|
@ -17,8 +17,6 @@ import { bleManager } from '@/ble/managers/bleManager'
|
||||||
import { HotUpdate } from '@/components/hot-update'
|
import { HotUpdate } from '@/components/hot-update'
|
||||||
import { useColorScheme } from '@/hooks/use-color-scheme'
|
import { useColorScheme } from '@/hooks/use-color-scheme'
|
||||||
import { setupGlobalFetchLogger } from '@/lib/fetch-logger'
|
import { setupGlobalFetchLogger } from '@/lib/fetch-logger'
|
||||||
import { useUserSession } from '@/stores/userStore'
|
|
||||||
import { storage } from '@/utils'
|
|
||||||
|
|
||||||
const isProd = process.env.EXPO_PUBLIC_ENV !== 'development'
|
const isProd = process.env.EXPO_PUBLIC_ENV !== 'development'
|
||||||
|
|
||||||
|
|
@ -40,6 +38,8 @@ export const unstable_settings = {
|
||||||
function RootLayout() {
|
function RootLayout() {
|
||||||
const ref = useNavigationContainerRef()
|
const ref = useNavigationContainerRef()
|
||||||
|
|
||||||
|
const currentRoute = usePathname()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref?.current) return
|
if (!ref?.current) return
|
||||||
|
|
||||||
|
|
@ -92,15 +92,15 @@ function RootLayout() {
|
||||||
<ShareIntentProvider>
|
<ShareIntentProvider>
|
||||||
<Providers>
|
<Providers>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="auth" options={{ headerShown: false }} />
|
<Stack.Screen name="auth" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="forgotPassword" options={{ headerShown: false }} />
|
<Stack.Screen name="forgotPassword" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="pointList" options={{ headerShown: false }} />
|
<Stack.Screen name="pointList" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="webview" options={{ headerShown: false }} />
|
<Stack.Screen name="webview" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="scan" options={{ headerShown: false }} />
|
<Stack.Screen name="scan" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="settings" options={{ headerShown: false }} />
|
<Stack.Screen name="settings" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="service" options={{ headerShown: false }} />
|
<Stack.Screen name="service" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="privacy" options={{ headerShown: false }} />
|
<Stack.Screen name="privacy" options={{ headerShown: false }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Providers>
|
</Providers>
|
||||||
</ShareIntentProvider>
|
</ShareIntentProvider>
|
||||||
|
|
@ -110,24 +110,16 @@ function RootLayout() {
|
||||||
function Providers({ children }: { children: React.ReactNode }) {
|
function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
const currentRoute = usePathname()
|
const currentRoute = usePathname()
|
||||||
|
// const session = useUserSession()
|
||||||
|
|
||||||
useUserSession()
|
|
||||||
useKeepAwake()
|
useKeepAwake()
|
||||||
|
|
||||||
const isFocused = useIsFocused()
|
// useEffect(() => {
|
||||||
|
// if (session.isPending) return
|
||||||
const loadLogin = useCallback(async () => {
|
// if (!isLoggedIn && !currentRoute.includes('/auth')) {
|
||||||
const isLogin = await storage.get('isLogin')
|
// router.replace('/auth')
|
||||||
if (!isLogin && currentRoute !== '/auth') {
|
// }
|
||||||
router.replace('/auth')
|
// }, [session.isPending, isLoggedIn, currentRoute])
|
||||||
}
|
|
||||||
}, [currentRoute])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isFocused) {
|
|
||||||
loadLogin()
|
|
||||||
}
|
|
||||||
}, [isFocused, loadLogin])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
|
|
|
||||||
94
app/auth.tsx
94
app/auth.tsx
|
|
@ -8,7 +8,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
|
||||||
|
|
||||||
import { APP_VERSION, VERSION } from '@/app.config'
|
import { APP_VERSION, VERSION } from '@/app.config'
|
||||||
import BannerSection from '@/components/BannerSection'
|
import BannerSection from '@/components/BannerSection'
|
||||||
import { getSession, phoneNumber, setAuthToken, signIn } from '@/lib/auth'
|
import { phoneNumber, setAuthToken, signIn } from '@/lib/auth'
|
||||||
import { isValidPhone } from '@/utils'
|
import { isValidPhone } from '@/utils'
|
||||||
|
|
||||||
const APP_NAME = '多米'
|
const APP_NAME = '多米'
|
||||||
|
|
@ -33,50 +33,19 @@ export default function Auth() {
|
||||||
const [currentBranch, setCurrentBranch] = useState<string>('unknown')
|
const [currentBranch, setCurrentBranch] = useState<string>('unknown')
|
||||||
const countdownTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
const countdownTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||||
|
|
||||||
// 获取当前分支信息
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getBranchInfo = async () => {
|
if (!Updates.isEnabled) {
|
||||||
try {
|
setCurrentBranch('development')
|
||||||
if (!Updates.isEnabled) {
|
return
|
||||||
setCurrentBranch('development')
|
}
|
||||||
return
|
const manifest = Updates.manifest
|
||||||
}
|
if (manifest?.extra?.eas?.branch) {
|
||||||
|
setCurrentBranch(manifest.extra.eas.branch)
|
||||||
// 方法1: 使用 Updates.manifest (同步)
|
} else if (Updates.channel) {
|
||||||
const manifest = Updates.manifest
|
setCurrentBranch(Updates.channel)
|
||||||
|
} else {
|
||||||
// 方法2: 或者尝试从更新中获取
|
setCurrentBranch('production')
|
||||||
let branch = 'production' // 默认值
|
|
||||||
|
|
||||||
if (manifest?.extra?.eas?.branch) {
|
|
||||||
branch = manifest.extra.eas.branch
|
|
||||||
} else if (manifest?.metadata?.branchName) {
|
|
||||||
branch = manifest.metadata.branchName
|
|
||||||
} else if (Updates.channel) {
|
|
||||||
branch = Updates.channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法3: 尝试获取当前运行的更新信息
|
|
||||||
try {
|
|
||||||
const currentlyRunning = await Updates.fetchUpdateAsync()
|
|
||||||
if (currentlyRunning.manifest?.extra?.eas?.branch) {
|
|
||||||
branch = currentlyRunning.manifest.extra.eas.branch
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to fetch current update:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentBranch(branch)
|
|
||||||
console.log('Current branch:', branch)
|
|
||||||
console.log('Updates.channel:', Updates.channel)
|
|
||||||
console.log('Manifest:', manifest)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to get branch info:', error)
|
|
||||||
setCurrentBranch('unknown')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBranchInfo()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const canSendCode = useMemo(() => {
|
const canSendCode = useMemo(() => {
|
||||||
|
|
@ -174,29 +143,24 @@ export default function Auth() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
Toast.showLoading({ title: '正在验证...' })
|
Toast.showLoading({ title: '正在验证...' })
|
||||||
try {
|
try {
|
||||||
// 验证验证码,如果后端配置了 signUpOnVerification,新用户会自动注册并创建 session
|
const result = await phoneNumber.verify(
|
||||||
// disableSession: false 表示验证成功后创建 session(自动登录)
|
{ phoneNumber: phone, code: code, disableSession: false },
|
||||||
await new Promise<void>((resolve, reject) => {
|
{
|
||||||
phoneNumber.verify(
|
onSuccess: async (ctx: any) => {
|
||||||
{ phoneNumber: phone, code: code, disableSession: false },
|
const authToken = ctx.response.headers.get('set-auth-token')
|
||||||
{
|
if (authToken) {
|
||||||
onSuccess: async (ctx: any) => {
|
setAuthToken(authToken)
|
||||||
const authToken = ctx.response.headers.get('set-auth-token')
|
}
|
||||||
if (authToken) {
|
|
||||||
setAuthToken(authToken)
|
|
||||||
}
|
|
||||||
await getSession()
|
|
||||||
Toast.show({ title: '登录成功!' })
|
|
||||||
setTimeout(() => {
|
|
||||||
router.replace('/(tabs)')
|
|
||||||
}, 500)
|
|
||||||
},
|
|
||||||
onError: (ctx: any) => {
|
|
||||||
reject(new Error(ctx.error.message))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
})
|
)
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
Toast.show({ title: result.error.message || '验证失败' })
|
||||||
|
} else {
|
||||||
|
Toast.show({ title: '登录成功!' })
|
||||||
|
router.replace('/(tabs)')
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('登录/注册失败:', error)
|
console.error('登录/注册失败:', error)
|
||||||
Toast.show({ title: error.message || '操作失败,请稍后重试' })
|
Toast.show({ title: error.message || '操作失败,请稍后重试' })
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ export const authClient = createAuthClient({
|
||||||
trustedOrigins: ['duooomi://', 'https://api.mixvideo.bowong.cc'],
|
trustedOrigins: ['duooomi://', 'https://api.mixvideo.bowong.cc'],
|
||||||
storage,
|
storage,
|
||||||
scheme: 'duooomi',
|
scheme: 'duooomi',
|
||||||
|
sessionOptions: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
headers: {
|
headers: {
|
||||||
'x-ownerid': OWNER_ID,
|
'x-ownerid': OWNER_ID,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
import { storage as storage2 } from '../utils/storage'
|
|
||||||
import { storage } from './storage.native'
|
|
||||||
|
|
||||||
interface FetchLoggerOptions {
|
interface FetchLoggerOptions {
|
||||||
enableLogging?: boolean
|
enableLogging?: boolean
|
||||||
logRequest?: boolean
|
logRequest?: boolean
|
||||||
|
|
@ -26,26 +23,7 @@ export const createFetchWithLogger = (options: FetchLoggerOptions = {}) => {
|
||||||
const method = init?.method || 'GET'
|
const method = init?.method || 'GET'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await storage.getItem('token')
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
init = {
|
|
||||||
...init,
|
|
||||||
headers: {
|
|
||||||
...init?.headers,
|
|
||||||
authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await originalFetch(input, init)
|
const response = await originalFetch(input, init)
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
console.warn('🔐 401 未授权,清空登录状态')
|
|
||||||
// router.replace('/auth')
|
|
||||||
storage2.set('isLogin', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import { root } from '@repo/core'
|
import { root } from '@repo/core'
|
||||||
import { DeviceController } from '@repo/sdk'
|
import { DeviceController } from '@repo/sdk'
|
||||||
import * as Application from 'expo-application'
|
import * as Application from 'expo-application'
|
||||||
|
import { router } from 'expo-router'
|
||||||
import { makeAutoObservable, runInAction } from 'mobx'
|
import { makeAutoObservable, runInAction } from 'mobx'
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
|
|
||||||
import { signOut as authSignOut, useSession } from '@/lib/auth'
|
import { getSession, signOut as authSignOut } from '@/lib/auth'
|
||||||
import { storage } from '@/utils'
|
import { storage } from '@/utils'
|
||||||
|
|
||||||
// console.log('useSession---------------', useSession)
|
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string
|
id: string
|
||||||
email: string
|
email: string
|
||||||
|
|
@ -33,17 +31,17 @@ interface User {
|
||||||
interface Session {
|
interface Session {
|
||||||
user?: User
|
user?: User
|
||||||
session?: {
|
session?: {
|
||||||
id: string
|
|
||||||
userId: string
|
|
||||||
expiresAt: Date
|
expiresAt: Date
|
||||||
|
token: string
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
|
ipAddress?: string | null
|
||||||
|
userAgent?: string | null
|
||||||
|
userId: string
|
||||||
|
impersonatedBy?: string | null
|
||||||
activeOrganizationId?: string | null
|
activeOrganizationId?: string | null
|
||||||
activeTeamId?: string | null
|
activeTeamId?: string | null
|
||||||
impersonatedBy?: string | null
|
id: string
|
||||||
ipAddress?: string | null
|
|
||||||
token?: string | null
|
|
||||||
userAgent?: string | null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,18 +75,31 @@ class UserStore {
|
||||||
await storage.set('isLogin', !!user)
|
await storage.set('isLogin', !!user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置会话信息
|
async getUserData() {
|
||||||
async setSession(session: Session | null) {
|
const sessionRes = await getSession()
|
||||||
runInAction(() => {
|
|
||||||
this.session = session
|
|
||||||
this.user = session?.user || null
|
|
||||||
this.isLogin = !!session?.user
|
|
||||||
|
|
||||||
if (session?.user) {
|
// console.log('getUserData sessionRes--------:', JSON.stringify(sessionRes))
|
||||||
this.bindDevice()
|
|
||||||
}
|
const data = sessionRes.data
|
||||||
|
if (!data) {
|
||||||
|
this.reset()
|
||||||
|
router.replace('/auth')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session, user } = data
|
||||||
|
|
||||||
|
// console.log('获取用户信息--------:', JSON.stringify(data))
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.session = { session, user }
|
||||||
|
this.user = user || null
|
||||||
|
this.isLogin = !!user
|
||||||
})
|
})
|
||||||
await storage.set('isLogin', !!session?.user)
|
|
||||||
|
if (user?.id) {
|
||||||
|
this.bindDevice()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置加载状态
|
// 设置加载状态
|
||||||
|
|
@ -118,18 +129,13 @@ class UserStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从session更新用户状态
|
|
||||||
updateFromSession(sessionData: Session | null) {
|
|
||||||
this.setSession(sessionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置store状态
|
// 重置store状态
|
||||||
reset() {
|
reset() {
|
||||||
this.user = null
|
|
||||||
this.session = null
|
this.session = null
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.error = null
|
this.error = null
|
||||||
this.scannedQR = null
|
this.scannedQR = null
|
||||||
|
this.setUser(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 二维码相关方法 ====================
|
// ==================== 二维码相关方法 ====================
|
||||||
|
|
@ -180,16 +186,3 @@ class UserStore {
|
||||||
|
|
||||||
// 创建单例实例
|
// 创建单例实例
|
||||||
export const userStore = new UserStore()
|
export const userStore = new UserStore()
|
||||||
|
|
||||||
// 创建一个hook来使用session数据更新store
|
|
||||||
export const useUserSession = () => {
|
|
||||||
const session = useSession()
|
|
||||||
|
|
||||||
// 使用useEffect在副作用中更新store状态,避免在render过程中修改
|
|
||||||
useEffect(() => {
|
|
||||||
userStore.setLoading(session.isPending)
|
|
||||||
userStore.updateFromSession(session.data)
|
|
||||||
}, [session.isPending, session.data])
|
|
||||||
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue