优化sse
This commit is contained in:
@@ -191,14 +191,15 @@ func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO,
|
|||||||
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
|
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
|
||||||
Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID).
|
Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID).
|
||||||
Order("c.id ASC").
|
Order("c.id ASC").
|
||||||
First(&existing).Error
|
Limit(1).
|
||||||
if err == nil {
|
Find(&existing).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.ID > 0 {
|
||||||
conversationID = existing.ID
|
conversationID = existing.ID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
conversation := model.ChatConversation{
|
conversation := model.ChatConversation{
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function rejectPendingRequests(scope: AuthScope, error: unknown) {
|
|||||||
pendingRequests.forEach(({ reject }) => reject(error))
|
pendingRequests.forEach(({ reject }) => reject(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshToken(scope: AuthScope): Promise<string> {
|
export async function refreshAccessToken(scope: AuthScope): Promise<string> {
|
||||||
const refreshToken = getRefreshToken(scope)
|
const refreshToken = getRefreshToken(scope)
|
||||||
if (!refreshToken) throw new Error('no refresh token')
|
if (!refreshToken) throw new Error('no refresh token')
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ apiClient.interceptors.response.use(
|
|||||||
|
|
||||||
state.refreshing = true
|
state.refreshing = true
|
||||||
try {
|
try {
|
||||||
const newToken = await refreshToken(scope)
|
const newToken = await refreshAccessToken(scope)
|
||||||
resolvePendingRequests(scope, newToken)
|
resolvePendingRequests(scope, newToken)
|
||||||
originalRequest._retry = true
|
originalRequest._retry = true
|
||||||
originalRequest.headers.Authorization = `Bearer ${newToken}`
|
originalRequest.headers.Authorization = `Bearer ${newToken}`
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { onBeforeUnmount, ref, type Ref } from 'vue'
|
import { onBeforeUnmount, ref, type Ref } from 'vue'
|
||||||
|
import { refreshAccessToken } from '@/api/client'
|
||||||
import { getAccessToken, type AuthScope } from '@/utils/authStorage'
|
import { getAccessToken, type AuthScope } from '@/utils/authStorage'
|
||||||
|
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
@@ -29,6 +30,7 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
|
|||||||
let source: EventSource | null = null
|
let source: EventSource | null = null
|
||||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let stopped = false
|
let stopped = false
|
||||||
|
let refreshing = false
|
||||||
const handlers: EventHandler[] = []
|
const handlers: EventHandler[] = []
|
||||||
|
|
||||||
function onEvent(handler: EventHandler) {
|
function onEvent(handler: EventHandler) {
|
||||||
@@ -36,6 +38,7 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
|
if (stopped || source) return
|
||||||
const token = getAccessToken(scope)
|
const token = getAccessToken(scope)
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
|
||||||
@@ -60,18 +63,27 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
})
|
})
|
||||||
|
|
||||||
source.onerror = () => {
|
source.onerror = async () => {
|
||||||
connected.value = false
|
connected.value = false
|
||||||
source?.close()
|
source?.close()
|
||||||
source = null
|
source = null
|
||||||
if (!stopped) {
|
if (stopped || refreshing) return
|
||||||
reconnectTimer = setTimeout(connect, reconnectDelay)
|
|
||||||
|
refreshing = true
|
||||||
|
try {
|
||||||
|
await refreshAccessToken(scope)
|
||||||
|
if (!stopped) {
|
||||||
|
reconnectTimer = setTimeout(connect, reconnectDelay)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
closeSource()
|
||||||
|
} finally {
|
||||||
|
refreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnect() {
|
function closeSource() {
|
||||||
stopped = true
|
|
||||||
if (reconnectTimer) {
|
if (reconnectTimer) {
|
||||||
clearTimeout(reconnectTimer)
|
clearTimeout(reconnectTimer)
|
||||||
reconnectTimer = null
|
reconnectTimer = null
|
||||||
@@ -81,6 +93,21 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
|
|||||||
connected.value = false
|
connected.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAuthStorageChanged(event: Event) {
|
||||||
|
const detail = (event as CustomEvent<{ scope?: AuthScope }>).detail
|
||||||
|
if (detail?.scope !== scope || stopped) return
|
||||||
|
closeSource()
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
stopped = true
|
||||||
|
closeSource()
|
||||||
|
window.removeEventListener('auth-storage-changed', handleAuthStorageChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('auth-storage-changed', handleAuthStorageChanged)
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
disconnect()
|
disconnect()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user