mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
ui: use broadcast channel to handle message update
This commit is contained in:
1 parent
27056c5b69
commit
87b980f87d
7 files changed
+133
-139
No files matched your search
@@ -206,7 +206,6 @@ export default function (env = {}) {
|
||||
{ from: root('static') },
|
||||
{ from: root(`${dirname(require.resolve('vditor/package.json'))}/dist`), to: 'vditor/dist' },
|
||||
{ from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' },
|
||||
{ from: root('.build/sharedworker.js'), to: 'sharedworker.js' },
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import cac from 'cac';
|
||||
import fs from 'fs-extra';
|
||||
import webpack from 'webpack';
|
||||
import esbuild from 'esbuild';
|
||||
import WebpackDevServer from 'webpack-dev-server';
|
||||
import gulp from 'gulp';
|
||||
import log from 'fancy-log';
|
||||
@@ -18,15 +17,6 @@ const argv = cac().parse();
|
||||
async function runWebpack({
|
||||
watch, production, measure, dev,
|
||||
}) {
|
||||
const result = esbuild.build({
|
||||
bundle: true,
|
||||
format: 'iife',
|
||||
entryPoints: [root('sharedworker.ts')],
|
||||
outfile: root('.build/sharedworker.js'),
|
||||
watch: dev || watch,
|
||||
minify: production,
|
||||
});
|
||||
if (!dev && !watch) await result;
|
||||
const compiler = webpack(webpackConfig({ watch, production, measure }));
|
||||
if (dev) {
|
||||
const server = new WebpackDevServer(compiler, {
|
||||
@@ -36,7 +26,7 @@ async function runWebpack({
|
||||
stats: 'none',
|
||||
index: root('public'),
|
||||
proxy: {
|
||||
context: (path) => !path.includes('sharedworker.js'),
|
||||
context: (path) => !path.includes('sockjs-node'),
|
||||
target: 'http://localhost:2333',
|
||||
ws: true,
|
||||
},
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { AutoloadPage } from 'vj/misc/Page';
|
||||
import { InfoDialog } from 'vj/components/dialog';
|
||||
import VjNotification from 'vj/components/notification/index';
|
||||
import { FLAG_ALERT } from 'vj/constant/message';
|
||||
import i18n from 'vj/utils/i18n';
|
||||
import tpl from 'vj/utils/tpl';
|
||||
|
||||
const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
||||
if (pagename === 'home_messages' || !UserContext._id) return;
|
||||
|
||||
if (UserContext.unreadMsg) {
|
||||
new VjNotification({
|
||||
type: 'info',
|
||||
message: i18n(`You have ${UserContext.unreadMsg > 1 ? 'new messages' : 'a new message'}.`),
|
||||
duration: 5000,
|
||||
action: () => window.open('/home/messages', '_blank'),
|
||||
}).show();
|
||||
}
|
||||
|
||||
const onmessage = (message) => {
|
||||
const msg = JSON.parse(message);
|
||||
console.log('Received message', msg);
|
||||
if (msg.mdoc.flag & FLAG_ALERT) {
|
||||
// Is alert
|
||||
return new InfoDialog({
|
||||
cancelByClickingBack: false,
|
||||
$body: tpl`
|
||||
<div class="typo">
|
||||
${{ templateRaw: true, html: i18n('Message from {0}:', `<div data-user>${msg.mdoc.from}</div>`) }}
|
||||
<p>${i18n(msg.mdoc.content)}</p>
|
||||
</div>`,
|
||||
}).open();
|
||||
}
|
||||
if (msg.udoc._id === 1 && msg.mdoc.flag & 4) {
|
||||
// System notification
|
||||
return new VjNotification({
|
||||
message: i18n('You received a system message, click here to view.'),
|
||||
duration: 15000,
|
||||
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
|
||||
}).show();
|
||||
}
|
||||
// Is message
|
||||
return new VjNotification({
|
||||
title: msg.udoc.uname,
|
||||
avatar: msg.udoc.avatarUrl,
|
||||
message: msg.mdoc.content,
|
||||
duration: 15000,
|
||||
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
|
||||
}).show();
|
||||
};
|
||||
|
||||
const path = new URL('/home/messages-conn', window.location.href).toString();
|
||||
const worker = new SharedWorker('/sharedworker.js', { name: 'Hydro Messages Shared Worker' });
|
||||
worker.port.start();
|
||||
worker.port.postMessage({ type: 'init', path, cookie: document.cookie });
|
||||
worker.port.onmessage = (e) => {
|
||||
if (e.data.type !== 'message') return;
|
||||
if (document.hidden) {
|
||||
worker.port.postMessage({ type: 'fail' });
|
||||
} else {
|
||||
worker.port.postMessage({ type: 'success' });
|
||||
onmessage(e.data.payload);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export default messagePage;
|
||||
@@ -0,0 +1,129 @@
|
||||
import { AutoloadPage } from 'vj/misc/Page';
|
||||
import { InfoDialog } from 'vj/components/dialog';
|
||||
import VjNotification from 'vj/components/notification/index';
|
||||
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||
import { FLAG_ALERT } from 'vj/constant/message';
|
||||
import i18n from 'vj/utils/i18n';
|
||||
import tpl from 'vj/utils/tpl';
|
||||
import { nanoid } from 'nanoid';
|
||||
import sleep from 'vj/utils/delay';
|
||||
|
||||
function asMaster() {
|
||||
console.log('init as master');
|
||||
const channel = new BroadcastChannel('hydro-messages');
|
||||
const interval = setInterval(() => channel.postMessage({ type: 'master' }), 5000);
|
||||
const url = new URL('/home/messages-conn', window.location.href.replace('http', 'ws'));
|
||||
url.searchParams.append('sid', document.cookie);
|
||||
const sock = new ReconnectingWebsocket(url.toString());
|
||||
function destory() {
|
||||
sock.close();
|
||||
channel.close();
|
||||
clearInterval(interval);
|
||||
}
|
||||
const pending = {};
|
||||
sock.onopen = () => console.log('Connected');
|
||||
sock.onerror = console.error;
|
||||
sock.onclose = (...args) => console.log('Closed', ...args);
|
||||
sock.onmessage = async (message) => {
|
||||
if (process.env.NODE_ENV !== 'production') console.log('onmessage: ', message);
|
||||
const payload = JSON.parse(message.data);
|
||||
if (['PermissionError', 'PrivilegeError'].includes(payload.error)) {
|
||||
destory();
|
||||
return;
|
||||
}
|
||||
const id = nanoid();
|
||||
channel.postMessage({ type: 'message', id, payload });
|
||||
const success = await new Promise<boolean>((resolve) => {
|
||||
pending[id] = resolve;
|
||||
setTimeout(() => {
|
||||
delete pending[id];
|
||||
resolve(false);
|
||||
}, 1000);
|
||||
});
|
||||
if (!success && window.Notification?.permission === 'granted') {
|
||||
// eslint-disable-next-line no-new
|
||||
new window.Notification(
|
||||
payload.udoc.uname || 'Hydro Notification',
|
||||
{
|
||||
icon: payload.udoc.avatarUrl || '/android-chrome-192x192.png',
|
||||
body: payload.mdoc.content,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
channel.onmessage = (msg) => {
|
||||
if (msg.data.type === 'message-push') pending[msg.data.id]?.(true);
|
||||
};
|
||||
}
|
||||
|
||||
const onmessage = (msg) => {
|
||||
console.log('Received message', msg);
|
||||
if (msg.mdoc.flag & FLAG_ALERT) {
|
||||
// Is alert
|
||||
return new InfoDialog({
|
||||
cancelByClickingBack: false,
|
||||
$body: tpl`
|
||||
<div class="typo">
|
||||
${{ templateRaw: true, html: i18n('Message from {0}:', `<div data-user>${msg.mdoc.from}</div>`) }}
|
||||
<p>${i18n(msg.mdoc.content)}</p>
|
||||
</div>`,
|
||||
}).open();
|
||||
}
|
||||
// Is message
|
||||
return new VjNotification({
|
||||
...(msg.udoc._id === 1 && msg.mdoc.flag & 4)
|
||||
? { message: i18n('You received a system message, click here to view.') }
|
||||
: {
|
||||
title: msg.udoc.uname,
|
||||
avatar: msg.udoc.avatarUrl,
|
||||
message: msg.mdoc.content,
|
||||
},
|
||||
duration: 15000,
|
||||
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
|
||||
}).show();
|
||||
};
|
||||
|
||||
async function asSlave() {
|
||||
console.log('message load as slave');
|
||||
const channel = new BroadcastChannel('hydro-messages');
|
||||
const selfId = nanoid();
|
||||
setInterval(() => channel.postMessage({ type: 'node', id: selfId }), 5000);
|
||||
let alive = false;
|
||||
let nodes = new Set<string>([selfId]);
|
||||
channel.onmessage = (msg) => {
|
||||
if (msg.data.target && msg.data.target !== selfId) return;
|
||||
if (msg.data.type === 'master') alive = true;
|
||||
if (msg.data.type === 'node') nodes.add(msg.data.id);
|
||||
if (msg.data.type === 'message') {
|
||||
if (!document.hidden) {
|
||||
channel.postMessage({ type: 'message-push', id: msg.data.id });
|
||||
onmessage(msg.data.payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
alive = false;
|
||||
nodes = new Set<string>([selfId]);
|
||||
await sleep(7000);
|
||||
if (!alive && Array.from(nodes).sort()[0] === selfId) asMaster();
|
||||
}
|
||||
}
|
||||
|
||||
const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
||||
if (pagename === 'home_messages' || !UserContext._id) return;
|
||||
if (UserContext.unreadMsg) {
|
||||
new VjNotification({
|
||||
type: 'info',
|
||||
message: i18n(`You have ${UserContext.unreadMsg > 1 ? 'new messages' : 'a new message'}.`),
|
||||
duration: 5000,
|
||||
action: () => window.open('/home/messages', '_blank'),
|
||||
}).show();
|
||||
}
|
||||
if (!window.BroadcastChannel) {
|
||||
console.error('BoardcastChannel not supported');
|
||||
return;
|
||||
}
|
||||
asSlave();
|
||||
});
|
||||
|
||||
export default messagePage;
|
||||
@@ -19,11 +19,8 @@ export default class Sock {
|
||||
this.sock.onmessage = (message) => {
|
||||
if (process.env.NODE_ENV !== 'production') console.log('Sock.onmessage: ', message);
|
||||
const msg = JSON.parse(message.data);
|
||||
if (msg.event === 'auth') {
|
||||
if (msg.error === 'PermissionError' || msg.error === 'PrivilegeError') {
|
||||
this.close();
|
||||
}
|
||||
} else this.onmessage?.(message);
|
||||
if (msg.error === 'PermissionError' || msg.error === 'PrivilegeError') this.close();
|
||||
else this.onmessage?.(message);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/ui-default",
|
||||
"version": "4.35.0",
|
||||
"version": "4.35.1",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "hydro.js",
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||
|
||||
console.log('SharedWorker init');
|
||||
const ports: MessagePort[] = [];
|
||||
let conn: ReconnectingWebsocket;
|
||||
|
||||
function initSharedConn(path: string, sid: string) {
|
||||
if (conn) return;
|
||||
console.log('Init connection for', path);
|
||||
const url = new URL(path);
|
||||
url.protocol = url.protocol.replace('http', 'ws');
|
||||
url.searchParams.append('sid', sid);
|
||||
const sock = new ReconnectingWebsocket(url.toString());
|
||||
conn = sock;
|
||||
sock.onopen = () => console.log('Connected');
|
||||
sock.onerror = console.error;
|
||||
sock.onclose = (...args) => console.log('Closed', ...args);
|
||||
sock.onmessage = async (message) => {
|
||||
if (process.env.NODE_ENV !== 'production') console.log('SharedWorker.port.onmessage: ', message);
|
||||
const payload = JSON.parse(message.data);
|
||||
if (payload.event === 'auth') {
|
||||
if (['PermissionError', 'PrivilegeError'].includes(payload.error)) {
|
||||
conn?.close();
|
||||
conn = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const port of ports) {
|
||||
port.postMessage({ type: 'message', payload: message.data });
|
||||
const success = await new Promise((resolve) => {
|
||||
const handle = (msg) => {
|
||||
if (['success', 'fail'].includes(msg.data.type)) {
|
||||
port.removeEventListener('message', handle);
|
||||
resolve(msg.data.type === 'success');
|
||||
}
|
||||
};
|
||||
port.addEventListener('message', handle);
|
||||
});
|
||||
if (success) return;
|
||||
}
|
||||
console.log('Failed to push notification');
|
||||
// TODO try system notification
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
onconnect = function (e) { // eslint-disable-line no-undef
|
||||
const port: MessagePort = e.ports[0];
|
||||
port.addEventListener('message', (msg) => {
|
||||
initSharedConn(msg.data.path, msg.data.cookie);
|
||||
});
|
||||
port.start();
|
||||
ports.push(port);
|
||||
};
|
||||
Reference in new issue
Block a user