mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +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('static') },
|
||||||
{ from: root(`${dirname(require.resolve('vditor/package.json'))}/dist`), to: 'vditor/dist' },
|
{ 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: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' },
|
||||||
{ from: root('.build/sharedworker.js'), to: 'sharedworker.js' },
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import cac from 'cac';
|
import cac from 'cac';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import esbuild from 'esbuild';
|
|
||||||
import WebpackDevServer from 'webpack-dev-server';
|
import WebpackDevServer from 'webpack-dev-server';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import log from 'fancy-log';
|
import log from 'fancy-log';
|
||||||
@@ -18,15 +17,6 @@ const argv = cac().parse();
|
|||||||
async function runWebpack({
|
async function runWebpack({
|
||||||
watch, production, measure, dev,
|
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 }));
|
const compiler = webpack(webpackConfig({ watch, production, measure }));
|
||||||
if (dev) {
|
if (dev) {
|
||||||
const server = new WebpackDevServer(compiler, {
|
const server = new WebpackDevServer(compiler, {
|
||||||
@@ -36,7 +26,7 @@ async function runWebpack({
|
|||||||
stats: 'none',
|
stats: 'none',
|
||||||
index: root('public'),
|
index: root('public'),
|
||||||
proxy: {
|
proxy: {
|
||||||
context: (path) => !path.includes('sharedworker.js'),
|
context: (path) => !path.includes('sockjs-node'),
|
||||||
target: 'http://localhost:2333',
|
target: 'http://localhost:2333',
|
||||||
ws: true,
|
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) => {
|
this.sock.onmessage = (message) => {
|
||||||
if (process.env.NODE_ENV !== 'production') console.log('Sock.onmessage: ', message);
|
if (process.env.NODE_ENV !== 'production') console.log('Sock.onmessage: ', message);
|
||||||
const msg = JSON.parse(message.data);
|
const msg = JSON.parse(message.data);
|
||||||
if (msg.event === 'auth') {
|
if (msg.error === 'PermissionError' || msg.error === 'PrivilegeError') this.close();
|
||||||
if (msg.error === 'PermissionError' || msg.error === 'PrivilegeError') {
|
else this.onmessage?.(message);
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
} else this.onmessage?.(message);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.35.0",
|
"version": "4.35.1",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"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