mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: SharedWorker notification
This commit is contained in:
1 parent
4e9e6ddbcb
commit
023a13b066
4 files changed
+125
-31
No files matched your search
@@ -146,10 +146,21 @@ export default function (env: { production?: boolean, measure?: boolean } = {})
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.[mc]?[jt]sx?$/,
|
test: /\.[mc]?[jt]sx?$/,
|
||||||
exclude: /@types\//,
|
exclude: [/@types\//, /components\/message\//],
|
||||||
type: 'javascript/auto',
|
type: 'javascript/auto',
|
||||||
use: [esbuildLoader()],
|
use: [esbuildLoader()],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.[mc]?[jt]sx?$/,
|
||||||
|
include: /components\/message\//,
|
||||||
|
type: 'javascript/auto',
|
||||||
|
use: [{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.styl$/,
|
test: /\.styl$/,
|
||||||
use: [extractCssLoader(), cssLoader(), postcssLoader(), stylusLoader()],
|
use: [extractCssLoader(), cssLoader(), postcssLoader(), stylusLoader()],
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import tpl from 'vj/utils/tpl';
|
|||||||
|
|
||||||
const onmessage = (msg) => {
|
const onmessage = (msg) => {
|
||||||
console.log('Received message', msg);
|
console.log('Received message', msg);
|
||||||
|
if (document.hidden) return false;
|
||||||
if (msg.mdoc.flag & FLAG_ALERT) {
|
if (msg.mdoc.flag & FLAG_ALERT) {
|
||||||
// Is alert
|
// Is alert
|
||||||
return new InfoDialog({
|
new InfoDialog({
|
||||||
cancelByClickingBack: false,
|
cancelByClickingBack: false,
|
||||||
$body: tpl`
|
$body: tpl`
|
||||||
<div class="typo">
|
<div class="typo">
|
||||||
@@ -19,9 +20,10 @@ const onmessage = (msg) => {
|
|||||||
<p>${i18n(msg.mdoc.content)}</p>
|
<p>${i18n(msg.mdoc.content)}</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
}).open();
|
}).open();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// Is message
|
// Is message
|
||||||
return new VjNotification({
|
new VjNotification({
|
||||||
...(msg.udoc._id === 1 && msg.mdoc.flag & 4)
|
...(msg.udoc._id === 1 && msg.mdoc.flag & 4)
|
||||||
? { message: i18n('You received a system message, click here to view.') }
|
? { message: i18n('You received a system message, click here to view.') }
|
||||||
: {
|
: {
|
||||||
@@ -32,6 +34,26 @@ const onmessage = (msg) => {
|
|||||||
duration: 15000,
|
duration: 15000,
|
||||||
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
|
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
|
||||||
}).show();
|
}).show();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = new URL('/home/messages-conn', window.location.href);
|
||||||
|
// TODO handle a better way for cookie
|
||||||
|
url.searchParams.append('sid', document.cookie);
|
||||||
|
const endpoint = url.toString().replace('http', 'ws');
|
||||||
|
|
||||||
|
const initWorkerMode = () => {
|
||||||
|
console.log('Messages: using SharedWorker');
|
||||||
|
const worker = new SharedWorker(new URL('./worker', import.meta.url), { name: 'Hydro Messages Worker' });
|
||||||
|
worker.port.start();
|
||||||
|
worker.port.postMessage({ type: 'conn', path: endpoint, cookie: document.cookie });
|
||||||
|
worker.port.onmessage = async (message) => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') console.log('onmessage: ', message);
|
||||||
|
const { payload, type } = message.data;
|
||||||
|
if (type === 'message') {
|
||||||
|
if (onmessage(payload)) worker.port.postMessage({ type: 'ack', id: payload.mdoc._id });
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
||||||
@@ -44,11 +66,16 @@ const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
|||||||
action: () => window.open('/home/messages', '_blank'),
|
action: () => window.open('/home/messages', '_blank'),
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
|
if (window.SharedWorker) {
|
||||||
|
initWorkerMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!window.BroadcastChannel) {
|
if (!window.BroadcastChannel) {
|
||||||
console.error('BoardcastChannel not supported');
|
console.error('BoardcastChannel not supported');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Messages: using BroadcastChannel');
|
||||||
let isMaster = false;
|
let isMaster = false;
|
||||||
const selfId = nanoid();
|
const selfId = nanoid();
|
||||||
const channel = new BroadcastChannel('hydro-messages');
|
const channel = new BroadcastChannel('hydro-messages');
|
||||||
@@ -70,45 +97,19 @@ const messagePage = new AutoloadPage('messagePage', (pagename) => {
|
|||||||
isMaster = true;
|
isMaster = true;
|
||||||
localStorage.setItem('page.master', selfId);
|
localStorage.setItem('page.master', selfId);
|
||||||
const masterChannel = new BroadcastChannel('hydro-messages');
|
const masterChannel = new BroadcastChannel('hydro-messages');
|
||||||
const url = new URL('/home/messages-conn', window.location.href.replace('http', 'ws'));
|
|
||||||
// TODO handle a better way for cookie
|
|
||||||
url.searchParams.append('sid', document.cookie);
|
|
||||||
const sock = new ReconnectingWebsocket(url.toString());
|
const sock = new ReconnectingWebsocket(url.toString());
|
||||||
const pending = {};
|
|
||||||
sock.onopen = () => console.log('Connected');
|
sock.onopen = () => console.log('Connected');
|
||||||
sock.onerror = console.error;
|
sock.onerror = console.error;
|
||||||
sock.onclose = (...args) => console.log('Closed', ...args);
|
sock.onclose = (...args) => console.log('Closed', ...args);
|
||||||
sock.onmessage = async (message) => {
|
sock.onmessage = async (message) => {
|
||||||
if (process.env.NODE_ENV !== 'production') console.log('onmessage: ', message);
|
if (process.env.NODE_ENV !== 'production') console.log('onmessage: ', message);
|
||||||
const payload = JSON.parse(message.data);
|
const payload = JSON.parse(message.data);
|
||||||
const id = nanoid();
|
masterChannel.postMessage({ type: 'message', payload });
|
||||||
masterChannel.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') {
|
|
||||||
const notification = new window.Notification(
|
|
||||||
payload.udoc.uname || 'Hydro Notification',
|
|
||||||
{
|
|
||||||
icon: payload.udoc.avatarUrl || '/android-chrome-192x192.png',
|
|
||||||
body: payload.mdoc.content,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
notification.onclick = () => window.open('/home/messages');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
masterChannel.onmessage = (msg) => {
|
|
||||||
if (msg.data.type === 'message-push') pending[msg.data.id]?.(true);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.onmessage = (msg) => {
|
channel.onmessage = (msg) => {
|
||||||
if (msg.data.type === 'message' && !document.hidden) {
|
if (msg.data.type === 'message' && !document.hidden) {
|
||||||
channel.postMessage({ type: 'message-push', id: msg.data.id });
|
|
||||||
onmessage(msg.data.payload);
|
onmessage(msg.data.payload);
|
||||||
}
|
}
|
||||||
if (msg.data.type === 'master') {
|
if (msg.data.type === 'master') {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
/// <reference types="@types/sharedworker" />
|
||||||
|
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||||
|
|
||||||
|
console.log('SharedWorker init');
|
||||||
|
|
||||||
|
let conn: ReconnectingWebsocket;
|
||||||
|
let lcookie: string;
|
||||||
|
const ports: MessagePort[] = [];
|
||||||
|
interface RequestInitSharedConnPayload {
|
||||||
|
type: 'conn';
|
||||||
|
cookie: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
interface RequestAckPayload {
|
||||||
|
type: 'ack';
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
type RequestPayload = RequestInitSharedConnPayload | RequestAckPayload;
|
||||||
|
const ack = {};
|
||||||
|
|
||||||
|
function broadcastMsg(message: any) {
|
||||||
|
for (const p of ports) p.postMessage(message);
|
||||||
|
}
|
||||||
|
function initConn(path: string, port: MessagePort, cookie: any) {
|
||||||
|
if (cookie !== lcookie) conn?.close();
|
||||||
|
else if (conn && conn.readyState === conn.OPEN) return;
|
||||||
|
lcookie = cookie;
|
||||||
|
console.log('Init connection for', path);
|
||||||
|
conn = new ReconnectingWebsocket(path);
|
||||||
|
ports.push(port);
|
||||||
|
conn.onopen = () => conn.send(cookie);
|
||||||
|
conn.onerror = () => broadcastMsg({ type: 'error' });
|
||||||
|
conn.onclose = (ev) => broadcastMsg({ type: 'close', error: ev.reason });
|
||||||
|
conn.onmessage = (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)) {
|
||||||
|
broadcastMsg({ type: 'close', error: payload.error });
|
||||||
|
conn.close();
|
||||||
|
} else {
|
||||||
|
console.log('Connected to', path);
|
||||||
|
broadcastMsg({ type: 'open' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
broadcastMsg({ type: 'message', payload });
|
||||||
|
let acked = false;
|
||||||
|
ack[payload.mdoc.id] = () => { acked = true; };
|
||||||
|
setTimeout(() => {
|
||||||
|
delete ack[payload.mdoc.id];
|
||||||
|
if (acked) return;
|
||||||
|
if (Notification?.permission !== 'granted') {
|
||||||
|
console.log('Notification permission denied');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const notification = new Notification(
|
||||||
|
payload.udoc.uname || 'Hydro Notification',
|
||||||
|
{
|
||||||
|
icon: payload.udoc.avatarUrl || '/android-chrome-192x192.png',
|
||||||
|
body: payload.mdoc.content,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
notification.onclick = () => window.open('/home/messages');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
onconnect = function (e) {
|
||||||
|
const port = e.ports[0];
|
||||||
|
|
||||||
|
port.addEventListener('message', (msg: { data: RequestPayload }) => {
|
||||||
|
if (msg.data.type === 'conn') initConn(msg.data.path, port, msg.data.cookie);
|
||||||
|
if (msg.data.type === 'ack') ack[msg.data.id]();
|
||||||
|
});
|
||||||
|
|
||||||
|
port.start();
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.39.19",
|
"version": "4.39.20",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.0",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/redux-logger": "^3.0.9",
|
"@types/redux-logger": "^3.0.9",
|
||||||
|
"@types/sharedworker": "^0.0.80",
|
||||||
"@vscode/codicons": "^0.0.32",
|
"@vscode/codicons": "^0.0.32",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"browser-update": "^3.3.40",
|
"browser-update": "^3.3.40",
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
"through2": "^4.0.2",
|
"through2": "^4.0.2",
|
||||||
"timeago-react": "^3.0.5",
|
"timeago-react": "^3.0.5",
|
||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
|
"ts-loader": "^9.3.1",
|
||||||
"vditor": "^3.8.17",
|
"vditor": "^3.8.17",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"web-streams-polyfill": "^3.2.1",
|
"web-streams-polyfill": "^3.2.1",
|
||||||
|
|||||||
Reference in new issue
Block a user