unread.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import { ref, computed, reactive } from 'vue'
  2. import { apiRequest } from '@/api/client'
  3. import { emptyUnread, type UnreadConversation, type UnreadSnapshot } from '@shared/unread'
  4. const createUnreadStore=()=>{
  5. const snapshot=ref<UnreadSnapshot>(emptyUnread()),error=ref(''),scope=ref('')
  6. let epoch=0,revision=0,timer:ReturnType<typeof setInterval>|undefined,inflight:Promise<void>|undefined
  7. function reset(account='') { epoch++;revision++;scope.value=account;snapshot.value=emptyUnread();error.value='';inflight=undefined;clearInterval(timer);timer=undefined }
  8. function accept(value:UnreadSnapshot){
  9. snapshot.value=value;error.value=''
  10. void window.yaoyaoDesktop?.unreadState?.(scope.value).catch(()=>{})
  11. }
  12. async function refresh(){
  13. if(!scope.value)return
  14. if(inflight)return inflight
  15. const generation=epoch,request=revision
  16. const pending=apiRequest<UnreadSnapshot>('/api/app/unread').then(value=>{
  17. if(generation!==epoch||request!==revision)return
  18. accept(value)
  19. }).catch(e=>{if(generation===epoch&&request===revision)error.value=e instanceof Error?e.message:'未读同步失败'})
  20. inflight=pending;try{await pending}finally{if(inflight===pending)inflight=undefined}
  21. }
  22. function start(account:string){reset(account);void window.yaoyaoDesktop?.unreadState?.(account).catch(()=>{});void refresh();timer=setInterval(()=>void refresh(),3000)}
  23. async function read(items:UnreadConversation[]){
  24. const generation=epoch,account=scope.value,request=++revision;inflight=undefined
  25. const value=await apiRequest<UnreadSnapshot>('/api/app/unread/read',{method:'POST',body:JSON.parse(JSON.stringify({items:items.map(({mode,id,profile,messages})=>({mode,id,profile,messages}))}))})
  26. if(generation!==epoch||account!==scope.value)return
  27. if(request===revision){accept(value);return}
  28. // Concurrent reads can finish out of order; fetch the latest authority.
  29. revision++;inflight=undefined;await refresh()
  30. }
  31. async function visible(mode:'bot'|'chat',id:string,profile:string|undefined,ids:string[]){
  32. if(!ids.length||!scope.value)return
  33. const row=snapshot.value.conversations.find(c=>c.mode===mode&&c.id===id&&(mode==='bot'||c.profile===profile))
  34. if(!row)return
  35. const selected=new Set(ids),messages=row.messages.filter(m=>selected.has(m.id))
  36. if(messages.length)await read([{...row,messages}])
  37. }
  38. const total=computed(()=>snapshot.value.total)
  39. return reactive({snapshot,total,error,reset,start,refresh,read,visible})
  40. }
  41. let store:ReturnType<typeof createUnreadStore>|undefined
  42. export const useUnreadStore=()=>store??=createUnreadStore()