ultimatepos/Modules/Communication/Resources/js/composables/useRealtime.js

148 lines
4.6 KiB
JavaScript
Vendored

import { ref } from 'vue';
import { getConfig } from '../utils/config';
export const REALTIME_KEY = Symbol('commRealtime');
function isRealtimeEnabled() {
const app = window.APP || {};
return typeof window.Echo !== 'undefined'
&& (app.PUSHER_ENABLED === true || app.PUSHER_ENABLED === '1' || app.PUSHER_ENABLED === 1);
}
/**
* Shared Laravel Echo service: multi-room subscriptions, whispers, user channel.
*/
export function useRealtime() {
const connected = ref(false);
const config = getConfig();
const businessId = config.businessId;
const userId = config.userId;
/** @type {Map<number, { name: string, channel: object }>} */
const subscribedRooms = new Map();
let userChannelName = null;
let connectionBound = false;
const handlers = new Set();
function register(handler) {
handlers.add(handler);
return () => handlers.delete(handler);
}
function notify(method, ...args) {
handlers.forEach((handler) => handler[method]?.(...args));
}
function roomChannel(roomId) {
return `business.${businessId}.chat.${roomId}`;
}
function userChannel() {
return `business.${businessId}.user.${userId}`;
}
function subscribeRoom(roomId) {
if (!isRealtimeEnabled() || !roomId) return;
const id = parseInt(roomId, 10);
if (subscribedRooms.has(id)) return;
const channelName = roomChannel(id);
const channel = window.Echo.private(channelName);
channel
.listen('.message.sent', (payload) => notify('onMessage', payload, id))
.listen('.user.typing', (payload) => notify('onTyping', payload, id))
.listenForWhisper('typing', (payload) => {
notify('onTyping', { ...payload, room_id: payload.room_id ?? id }, id);
});
subscribedRooms.set(id, { name: channelName, channel });
}
function unsubscribeRoom(roomId) {
const id = parseInt(roomId, 10);
const entry = subscribedRooms.get(id);
if (!entry || typeof window.Echo === 'undefined') return;
window.Echo.leave(entry.name);
subscribedRooms.delete(id);
}
function syncRoomSubscriptions(roomIds) {
if (!isRealtimeEnabled()) return;
const next = new Set((roomIds || []).map((id) => parseInt(id, 10)));
subscribedRooms.forEach((_, id) => {
if (!next.has(id)) unsubscribeRoom(id);
});
next.forEach((id) => subscribeRoom(id));
}
function subscribeUserChannel() {
if (!isRealtimeEnabled() || userChannelName) return;
userChannelName = userChannel();
window.Echo.private(userChannelName)
.listen('.meeting.invite', (payload) => notify('onMeetingInvite', payload));
}
/** Send typing via WebSocket whisper (no HTTP). */
function whisperTyping(roomId, extra = {}) {
if (!isRealtimeEnabled()) return false;
const id = parseInt(roomId, 10);
const entry = subscribedRooms.get(id);
if (!entry?.channel?.whisper) return false;
entry.channel.whisper('typing', {
room_id: id,
user_id: userId,
user_name: config.userName || extra.user_name || 'User',
...extra,
});
return true;
}
function bindConnection() {
if (!isRealtimeEnabled() || connectionBound || !window.Echo?.connector?.pusher) return;
connectionBound = true;
const conn = window.Echo.connector.pusher.connection;
const onConnected = () => {
connected.value = true;
notify('onConnected');
};
const onDisconnected = () => {
connected.value = false;
notify('onDisconnected');
};
conn.bind('connected', onConnected);
conn.bind('disconnected', onDisconnected);
conn.bind('unavailable', onDisconnected);
conn.bind('failed', onDisconnected);
if (conn.state === 'connected') {
onConnected();
}
}
function cleanup() {
if (typeof window.Echo === 'undefined') return;
subscribedRooms.forEach((entry) => window.Echo.leave(entry.name));
subscribedRooms.clear();
if (userChannelName) {
window.Echo.leave(userChannelName);
userChannelName = null;
}
handlers.clear();
connectionBound = false;
}
return {
connected,
register,
isRealtimeEnabled,
bindConnection,
syncRoomSubscriptions,
subscribeUserChannel,
whisperTyping,
cleanup,
};
}