/**
* SignalR 通知模块
* 处理服务器推送的通知消息
*/
import {dialog} from './dialog.js';
import {outPutSuccess, outPutInfo, outPutWarning, outPutError} from './console-logger.js';
/**
* 更新连接状态指示器
* @param {string} status - 连接状态 'connected' | 'disconnected' | 'reconnecting'
*/
function updateConnectionStatus(status) {
const statusElement = document.getElementById('signalr-status');
if (!statusElement) return;
statusElement.classList.remove('connection-status--connected', 'connection-status--disconnected');
switch (status) {
case 'connected':
statusElement.classList.add('connection-status--connected');
statusElement.title = "消息服务:已连接";
break;
case 'disconnected':
statusElement.classList.add('connection-status--disconnected');
statusElement.title = "消息服务:已断开";
break;
case 'reconnecting':
statusElement.classList.add('connection-status--disconnected');
statusElement.title = "消息服务:正在重新连接...";
break;
default:
statusElement.title = "消息服务:未连接";
break;
}
}
/**
* 初始化主通知连接
*/
async function initMainNotification() {
try {
const connection = new signalR
.HubConnectionBuilder()
.withUrl("/notification", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.withAutomaticReconnect()
.build();
// 连接状态监控
connection.onreconnecting(() => {
console.warn("SignalR Reconnecting...");
updateConnectionStatus('reconnecting');
});
connection.onreconnected(() => {
console.info("SignalR Reconnected.");
updateConnectionStatus('connected');
});
connection.onclose(() => {
console.error("SignalR Disconnected.");
updateConnectionStatus('disconnected');
});
await connection.start();
updateConnectionStatus('connected');
await connection.invoke("Init");
// 推送消息通知
connection.on("pushMessage", async function (result) {
const option = {
title: "小喇叭开始广播辣",
content: result.markdown
};
const notification = await requestBrowserNotification(option);
if (notification !== null) {
notification.onclick = function () {
window.open("/mumble.html");
};
}
console.info(result);
});
// 日志消息通知
connection.on("pushLogMessage", function (type, message) {
const timestamp = new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
if (type === 0) {
// 成功
outPutSuccess(message);
dialog.toast(`${timestamp}\n${message}`, 'success');
} else if (type === 1) {
// 信息
outPutInfo(message);
dialog.toast(`${timestamp}\n${message}`, 'warning');
} else if (type === 2) {
// 错误
outPutError(message);
dialog.toast(`${timestamp}\n${message}`, 'error');
}
});
// 订阅通知
let newsPublishBox = null;
const subscribeData = [];
connection.on("cnBetaSubscribe", function (result) {
const values = [];
if (result.hasOwnProperty("progressValues") && Array.isArray(result["progressValues"])) {
for (let i = 0; i < result["progressValues"].length; i++) {
const value = (result["progressValues"][i] * 100).toFixed(2);
values.push(parseFloat(value));
}
}
subscribeData.push(result);
if (newsPublishBox == null) {
newsPublishBox = dialog.showNotification({
title: "新闻订阅发布",
content: result.message,
bars: values,
type: "info"
});
} else {
dialog.setNotificationContent(newsPublishBox, result.message);
dialog.setNotificationProgress(newsPublishBox, values);
}
switch (result.type) {
case 0:
outPutSuccess(result.message);
break;
case 1:
outPutInfo(result.message);
break;
case 2:
outPutError(result.message);
break;
case 3:
outPutSuccess(result.message);
setTimeout(function () {
dialog.closeNotification(newsPublishBox);
newsPublishBox = null;
}, 1000);
console.log(subscribeData);
break;
}
});
// 就绪通知
connection.on("ready", function (result) {
console.info(result);
});
// 系统通知
connection.on("systemNotification", function (msg) {
dialog.toast(msg, 'info');
});
// 站点地图生成进度通知
connection.on("siteMapPushMessage", function (data) {
// MessageLevel: 0=Info, 1=Warning, 2=Error
const typeMap = ['success', 'warning', 'error'];
const type = typeMap[data.level] || 'info';
// 格式化时间
const time = new Date(data.timestamp).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// 在右上角显示 toast(移动端会自动适配到底部)
dialog.toast(`[${time}] ${data.message}`, type, 4000, 'top-right');
// 同时输出到控制台
if (type === 'error') {
outPutError(`[Sitemap] ${data.message}`);
} else if (type === 'warning') {
outPutWarning(`[Sitemap] ${data.message}`);
} else {
outPutInfo(`[Sitemap] ${data.message}`);
}
});
return connection;
} catch (error) {
console.error('SignalR 主通知连接失败:', error);
updateConnectionStatus('disconnected');
return null;
}
}
/**
* 初始化应用通知连接
*/
async function initAppNotification() {
try {
const appConnection = new signalR
.HubConnectionBuilder()
.withUrl("/app/notification", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.withAutomaticReconnect()
.build();
await appConnection.start();
appConnection.on("systemMessage", function (level, message) {
if (level === 0) {
// 成功
outPutSuccess(message);
dialog.toast(message, 'success');
} else if (level === 1) {
// 警告
outPutWarning(message);
dialog.toast(message, 'warning');
} else if (level === 2) {
// 错误
outPutError(message);
dialog.toast(message, 'error');
}
});
return appConnection;
} catch (error) {
console.error('SignalR 应用通知连接失败:', error);
return null;
}
}
/**
* 请求浏览器原生通知权限并显示通知
* @param {Object} option - 通知选项
* @param {string} option.title - 通知标题
* @param {string} option.content - 通知内容
* @param {string} [option.image] - 通知图标
* @param {string} [option.tag] - 通知标签
* @returns {Promise<Notification|null>}
*/
async function requestBrowserNotification(option) {
if (!("Notification" in window)) {
return null;
}
const permission = await Notification.requestPermission();
const setting = {
title: "",
content: "",
image: "https://cdn.dpangzi.com/logo.png",
...option
};
if (permission === "granted") {
return new Notification(setting.title, {
icon: setting.image,
body: setting.content,
lang: "zh-cn",
tag: setting.tag
});
}
return null;
}
/**
* 初始化所有 SignalR 连接
*/
export async function initSystemNotifications() {
// 阻止状态图标的点击冒泡,避免触发彩蛋
const statusElement = document.getElementById('signalr-status');
if (statusElement) {
statusElement.addEventListener('click', (e) => {
e.stopPropagation();
});
}
await Promise.all([
initMainNotification(),
initAppNotification()
]);
}
评论加载中...