fix: 修正聊天室雙向即時同步失效問題
- echo.js:還原 disconnect()/connect(),確保 Reverb 重連後重新授權 presence channel - BookingChat.vue:移除 handleNotificationFallback(會覆蓋樂觀更新訊息導致重複) - BookingChat.vue:改用 state_change 事件偵測重連('reconnected' 並非合法 Pusher.js 事件) - BookingChat.vue:移除未使用的 useAuthStore / useCoachAuthStore / currentUserId 依賴 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,6 @@ import api from '../api/axios'
|
|||||||
import coachApi from '../api/coachAxios'
|
import coachApi from '../api/coachAxios'
|
||||||
import echo from '../plugins/echo'
|
import echo from '../plugins/echo'
|
||||||
import { useNotificationStore } from '../stores/notifications'
|
import { useNotificationStore } from '../stores/notifications'
|
||||||
import { useAuthStore } from '../stores/auth'
|
|
||||||
import { useCoachAuthStore } from '../stores/coachAuth'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
bookingId: { type: Number, required: true },
|
bookingId: { type: Number, required: true },
|
||||||
@@ -23,12 +21,6 @@ const otherUserOnline = ref(false)
|
|||||||
const channel = ref(null)
|
const channel = ref(null)
|
||||||
|
|
||||||
const notificationStore = useNotificationStore()
|
const notificationStore = useNotificationStore()
|
||||||
const authStore = useAuthStore()
|
|
||||||
const coachAuthStore = useCoachAuthStore()
|
|
||||||
|
|
||||||
const currentUserId = computed(() =>
|
|
||||||
props.currentUserType === 'provider' ? coachAuthStore.user?.id : authStore.user?.id
|
|
||||||
)
|
|
||||||
|
|
||||||
const isConfirmed = computed(() => props.bookingStatus === 'confirmed')
|
const isConfirmed = computed(() => props.bookingStatus === 'confirmed')
|
||||||
const isCompleted = computed(() => props.bookingStatus === 'completed')
|
const isCompleted = computed(() => props.bookingStatus === 'completed')
|
||||||
@@ -103,7 +95,7 @@ async function sendText() {
|
|||||||
const tempId = `_temp_${Date.now()}`
|
const tempId = `_temp_${Date.now()}`
|
||||||
messages.value.push({
|
messages.value.push({
|
||||||
id: tempId,
|
id: tempId,
|
||||||
sender_id: currentUserId.value,
|
sender_id: null,
|
||||||
sender_type: props.currentUserType,
|
sender_type: props.currentUserType,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
content,
|
content,
|
||||||
@@ -201,39 +193,30 @@ function subscribeChannel() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleReconnected() {
|
// updateEchoToken() 會 disconnect/connect,connection 重建後需重訂 presence channel
|
||||||
|
// Pusher.js 的重連事件走 state_change,用 previous 判斷是否為重連而非初次連線
|
||||||
|
let everConnected = false
|
||||||
|
function onConnectionStateChange({ current }) {
|
||||||
|
if (current !== 'connected') return
|
||||||
|
if (!everConnected) { everConnected = true; return }
|
||||||
|
// 重連:重訂 presence channel
|
||||||
if (!isConfirmed.value) return
|
if (!isConfirmed.value) return
|
||||||
echo.leave(`booking.${props.bookingId}`)
|
echo.leave(`booking.${props.bookingId}`)
|
||||||
channel.value = null
|
channel.value = null
|
||||||
subscribeChannel()
|
subscribeChannel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback:presence channel 失效時,收到 bell 通知也補拉訊息
|
|
||||||
async function handleNotificationFallback() {
|
|
||||||
const lastId = messages.value[messages.value.length - 1]?.id
|
|
||||||
if (lastId && typeof lastId === 'string') return // 還在樂觀更新中,不拉
|
|
||||||
await loadHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await requestBrowserNotificationPermission()
|
await requestBrowserNotificationPermission()
|
||||||
await loadHistory()
|
await loadHistory()
|
||||||
if (isConfirmed.value) {
|
if (isConfirmed.value) {
|
||||||
subscribeChannel()
|
subscribeChannel()
|
||||||
echo.connector.pusher.connection.bind('reconnected', handleReconnected)
|
echo.connector.pusher.connection.bind('state_change', onConnectionStateChange)
|
||||||
if (currentUserId.value) {
|
|
||||||
echo.private(`App.Models.User.${currentUserId.value}`)
|
|
||||||
.listen('.notification.created', handleNotificationFallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
echo.connector.pusher.connection.unbind('reconnected', handleReconnected)
|
echo.connector.pusher.connection.unbind('state_change', onConnectionStateChange)
|
||||||
if (currentUserId.value) {
|
|
||||||
echo.private(`App.Models.User.${currentUserId.value}`)
|
|
||||||
.stopListening('.notification.created', handleNotificationFallback)
|
|
||||||
}
|
|
||||||
if (channel.value) {
|
if (channel.value) {
|
||||||
channel.value.whisper('presence', { user_type: props.currentUserType, online: false })
|
channel.value.whisper('presence', { user_type: props.currentUserType, online: false })
|
||||||
echo.leave(`booking.${props.bookingId}`)
|
echo.leave(`booking.${props.bookingId}`)
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ const echo = new Echo({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 登入後更新 auth header,讓 presence channel 授權帶正確 token
|
// 登入後更新 auth header 並強制重連,確保 Reverb 用新 token 授權 channel
|
||||||
export function updateEchoToken() {
|
export function updateEchoToken() {
|
||||||
const token = getAuthToken()
|
const token = getAuthToken()
|
||||||
const bearer = `Bearer ${token}`
|
const bearer = `Bearer ${token}`
|
||||||
echo.options.auth.headers.Authorization = bearer
|
echo.options.auth.headers.Authorization = bearer
|
||||||
// 同步更新 Pusher 內部 config(避免 Pusher.js 持有舊的 header copy)
|
|
||||||
if (echo.connector?.pusher?.config?.auth?.headers) {
|
if (echo.connector?.pusher?.config?.auth?.headers) {
|
||||||
echo.connector.pusher.config.auth.headers.Authorization = bearer
|
echo.connector.pusher.config.auth.headers.Authorization = bearer
|
||||||
}
|
}
|
||||||
// 不 disconnect/connect:避免中斷 BookingChat 已訂閱的 presence channel
|
echo.disconnect()
|
||||||
|
echo.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default echo
|
export default echo
|
||||||
|
|||||||
Reference in New Issue
Block a user