File size: 5,203 Bytes
00a912e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | 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,
}
}
|