diff --git a/packages/ui-default/build/config/webpack.js b/packages/ui-default/build/config/webpack.js
index 6bd105436..d9668ddc0 100644
--- a/packages/ui-default/build/config/webpack.js
+++ b/packages/ui-default/build/config/webpack.js
@@ -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({
diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts
index e97125b83..dec6bab96 100644
--- a/packages/ui-default/build/main.ts
+++ b/packages/ui-default/build/main.ts
@@ -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,
},
diff --git a/packages/ui-default/components/message/index.page.js b/packages/ui-default/components/message/index.page.js
deleted file mode 100644
index a396f23a5..000000000
--- a/packages/ui-default/components/message/index.page.js
+++ /dev/null
@@ -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`
-
- ${{ templateRaw: true, html: i18n('Message from {0}:', `
${msg.mdoc.from}
`) }}
-
${i18n(msg.mdoc.content)}
-
`,
- }).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;
diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts
new file mode 100644
index 000000000..088646162
--- /dev/null
+++ b/packages/ui-default/components/message/index.page.ts
@@ -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((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`
+
+ ${{ templateRaw: true, html: i18n('Message from {0}:', `
${msg.mdoc.from}
`) }}
+
${i18n(msg.mdoc.content)}
+
`,
+ }).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([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([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;
diff --git a/packages/ui-default/components/socket/index.js b/packages/ui-default/components/socket/index.js
index 0435d062b..52208e113 100644
--- a/packages/ui-default/components/socket/index.js
+++ b/packages/ui-default/components/socket/index.js
@@ -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);
};
}
diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json
index 70504d0e6..a879f8f0a 100644
--- a/packages/ui-default/package.json
+++ b/packages/ui-default/package.json
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
- "version": "4.35.0",
+ "version": "4.35.1",
"author": "undefined ",
"license": "AGPL-3.0",
"main": "hydro.js",
diff --git a/packages/ui-default/sharedworker.ts b/packages/ui-default/sharedworker.ts
deleted file mode 100644
index 798ed7045..000000000
--- a/packages/ui-default/sharedworker.ts
+++ /dev/null
@@ -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);
-};