This commit is contained in:
2026-02-17 20:43:23 +03:00
commit 55d819f935
22 changed files with 5874 additions and 0 deletions

47
src/App.vue Normal file
View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import WebSocketComponent from '@/components/WebSocketComponent.vue'
import { ref } from 'vue'
const email = ref('vadim.olonin@gmail.com')
async function onSubmit() {
console.log('onSubmit')
try {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: email.value }),
}
const res = await fetch('http://localhost:5173/login', options)
if (res.ok) {
const data = await res.json()
console.log(data)
}
} catch (error) {
console.log(error)
}
}
</script>
<template>
<div>
<div>chat</div>
<form @submit.prevent="onSubmit">
<div>login</div>
<div>
<input v-model="email" placeholder="email" type="email" />
<button>login</button>
</div>
</form>
<hr />
<div>
<WebSocketComponent />
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useSharedWebSocket, WsDataType } from '../composables/useSharedWebSocket'
const message = ref('')
const { messages, chats, isConnected, error, send } = useSharedWebSocket({
// onMessage: (data) => {
// console.log('Received message:', data)
// },
})
const sendMessage = () => {
if (message.value.trim()) {
send({
type: WsDataType.MESSAGE,
data: {
text: message.value,
},
})
message.value = ''
}
}
</script>
<template>
<div>
<div class="status" :class="{ isConnected, disconnected: !isConnected }">
status: {{ isConnected ? 'connected' : 'disconnected' }}
</div>
<div v-if="error" class="error">Error: {{ error }}</div>
<hr />
<div>
<div>chats</div>
<div v-for="chat in chats" :key="chat.id">
<div>{{ chat.id }}</div>
<div>{{ chat.users }}</div>
</div>
</div>
<hr />
<div>
<div>messages:</div>
<div v-for="message in messages" :key="message.id">
{{ message }}
</div>
</div>
<div class="controls">
<input v-model="message" @keyup.enter="sendMessage" placeholder="Type message..." />
<button @click="sendMessage" :disabled="!isConnected">Send</button>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,129 @@
import { ref, onUnmounted, onMounted } from 'vue'
export interface Message {
id: number
text: string
userId: string
createdAt: string
}
export interface Chat {
id: string
type_id: number
users: User[]
}
export interface User {
id: number
email: string
}
export interface WsData {
type: WsDataType
// data: Chat | Message | User | User[] | Message[] | Chat[]
data: {text: string}
}
export enum WsDataType {
CHAT = 'CHAT',
USER = 'USER',
MESSAGE = 'MESSAGE',
STATUS = 'STATUS',
ERROR = 'ERROR',
}
export enum COMMAND {
CONNECT = 'CONNECT',
SEND = 'SEND',
CLOSE = 'CLOSE',
}
export function useSharedWebSocket(options?: { url?: string; autoConnect?: true }) {
const url = options?.url || 'ws://localhost:3000/ws'
const autoConnect = options?.autoConnect ?? true
console.log('useSharedWebSocket', url, autoConnect)
const messages = ref<Message[]>([])
const chats = ref<Chat[]>([])
const users = ref<unknown[]>([])
const isConnected = ref(false)
const error = ref<string>()
const worker = ref<SharedWorker>()
const initWorker = () => {
if (!window.SharedWorker) {
console.warn('SharedWorker not supported')
error.value = 'SharedWorker not supported'
}
worker.value = new SharedWorker(new URL('@/workers/worker.js', import.meta.url), {
type: 'module',
})
worker.value.port.onmessage = (event) => {
const { type, data, connected, message } = event.data
console.log(event.data)
switch (type) {
case WsDataType.USER:
console.log('USER')
break
case WsDataType.CHAT:
console.log('chat')
chats.value = data
break
case WsDataType.MESSAGE:
messages.value.push(data)
// if (options.onMessage) {
// options.onMessage(data)
// }
break
case WsDataType.STATUS:
isConnected.value = connected
break
case WsDataType.ERROR:
error.value = message
break
}
}
worker.value.port.postMessage({
command: COMMAND.CONNECT,
data: { url },
})
}
const send = (data: WsData) => {
if (worker.value) {
worker.value.port.postMessage({
command: COMMAND.SEND,
data: data,
})
}
}
const close = () => {
if (worker.value) {
worker.value.port.postMessage({ command: COMMAND.CLOSE })
}
}
onMounted(() => {
if (autoConnect) initWorker()
})
onUnmounted(() => {
if (worker.value) close()
})
return {
messages,
chats,
isConnected,
error,
send,
close,
initWorker,
}
}

9
src/main.ts Normal file
View File

@@ -0,0 +1,9 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')

88
src/workers/worker.js Normal file
View File

@@ -0,0 +1,88 @@
let socket
let clients = []
let reconnectAttempts = 0
const MAX_RECONNECT_ATTEMPTS = 5
function connectWebSocket(url) {
try {
socket = new WebSocket(url)
socket.onopen = () => {
console.log('[Shared Worker] WebSocket connected')
reconnectAttempts = 0
broadcast({ type: 'STATUS', connected: true })
}
socket.onmessage = (event) => {
console.log('[Shared Worker] Get message')
const data = JSON.parse(event.data)
broadcast(data)
}
socket.onclose = () => {
console.log('[Shared Worker] WebSocket disconnected')
broadcast({ type: 'STATUS', connected: false })
// if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
// reconnectAttempts++
// setTimeout(() => {
// console.log(`[Shared Worker] Reconnecting (attempt ${reconnectAttempts})...`)
// connectWebSocket(url)
// }, 1000 * reconnectAttempts)
// }
}
socket.onerror = (error) => {
console.error('[Shared Worker] WebSocket error:', error)
broadcast({ type: 'ERROR', message: error.message })
}
} catch (error) {
console.error('[Shared Worker] Failed to create WebSocket:', error)
}
}
function broadcast(message) {
clients.forEach((port) => port.postMessage(message))
}
onconnect = function (event) {
const port = event.ports[0]
clients.push(port)
port.onmessage = function (e) {
const { command, data } = e.data
console.log('Received command', command, data)
switch (command) {
case 'CONNECT':
if (!socket || socket.readyState !== WebSocket.OPEN) {
connectWebSocket(data.url)
}
break
case 'SEND':
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(data))
} else {
port.postMessage('[Shared Worker] WebSocket not open. Cannot send message.')
}
break
case 'CLOSE':
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close()
}
break
}
}
const status = { type: 'STATUS' }
if (socket) {
status.connected = socket.readyState === WebSocket.OPEN
} else {
status.connected = false
}
port.postMessage(status)
}