2026-09-29 16:22:37 +03:30
|
|
|
import { computed, ref, shallowRef } from 'vue';
|
|
|
|
|
import type { ConversationRoomDto, ConversationSessionDto } from '@mento/shared';
|
2026-09-29 10:34:01 +03:30
|
|
|
import { api } from '@/lib/api';
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
export type ChatDelivery = 'sending' | 'sent' | 'read';
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
export type ChatMessage = {
|
|
|
|
|
id: string;
|
|
|
|
|
roomId: string;
|
|
|
|
|
senderId: string;
|
|
|
|
|
content: string;
|
|
|
|
|
type: 'text' | 'image' | 'voice';
|
|
|
|
|
createdAt: string;
|
|
|
|
|
reply: string | null;
|
|
|
|
|
optimistic?: boolean;
|
2026-09-29 16:22:37 +03:30
|
|
|
delivery?: ChatDelivery;
|
|
|
|
|
readAt?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type RoomLiveEvent = {
|
|
|
|
|
roomId: string;
|
|
|
|
|
preview: string;
|
|
|
|
|
at: string;
|
|
|
|
|
fromSelf: boolean;
|
2026-09-29 10:34:01 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const PAGE_SIZE = 50;
|
|
|
|
|
|
|
|
|
|
let socket: WebSocket | null = null;
|
|
|
|
|
let sessionCache: ConversationSessionDto | null = null;
|
2026-09-29 16:22:37 +03:30
|
|
|
let connectPromise: Promise<void> | null = null;
|
|
|
|
|
let pingTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
|
let wantSocket = false;
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
const status = ref<'idle' | 'connecting' | 'open' | 'closed'>('idle');
|
|
|
|
|
const messages = shallowRef<ChatMessage[]>([]);
|
|
|
|
|
const activeRoomId = ref('');
|
2026-09-29 16:22:37 +03:30
|
|
|
const unreadTotal = ref(0);
|
|
|
|
|
const pageOpen = ref(false);
|
|
|
|
|
|
|
|
|
|
type RoomListener = (event: RoomLiveEvent) => void;
|
|
|
|
|
const roomListeners = new Set<RoomListener>();
|
|
|
|
|
|
|
|
|
|
export function onRoomLiveEvent(listener: RoomListener) {
|
|
|
|
|
roomListeners.add(listener);
|
|
|
|
|
return () => roomListeners.delete(listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function emitRoomLive(event: RoomLiveEvent) {
|
|
|
|
|
for (const listener of roomListeners) {
|
|
|
|
|
try {
|
|
|
|
|
listener(event);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore listener errors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-29 10:34:01 +03:30
|
|
|
|
2026-09-29 11:56:32 +03:30
|
|
|
function messageTs(item: ChatMessage): number {
|
|
|
|
|
const t = new Date(item.createdAt).getTime();
|
|
|
|
|
return Number.isNaN(t) ? 0 : t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sortAsc(items: ChatMessage[]): ChatMessage[] {
|
|
|
|
|
return [...items].sort((a, b) => {
|
|
|
|
|
const d = messageTs(a) - messageTs(b);
|
|
|
|
|
if (d !== 0) return d;
|
|
|
|
|
return a.id.localeCompare(b.id);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeIncoming(
|
|
|
|
|
raw: Record<string, unknown>,
|
|
|
|
|
roomFallback: string,
|
|
|
|
|
): ChatMessage | null {
|
2026-09-29 10:34:01 +03:30
|
|
|
const base =
|
|
|
|
|
raw.message && typeof raw.message === 'object'
|
|
|
|
|
? (raw.message as Record<string, unknown>)
|
|
|
|
|
: raw;
|
|
|
|
|
const content = String(base.content ?? base.text ?? base.body ?? '').trim();
|
|
|
|
|
if (!content && !base.attachments) return null;
|
2026-09-29 11:56:32 +03:30
|
|
|
const id = String(base.id ?? base.message_id ?? '').trim();
|
2026-09-29 16:22:37 +03:30
|
|
|
const readAt = base.read_at ?? base.readAt ?? null;
|
2026-09-29 10:34:01 +03:30
|
|
|
return {
|
2026-09-29 11:56:32 +03:30
|
|
|
id: id || `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
2026-09-29 10:34:01 +03:30
|
|
|
roomId: String(base.room_id ?? raw.room_id ?? roomFallback),
|
|
|
|
|
senderId: String(
|
|
|
|
|
base.sender_id ?? base.user_id ?? base.from_user_id ?? raw.sender_id ?? '',
|
|
|
|
|
),
|
|
|
|
|
content,
|
|
|
|
|
type: (String(base.type_content ?? base.type ?? 'text') as ChatMessage['type']) || 'text',
|
|
|
|
|
createdAt: String(
|
|
|
|
|
base.created_at ?? base.createdAt ?? raw.created_at ?? new Date().toISOString(),
|
|
|
|
|
),
|
|
|
|
|
reply: base.reply ? String(base.reply) : null,
|
2026-09-29 16:22:37 +03:30
|
|
|
readAt: readAt ? String(readAt) : null,
|
|
|
|
|
delivery: readAt ? 'read' : 'sent',
|
2026-09-29 10:34:01 +03:30
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 11:56:32 +03:30
|
|
|
function mergeMessages(incoming: ChatMessage, list: ChatMessage[]): ChatMessage[] {
|
|
|
|
|
const byId = list.findIndex((m) => m.id === incoming.id);
|
|
|
|
|
if (byId >= 0) {
|
2026-09-29 16:22:37 +03:30
|
|
|
const prev = list[byId];
|
2026-09-29 11:56:32 +03:30
|
|
|
const next = [...list];
|
2026-09-29 16:22:37 +03:30
|
|
|
next[byId] = {
|
|
|
|
|
...prev,
|
|
|
|
|
...incoming,
|
|
|
|
|
optimistic: false,
|
|
|
|
|
delivery:
|
|
|
|
|
incoming.delivery === 'read' || prev.delivery === 'read'
|
|
|
|
|
? 'read'
|
|
|
|
|
: incoming.delivery || prev.delivery || 'sent',
|
|
|
|
|
readAt: incoming.readAt ?? prev.readAt,
|
|
|
|
|
};
|
2026-09-29 11:56:32 +03:30
|
|
|
return sortAsc(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const optimisticIdx = list.findIndex(
|
|
|
|
|
(m) =>
|
|
|
|
|
m.optimistic &&
|
|
|
|
|
m.roomId === incoming.roomId &&
|
|
|
|
|
m.senderId === incoming.senderId &&
|
|
|
|
|
m.content === incoming.content,
|
|
|
|
|
);
|
|
|
|
|
if (optimisticIdx >= 0) {
|
2026-09-29 16:22:37 +03:30
|
|
|
const prev = list[optimisticIdx];
|
2026-09-29 11:56:32 +03:30
|
|
|
const next = [...list];
|
|
|
|
|
next[optimisticIdx] = {
|
2026-09-29 16:22:37 +03:30
|
|
|
...prev,
|
2026-09-29 11:56:32 +03:30
|
|
|
...incoming,
|
|
|
|
|
optimistic: false,
|
2026-09-29 16:22:37 +03:30
|
|
|
delivery: incoming.readAt ? 'read' : 'sent',
|
2026-09-29 11:56:32 +03:30
|
|
|
};
|
|
|
|
|
return sortAsc(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sortAsc([...list, incoming]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
function applySeenPayload(payload: Record<string, unknown>) {
|
|
|
|
|
const roomId = String(payload.room_id ?? '');
|
|
|
|
|
if (!roomId || roomId !== activeRoomId.value) return;
|
|
|
|
|
|
|
|
|
|
const myId = sessionCache?.chatUserId ?? '';
|
|
|
|
|
const readerId = String(payload.reader_id ?? '');
|
|
|
|
|
// وقتی طرف مقابل خوانده، پیامهای من تیک دوتایی میخورند
|
|
|
|
|
if (!readerId || readerId === myId) return;
|
|
|
|
|
|
|
|
|
|
const ids = new Set(
|
|
|
|
|
Array.isArray(payload.seen_message_ids)
|
|
|
|
|
? payload.seen_message_ids.map((id) => String(id))
|
|
|
|
|
: [],
|
|
|
|
|
);
|
|
|
|
|
if (!ids.size) {
|
|
|
|
|
// اگر id لیست نبود، همه پیامهای خودم در این room را read کن
|
|
|
|
|
messages.value = messages.value.map((m) =>
|
|
|
|
|
m.roomId === roomId && m.senderId === myId
|
|
|
|
|
? { ...m, delivery: 'read' as const, readAt: String(payload.seen_at ?? new Date().toISOString()) }
|
|
|
|
|
: m,
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const seenAt = String(payload.seen_at ?? new Date().toISOString());
|
|
|
|
|
messages.value = messages.value.map((m) => {
|
|
|
|
|
if (!ids.has(m.id)) return m;
|
|
|
|
|
if (m.senderId !== myId) return m;
|
|
|
|
|
return { ...m, delivery: 'read' as const, readAt: seenAt };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
async function ensureSession(): Promise<ConversationSessionDto> {
|
|
|
|
|
if (sessionCache) return sessionCache;
|
|
|
|
|
const { data } = await api.get<ConversationSessionDto>('/conversations/session');
|
|
|
|
|
sessionCache = data;
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearPing() {
|
|
|
|
|
if (pingTimer) {
|
|
|
|
|
clearInterval(pingTimer);
|
|
|
|
|
pingTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
function clearReconnect() {
|
|
|
|
|
if (reconnectTimer) {
|
|
|
|
|
clearTimeout(reconnectTimer);
|
|
|
|
|
reconnectTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scheduleReconnect() {
|
|
|
|
|
if (!wantSocket || reconnectTimer) return;
|
|
|
|
|
reconnectTimer = setTimeout(() => {
|
|
|
|
|
reconnectTimer = null;
|
|
|
|
|
void connectSocket().catch(() => {
|
|
|
|
|
scheduleReconnect();
|
|
|
|
|
});
|
|
|
|
|
}, 2500);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
function disconnectSocket() {
|
|
|
|
|
clearPing();
|
2026-09-29 16:22:37 +03:30
|
|
|
clearReconnect();
|
2026-09-29 10:34:01 +03:30
|
|
|
if (socket) {
|
2026-09-29 16:22:37 +03:30
|
|
|
socket.onmessage = null;
|
|
|
|
|
socket.onclose = null;
|
|
|
|
|
socket.onerror = null;
|
2026-09-29 10:34:01 +03:30
|
|
|
socket.close();
|
|
|
|
|
socket = null;
|
|
|
|
|
}
|
|
|
|
|
status.value = 'closed';
|
2026-09-29 16:22:37 +03:30
|
|
|
connectPromise = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleIncomingMessage(msg: ChatMessage) {
|
|
|
|
|
const myId = sessionCache?.chatUserId ?? '';
|
|
|
|
|
const fromSelf = Boolean(myId && msg.senderId === myId);
|
|
|
|
|
|
|
|
|
|
if (msg.roomId === activeRoomId.value) {
|
|
|
|
|
messages.value = mergeMessages(
|
|
|
|
|
{
|
|
|
|
|
...msg,
|
|
|
|
|
delivery: fromSelf ? msg.delivery || 'sent' : msg.delivery,
|
|
|
|
|
},
|
|
|
|
|
messages.value,
|
|
|
|
|
);
|
|
|
|
|
if (!fromSelf && pageOpen.value) {
|
|
|
|
|
sendRead(msg.roomId);
|
|
|
|
|
}
|
|
|
|
|
} else if (!fromSelf) {
|
|
|
|
|
unreadTotal.value += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emitRoomLive({
|
|
|
|
|
roomId: msg.roomId,
|
|
|
|
|
preview: msg.content,
|
|
|
|
|
at: msg.createdAt,
|
|
|
|
|
fromSelf,
|
|
|
|
|
});
|
2026-09-29 10:34:01 +03:30
|
|
|
}
|
|
|
|
|
|
2026-09-29 11:56:32 +03:30
|
|
|
function onSocketMessage(event: MessageEvent) {
|
|
|
|
|
const raw = String(event.data ?? '').trim();
|
|
|
|
|
if (!raw || raw === 'pong' || raw === 'ping') return;
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
2026-09-29 16:22:37 +03:30
|
|
|
const type = String(parsed.type ?? parsed.event ?? '').toLowerCase();
|
|
|
|
|
if (type === 'pong' || type === 'ping' || type === 'typing' || type === 'read') return;
|
|
|
|
|
if (type === 'seen') {
|
|
|
|
|
applySeenPayload(parsed);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-09-29 11:56:32 +03:30
|
|
|
const msg = normalizeIncoming(parsed, activeRoomId.value);
|
2026-09-29 16:22:37 +03:30
|
|
|
if (!msg) return;
|
|
|
|
|
handleIncomingMessage(msg);
|
2026-09-29 11:56:32 +03:30
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
async function connectSocket() {
|
2026-09-29 16:22:37 +03:30
|
|
|
wantSocket = true;
|
2026-09-29 10:34:01 +03:30
|
|
|
if (socket?.readyState === WebSocket.OPEN) return;
|
2026-09-29 16:22:37 +03:30
|
|
|
if (connectPromise) return connectPromise;
|
2026-09-29 10:34:01 +03:30
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
connectPromise = (async () => {
|
|
|
|
|
const session = await ensureSession();
|
|
|
|
|
if (socket?.readyState === WebSocket.OPEN) return;
|
2026-09-29 10:34:01 +03:30
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
disconnectSocket();
|
|
|
|
|
wantSocket = true;
|
|
|
|
|
status.value = 'connecting';
|
|
|
|
|
socket = new WebSocket(session.wsUrl);
|
|
|
|
|
|
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
|
|
|
if (!socket) return reject(new Error('socket missing'));
|
|
|
|
|
const t = setTimeout(() => reject(new Error('ws timeout')), 12000);
|
|
|
|
|
socket.addEventListener(
|
|
|
|
|
'open',
|
|
|
|
|
() => {
|
|
|
|
|
clearTimeout(t);
|
|
|
|
|
status.value = 'open';
|
|
|
|
|
pingTimer = setInterval(() => {
|
|
|
|
|
if (socket?.readyState === WebSocket.OPEN) socket.send('ping');
|
|
|
|
|
}, 15000);
|
|
|
|
|
resolve();
|
|
|
|
|
},
|
|
|
|
|
{ once: true },
|
|
|
|
|
);
|
|
|
|
|
socket.addEventListener(
|
|
|
|
|
'error',
|
|
|
|
|
() => {
|
|
|
|
|
clearTimeout(t);
|
|
|
|
|
reject(new Error('ws error'));
|
|
|
|
|
},
|
|
|
|
|
{ once: true },
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!socket) return;
|
|
|
|
|
socket.addEventListener('message', onSocketMessage);
|
|
|
|
|
socket.addEventListener('close', () => {
|
|
|
|
|
status.value = 'closed';
|
|
|
|
|
clearPing();
|
|
|
|
|
socket = null;
|
|
|
|
|
connectPromise = null;
|
|
|
|
|
if (wantSocket) scheduleReconnect();
|
|
|
|
|
});
|
|
|
|
|
})().finally(() => {
|
|
|
|
|
connectPromise = null;
|
2026-09-29 10:34:01 +03:30
|
|
|
});
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
return connectPromise;
|
|
|
|
|
}
|
2026-09-29 10:34:01 +03:30
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
function sendRead(roomId: string) {
|
|
|
|
|
if (!socket || socket.readyState !== WebSocket.OPEN || !roomId) return;
|
|
|
|
|
socket.send(JSON.stringify({ type: 'read', room_id: roomId }));
|
|
|
|
|
}
|
2026-09-29 10:34:01 +03:30
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
export async function refreshUnreadTotal() {
|
|
|
|
|
try {
|
|
|
|
|
const { data } = await api.get<ConversationRoomDto[]>('/conversations/rooms');
|
|
|
|
|
unreadTotal.value = data.reduce((sum, room) => sum + (room.unreadCount || 0), 0);
|
|
|
|
|
return unreadTotal.value;
|
|
|
|
|
} catch {
|
|
|
|
|
return unreadTotal.value;
|
|
|
|
|
}
|
2026-09-29 10:34:01 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useOrgChat() {
|
|
|
|
|
const loadingMessages = ref(false);
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
async function ensureConnected() {
|
|
|
|
|
await connectSocket();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setConversationsPageOpen(open: boolean) {
|
|
|
|
|
pageOpen.value = open;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
async function loadMessages(roomId: string) {
|
|
|
|
|
activeRoomId.value = roomId;
|
|
|
|
|
loadingMessages.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const session = await ensureSession();
|
|
|
|
|
await connectSocket();
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
api_key: new URL(session.wsUrl).searchParams.get('api_key') ?? '',
|
|
|
|
|
room_id: roomId,
|
|
|
|
|
user_id: session.chatUserId,
|
|
|
|
|
limit: String(PAGE_SIZE),
|
|
|
|
|
offset: '0',
|
|
|
|
|
});
|
|
|
|
|
const url = `${session.messagesBaseUrl}/api/messages?${params.toString()}`;
|
|
|
|
|
const res = await fetch(url, { headers: { Accept: 'application/json' }, cache: 'no-store' });
|
|
|
|
|
if (!res.ok) throw new Error('messages failed');
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const list = Array.isArray(body) ? body : body?.data ?? [];
|
2026-09-29 16:22:37 +03:30
|
|
|
const myId = session.chatUserId;
|
|
|
|
|
const normalized = (list
|
2026-09-29 10:34:01 +03:30
|
|
|
.map((item: Record<string, unknown>) => normalizeIncoming(item, roomId))
|
2026-09-29 16:22:37 +03:30
|
|
|
.filter(Boolean) as ChatMessage[]).map((m) => {
|
|
|
|
|
if (m.senderId !== myId) return m;
|
|
|
|
|
return {
|
|
|
|
|
...m,
|
|
|
|
|
delivery: (m.readAt ? 'read' : 'sent') as ChatDelivery,
|
|
|
|
|
};
|
|
|
|
|
});
|
2026-09-29 11:56:32 +03:30
|
|
|
messages.value = sortAsc(normalized);
|
2026-09-29 16:22:37 +03:30
|
|
|
sendRead(roomId);
|
2026-09-29 10:34:01 +03:30
|
|
|
} finally {
|
|
|
|
|
loadingMessages.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sendText(roomId: string, text: string, reply: string | null = null) {
|
|
|
|
|
const trimmed = text.trim();
|
|
|
|
|
if (!trimmed) return;
|
|
|
|
|
await connectSocket();
|
|
|
|
|
if (!socket || socket.readyState !== WebSocket.OPEN) throw new Error('ws not open');
|
|
|
|
|
const session = await ensureSession();
|
|
|
|
|
const id = crypto.randomUUID();
|
|
|
|
|
const payload: Record<string, string> = {
|
|
|
|
|
id,
|
|
|
|
|
type: 'text',
|
|
|
|
|
room_id: roomId,
|
|
|
|
|
content: trimmed,
|
|
|
|
|
};
|
|
|
|
|
if (reply) payload.reply = reply;
|
|
|
|
|
const optimistic: ChatMessage = {
|
|
|
|
|
id,
|
|
|
|
|
roomId,
|
|
|
|
|
senderId: session.chatUserId,
|
|
|
|
|
content: trimmed,
|
|
|
|
|
type: 'text',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
reply,
|
|
|
|
|
optimistic: true,
|
2026-09-29 16:22:37 +03:30
|
|
|
delivery: 'sending',
|
2026-09-29 10:34:01 +03:30
|
|
|
};
|
2026-09-29 11:56:32 +03:30
|
|
|
messages.value = mergeMessages(optimistic, messages.value);
|
2026-09-29 16:22:37 +03:30
|
|
|
emitRoomLive({
|
|
|
|
|
roomId,
|
|
|
|
|
preview: trimmed,
|
|
|
|
|
at: optimistic.createdAt,
|
|
|
|
|
fromSelf: true,
|
|
|
|
|
});
|
2026-09-29 10:34:01 +03:30
|
|
|
socket.send(JSON.stringify(payload));
|
2026-09-29 16:22:37 +03:30
|
|
|
messages.value = messages.value.map((m) =>
|
|
|
|
|
m.id === id ? { ...m, optimistic: false, delivery: 'sent' as const } : m,
|
|
|
|
|
);
|
2026-09-29 10:34:01 +03:30
|
|
|
}
|
|
|
|
|
|
2026-09-29 16:22:37 +03:30
|
|
|
function clearActiveRoom() {
|
|
|
|
|
activeRoomId.value = '';
|
|
|
|
|
messages.value = [];
|
2026-09-29 10:34:01 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetSession() {
|
2026-09-29 16:22:37 +03:30
|
|
|
wantSocket = false;
|
2026-09-29 10:34:01 +03:30
|
|
|
sessionCache = null;
|
|
|
|
|
disconnectSocket();
|
|
|
|
|
messages.value = [];
|
|
|
|
|
activeRoomId.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
status: computed(() => status.value),
|
|
|
|
|
messages: computed(() => messages.value),
|
2026-09-29 16:22:37 +03:30
|
|
|
unreadTotal: computed(() => unreadTotal.value),
|
2026-09-29 10:34:01 +03:30
|
|
|
loadingMessages,
|
|
|
|
|
loadMessages,
|
|
|
|
|
sendText,
|
|
|
|
|
sendRead,
|
2026-09-29 16:22:37 +03:30
|
|
|
ensureConnected,
|
|
|
|
|
setConversationsPageOpen,
|
|
|
|
|
clearActiveRoom,
|
2026-09-29 10:34:01 +03:30
|
|
|
resetSession,
|
|
|
|
|
ensureSession,
|
2026-09-29 16:22:37 +03:30
|
|
|
refreshUnreadTotal,
|
2026-09-29 10:34:01 +03:30
|
|
|
};
|
|
|
|
|
}
|