mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +08:00
227 lines
6.5 KiB
TypeScript
227 lines
6.5 KiB
TypeScript
import { notification } from 'antd';
|
|
import type { MessageVO } from '@/api/system/message/types';
|
|
import { globalHeaders } from '@/api/request';
|
|
import { getMessageBox } from '@/api/system/message';
|
|
import { useNoticeStore, type NoticeItem } from '@/stores/noticeStore';
|
|
import { useUserStore } from '@/stores/userStore';
|
|
import { getToken } from '@/utils/auth';
|
|
import { appEnv } from '@/utils/env';
|
|
import { getReadMessageIds } from '@/utils/messageRead';
|
|
import { parsePushMessage, resolveNoticeGroup, resolveNoticeTitle, shouldAppendNotice } from '@/utils/pushMessage';
|
|
|
|
let eventSource: EventSource | undefined;
|
|
let webSocket: WebSocket | undefined;
|
|
let reconnectTimer: number | undefined;
|
|
let reconnectAttempts = 0;
|
|
let pushClosed = true;
|
|
const KICKED_MESSAGE = 'kicked';
|
|
let pushKicked = false;
|
|
let resumePushTimer: number | undefined;
|
|
|
|
function messageEnabled() {
|
|
return appEnv.messageEnabled;
|
|
}
|
|
|
|
function formatNoticeTime(timestamp?: number | string) {
|
|
const time = timestamp ? new Date(timestamp) : new Date();
|
|
return time.toLocaleString();
|
|
}
|
|
|
|
function currentUserId() {
|
|
return useUserStore.getState().userInfo?.user.userId;
|
|
}
|
|
|
|
function withRead(messageId?: string | number) {
|
|
if (messageId === undefined || messageId === null) return false;
|
|
return getReadMessageIds(currentUserId()).has(String(messageId));
|
|
}
|
|
|
|
function toNoticeItem(item: MessageVO, category: NoticeItem['category']): NoticeItem {
|
|
const timestamp = item.createTime ? new Date(item.createTime).getTime() : Date.now();
|
|
return {
|
|
...item,
|
|
category,
|
|
read: withRead(item.messageId),
|
|
timestamp,
|
|
time: formatNoticeTime(timestamp)
|
|
};
|
|
}
|
|
|
|
function appendNotice(raw: string) {
|
|
const payload = parsePushMessage(raw);
|
|
if (!shouldAppendNotice(payload)) return;
|
|
|
|
const title = resolveNoticeTitle(payload);
|
|
const notice: NoticeItem = {
|
|
messageId: payload.messageId || `${payload.type || 'message'}:${payload.timestamp || Date.now()}`,
|
|
category: resolveNoticeGroup(payload),
|
|
type: payload.type || 'message',
|
|
source: payload.source || 'backend',
|
|
title,
|
|
message: payload.message || '',
|
|
content: typeof payload.data?.noticeContent === 'string' ? payload.data.noticeContent : undefined,
|
|
data: payload.data || null,
|
|
path: payload.path,
|
|
read: withRead(payload.messageId),
|
|
timestamp: payload.timestamp || Date.now(),
|
|
time: formatNoticeTime(payload.timestamp)
|
|
};
|
|
useNoticeStore.getState().addNotice(notice);
|
|
notification.success({ message: title, description: notice.message, duration: 3 });
|
|
}
|
|
|
|
function handlePushMessage(raw: string) {
|
|
if (raw === KICKED_MESSAGE) {
|
|
pushKicked = true;
|
|
closePush();
|
|
return;
|
|
}
|
|
appendNotice(raw);
|
|
}
|
|
|
|
function buildHttpUrl(path: string) {
|
|
const base = appEnv.baseApi;
|
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
const url = `${base}${normalizedPath}`;
|
|
const separator = url.includes('?') ? '&' : '?';
|
|
return `${url}${separator}Authorization=Bearer ${encodeURIComponent(getToken() || '')}&clientid=${encodeURIComponent(appEnv.clientId)}`;
|
|
}
|
|
|
|
function buildWsUrl(path: string) {
|
|
const httpUrl = buildHttpUrl(path);
|
|
if (/^https?:\/\//.test(httpUrl)) {
|
|
return httpUrl.replace(/^http/, 'ws');
|
|
}
|
|
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
return `${protocol}${window.location.host}${httpUrl}`;
|
|
}
|
|
|
|
function clearReconnectTimer() {
|
|
if (reconnectTimer) {
|
|
window.clearTimeout(reconnectTimer);
|
|
reconnectTimer = undefined;
|
|
}
|
|
}
|
|
|
|
function scheduleReconnect(connect: () => void, maxRetries: number, delay: number) {
|
|
if (pushClosed) return;
|
|
if (reconnectAttempts >= maxRetries) return;
|
|
reconnectAttempts += 1;
|
|
clearReconnectTimer();
|
|
reconnectTimer = window.setTimeout(connect, delay);
|
|
}
|
|
|
|
function initSsePush(path: string) {
|
|
const connect = () => {
|
|
if (pushClosed) return;
|
|
eventSource?.close();
|
|
eventSource = new EventSource(buildHttpUrl(path));
|
|
eventSource.onopen = () => {
|
|
reconnectAttempts = 0;
|
|
};
|
|
eventSource.onmessage = event => {
|
|
if (event.data) handlePushMessage(event.data);
|
|
};
|
|
eventSource.onerror = () => {
|
|
eventSource?.close();
|
|
scheduleReconnect(connect, 5, 5000);
|
|
};
|
|
};
|
|
connect();
|
|
}
|
|
|
|
function initWsPush(path: string) {
|
|
const connect = () => {
|
|
if (pushClosed) return;
|
|
webSocket?.close();
|
|
webSocket = new WebSocket(buildWsUrl(path));
|
|
webSocket.onopen = () => {
|
|
reconnectAttempts = 0;
|
|
};
|
|
webSocket.onmessage = event => {
|
|
if (String(event.data) === 'pong') return;
|
|
handlePushMessage(String(event.data));
|
|
};
|
|
webSocket.onclose = () => scheduleReconnect(connect, 3, 1000);
|
|
};
|
|
connect();
|
|
}
|
|
|
|
export async function initMessageBox() {
|
|
if (!messageEnabled()) {
|
|
useNoticeStore.getState().clearNotice();
|
|
return;
|
|
}
|
|
const res = await getMessageBox();
|
|
useNoticeStore
|
|
.getState()
|
|
.setNotices([
|
|
...(res.data?.systemList || []).map(item => toNoticeItem(item, 'system')),
|
|
...(res.data?.noticeList || []).map(item => toNoticeItem(item, 'notice')),
|
|
...(res.data?.workflowList || []).map(item => toNoticeItem(item, 'workflow'))
|
|
]);
|
|
}
|
|
|
|
export function initPush() {
|
|
closePush();
|
|
if (!messageEnabled()) return;
|
|
pushKicked = false;
|
|
pushClosed = false;
|
|
if (appEnv.messageTransport === 'websocket') {
|
|
initWsPush(appEnv.messagePath);
|
|
return;
|
|
}
|
|
initSsePush(appEnv.messagePath);
|
|
}
|
|
|
|
export function closePush() {
|
|
pushClosed = true;
|
|
clearReconnectTimer();
|
|
if (webSocket) {
|
|
webSocket.onopen = null;
|
|
webSocket.onmessage = null;
|
|
webSocket.onclose = null;
|
|
webSocket.onerror = null;
|
|
}
|
|
if (eventSource) {
|
|
eventSource.onopen = null;
|
|
eventSource.onmessage = null;
|
|
eventSource.onerror = null;
|
|
}
|
|
eventSource?.close();
|
|
webSocket?.close();
|
|
eventSource = undefined;
|
|
webSocket = undefined;
|
|
reconnectAttempts = 0;
|
|
}
|
|
|
|
function resumePushIfNeeded() {
|
|
if (!pushKicked || !getToken() || document.visibilityState !== 'visible') {
|
|
return;
|
|
}
|
|
if (resumePushTimer) {
|
|
window.clearTimeout(resumePushTimer);
|
|
}
|
|
resumePushTimer = window.setTimeout(async () => {
|
|
resumePushTimer = undefined;
|
|
if (!pushKicked || !getToken() || document.visibilityState !== 'visible') {
|
|
return;
|
|
}
|
|
try {
|
|
await initMessageBox();
|
|
} finally {
|
|
initPush();
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('focus', resumePushIfNeeded);
|
|
document.addEventListener('visibilitychange', resumePushIfNeeded);
|
|
window.addEventListener('online', resumePushIfNeeded);
|
|
}
|
|
|
|
export function pushHeaders() {
|
|
return globalHeaders();
|
|
}
|