diff --git a/frontend/src/components/BookingChat.vue b/frontend/src/components/BookingChat.vue index 5d6ccaa..569e8c8 100644 --- a/frontend/src/components/BookingChat.vue +++ b/frontend/src/components/BookingChat.vue @@ -4,8 +4,6 @@ import api from '../api/axios' import coachApi from '../api/coachAxios' import echo from '../plugins/echo' import { useNotificationStore } from '../stores/notifications' -import { useAuthStore } from '../stores/auth' -import { useCoachAuthStore } from '../stores/coachAuth' const props = defineProps({ bookingId: { type: Number, required: true }, @@ -23,12 +21,6 @@ const otherUserOnline = ref(false) const channel = ref(null) 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 isCompleted = computed(() => props.bookingStatus === 'completed') @@ -103,7 +95,7 @@ async function sendText() { const tempId = `_temp_${Date.now()}` messages.value.push({ id: tempId, - sender_id: currentUserId.value, + sender_id: null, sender_type: props.currentUserType, type: 'text', 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 echo.leave(`booking.${props.bookingId}`) channel.value = null 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 () => { await requestBrowserNotificationPermission() await loadHistory() if (isConfirmed.value) { subscribeChannel() - echo.connector.pusher.connection.bind('reconnected', handleReconnected) - if (currentUserId.value) { - echo.private(`App.Models.User.${currentUserId.value}`) - .listen('.notification.created', handleNotificationFallback) - } + echo.connector.pusher.connection.bind('state_change', onConnectionStateChange) } }) onUnmounted(() => { - echo.connector.pusher.connection.unbind('reconnected', handleReconnected) - if (currentUserId.value) { - echo.private(`App.Models.User.${currentUserId.value}`) - .stopListening('.notification.created', handleNotificationFallback) - } + echo.connector.pusher.connection.unbind('state_change', onConnectionStateChange) if (channel.value) { channel.value.whisper('presence', { user_type: props.currentUserType, online: false }) echo.leave(`booking.${props.bookingId}`) diff --git a/frontend/src/plugins/echo.js b/frontend/src/plugins/echo.js index 3a06c56..adba795 100644 --- a/frontend/src/plugins/echo.js +++ b/frontend/src/plugins/echo.js @@ -24,16 +24,16 @@ const echo = new Echo({ }, }) -// 登入後更新 auth header,讓 presence channel 授權帶正確 token +// 登入後更新 auth header 並強制重連,確保 Reverb 用新 token 授權 channel export function updateEchoToken() { const token = getAuthToken() const bearer = `Bearer ${token}` echo.options.auth.headers.Authorization = bearer - // 同步更新 Pusher 內部 config(避免 Pusher.js 持有舊的 header copy) if (echo.connector?.pusher?.config?.auth?.headers) { echo.connector.pusher.config.auth.headers.Authorization = bearer } - // 不 disconnect/connect:避免中斷 BookingChat 已訂閱的 presence channel + echo.disconnect() + echo.connect() } export default echo