websocket消息支持 (#188)

* Release (#162)

* feat:  websocket server demo

* feat:  ws server demo dev

* feat:  ws server and mobile page

* feat:  手机端发送消息

* feat:  手机网页发送消息

* 添加了抽奖中和抽奖完成时的音效

* feat:  自定义设置弹幕服务器地址

* feat:  ws not done

* fix: 🐛 fix pr-185 #185

为播放音效添加控制

* feat:  server worker demo not done

* feat:  websocket server

* feat:  全局接收websocket消息并存储到indexdb中

---------

Co-authored-by: Silence@2024 <707261624@qq.com>
This commit is contained in:
LOG1997
2026-01-09 17:11:43 +08:00
committed by GitHub
parent 52d2fcd0cb
commit 3eac4e1aac
40 changed files with 3489 additions and 279 deletions

View File

@@ -0,0 +1,28 @@
import { onUnmounted } from 'vue'
import TimerWorker from './timerWorker.ts?worker'
export function useTimerWorker(interval: number) {
let timerWorker: Worker | null = null
const init = (callback: () => void) => {
close()
timerWorker = new TimerWorker()
timerWorker.postMessage({ interval })
if (timerWorker.onmessage)
return
timerWorker.addEventListener('message', () => callback())
}
function close() {
timerWorker?.terminate()
timerWorker = null
}
onUnmounted(() => {
close()
})
return {
init,
close,
}
}

View File

@@ -0,0 +1,12 @@
/* eslint-disable no-restricted-globals */
let intervalId: number | null = null
self.addEventListener('message', (e) => {
const { interval } = e.data
if (!interval)
throw new Error('invalid params')
if (intervalId)
clearInterval(intervalId)
intervalId = setInterval(() => {
self.postMessage(true)
}, interval)
})

114
src/hooks/useWebsocket.ts Normal file
View File

@@ -0,0 +1,114 @@
import type { IMsgType } from '@/types/msgType'
import type { WsMsgData } from '@/types/storeType'
import { v4 as uuidv4 } from 'uuid'
import { onMounted, ref } from 'vue'
import { useTimerWorker } from './useTimerWorker'
export function useWebsocket() {
const { init: initWorker, close: closeWorker } = useTimerWorker(30 * 1000)
const status = ref<{ status: WebSocket['readyState'], connected: boolean }>()
const data = ref<WsMsgData>()
const registration = ref<ServiceWorkerRegistration | null>(null)
async function registerSW() {
if ('serviceWorker' in navigator) {
try {
registration.value = await navigator.serviceWorker.register('/log-lottery/sw.js')
console.log('Service Worker 注册成功:', registration)
listenSWMessage()
}
catch (error) {
console.error('Service Worker 注册失败:', error)
}
}
else {
console.error('浏览器不支持 Service Worker')
}
}
function open(url: string) {
navigator.serviceWorker.ready.then((registration) => {
registration.active?.postMessage({
type: 'CONNECT_WS',
payload: { url },
})
})
}
function close() {
closeWorker()
navigator.serviceWorker.ready.then((registration) => {
registration.active?.postMessage({
type: 'DISCONNECT_WS',
})
})
}
function send(message: string) {
navigator.serviceWorker.ready.then((registration) => {
registration.active?.postMessage({
type: 'SEND_WS_MESSAGE',
payload: { message, id: uuidv4() },
})
})
}
function getStatus() {
navigator.serviceWorker.ready.then((registration) => {
registration.active?.postMessage({
type: 'GET_WS_STATUS',
})
})
}
// 监听service worker消息
function listenSWMessage() {
navigator.serviceWorker.addEventListener('message', (event) => {
const msgType = event.data.type
switch (msgType) {
case 'WS_STATUS':
status.value = event.data.payload
break
case 'WS_MESSAGE':{
const receivedMsg: IMsgType = event.data.payload as IMsgType
data.value = {
...receivedMsg,
id: uuidv4(),
}
break
}
case 'WS_ERROR':
console.error('ws error:', event.data.payload)
status.value = {
status: WebSocket.CLOSED,
connected: false,
}
closeWorker()
break
case 'WS_CLOSE':
status.value = {
status: WebSocket.CLOSED,
connected: false,
}
closeWorker()
break
case 'WS_OPEN':
status.value = {
status: WebSocket.OPEN,
connected: true,
}
initWorker(getStatus)
break
}
})
}
onMounted(() => {
registerSW()
getStatus()
})
return {
open,
close,
send,
status,
data,
}
}