Files
BRETT/app/plugins/notifications.client.ts
T
FliesentischKingandDevin 456776ea68 Shorten thread route from /thread to /t
All internal links (tiles, prev/next nav, comments, notifications,
breadcrumb, dropzone route check) point to /t/<id>. A global route
middleware permanently redirects old /thread/<id> URLs including
query string and comment anchor.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-04 15:24:32 +02:00

95 lines
3.1 KiB
TypeScript

import { toast } from 'vue-sonner'
import type { ReactionEventPayload } from '@/composables/useCommentReactions'
import type { UploadReactionEventPayload } from '@/composables/useUploadReactions'
import type { LiveComment } from '@/composables/useLiveComments'
import type { AppNotification } from '@/composables/useNotifications'
export default defineNuxtPlugin(() => {
const { $i18n } = useNuxtApp()
const { user } = useAuth()
const { fetchAll, addLive, reset, removeByUploads } = useNotifications()
const { pushLive } = useLiveComments()
const motd = useMotd()
const { applyEvent } = useCommentReactions()
const { applyEvent: applyUploadEvent } = useUploadReactions()
let ws: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
function connect() {
if (ws) return
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
const socket = new WebSocket(`${protocol}://${location.host}/_ws`)
ws = socket
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data as string)
if (message.type === 'comment') {
pushLive(message.comment as LiveComment)
}
else if (message.type === 'reaction') {
applyEvent(message as ReactionEventPayload)
}
else if (message.type === 'upload-reaction') {
applyUploadEvent(message as UploadReactionEventPayload)
}
else if (message.type === 'upload-deleted') {
const ids = Array.isArray(message.ids)
? message.ids.filter((i: unknown) => typeof i === 'string') as string[]
: []
if (ids.length) {
removeByUploads(ids)
refreshNuxtData(['uploads', 'latest-comments'])
}
}
else if (message.type === 'motd') {
motd.value = typeof message.message === 'string' ? message.message : ''
}
else if (message.type === 'notification') {
const n = message.notification as AppNotification
addLive(n, typeof message.unreadCount === 'number' ? message.unreadCount : undefined)
toast($i18n.t(n.isMention ? 'notifications.toastMentioned' : 'notifications.toastCommented', { name: n.actorName }), {
id: `notification-${n.uploadId}`,
description: n.content?.slice(0, 80) || undefined,
action: {
label: $i18n.t('notifications.view'),
onClick: () => navigateTo(`/t/${n.uploadId}#comment-${n.commentId}`),
},
})
}
}
catch {
// ungültige Nachricht ignorieren
}
}
socket.onclose = () => {
if (ws === socket) ws = null
reconnectTimer = setTimeout(connect, 3000)
}
socket.onerror = () => {
socket.close()
}
}
function disconnect() {
clearTimeout(reconnectTimer)
ws?.close()
ws = null
}
// Bei Login/Logout neu verbinden, damit die Server-seitige User-Zuordnung stimmt
watch(() => user.value?.id, (id) => {
disconnect()
connect()
if (id) {
fetchAll().catch(() => {})
}
else {
reset()
}
}, { immediate: true })
})