ghp / packages /client /src /hooks /useAuth.ts
QSLY's picture
deploy: build Hugging Face Space from source
00a912e
Raw
History Blame Contribute Delete
5.2 kB
import { storage } from '@/lib/storage'
import { useSocketContext } from '@/providers/socket-context'
import type { MusicSource, MyPlatformAuth, PlatformAuthStatus } from '@music-together/shared'
import { EVENTS, QR_STATUS } from '@music-together/shared'
import { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
/**
* Hook:管理平台认证 UI 状态
*
* 职责:
* - QR 扫码登录状态(二维码数据、扫码状态、加载状态)
* - 平台认证状态(聚合状态、个人状态)
* - 用户操作方法(requestQrCode、setCookie、logout)
*
* 不负责:
* - Cookie 持久化(由 useAuthSync 处理)
* - Cookie 验证结果 toast(由 useAuthSync 处理)
*/
export function useAuth() {
const { socket } = useSocketContext()
const [platformStatus, setPlatformStatus] = useState<PlatformAuthStatus[]>([])
const [myStatus, setMyStatus] = useState<MyPlatformAuth[]>([])
const [statusLoaded, setStatusLoaded] = useState(false)
const [qrData, setQrData] = useState<{ key: string; qrimg: string } | null>(null)
const [qrStatus, setQrStatus] = useState<{ status: number; message: string } | null>(null)
const [isQrLoading, setIsQrLoading] = useState(false)
const [isClaimingKugouConceptVip, setIsClaimingKugouConceptVip] = useState(false)
const [qrPlatform, setQrPlatform] = useState<MusicSource>('netease')
// Ref 跟踪最新的 qrPlatform,避免重建回调导致重启轮询
const qrPlatformRef = useRef<MusicSource>(qrPlatform)
const qrKeyRef = useRef<string | null>(null)
const qrCompletedRef = useRef(false)
useEffect(() => {
const onStatusUpdate = (data: PlatformAuthStatus[]) => {
setPlatformStatus(data)
}
const onMyStatus = (data: MyPlatformAuth[]) => {
setMyStatus(data)
setStatusLoaded(true)
}
const onQrGenerated = (data: { key: string; qrimg: string }) => {
qrCompletedRef.current = false
qrKeyRef.current = data.key
setQrData(data)
setQrStatus({ status: 801, message: '等待扫码' })
setIsQrLoading(false)
}
const onQrStatus = (data: { status: number; message: string; key?: string }) => {
if (qrCompletedRef.current) return
if (data.key && data.key !== qrKeyRef.current) return
if (data.status === QR_STATUS.SUCCESS || data.status === QR_STATUS.EXPIRED) {
qrCompletedRef.current = true
qrKeyRef.current = null
}
setQrStatus(data)
if (data.status === QR_STATUS.SUCCESS || data.status === QR_STATUS.EXPIRED) {
setIsQrLoading(false)
}
}
const onKugouConceptVipClaim = (data: { success: boolean; message: string }) => {
setIsClaimingKugouConceptVip(false)
if (data.success) toast.success(data.message)
else toast.error(data.message)
}
socket.on(EVENTS.AUTH_STATUS_UPDATE, onStatusUpdate)
socket.on(EVENTS.AUTH_MY_STATUS, onMyStatus)
socket.on(EVENTS.AUTH_QR_GENERATED, onQrGenerated)
socket.on(EVENTS.AUTH_QR_STATUS, onQrStatus)
socket.on(EVENTS.AUTH_CLAIM_KUGOU_CONCEPT_VIP_RESULT, onKugouConceptVipClaim)
// 挂载时拉取当前状态(覆盖延迟挂载场景)
socket.emit(EVENTS.AUTH_GET_STATUS)
return () => {
socket.off(EVENTS.AUTH_STATUS_UPDATE, onStatusUpdate)
socket.off(EVENTS.AUTH_MY_STATUS, onMyStatus)
socket.off(EVENTS.AUTH_QR_GENERATED, onQrGenerated)
socket.off(EVENTS.AUTH_QR_STATUS, onQrStatus)
socket.off(EVENTS.AUTH_CLAIM_KUGOU_CONCEPT_VIP_RESULT, onKugouConceptVipClaim)
}
}, [socket])
const requestQrCode = useCallback(
(platform: MusicSource) => {
setQrData(null)
qrCompletedRef.current = false
qrKeyRef.current = null
setQrStatus(null)
setIsQrLoading(true)
setQrPlatform(platform)
qrPlatformRef.current = platform
socket.emit(EVENTS.AUTH_REQUEST_QR, { platform })
},
[socket],
)
const checkQrStatus = useCallback(
(key: string) => {
if (qrCompletedRef.current || qrKeyRef.current !== key) return
socket.emit(EVENTS.AUTH_CHECK_QR, { key, platform: qrPlatformRef.current })
},
[socket],
)
const setCookie = useCallback(
(platform: MusicSource, cookie: string) => {
socket.emit(EVENTS.AUTH_SET_COOKIE, { platform, cookie })
},
[socket],
)
const logout = useCallback(
(platform: MusicSource) => {
storage.removeAuthCookie(platform)
socket.emit(EVENTS.AUTH_LOGOUT, { platform })
},
[socket],
)
const resetQr = useCallback(() => {
qrCompletedRef.current = true
qrKeyRef.current = null
setQrData(null)
setQrStatus(null)
setIsQrLoading(false)
}, [])
const claimKugouConceptVip = useCallback(() => {
if (isClaimingKugouConceptVip) return
setIsClaimingKugouConceptVip(true)
socket.emit(EVENTS.AUTH_CLAIM_KUGOU_CONCEPT_VIP)
}, [isClaimingKugouConceptVip, socket])
return {
platformStatus,
myStatus,
statusLoaded,
qrData,
qrStatus,
qrPlatform,
isQrLoading,
requestQrCode,
checkQrStatus,
setCookie,
logout,
resetQr,
claimKugouConceptVip,
isClaimingKugouConceptVip,
}
}